fusabi_plugin_runtime/
runtime.rs

1//! Plugin runtime for managing the plugin lifecycle.
2
3use 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/// Configuration for the plugin runtime.
15#[derive(Debug, Clone)]
16pub struct RuntimeConfig {
17    /// Loader configuration.
18    pub loader: LoaderConfig,
19    /// Registry configuration.
20    pub registry: RegistryConfig,
21    /// Plugin directories to scan.
22    pub plugin_dirs: Vec<PathBuf>,
23    /// Whether to auto-discover plugins.
24    pub auto_discover: bool,
25    /// File patterns to match for plugins.
26    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    /// Create a new runtime configuration.
47    pub fn new() -> Self {
48        Self::default()
49    }
50
51    /// Set the loader configuration.
52    pub fn with_loader(mut self, loader: LoaderConfig) -> Self {
53        self.loader = loader;
54        self
55    }
56
57    /// Set the registry configuration.
58    pub fn with_registry(mut self, registry: RegistryConfig) -> Self {
59        self.registry = registry;
60        self
61    }
62
63    /// Add a plugin directory.
64    pub fn with_plugin_dir(mut self, dir: impl Into<PathBuf>) -> Self {
65        self.plugin_dirs.push(dir.into());
66        self
67    }
68
69    /// Enable auto-discovery.
70    pub fn with_auto_discover(mut self, auto: bool) -> Self {
71        self.auto_discover = auto;
72        self
73    }
74
75    /// Set plugin patterns.
76    pub fn with_plugin_patterns(mut self, patterns: Vec<String>) -> Self {
77        self.plugin_patterns = patterns;
78        self
79    }
80}
81
82/// Plugin runtime for managing plugins.
83pub struct PluginRuntime {
84    config: RuntimeConfig,
85    loader: PluginLoader,
86    registry: PluginRegistry,
87    hooks: Arc<RwLock<LifecycleHooks>>,
88}
89
90impl PluginRuntime {
91    /// Create a new plugin runtime.
92    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    /// Create with default configuration.
105    pub fn default_config() -> Result<Self> {
106        Self::new(RuntimeConfig::default())
107    }
108
109    /// Get the runtime configuration.
110    pub fn config(&self) -> &RuntimeConfig {
111        &self.config
112    }
113
114    /// Get the plugin loader.
115    pub fn loader(&self) -> &PluginLoader {
116        &self.loader
117    }
118
119    /// Get the plugin registry.
120    pub fn registry(&self) -> &PluginRegistry {
121        &self.registry
122    }
123
124    /// Add a lifecycle event handler.
125    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    /// Load a plugin from a manifest file.
133    #[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    /// Load a plugin from source.
141    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    /// Load a plugin from bytecode.
148    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    /// Unload a plugin by name.
155    pub fn unload(&self, name: &str) -> Result<()> {
156        self.registry.unregister(name)?;
157        Ok(())
158    }
159
160    /// Get a plugin by name.
161    pub fn get(&self, name: &str) -> Option<PluginHandle> {
162        self.registry.get(name)
163    }
164
165    /// Check if a plugin is loaded.
166    pub fn has_plugin(&self, name: &str) -> bool {
167        self.registry.contains(name)
168    }
169
170    /// Get all loaded plugins.
171    pub fn plugins(&self) -> Vec<PluginHandle> {
172        self.registry.all()
173    }
174
175    /// Get running plugins.
176    pub fn running(&self) -> Vec<PluginHandle> {
177        self.registry.running()
178    }
179
180    /// Get plugin count.
181    pub fn plugin_count(&self) -> usize {
182        self.registry.len()
183    }
184
185    /// Get registry statistics.
186    pub fn stats(&self) -> RegistryStats {
187        self.registry.stats()
188    }
189
190    /// Start a plugin.
191    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    /// Stop a plugin.
204    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    /// Reload a plugin.
217    pub fn reload(&self, name: &str) -> Result<()> {
218        self.registry.reload(name)
219    }
220
221    /// Start all plugins.
222    pub fn start_all(&self) -> Vec<Result<()>> {
223        self.registry.start_all()
224    }
225
226    /// Stop all plugins.
227    pub fn stop_all(&self) -> Vec<Result<()>> {
228        self.registry.stop_all()
229    }
230
231    /// Reload all plugins.
232    pub fn reload_all(&self) -> Vec<Result<()>> {
233        self.registry.reload_all()
234    }
235
236    /// Discover and load plugins from configured directories.
237    #[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    /// Call a function on a plugin.
279    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    /// Broadcast a function call to all running plugins.
294    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    /// Clean up unloaded plugins.
312    pub fn cleanup(&self) -> usize {
313        self.registry.cleanup()
314    }
315
316    /// Shutdown the runtime.
317    pub fn shutdown(&self) {
318        // Stop all running plugins
319        self.stop_all();
320
321        // Unload all
322        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// glob is an optional dependency for discovery
372#[cfg(feature = "serde")]
373mod glob {
374    pub fn glob(pattern: &str) -> std::io::Result<impl Iterator<Item = std::io::Result<std::path::PathBuf>>> {
375        // Simplified glob implementation for testing
376        // In production, would use the actual glob crate
377        Ok(std::iter::empty())
378    }
379}