Skip to main content

homecore_plugins/
runtime.rs

1//! `PluginRuntime` trait + `InProcessRuntime` (P1).
2//!
3//! Abstracts over Wasmtime (P2, `--features wasmtime`) and native in-process
4//! Rust plugins (P1, always-on). Constrained builds use the compiled-in native
5//! registry rather than an unaudited interpreter backend.
6//!
7//! # Architecture
8//!
9//! ```text
10//! PluginRegistry
11//!       │
12//!       ▼
13//! PluginRuntime  ◄─── InProcessRuntime  (P1, native Rust, <1 µs call)
14//!                ◄─── WasmtimeRuntime   (P2, Cranelift JIT, ~5 ms cold start)
15//! ```
16
17use std::sync::Arc;
18
19use async_trait::async_trait;
20use homecore::HomeCore;
21
22use crate::error::PluginError;
23use crate::manifest::PluginManifest;
24use crate::plugin::{HomeCorePlugin, PluginId};
25use crate::StateChangedEventJson;
26
27/// A loaded plugin handle — returned by [`PluginRuntime::load`].
28pub struct LoadedPlugin {
29    pub id: PluginId,
30    pub manifest: PluginManifest,
31    /// Underlying plugin instance (boxed trait object).
32    pub(crate) instance: Arc<dyn HomeCorePlugin>,
33}
34
35impl LoadedPlugin {
36    /// Delegate to the inner plugin's `setup` method.
37    pub async fn setup(&self, hc: HomeCore) -> Result<(), PluginError> {
38        self.instance.setup(hc).await
39    }
40
41    /// Delegate to the inner plugin's `unload` method.
42    pub async fn unload(&self) -> Result<(), PluginError> {
43        self.instance.unload().await
44    }
45
46    /// Dispatch a committed state change to this plugin.
47    pub async fn state_changed(&self, event: &StateChangedEventJson) -> Result<(), PluginError> {
48        self.instance.state_changed(event).await
49    }
50}
51
52/// Abstraction over the WASM (and native) plugin execution environment.
53///
54/// P2 will supply a `WasmtimeRuntime` that compiles `.wasm` bytes with
55/// Cranelift; P3 adds a `Wasm3Runtime` for constrained targets. Both will
56/// implement this trait so the registry is runtime-agnostic.
57#[async_trait]
58pub trait PluginRuntime: Send + Sync + 'static {
59    /// Load a plugin from a boxed [`HomeCorePlugin`] implementation and a
60    /// parsed `PluginManifest`. Returns a `LoadedPlugin` handle.
61    async fn load(
62        &self,
63        id: PluginId,
64        manifest: PluginManifest,
65        plugin: Arc<dyn HomeCorePlugin>,
66    ) -> Result<LoadedPlugin, PluginError>;
67}
68
69/// Native in-process runtime — loads first-party Rust plugins directly.
70///
71/// No WASM compilation; no sandbox. Intended for first-party plugins
72/// (RuView MQTT bridge, presence sensor, etc.) that are compiled into the
73/// HOMECORE binary and therefore trusted. Third-party / community plugins
74/// must use the `WasmtimeRuntime` (P2) for isolation.
75pub struct InProcessRuntime;
76
77#[async_trait]
78impl PluginRuntime for InProcessRuntime {
79    async fn load(
80        &self,
81        id: PluginId,
82        manifest: PluginManifest,
83        plugin: Arc<dyn HomeCorePlugin>,
84    ) -> Result<LoadedPlugin, PluginError> {
85        Ok(LoadedPlugin {
86            id,
87            manifest,
88            instance: plugin,
89        })
90    }
91}
92
93// ── Feature-gated Wasmtime implementation (P2) ───────────────────────────
94//
95// The full `WasmtimeRuntime` lives in `crate::wasmtime_runtime` (P2).
96// It is re-exported from `crate::lib` as `WasmtimeRuntime` when the
97// `wasmtime` feature is enabled.  The `PluginRuntime` trait below is
98// kept intentionally narrow (in-process plugin contract) so the WASM
99// path can use its own `WasmPlugin` wrapper without forcing the trait
100// to carry WASM-specific concerns.