Skip to content

Brand Host

plant_runner provides the host-side command channel between a running WASM brand component and the forge runtime. These types have no dependency on wasmtime or any WIT bindings; they are the protocol boundary that plant_world uses to bridge its brand component hosting into the generic runner.

BrandHostCommand

pub enum BrandHostCommand {
    EnqueuePatch { hob_id: String, payload: String },
    ClockStep,
    ClockPause,
    ClockResume,
    ClockStatus,
}
Variant Triggered by
EnqueuePatch Brand component submits a state mutation for a specific hob
ClockStep Brand component requests a single frame advance
ClockPause Brand component requests the clock be paused
ClockResume Brand component requests the clock be resumed
ClockStatus Brand component queries current clock state

HostCommandSink

pub trait HostCommandSink: Send + Sync {
    fn submit(&self, cmd: BrandHostCommand) -> Result<(), String>;
}

HostCommandSink is the synchronous submission API exposed to WIT host import implementations. It is intentionally synchronous: the implementation is expected to enqueue work onto the async runtime (e.g. via tokio::sync::mpsc) rather than blocking the calling thread.

Architecture

WASM brand component
    |
    |  (WIT host import call)
    v
plant_world::brand_plugin::loader
    |  (calls submit())
    v
HostCommandSink implementation  (in plant_world or plant_runner host)
    |  (enqueues via mpsc)
    v
Async forge runner task
    |  (dispatches to FrameManager / clock)
    v
FrameManager / ClockState

The WIT bindgen block, BrandHostState, and BrandComponentService remain in plant_world::brand_plugin because they are bound to the plant:brand/plugin@0.1.0 WIT world. BrandHostCommand and HostCommandSink live in plant_runner because they represent the generic command surface that any forged kind's WASM host would need.

Usage

Implement HostCommandSink in the brand plugin layer:

struct MyCommandSink {
    tx: mpsc::Sender<BrandHostCommand>,
}

impl HostCommandSink for MyCommandSink {
    fn submit(&self, cmd: BrandHostCommand) -> Result<(), String> {
        self.tx.try_send(cmd).map_err(|e| e.to_string())
    }
}

Supply the sink to FrameManager so it can route brand commands:

frame_manager.set_command_sink(Arc::new(MyCommandSink { tx }));

Relationship to plant_world

plant_world::brand_plugin::loader imports these types via:

pub use plant_runner::{BrandHostCommand, HostCommandSink};

No duplicate definitions exist in world. The WIT-specific binding code around them remains in world because it references plant:brand/plugin@0.1.0 types that are not generic.