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.