arora 11.4.0

Opinionated Arora runtime: an engine pre-wired with the native behavior-tree control nodes and the Semio backend.
# arora

The **opinionated Arora runtime**. Where [`arora-engine`](../arora-engine) is
the bare, unopinionated engine, this crate wires the whole device into one
object, [`Arora`]: the engine (with the WebAssembly and native executors), the
shared data store, the HAL and bridge I/O seams, one behavior interpreter, and
the step loop that drives them. The basic behavior-tree control nodes are
native in [`arora-behavior-tree`](../arora-behavior-tree), so no module needs
to be loaded to run a tree of them.

> **Architecture, with diagrams:** [`docs/runtime-and-data-flow.md`]docs/runtime-and-data-flow.md
> shows all of the device's components and how data flows between them, plus a
> sequence diagram of one `step`. It pairs with
> [arora-behavior's interpreter workflow]../arora-behavior/docs/interpreter-workflow.md.

## Build and run it

The crate is a default workspace member, so `cargo build` produces the `arora`
binary: the headless device runner — no HAL-attached display; a head like Vizij
embeds the library instead. It reads its configuration from the environment,
serves the open local bridge on `ws://127.0.0.1:9000` in the default build,
and connects to Semio Studio in a build with the `studio-bridge` feature. The
front end is the terminal operator UI when the process is attached to a
terminal (feature `tui`), headless otherwise.

```sh
cargo build                      # builds arora into target/debug/arora

# install a Groot behavior tree as the device's behavior, then serve
./target/debug/arora crates/arora/examples/hello_tree.groot.xml

# or just serve, waiting for a behavior over the bridge
./target/debug/arora

# load a module from a directory besides those in the device directory
./target/debug/arora --module node_modules/@vizij/animation-module/artifact
```

At start the device loads every module in its device directory's `modules/`
(the layout is [below](#where-a-device-keeps-its-data)), then each `--module`
directory. A module that cannot be loaded fails the start, naming it.

## Use it as a library

Build a device with the [builder](src/lib.rs): pick a data store, a HAL, zero
or more bridges, the modules whose functions behaviors may call, and the
behavior interpreter — each with a default, so `Arora::builder().build()` is a
self-contained in-process device (fake HAL, no bridge, an empty behavior, a
private `SimpleDataStore`). Drive it with `step` once per frame, or `run`, the
visible loop over `step`. The run family at the crate root is sugar over the
builder:

```rust,no_run
#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // The default device: in-process fake HAL, the open local bridge.
    arora::run().await
}
```

```rust,no_run
// A device over your hardware — the one call that turns a HAL into a running
// device. `run_with` also takes the bridge and the store; `run_with_frontend`
// the operator front end as well.
arora::run_with_hal(Box::new(my_hal)).await?;
```

Stopping a run is dropping its future: everything the run holds lives inside
it, so dropping it is a complete teardown, after which a host can build and run
a fresh device.

A device-specific Arora is built from the outside, with no fork and no
per-device feature flag: the [`device` example](examples/device.rs) provides
its own `Hal` and hands it to `run_with`; swap in a real robot HAL and the
studio-bridge connector and it is a real device build. `DeviceCli` is the clap
options the binary parses (the Groot file, `--open`, `--module`); flatten it
into a larger CLI and inject the results through the builder —
`DeviceCli::modules` reads the modules it names together with the device
directory's, each for `with_module`.

On wasm, build the library with `--no-default-features`: the `native` feature
carries the wasmtime and dynamic-library hosts, the operator flow, and the
binary.

## Where a device keeps its data

A device keeps what is its own from one run to the next in its **device
directory**, each use in a subdirectory of its own:

```text
<data_local_dir>/semio/arora/      per user: ~/Library/Application Support (macOS),
│                                  ~/.local/share (Linux), %LOCALAPPDATA% (Windows)
└── devices/
    └── <local id>/                the device directory: DEVICE_LOCAL_ID, `default` when unset;
        │                          DEVICE_DIR replaces this whole path
        ├── modules/               the modules the device loads at start, in name order
        │   └── <name>/            a module directory (what --module names elsewhere):
        │       ├── header.json    the module's header (its low-level Header, as JSON)
        │       └── *.wasm         its artifact: the one file with the executor's extension —
        │                          .wasm for wasm, .so / .dylib / .dll for native
        └── studio/                the Studio connection's credentials, kept by
            ├── key                arora-studio-bridge-client's DeviceCredentials
            └── refresh_token
```

The local id tells apart the devices one user runs on one host. It is local
only: the name a device shows (`DEVICE_NAME`) can change without making it
another device, and the id Studio knows it by is assigned at its first sign-in.
Anything else a device keeps goes in a subdirectory of its own beside these.

A module directory is the layout a module is published in: the
`@vizij/animation-module` package's `artifact/` — `header.json` beside
`vizij_animation_module.wasm` — copied to `modules/animation/` is loaded at the
next start. The artifact's name is free; its extension names it, so a module
directory holds one artifact. Entries whose name starts with `.` are neither
module directories nor artifacts: OS metadata (`.DS_Store`, an AppleDouble
`._x.wasm`) lands beside what it describes and is ignored. A module the device
cannot load — a header it cannot read, no artifact or several, an executor it
does not run, one module in two directories, an artifact the engine rejects —
fails the start, naming the module. A loaded module's functions are reachable
by any call — in-process (`Arora::call`), over a bridge — and `DescribeMethods`
lists them under one rule: a function whose parameters and return are all
primitives is described, any other still dispatches
([`module_discovery`](src/module_discovery.rs)). A Groot tree the binary
installs binds no module function: its nodes are the native control nodes.
[`module_dir`](src/module_dir.rs) reads module directories for an embedder.

The chain down to the bridge's code, in a `studio-bridge` build:

1. [`device_dir`]src/device_dir.rs resolves the device directory
   (`device_dir::from_env`): `DEVICE_DIR`; else `<IDENTITY_FILE>_dir` while
   that deprecated variable is set; else
   `device_dir::of(<DEVICE_LOCAL_ID, or default>)`. The per-user directory
   needs a home: a container whose user has no passwd entry sets `DEVICE_DIR`.
   An embedder whose platform gives it a data directory (Android, a Tauri app)
   passes its own to `studio::connect_with_device_dir`.
2. [`studio::credentials`]src/studio/credentials.rs names `<device dir>/studio`
   and hands it to `DeviceCredentials::in_dir`. arora never writes inside it;
   the files there are the client's.
3. `studio::connect` passes `DeviceCredentials::refresh_token()` and `saver()` to
   `ZenohDeviceClient::new`, whose Firebase authenticator signs the device in
   with the token and calls the saver with each rotated one.

Credentials from arora 11.0 and earlier, in `.semio/arora` under the
executable's directory, the home directory or the current directory, move into
`devices/default/studio/` at the device's first start. A deprecated
`IDENTITY_FILE` migrates to `<IDENTITY_FILE>_dir`, which that run uses as its
device directory.

See the [root map](../../readme.md) for where this sits in Arora.

[`Arora`]: src/lib.rs