cygnixy_plugin_interface/
lib.rs

1use libloading::{Library, Symbol};
2use mlua::Lua;
3use std::collections::HashMap;
4use std::error::Error;
5use tracing::{error, trace};
6
7/// Trait that all Lua plugins must implement.
8///
9/// Provides the required methods for plugin lifecycle management and Lua function registration.
10pub trait PluginLua: Send + Sync {
11    /// Returns the name of the plugin.
12    fn name(&self) -> &str;
13
14    /// Called when the plugin is loaded.
15    ///
16    /// This method is used for initializing resources or performing setup tasks.
17    fn on_load(&mut self) -> Result<(), Box<dyn Error>>;
18
19    /// Called when the plugin is unloaded.
20    ///
21    /// This method allows the plugin to clean up resources before it is removed.
22    fn on_unload(&mut self) -> Result<(), Box<dyn Error>>;
23
24    /// Returns a map of Lua functions to be registered with the Lua state.
25    ///
26    /// Each function is associated with a name, allowing it to be called from Lua scripts.
27    fn get_lua_functions(&self, lua: &Lua, name: &str) -> HashMap<String, mlua::Function>;
28}
29
30/// Manages loading, unloading, and interacting with Lua plugins.
31///
32/// This structure is responsible for:
33/// - Loading dynamic libraries that contain Lua plugins.
34/// - Managing plugin instances and ensuring they are correctly initialized and cleaned up.
35/// - Automatically registering Lua functions provided by plugins.
36pub struct PluginManager {
37    /// A map of plugin names to plugin instances.
38    plugins: HashMap<String, Box<dyn PluginLua>>,
39    /// Keeps track of loaded libraries to prevent premature unloading.
40    libraries: Vec<Library>,
41}
42
43impl Default for PluginManager {
44    /// Creates a default instance of `PluginManager`.
45    fn default() -> Self {
46        Self::new()
47    }
48}
49
50impl PluginManager {
51    /// Creates a new `PluginManager`.
52    pub fn new() -> Self {
53        PluginManager {
54            plugins: HashMap::new(),
55            libraries: Vec::new(),
56        }
57    }
58
59    /// Loads a plugin from a dynamic library.
60    ///
61    /// # Parameters
62    /// - `path`: Path to the dynamic library containing the plugin.
63    ///
64    /// # Returns
65    /// - `Ok(())` if the plugin is successfully loaded.
66    /// - `Err` if there is an error during loading or initialization.
67    ///
68    /// # Safety
69    /// This method uses unsafe code to interact with the dynamic library and call the plugin's
70    /// exported `create_plugin` function.
71    pub fn load_plugin(&mut self, path: &str) -> Result<(), Box<dyn Error>> {
72        type PluginCreate = unsafe fn() -> *mut dyn PluginLua;
73
74        unsafe {
75            // Load the dynamic library.
76            let lib = Library::new(path)?;
77            trace!("Library loaded from path: {}", path);
78
79            // Locate and invoke the plugin's create function.
80            let create_plugin: Symbol<PluginCreate> = lib.get(b"create_plugin")?;
81            let mut boxed_raw_plugin = Box::from_raw(create_plugin());
82
83            // Initialize the plugin by calling its `on_load` method.
84            boxed_raw_plugin.on_load()?;
85            trace!("Plugin '{}' loaded successfully.", boxed_raw_plugin.name());
86
87            self.plugins
88                .insert(boxed_raw_plugin.name().to_string(), boxed_raw_plugin);
89            self.libraries.push(lib);
90        }
91
92        Ok(())
93    }
94
95    /// Unloads a plugin by its name.
96    ///
97    /// # Parameters
98    /// - `name`: Name of the plugin to be unloaded.
99    ///
100    /// # Returns
101    /// - `Ok(())` if the plugin is successfully unloaded.
102    /// - `Err` if the plugin fails to clean up resources or is not found.
103    pub fn unload_plugin(&mut self, name: &str) -> Result<(), Box<dyn Error>> {
104        if let Some(mut plugin) = self.plugins.remove(name) {
105            // Call `on_unload` to allow the plugin to clean up resources.
106            plugin.on_unload()?;
107            trace!("Plugin '{}' unloaded successfully.", name);
108        } else {
109            trace!("Plugin '{}' not found during unload.", name);
110        }
111
112        Ok(())
113    }
114
115    /// Retrieves a reference to a loaded plugin by its name.
116    ///
117    /// # Parameters
118    /// - `name`: Name of the plugin.
119    ///
120    /// # Returns
121    /// - `Some(&dyn PluginLua)` if the plugin is found.
122    /// - `None` if the plugin is not loaded.
123    pub fn get_plugin(&self, name: &str) -> Option<&dyn PluginLua> {
124        self.plugins.get(name).map(|plugin| plugin.as_ref())
125    }
126
127    /// Registers a plugin instance directly, bypassing file loading.
128    ///
129    /// # Parameters
130    /// - `plugin`: A boxed instance of a plugin implementing the `PluginLua` trait.
131    ///
132    /// # Returns
133    /// - `Ok(())` if the plugin was successfully registered.
134    /// - `Err(Box<dyn Error>)` if an error occurs during plugin initialization.
135    ///
136    /// # Example
137    /// ```rust
138    /// let plugin: Box<dyn PluginLua> = Box::new(MyPlugin::new());
139    /// plugin_manager.register_plugin_instance(plugin)?;
140    /// ```
141    ///
142    /// # Notes
143    /// - The plugin's `on_load` method is called during this process to initialize the plugin.
144    /// - The plugin is stored in the internal plugin map for future reference.
145    pub fn register_plugin_instance(
146        &mut self,
147        mut plugin: Box<dyn PluginLua>,
148    ) -> Result<(), Box<dyn Error>> {
149        let plugin_name = plugin.name().to_string();
150        plugin.on_load()?; // Initialize the plugin
151        self.plugins.insert(plugin_name, plugin);
152        Ok(())
153    }
154
155    /// Registers all Lua functions from all loaded plugins with the given Lua state.
156    ///
157    /// # Parameters
158    /// - `lua`: The Lua state where the functions should be registered.
159    ///
160    /// # Returns
161    /// - `Ok(())` if all functions are registered successfully.
162    /// - `Err` if there is an error during registration.
163    pub fn register_all_plugins(&self, lua: &Lua, name: &str) -> Result<(), Box<dyn Error>> {
164        for plugin in self.plugins.values() {
165            trace!("Registering functions for plugin '{}'.", plugin.name());
166            let plugin_table = lua.create_table()?;
167            for (name, function) in plugin.get_lua_functions(lua, name) {
168                plugin_table.set(name, function)?;
169            }
170            lua.globals().set(plugin.name(), plugin_table)?;
171            trace!(
172                "Functions for plugin '{}' registered successfully.",
173                plugin.name()
174            );
175        }
176        Ok(())
177    }
178}
179
180impl Drop for PluginManager {
181    /// Ensures that all plugins are unloaded and cleaned up when the `PluginManager` is dropped.
182    fn drop(&mut self) {
183        for (_, mut plugin) in self.plugins.drain() {
184            // Call `on_unload` for proper cleanup before unloading.
185            if let Err(e) = plugin.on_unload() {
186                error!("Error unloading plugin: {}", e);
187            }
188        }
189        trace!("Plugins unloaded.");
190    }
191}
192
193/// Macro to export the plugin's create function.
194///
195/// This macro defines the `create_plugin` function that is used to instantiate the plugin
196/// from a dynamic library.
197///
198/// # Example
199/// ```rust
200/// export_plugin!(MyPlugin);
201/// ```
202#[macro_export]
203macro_rules! export_plugin {
204    ($plugin_type:ty) => {
205        #[no_mangle]
206        pub extern "C" fn create_plugin() -> *mut dyn PluginLua {
207            let plugin = <$plugin_type>::new();
208            Box::into_raw(Box::new(plugin))
209        }
210    };
211}