openstranded-common-wasmcontract 0.2.0

OpenStranded WASM contract types — shared between engine and plugin-api
Documentation
use crate::{RegistryEntry, Service, ServiceError, Value};

/// Log severity level.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LogLevel {
    Debug,
    Info,
    Warn,
    Error,
}

/// Host-side API surface provided to WASM plugins.
///
/// During the build/ready/finish lifecycle phases, the plugin receives
/// a `&mut dyn GameAPI` to interact with the engine.
///
/// # Implementations
///
/// - **Host (engine):** [`GameAPIHost`] — real implementation backed by ECS, Registry, ServiceRegistry.
/// - **Test:** `MockGameAPI` (in `openstranded-plugin-api` behind `test-utils` feature)
///   for unit-testing plugins natively.
///
/// # Object safety
///
/// This trait is not object-safe due to `get_service()` returning `&dyn Service`.
/// In practice, callers use `&mut dyn GameAPI` or concrete types.
/// WASM plugins access the API through host import functions, not through this trait.
pub trait GameAPI {
    // ── Registry (content pack data) ──────────────────────────────

    /// Get all file entries for a domain.
    fn registry_domain(&self, name: &str) -> Result<Vec<RegistryEntry>, ServiceError>;

    /// Get raw bytes of a specific file within a domain.
    fn registry_file(&self, domain: &str, filename: &str) -> Result<Vec<u8>, ServiceError>;

    // ── Content file access ──────────────────────────────────────

    /// Read any file from the active Content Pack by relative path.
    ///
    /// Useful for loading assets (`.glb`, `.png`, `.wav`) that are not in
    /// the Registry but are referenced by path.
    fn read_content_file(&self, path: &str) -> Result<Vec<u8>, ServiceError>;

    // ── Services (cross-plugin communication) ────────────────────

    /// Register a service under the given domain name.
    fn register_service(
        &mut self,
        domain: &str,
        service: Box<dyn Service>,
    ) -> Result<(), ServiceError>;

    /// Get a reference to a registered service (native use only).
    fn get_service(&self, domain: &str) -> Result<&dyn Service, ServiceError>;

    /// Call a method on another plugin's service (WASM-friendly).
    ///
    /// This is the primary way for WASM plugins to communicate.
    /// The call is serialized, sent to the host, and dispatched to the
    /// target plugin.
    fn call_service(
        &self,
        domain: &str,
        method: &str,
        args: &[Value],
    ) -> Result<Value, ServiceError>;

    /// Check whether a service domain is registered.
    fn has_service(&self, domain: &str) -> bool;

    // ── ECS bridge (buffered writes) ─────────────────────────────

    /// Read a component from an entity as raw serialised bytes.
    fn get_component(&self, entity: u64, type_name: &str) -> Result<Vec<u8>, ServiceError>;

    /// Buffer a component write. Applied atomically at the flush point.
    fn set_component(
        &mut self,
        entity: u64,
        type_name: &str,
        data: &[u8],
    ) -> Result<(), ServiceError>;

    /// Find all entities that have a component with the given type name.
    fn query_entities(&self, component_name: &str) -> Result<Vec<u64>, ServiceError>;

    /// Buffer a spawn request. Returns the new entity ID after flush.
    fn spawn_entity(&mut self, archetype: &str) -> Result<u64, ServiceError>;

    /// Buffer a despawn request.
    fn despawn_entity(&mut self, entity: u64) -> Result<(), ServiceError>;

    // ── Configuration ────────────────────────────────────────────

    /// Read an engine config value by dotted key path.
    ///
    /// Examples: `"audio.master_volume"`, `"window.width"`.
    /// Returns `Null` if the key does not exist (not an error).
    fn read_config(&self, key: &str) -> Result<Value, ServiceError>;

    /// Read the full keybinds table as a `Map<String, String>`.
    fn read_keybinds(&self) -> Result<Value, ServiceError>;

    // ── Save / Load ──────────────────────────────────────────────

    /// Register a named blob of save data for the current save slot.
    ///
    /// Called in response to a `"save"` event. The engine collects
    /// all domains and writes them to disk.
    fn register_save_data(
        &mut self,
        domain: &str,
        data: Vec<u8>,
    ) -> Result<(), ServiceError>;

    /// Load a previously saved data blob by domain name.
    ///
    /// Returns `None` if no data was saved under this domain.
    fn load_save_data(&self, domain: &str) -> Result<Option<Vec<u8>>, ServiceError>;

    // ── Events ───────────────────────────────────────────────────

    /// Emit a named event with attached data.
    ///
    /// The event is broadcast to all registered handlers:
    /// - WASM plugins with a matching `on_event` export are called
    /// - Lua hooks registered via `core_api.on()` are triggered
    fn emit_event(&self, name: &str, data: &Value) -> Result<(), ServiceError>;

    // ── Logging ──────────────────────────────────────────────────

    /// Log a message at the given level.
    fn log(&self, level: LogLevel, message: &str);
}