← All projects
AuthoringIn production

tonari

A thin wrapper over izumi that adds the one thing a compiled binary normally cannot have: knobs someone else can turn.

v0.4.8
Language
Rust → wasm32-unknown-unknown
Wraps
izumi
Variable types
Float · Int · Bool · Color · String
Distribution
Versioned .wasm in object storage

01Overview

An animation is a program, which is exactly why it is hard to hand to the person writing the lesson. tonari closes that gap. An animation implements a second trait alongside izumi’s, declaring the variables it exposes — floats, ints, booleans, colours and strings, each with a label, a default, and a range and step where they apply.

A macro then generates the WebAssembly exports that let a host enumerate those variables, allocate memory to pass values across, and apply them. The editor renders a form from the declaration, the saved values ride along with the lesson, and the player applies them at load. The Rust is never touched, and the module is never rebuilt.

Modules are versioned and reported alongside the izumi and tonari versions they were built against, so a host can tell exactly what it is running. Beyond that, tonari carries the shared visual layer the lesson modules draw from — a common palette, UI helpers, and baked figure geometry for biology, with each anatomy atlas behind its own cargo feature so a module links only the geometry it actually renders.

02Usage

Implement TonariApp alongside izumi’s App, then swap the macro. Everything else is generated.

Declaring variables
use tonari::prelude::*;
use tonari::variables::{TonariApp, VariableDef, VariableType, VariableValue};

impl TonariApp for MyScene {
    fn module_version() -> &'static str { "1.2.0" }

    fn list_variables() -> Vec<VariableDef> {
        vec![
            VariableDef {
                name: "speed",
                label: "Animation speed",
                var_type: VariableType::Float,
                default: VariableValue::Float(1.0),
                min: Some(0.1),
                max: Some(4.0),
                step: Some(0.1),
            },
            VariableDef {
                name: "accent",
                label: "Accent colour",
                var_type: VariableType::Color,
                default: VariableValue::Color(0x50B4FF),
                min: None, max: None, step: None,
            },
        ]
    }

    // Called by the host whenever a value changes; marks the scene dirty.
    fn set_variable(&mut self, name: &str, value: VariableValue) {
        match (name, value) {
            ("speed", VariableValue::Float(v)) => self.speed = v,
            ("accent", VariableValue::Color(v)) => self.accent = unpack(v),
            _ => {}
        }
    }
}

tonari_main!(MyScene);
What the host sees
[
  { "name": "speed",  "label": "Animation speed", "type": "float",
    "default": 1.0, "min": 0.1, "max": 4.0, "step": 0.1 },
  { "name": "accent", "label": "Accent colour",   "type": "color",
    "default": "#50B4FF" }
]

03Technologies

The variable system
variables.rs

Five types, each with a label for the UI and bounds where they mean something. Colours cross the boundary as a packed integer; strings and everything else as UTF-8 through an allocated buffer the host frees when it is done.

Generated exports
macros.rs

One macro expands to every izumi export plus allocation and free, a versions blob, the variable declaration list, and the setter — which applies the value and marks the scene dirty so the next frame actually redraws.

Version reporting
version.rs

The module reports its own version alongside the izumi and tonari versions it was compiled against, so a mismatch between an editor’s expectations and a deployed module is visible rather than mysterious.

Shared visual layer
palette · ui · figure atlases

The common palette and UI helpers every lesson module draws from, plus baked anatomy geometry for biology. Each figure sits behind its own cargo feature, all off by default, so a module’s binary carries only the geometry it renders.