Skip to content

Frame Manager

FrameManager is the runtime orchestrator that drives the FramePhase lifecycle and dispatches registered ForgeService implementations on each tick. It now lives in plant_runner and is imported into plant_world via a thin shim.

For background on frame phases and service dispatch ordering, see Frame Lifecycle.

FrameManager

pub struct FrameManager { /* async inner state */ }

FrameManager is the primary entry point for hosts. It manages:

  • The current FramePhase and FrameState
  • The ordered set of registered services (RegisteredService)
  • The HookDispatcher for string-keyed lifecycle callbacks
  • Frame history and dust accounting

Key Methods

impl FrameManager {
    pub fn new(config: FrameLifecycleConfig) -> Self;

    /// Register a ForgeService implementation. Services are dispatched
    /// in ascending priority order within each phase.
    pub async fn register_service(
        &mut self,
        service: Box<dyn ForgeService>,
    ) -> Result<()>;

    /// Advance one full frame (BeginningFrame -> ... -> Idle).
    pub async fn tick(&mut self) -> Result<FrameId>;

    /// Describe all currently registered services (for diagnostics).
    pub fn service_descriptors(&self) -> Vec<ServiceDescriptor>;
}

Construction

let config = FrameLifecycleConfig::default();
let mut fm = FrameManager::new(config);

// Register services before the first tick
fm.register_service(Box::new(HobService::new())).await?;
fm.register_service(Box::new(region_forge_service)).await?;

RegisteredService

pub struct RegisteredService {
    pub descriptor: ServiceDescriptor,
    pub service: Box<dyn ForgeService>,
}

Holds both the descriptor (for inspection) and the live service (for dispatch). RegisteredService records are kept sorted by priority.

ServiceDescriptor

#[derive(Debug, Clone, Serialize)]
pub struct ServiceDescriptor {
    pub kind: String,
    pub priority: u32,
    pub frame_budget: u32,
    pub active_interval_frames: u64,
}

Returned by FrameManager::service_descriptors() for external health checks and diagnostics. The kind field equals the service's service_name() return value.

active_interval_frames controls how often on_frame_active fires for that service. A value of 1 means every frame; 10 means every 10th frame. Other hooks (on_frame_init, on_frame_aggregation, on_frame_cleanup) always fire every frame regardless of this setting.

The default is DEFAULT_SERVICE_INTERVAL:

pub const DEFAULT_SERVICE_INTERVAL: u64 = 1;

BrandPatchSink

pub trait BrandPatchSink: Send + Sync {
    fn enqueue_input_patch(&self, hob_id: String, payload: String) -> Result<(), String>;
}

BrandPatchSink decouples FrameManager from the brand plugin feature. The manager holds an optional Arc<dyn BrandPatchSink> and calls it when brand commands are received, without taking a direct dependency on wasmtime or any WIT bindings.

Implementations live in plant_world::brand_plugin and are injected into the manager during host startup:

frame_manager.set_patch_sink(Arc::new(my_brand_host));

Relationship to plant_world

plant_world imports FrameManager via a shim:

// world/src/frame_manager.rs
pub use plant_runner::frame_manager::*;

Call sites in plant_world that use FrameManager, RegisteredService, ServiceDescriptor, BrandPatchSink, or DEFAULT_SERVICE_INTERVAL compile against plant_runner types transparently.