Skip to main content

palladium_plugin/
plugin_rpc.rs

1//! [`PluginRpcHandler`] implementation for [`PluginRegistry`].
2//!
3//! Wraps a `Arc<Mutex<PluginRegistry>>` so that the running engine's control
4//! plane can forward `plugin.*` JSON-RPC calls to the registry without a
5//! hard dependency from `pd-runtime` on `pd-plugin`.
6
7use serde_json::{json, Value};
8use std::sync::{Arc, Mutex};
9
10use palladium_runtime::PluginRpcHandler;
11
12use crate::manifest::PluginKind;
13use crate::registry::PluginRegistry;
14use crate::wasm::WasmHost;
15
16/// A `PluginRpcHandler` backed by a locked `PluginRegistry`.
17///
18/// Each RPC call acquires the mutex for the duration of the operation.
19/// For `plugin.reload`, the handler re-loads from the same path that was
20/// recorded at `plugin.load` time; see [`RegistryRpcHandler::with_wasm_host`].
21pub struct RegistryRpcHandler<R: palladium_runtime::Reactor> {
22    registry: Arc<Mutex<PluginRegistry<R>>>,
23    /// Path map: plugin name → original file path (for reload).
24    paths: Arc<Mutex<std::collections::HashMap<String, std::path::PathBuf>>>,
25    /// WASM host used when loading `.wasm` plugins (None = native-only).
26    wasm_host: Option<Arc<dyn WasmHost>>,
27}
28
29impl<R: palladium_runtime::Reactor> RegistryRpcHandler<R> {
30    /// Create a handler backed by an empty registry.
31    pub fn new() -> Self {
32        Self {
33            registry: Arc::new(Mutex::new(PluginRegistry::new())),
34            paths: Arc::new(Mutex::new(std::collections::HashMap::new())),
35            wasm_host: None,
36        }
37    }
38
39    /// Attach an existing registry (e.g., pre-loaded with static plugins).
40    pub fn with_registry(registry: Arc<Mutex<PluginRegistry<R>>>) -> Self {
41        Self {
42            registry,
43            paths: Arc::new(Mutex::new(std::collections::HashMap::new())),
44            wasm_host: None,
45        }
46    }
47
48    /// Enable WASM plugin loading with the given `WasmHost`.
49    pub fn with_wasm_host(mut self, host: Arc<dyn WasmHost>) -> Self {
50        self.wasm_host = Some(host);
51        self
52    }
53
54    /// Expose the underlying registry for spawning actors.
55    pub fn registry(&self) -> Arc<Mutex<PluginRegistry<R>>> {
56        Arc::clone(&self.registry)
57    }
58}
59
60impl<R: palladium_runtime::Reactor> Default for RegistryRpcHandler<R> {
61    fn default() -> Self {
62        Self::new()
63    }
64}
65
66impl<R: palladium_runtime::Reactor> PluginRpcHandler<R> for RegistryRpcHandler<R> {
67    fn list_plugins(&self) -> Vec<Value> {
68        let reg = self.registry.lock().unwrap();
69        let infos = reg.list();
70        let paths = self.paths.lock().unwrap().clone();
71        infos
72            .into_iter()
73            .map(|info| {
74                let actor_types = reg.actor_types_for_plugin(&info.name);
75                let path = paths
76                    .get(&info.name)
77                    .map(|p| p.to_string_lossy().into_owned());
78                json!({
79                    "name": info.name,
80                    "version": info.version,
81                    "kind": match info.kind {
82                        PluginKind::Native => "native",
83                        PluginKind::Wasm   => "wasm",
84                    },
85                    "actor_type_count": info.actor_type_count,
86                    "actor_types": actor_types,
87                    "path": path,
88                })
89            })
90            .collect()
91    }
92
93    fn load_plugin(&self, path_str: &str) -> Result<Value, String> {
94        let path = std::path::Path::new(path_str);
95        let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
96
97        let (info, actor_types) = {
98            let mut reg = self.registry.lock().unwrap();
99            let info = if ext == "wasm" {
100                let host = self
101                    .wasm_host
102                    .as_ref()
103                    .ok_or_else(|| "WASM host not configured".to_string())?;
104                reg.load_wasm(path, Arc::clone(host))
105                    .map_err(|e: crate::error::PluginError| e.to_string())?
106            } else {
107                reg.load_native(path)
108                    .map_err(|e: crate::error::PluginError| e.to_string())?
109            };
110            let actor_types = reg.actor_types_for_plugin(&info.name);
111            (info, actor_types)
112        };
113
114        // Record the path for later reload.
115        self.paths
116            .lock()
117            .unwrap()
118            .insert(info.name.clone(), path.to_path_buf());
119
120        Ok(json!({
121            "name": info.name,
122            "version": info.version,
123            "kind": match info.kind {
124                PluginKind::Native => "native",
125                PluginKind::Wasm   => "wasm",
126            },
127            "actor_type_count": info.actor_type_count,
128            "actor_types": actor_types,
129            "path": path.to_string_lossy().to_string(),
130        }))
131    }
132
133    fn unload_plugin(&self, name: &str) -> Result<(), String> {
134        self.registry
135            .lock()
136            .unwrap()
137            .unload(name)
138            .map_err(|e: crate::error::PluginError| e.to_string())?;
139        self.paths.lock().unwrap().remove(name);
140        Ok(())
141    }
142
143    fn reload_plugin(&self, name: &str) -> Result<Value, String> {
144        let path = self
145            .paths
146            .lock()
147            .unwrap()
148            .get(name)
149            .cloned()
150            .ok_or_else(|| format!("No recorded path for plugin '{name}' — load it first"))?;
151
152        // Unload first; ignore error if not loaded (idempotent reload).
153        let _ = self.registry.lock().unwrap().unload(name);
154
155        // Re-load from the same path.
156        self.load_plugin(path.to_str().unwrap_or(""))
157    }
158
159    fn spawn_actor(
160        &self,
161        type_name: &str,
162        config: &[u8],
163    ) -> Result<Box<dyn palladium_actor::Actor<R>>, String> {
164        let reg = self.registry.lock().unwrap();
165        reg.create_actor(type_name, config)
166            .map_err(|e: crate::error::PluginError| e.to_string())
167    }
168}