Loader behaviour at service start, per-module lifecycle hooks, and the dispatch contract.
A .augins file is a zip with at minimum:
my-module.augins
+-- metadata.json (required, see Manifest-Schema)
+-- <ID>.so (required, the module .so)
+-- libfoo.so, libbar.so (optional, dlopen siblings)
+-- models/, calibration/ (optional, bundled assets)
<ID> is the ID field from the manifest, verbatim. No lib prefix.
The loader does dlopen("<ID>.so", ...) once it has extracted the
zip; if the filename does not match, the module will not load.
The service scans
/data/data/com.augmented_insanity.runtime.out_of_process/files/modules/
for *.augins files. Push your module there with:
adb push my-module.augins /data/local/tmp/
adb shell run-as com.augmented_insanity.runtime.out_of_process \
cp /data/local/tmp/my-module.augins files/modules/
The sample modules' installFooAugins Gradle tasks automate this.
See Building-A-Module.
[Planned] v0.2.x: APK-bundled fallback under assets/modules/
for default modules that ship with the runtime APK; user-installed
modules with the same ID will override APK-bundled ones.
When the service process starts (Java MonadoService.onCreate
calls into native via nativeStartServer), the loader runs in
this order:
- Bind host API to the JVM and Application Context. After this,
host->get_jvm()andhost->get_context()return valid handles. - Scan the modules dir for
*.augins. Each entry is processed independently; a failure on one entry does not affect others. - Extract the zip into a per-module cache subdirectory. A
size-stamp file (
<entry>.size) lets a subsequent start skip extraction when the source zip is byte-identical. - Parse
metadata.jsonand validateManifest_Version,ID,Version,Implemented_Functions. Bad manifests are logged and the module is skipped. - Preload sibling
.sofiles in the extraction dir withRTLD_NOW | RTLD_GLOBALso the main module.soresolves their symbols. dlopen <ID>.sowithRTLD_NOW. Each name fromImplemented_Functionsisdlsym'd. Missing symbols are warned but do not block the module.- Register dispatch entries for each resolved symbol in the dispatch map (see Dispatch model below).
dlsym aug_on_module_loadandaug_on_module_unload(both optional) and store them in the lifecycle list.- After all modules are loaded: sort the dispatch map by
priority, then fire
aug_on_module_loadfor each module in load order. A non-zero return rejects the module (dispatch entries are not yet unwound -- see[Planned]note in Roadmap).
At service shutdown the loader fires aug_on_module_unload in
reverse order, then dlcloses each handle.
Both hooks are optional. A module that does not need worker threads, ArSessions, or any setup beyond what dlopen already does can omit both and export only its dispatched functions.
// Called once after dlopen + dispatch entries registered.
// Return non-zero to reject the module (dlclose follows).
int aug_on_module_load(const struct aug_host_api *host);
// Called once at service shutdown, before dlclose. Join workers
// and release resources here.
void aug_on_module_unload(void);Inside aug_on_module_load, modules typically:
- Validate
host->struct_version >= AUG_HOST_API_VERSION(or the lower minimum the module actually needs). - Cache the
hostpointer in a module-private global. - Capture
host->get_module_data_dir()if needed on worker threads. - Spawn background threads (sensor pollers, ArSession ticker, ONNX inference workers, ...).
The runtime calls aug_on_module_load from the service's main
thread, which is already attached to the JVM. Calling
DetachCurrentThread on this thread is a JNI-spec fatal
("attempting to detach while still running code"). Use
vm->GetEnv(...) to obtain the JNIEnv inside the load hook.
Five locked design decisions govern how modules participate in dispatch. These are not configurable per-module in v0.2 base.
| ID | Rule | Note |
|---|---|---|
| Q1 | Last-write-wins on output conflicts | Modules share a single output struct; later writes survive. |
| Q2 | Runtime default runs before modules | Modules see the baseline output and decorate or overwrite. |
| Q3 | No short-circuit, no exclusivity | Any module can register for any function name. The chain runs to completion unless aborted by Q5. |
| Q4 | Priority field in manifest orders the chain | Lower Priority runs earlier. Combined with Q1, higher Priority overwrites earlier writers. |
| Q5 | Abort on first non-success XrResult | Subsequent modules in the chain are skipped; the client sees that result. |
[Planned] v0.2.x adds a per-module Failure_Mode field
("abort" / "recoverable" / "critical") for tuning Q5.
The Implemented_Functions array holds OpenXR function names or
Aug-Ins synthetic names. Examples:
"xrLocateSpace"-- the real OpenXR function. Backed by the service-side adapteraug_adapter_space_locate_space."aug_LocateDeviceInSpace"-- an Aug-Ins synthetic name for a function that has no direct OpenXR equivalent (it backs half ofxrLocateViewson the service-side IPC layer). Theaug_prefix marks it as Aug-Ins-specific.
The runtime adapter decides which IPC call routes to which name.
See Service-Side-Dispatch for the
current aug_implemented_adapters set.
A module that lists a name without a registered adapter is loaded without that entry; the loader logs a warning. Adding the adapter and rebuilding the runtime activates the existing module's entry on the next start.
Each module's asset directory:
/data/data/com.augmented_insanity.runtime.out_of_process/cache/opennedmodules/_staged/<id>.augins/
Available via host->get_module_data_dir() from any call the
runtime made into the module (including aug_on_module_load and
any dispatched function). The implementation is thread-local
storage pushed and popped by the dispatcher around every call.
A worker thread the module spawned itself does not have the TLS
populated, so get_module_data_dir() returns an empty string
there. Capture the path inside aug_on_module_load and stash it
in a module-private global before spawning workers.
| Failure | Response |
|---|---|
metadata.json parse failure |
Log, skip module, continue. |
Manifest_Version mismatch |
Log, skip module, continue. |
| Zip extraction failure | Log, skip module, continue. |
| dlopen failure | Log, skip module, continue. |
Symbol in Implemented_Functions missing |
Warn for that name, register the others, partial load OK. |
aug_on_module_load returns non-zero |
Log; dispatch entries currently stay registered. See [Planned] cleanup in Roadmap. |
The loader logs and continues on every failure path; one broken module does not block the rest.