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}