fusabi_plugin_runtime/
runtime.rs1use std::path::PathBuf;
4use std::sync::Arc;
5
6use parking_lot::RwLock;
7
8use crate::error::{Error, Result};
9use crate::lifecycle::{LifecycleHooks, LifecycleState};
10use crate::loader::{LoaderConfig, PluginLoader};
11use crate::plugin::PluginHandle;
12use crate::registry::{PluginRegistry, RegistryConfig, RegistryStats};
13
14#[derive(Debug, Clone)]
16pub struct RuntimeConfig {
17 pub loader: LoaderConfig,
19 pub registry: RegistryConfig,
21 pub plugin_dirs: Vec<PathBuf>,
23 pub auto_discover: bool,
25 pub plugin_patterns: Vec<String>,
27}
28
29impl Default for RuntimeConfig {
30 fn default() -> Self {
31 Self {
32 loader: LoaderConfig::default(),
33 registry: RegistryConfig::default(),
34 plugin_dirs: Vec::new(),
35 auto_discover: false,
36 plugin_patterns: vec![
37 "*.toml".to_string(),
38 "plugin.toml".to_string(),
39 "fusabi.toml".to_string(),
40 ],
41 }
42 }
43}
44
45impl RuntimeConfig {
46 pub fn new() -> Self {
48 Self::default()
49 }
50
51 pub fn with_loader(mut self, loader: LoaderConfig) -> Self {
53 self.loader = loader;
54 self
55 }
56
57 pub fn with_registry(mut self, registry: RegistryConfig) -> Self {
59 self.registry = registry;
60 self
61 }
62
63 pub fn with_plugin_dir(mut self, dir: impl Into<PathBuf>) -> Self {
65 self.plugin_dirs.push(dir.into());
66 self
67 }
68
69 pub fn with_auto_discover(mut self, auto: bool) -> Self {
71 self.auto_discover = auto;
72 self
73 }
74
75 pub fn with_plugin_patterns(mut self, patterns: Vec<String>) -> Self {
77 self.plugin_patterns = patterns;
78 self
79 }
80}
81
82pub struct PluginRuntime {
84 config: RuntimeConfig,
85 loader: PluginLoader,
86 registry: PluginRegistry,
87 hooks: Arc<RwLock<LifecycleHooks>>,
88}
89
90impl PluginRuntime {
91 pub fn new(config: RuntimeConfig) -> Result<Self> {
93 let loader = PluginLoader::new(config.loader.clone())?;
94 let registry = PluginRegistry::new(config.registry.clone());
95
96 Ok(Self {
97 config,
98 loader,
99 registry,
100 hooks: Arc::new(RwLock::new(LifecycleHooks::new())),
101 })
102 }
103
104 pub fn default_config() -> Result<Self> {
106 Self::new(RuntimeConfig::default())
107 }
108
109 pub fn config(&self) -> &RuntimeConfig {
111 &self.config
112 }
113
114 pub fn loader(&self) -> &PluginLoader {
116 &self.loader
117 }
118
119 pub fn registry(&self) -> &PluginRegistry {
121 &self.registry
122 }
123
124 pub fn on_event<F>(&self, handler: F)
126 where
127 F: Fn(&crate::lifecycle::LifecycleEvent) + Send + Sync + 'static,
128 {
129 self.hooks.write().on_event(handler);
130 }
131
132 #[cfg(feature = "serde")]
134 pub fn load_manifest(&self, path: impl Into<PathBuf>) -> Result<PluginHandle> {
135 let plugin = self.loader.load_from_manifest(path.into())?;
136 self.registry.register(plugin.clone())?;
137 Ok(plugin)
138 }
139
140 pub fn load_source(&self, path: impl Into<PathBuf>) -> Result<PluginHandle> {
142 let plugin = self.loader.load_source(path.into())?;
143 self.registry.register(plugin.clone())?;
144 Ok(plugin)
145 }
146
147 pub fn load_bytecode(&self, path: impl Into<PathBuf>) -> Result<PluginHandle> {
149 let plugin = self.loader.load_bytecode_file(path.into())?;
150 self.registry.register(plugin.clone())?;
151 Ok(plugin)
152 }
153
154 pub fn unload(&self, name: &str) -> Result<()> {
156 self.registry.unregister(name)?;
157 Ok(())
158 }
159
160 pub fn get(&self, name: &str) -> Option<PluginHandle> {
162 self.registry.get(name)
163 }
164
165 pub fn has_plugin(&self, name: &str) -> bool {
167 self.registry.contains(name)
168 }
169
170 pub fn plugins(&self) -> Vec<PluginHandle> {
172 self.registry.all()
173 }
174
175 pub fn running(&self) -> Vec<PluginHandle> {
177 self.registry.running()
178 }
179
180 pub fn plugin_count(&self) -> usize {
182 self.registry.len()
183 }
184
185 pub fn stats(&self) -> RegistryStats {
187 self.registry.stats()
188 }
189
190 pub fn start(&self, name: &str) -> Result<()> {
192 let plugin = self
193 .registry
194 .get(name)
195 .ok_or_else(|| Error::plugin_not_found(name))?;
196
197 plugin.inner().start()?;
198 self.hooks.read().emit_started(name);
199
200 Ok(())
201 }
202
203 pub fn stop(&self, name: &str) -> Result<()> {
205 let plugin = self
206 .registry
207 .get(name)
208 .ok_or_else(|| Error::plugin_not_found(name))?;
209
210 plugin.inner().stop()?;
211 self.hooks.read().emit_stopped(name);
212
213 Ok(())
214 }
215
216 pub fn reload(&self, name: &str) -> Result<()> {
218 self.registry.reload(name)
219 }
220
221 pub fn start_all(&self) -> Vec<Result<()>> {
223 self.registry.start_all()
224 }
225
226 pub fn stop_all(&self) -> Vec<Result<()>> {
228 self.registry.stop_all()
229 }
230
231 pub fn reload_all(&self) -> Vec<Result<()>> {
233 self.registry.reload_all()
234 }
235
236 #[cfg(feature = "serde")]
238 pub fn discover(&self) -> Result<Vec<PluginHandle>> {
239 let mut loaded = Vec::new();
240
241 for dir in &self.config.plugin_dirs {
242 if !dir.exists() {
243 tracing::warn!("Plugin directory does not exist: {}", dir.display());
244 continue;
245 }
246
247 for pattern in &self.config.plugin_patterns {
248 let glob_pattern = dir.join(pattern);
249 let glob_str = glob_pattern.to_string_lossy();
250
251 if let Ok(entries) = glob::glob(&glob_str) {
252 for entry in entries.flatten() {
253 match self.load_manifest(&entry) {
254 Ok(plugin) => {
255 tracing::info!(
256 "Loaded plugin {} from {}",
257 plugin.name(),
258 entry.display()
259 );
260 loaded.push(plugin);
261 }
262 Err(e) => {
263 tracing::error!(
264 "Failed to load plugin from {}: {}",
265 entry.display(),
266 e
267 );
268 }
269 }
270 }
271 }
272 }
273 }
274
275 Ok(loaded)
276 }
277
278 pub fn call(
280 &self,
281 plugin_name: &str,
282 function: &str,
283 args: &[fusabi_host::Value],
284 ) -> Result<fusabi_host::Value> {
285 let plugin = self
286 .registry
287 .get(plugin_name)
288 .ok_or_else(|| Error::plugin_not_found(plugin_name))?;
289
290 plugin.call(function, args)
291 }
292
293 pub fn broadcast(
295 &self,
296 function: &str,
297 args: &[fusabi_host::Value],
298 ) -> Vec<(String, Result<fusabi_host::Value>)> {
299 self.registry
300 .running()
301 .into_iter()
302 .filter(|p| p.has_export(function))
303 .map(|p| {
304 let name = p.name();
305 let result = p.call(function, args);
306 (name, result)
307 })
308 .collect()
309 }
310
311 pub fn cleanup(&self) -> usize {
313 self.registry.cleanup()
314 }
315
316 pub fn shutdown(&self) {
318 self.stop_all();
320
321 self.registry.unload_all();
323 }
324}
325
326impl std::fmt::Debug for PluginRuntime {
327 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
328 f.debug_struct("PluginRuntime")
329 .field("config", &self.config)
330 .field("plugin_count", &self.registry.len())
331 .finish()
332 }
333}
334
335impl Drop for PluginRuntime {
336 fn drop(&mut self) {
337 self.shutdown();
338 }
339}
340
341#[cfg(test)]
342mod tests {
343 use super::*;
344
345 #[test]
346 fn test_runtime_creation() {
347 let runtime = PluginRuntime::default_config().unwrap();
348 assert_eq!(runtime.plugin_count(), 0);
349 }
350
351 #[test]
352 fn test_runtime_config_builder() {
353 let config = RuntimeConfig::new()
354 .with_plugin_dir("/plugins")
355 .with_auto_discover(true);
356
357 assert_eq!(config.plugin_dirs.len(), 1);
358 assert!(config.auto_discover);
359 }
360
361 #[test]
362 fn test_runtime_stats() {
363 let runtime = PluginRuntime::default_config().unwrap();
364 let stats = runtime.stats();
365
366 assert_eq!(stats.total, 0);
367 assert_eq!(stats.running, 0);
368 }
369}
370
371#[cfg(feature = "serde")]
373mod glob {
374 pub fn glob(pattern: &str) -> std::io::Result<impl Iterator<Item = std::io::Result<std::path::PathBuf>>> {
375 Ok(std::iter::empty())
378 }
379}