use std::path::PathBuf;
use std::sync::Arc;
use parking_lot::RwLock;
use crate::error::{Error, Result};
use crate::lifecycle::{LifecycleHooks, LifecycleState};
use crate::loader::{LoaderConfig, PluginLoader};
use crate::plugin::PluginHandle;
use crate::registry::{PluginRegistry, RegistryConfig, RegistryStats};
#[derive(Debug, Clone)]
pub struct RuntimeConfig {
pub loader: LoaderConfig,
pub registry: RegistryConfig,
pub plugin_dirs: Vec<PathBuf>,
pub auto_discover: bool,
pub plugin_patterns: Vec<String>,
}
impl Default for RuntimeConfig {
fn default() -> Self {
Self {
loader: LoaderConfig::default(),
registry: RegistryConfig::default(),
plugin_dirs: Vec::new(),
auto_discover: false,
plugin_patterns: vec![
"*.toml".to_string(),
"plugin.toml".to_string(),
"fusabi.toml".to_string(),
],
}
}
}
impl RuntimeConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_loader(mut self, loader: LoaderConfig) -> Self {
self.loader = loader;
self
}
pub fn with_registry(mut self, registry: RegistryConfig) -> Self {
self.registry = registry;
self
}
pub fn with_plugin_dir(mut self, dir: impl Into<PathBuf>) -> Self {
self.plugin_dirs.push(dir.into());
self
}
pub fn with_auto_discover(mut self, auto: bool) -> Self {
self.auto_discover = auto;
self
}
pub fn with_plugin_patterns(mut self, patterns: Vec<String>) -> Self {
self.plugin_patterns = patterns;
self
}
}
pub struct PluginRuntime {
config: RuntimeConfig,
loader: PluginLoader,
registry: PluginRegistry,
hooks: Arc<RwLock<LifecycleHooks>>,
}
impl PluginRuntime {
pub fn new(config: RuntimeConfig) -> Result<Self> {
let loader = PluginLoader::new(config.loader.clone())?;
let registry = PluginRegistry::new(config.registry.clone());
Ok(Self {
config,
loader,
registry,
hooks: Arc::new(RwLock::new(LifecycleHooks::new())),
})
}
pub fn default_config() -> Result<Self> {
Self::new(RuntimeConfig::default())
}
pub fn config(&self) -> &RuntimeConfig {
&self.config
}
pub fn loader(&self) -> &PluginLoader {
&self.loader
}
pub fn registry(&self) -> &PluginRegistry {
&self.registry
}
pub fn on_event<F>(&self, handler: F)
where
F: Fn(&crate::lifecycle::LifecycleEvent) + Send + Sync + 'static,
{
self.hooks.write().on_event(handler);
}
#[cfg(feature = "serde")]
pub fn load_manifest(&self, path: impl Into<PathBuf>) -> Result<PluginHandle> {
let plugin = self.loader.load_from_manifest(path.into())?;
self.registry.register(plugin.clone())?;
Ok(plugin)
}
pub fn load_source(&self, path: impl Into<PathBuf>) -> Result<PluginHandle> {
let plugin = self.loader.load_source(path.into())?;
self.registry.register(plugin.clone())?;
Ok(plugin)
}
pub fn load_bytecode(&self, path: impl Into<PathBuf>) -> Result<PluginHandle> {
let plugin = self.loader.load_bytecode_file(path.into())?;
self.registry.register(plugin.clone())?;
Ok(plugin)
}
pub fn unload(&self, name: &str) -> Result<()> {
self.registry.unregister(name)?;
Ok(())
}
pub fn get(&self, name: &str) -> Option<PluginHandle> {
self.registry.get(name)
}
pub fn has_plugin(&self, name: &str) -> bool {
self.registry.contains(name)
}
pub fn plugins(&self) -> Vec<PluginHandle> {
self.registry.all()
}
pub fn running(&self) -> Vec<PluginHandle> {
self.registry.running()
}
pub fn plugin_count(&self) -> usize {
self.registry.len()
}
pub fn stats(&self) -> RegistryStats {
self.registry.stats()
}
pub fn start(&self, name: &str) -> Result<()> {
let plugin = self
.registry
.get(name)
.ok_or_else(|| Error::plugin_not_found(name))?;
plugin.inner().start()?;
self.hooks.read().emit_started(name);
Ok(())
}
pub fn stop(&self, name: &str) -> Result<()> {
let plugin = self
.registry
.get(name)
.ok_or_else(|| Error::plugin_not_found(name))?;
plugin.inner().stop()?;
self.hooks.read().emit_stopped(name);
Ok(())
}
pub fn reload(&self, name: &str) -> Result<()> {
self.registry.reload(name)
}
pub fn start_all(&self) -> Vec<Result<()>> {
self.registry.start_all()
}
pub fn stop_all(&self) -> Vec<Result<()>> {
self.registry.stop_all()
}
pub fn reload_all(&self) -> Vec<Result<()>> {
self.registry.reload_all()
}
#[cfg(feature = "serde")]
pub fn discover(&self) -> Result<Vec<PluginHandle>> {
let mut loaded = Vec::new();
for dir in &self.config.plugin_dirs {
if !dir.exists() {
tracing::warn!("Plugin directory does not exist: {}", dir.display());
continue;
}
for pattern in &self.config.plugin_patterns {
let glob_pattern = dir.join(pattern);
let glob_str = glob_pattern.to_string_lossy();
if let Ok(entries) = glob::glob(&glob_str) {
for entry in entries.flatten() {
match self.load_manifest(&entry) {
Ok(plugin) => {
tracing::info!(
"Loaded plugin {} from {}",
plugin.name(),
entry.display()
);
loaded.push(plugin);
}
Err(e) => {
tracing::error!(
"Failed to load plugin from {}: {}",
entry.display(),
e
);
}
}
}
}
}
}
Ok(loaded)
}
pub fn call(
&self,
plugin_name: &str,
function: &str,
args: &[fusabi_host::Value],
) -> Result<fusabi_host::Value> {
let plugin = self
.registry
.get(plugin_name)
.ok_or_else(|| Error::plugin_not_found(plugin_name))?;
plugin.call(function, args)
}
pub fn broadcast(
&self,
function: &str,
args: &[fusabi_host::Value],
) -> Vec<(String, Result<fusabi_host::Value>)> {
self.registry
.running()
.into_iter()
.filter(|p| p.has_export(function))
.map(|p| {
let name = p.name();
let result = p.call(function, args);
(name, result)
})
.collect()
}
pub fn cleanup(&self) -> usize {
self.registry.cleanup()
}
pub fn shutdown(&self) {
self.stop_all();
self.registry.unload_all();
}
}
impl std::fmt::Debug for PluginRuntime {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PluginRuntime")
.field("config", &self.config)
.field("plugin_count", &self.registry.len())
.finish()
}
}
impl Drop for PluginRuntime {
fn drop(&mut self) {
self.shutdown();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_runtime_creation() {
let runtime = PluginRuntime::default_config().unwrap();
assert_eq!(runtime.plugin_count(), 0);
}
#[test]
fn test_runtime_config_builder() {
let config = RuntimeConfig::new()
.with_plugin_dir("/plugins")
.with_auto_discover(true);
assert_eq!(config.plugin_dirs.len(), 1);
assert!(config.auto_discover);
}
#[test]
fn test_runtime_stats() {
let runtime = PluginRuntime::default_config().unwrap();
let stats = runtime.stats();
assert_eq!(stats.total, 0);
assert_eq!(stats.running, 0);
}
}
#[cfg(feature = "serde")]
mod glob {
pub fn glob(pattern: &str) -> std::io::Result<impl Iterator<Item = std::io::Result<std::path::PathBuf>>> {
Ok(std::iter::empty())
}
}