arora-web
Run an Arora device inside a browser. Compiles arora (with
--no-default-features, so no wasmtime, no libloading) to
wasm32-unknown-unknown and exposes two things:
BrowserRuntime— the reusable Rust primitive that runs a full Arora runtime in the browser over an injected HAL, bridge, and data store (all trait objects), with a synchronousstep()and Value↔JSON store accessors. Each device ships a thin#[wasm_bindgen]cdylib that constructs its concrete parts and forwards to aBrowserRuntime— the wasm-bindgen boundary can't carryArc<dyn Trait>, so the generic core stays Rust-side.Engine/BehaviorTreeRunner— a lower-level JS surface for loading guest modules and running behavior trees directly on the engine.
BrowserRuntime (the primitive)
BrowserRuntime is a plain Rust type, not a JS class:
let store: = new;
let mut rt = start.await?;
rt.queue_behavior; // or rt.queue_groot_xml(xml)?
// drive from requestAnimationFrame / a Web Worker loop:
rt.step?; // -> bool (false once unregistered)
Its methods (step, set_value, write_values, read_values, snapshot,
drain_changes) all take/return JsValue or JSON strings, so a device's cdylib
forwards them as one-liners. Values cross the JS boundary as JSON in the Arora
Value vocabulary, e.g. {"f32": 0.75}. drain_changes is the poll-based
counterpart to a store subscription (JavaScript can't await the std channel
DataStore::subscribe delivers on), so call it right after step.
AroraRuntime is the bundled demo device built on BrowserRuntime: the
in-process fake HAL and bridge over a plain SimpleDataStore, driving native
behavior-tree nodes. Downstream devices (e.g. Vizij) ship their own wrapper the
same way.
Engine / BehaviorTreeRunner JS surface (see src/lib.rs):
class Engine {
constructor();
loadModule(headerJson: string, executable: Uint8Array): string; // returns module id; sync compile (< 8 MB in Chrome)
prepareModule(headerJson: string, executable: Uint8Array): Promise<void>; // async compile + instantiate, any size
loadPreparedModule(headerJson: string): string; // completes a prepareModule load; returns module id
call(callJson: string): string; // returns result JSON
listModules(): string; // returns JSON array of loaded module headers
}
class BehaviorTreeRunner {
constructor();
loadModule(headerJson: string, executable: Uint8Array): string;
prepareModule(headerJson: string, executable: Uint8Array): Promise<void>;
loadPreparedModule(headerJson: string): string;
listModules(): string; // returns JSON array of loaded module headers
setVariable(varId: string, valueJson: string): void;
tick(nodesJson: string): string; // returns {status, trace, variables}
run(nodesJson: string): string; // runs until not Running
}
loadModule compiles and instantiates synchronously, which Chrome rejects
above 8 MB on the main thread. The prepareModule + loadPreparedModule
pair routes through the async WebAssembly.instantiate and works for any
size in every browser.
callJson matches arora_engine::call::Call:
{"id":"<function-uuid>","args":[...]}. If the call doesn't carry a
module_id, arora-web looks one up from the function ID it was loaded
with.
Each node in nodesJson is:
,
"return_binding": "<var-uuid>"
}
When return_binding is set the function's raw return value is stored in
that variable and the node always reports success to its parent.
Build
Output lands under crates/arora-web/pkg/. To consume from a bundler
swap --target web for --target bundler.
Integration test (headless browser)
# First, force a wasm32-wasip1 build of the test guest module:
# Then run the wasm-bindgen-test in a headless browser:
GECKODRIVER=
# (or --chrome — see notes below)
The test loads test-rust-wasm.wasm through Engine.loadModule and
calls ping, asserting the round-trip works.
Browser pick:
wasm-packdownloads a pinnedchromedriver; if it doesn't match the locally installed Chrome it 404s. Firefox / geckodriver is more forgiving; CI uses--firefox.Apple Silicon: the
geckodriverwasm-pack auto-downloads is x86_64 and SIGABRTs under Rosetta with a "rosetta error: Attachment of code signature supplement failed" message. Install a native arm64 driver (brew install geckodriver) and point at it via theGECKODRIVERenv var (as shown above). Same idea forchromedrivervia theCHROMEDRIVERenv var.
Demo pages
# open http://localhost:8080 – Engine call demo (ping, succeed, cos)
# open http://localhost:8080/demo.html – BehaviorTreeRunner tick demo
serve.sh runs wasm-pack build, stages guest modules under
www/modules/<name>/, then starts python3 -m http.server.
demo.html shows a live behavior tree: each tick increments x by 0.1
via add(), then computes cos(x). Variables persist across ticks.
The SVG tree panel highlights node status; the side panel displays
variable values and a tick log.
Why a separate crate
arora is dual-target now (wasmtime-host and native-host features
default-on for native; gated out on wasm32, where the browser executor
takes over). arora-web exists purely to attach the wasm-bindgen
surface and keep host builds free of wasm-bindgen deps.