Skip to main content

astrelis/
plugin.rs

1//! Plugin system for extending engine functionality.
2//!
3//! Plugins are the primary way to add features to the engine.
4//! Each plugin can register resources, set up systems, and hook
5//! into the engine lifecycle.
6
7use crate::resource::Resources;
8use std::any::type_name;
9
10/// Trait for compile-time type-safe plugin dependency specification.
11///
12/// This trait is implemented for plugin types and tuples of plugin types,
13/// allowing dependencies to be checked at compile time.
14///
15/// # Example
16///
17/// ```ignore
18/// impl Plugin for MyPlugin {
19///     type Dependencies = (RenderPlugin, AssetPlugin);
20///     // ...
21/// }
22/// ```
23pub trait PluginSet {
24    /// Returns the type names of all plugins in this set.
25    fn names() -> Vec<&'static str>;
26}
27
28/// Empty dependency set (no dependencies).
29impl PluginSet for () {
30    fn names() -> Vec<&'static str> {
31        vec![]
32    }
33}
34
35/// Single plugin dependency.
36impl<P: Plugin> PluginSet for P {
37    fn names() -> Vec<&'static str> {
38        vec![type_name::<P>()]
39    }
40}
41
42/// Two plugin dependencies.
43impl<P1: Plugin, P2: Plugin> PluginSet for (P1, P2) {
44    fn names() -> Vec<&'static str> {
45        vec![type_name::<P1>(), type_name::<P2>()]
46    }
47}
48
49/// Three plugin dependencies.
50impl<P1: Plugin, P2: Plugin, P3: Plugin> PluginSet for (P1, P2, P3) {
51    fn names() -> Vec<&'static str> {
52        vec![type_name::<P1>(), type_name::<P2>(), type_name::<P3>()]
53    }
54}
55
56/// Four plugin dependencies.
57impl<P1: Plugin, P2: Plugin, P3: Plugin, P4: Plugin> PluginSet for (P1, P2, P3, P4) {
58    fn names() -> Vec<&'static str> {
59        vec![
60            type_name::<P1>(),
61            type_name::<P2>(),
62            type_name::<P3>(),
63            type_name::<P4>(),
64        ]
65    }
66}
67
68/// Five plugin dependencies.
69impl<P1: Plugin, P2: Plugin, P3: Plugin, P4: Plugin, P5: Plugin> PluginSet
70    for (P1, P2, P3, P4, P5)
71{
72    fn names() -> Vec<&'static str> {
73        vec![
74            type_name::<P1>(),
75            type_name::<P2>(),
76            type_name::<P3>(),
77            type_name::<P4>(),
78            type_name::<P5>(),
79        ]
80    }
81}
82
83/// Six plugin dependencies.
84impl<P1: Plugin, P2: Plugin, P3: Plugin, P4: Plugin, P5: Plugin, P6: Plugin> PluginSet
85    for (P1, P2, P3, P4, P5, P6)
86{
87    fn names() -> Vec<&'static str> {
88        vec![
89            type_name::<P1>(),
90            type_name::<P2>(),
91            type_name::<P3>(),
92            type_name::<P4>(),
93            type_name::<P5>(),
94            type_name::<P6>(),
95        ]
96    }
97}
98
99/// Object-safe plugin trait for runtime plugin management.
100///
101/// This trait is automatically implemented for all types that implement `Plugin`.
102/// It allows plugins to be stored as trait objects (`Box<dyn PluginDyn>`).
103pub trait PluginDyn: Send + Sync {
104    /// Returns the unique name of this plugin.
105    fn name(&self) -> &'static str;
106
107    /// Returns the names of plugins this plugin depends on.
108    fn dependencies(&self) -> Vec<&'static str>;
109
110    /// Called when the plugin is added to the engine.
111    fn build(&self, resources: &mut Resources);
112
113    /// Called after all plugins have been built.
114    fn finish(&self, resources: &mut Resources);
115
116    /// Called when the plugin is removed from the engine.
117    fn cleanup(&self, resources: &mut Resources);
118}
119
120/// Trait for engine plugins with compile-time type-safe dependencies.
121///
122/// Plugins are building blocks that add functionality to the engine.
123/// They can register resources, depend on other plugins, and hook
124/// into the engine lifecycle.
125///
126/// # Example
127///
128/// ```
129/// use astrelis::{Plugin, Resources};
130///
131/// struct MyPlugin;
132///
133/// impl Plugin for MyPlugin {
134///     type Dependencies = ();
135///
136///     fn name(&self) -> &'static str {
137///         "MyPlugin"
138///     }
139///
140///     fn build(&self, resources: &mut Resources) {
141///         // Register resources
142///         resources.insert(MyResource::new());
143///     }
144/// }
145///
146/// struct MyResource {
147///     value: i32,
148/// }
149///
150/// impl MyResource {
151///     fn new() -> Self {
152///         Self { value: 42 }
153///     }
154/// }
155/// ```
156///
157/// # Plugin Dependencies
158///
159/// Plugins can declare dependencies using the `Dependencies` associated type:
160///
161/// ```ignore
162/// impl Plugin for MyPlugin {
163///     type Dependencies = (RenderPlugin, AssetPlugin);
164///     // ...
165/// }
166/// ```
167///
168/// This provides compile-time type checking of dependencies.
169pub trait Plugin: Send + Sync + 'static {
170    /// Type-safe plugin dependencies.
171    ///
172    /// Specify dependencies as a tuple of plugin types:
173    /// - `()` for no dependencies
174    /// - `P` for a single dependency
175    /// - `(P1, P2)` for two dependencies
176    /// - `(P1, P2, P3)` for three dependencies, etc.
177    ///
178    /// # Example
179    ///
180    /// ```ignore
181    /// impl Plugin for MyPlugin {
182    ///     type Dependencies = (RenderPlugin, AssetPlugin);
183    ///     // ...
184    /// }
185    /// ```
186    type Dependencies: PluginSet;
187
188    /// Returns the unique name of this plugin.
189    ///
190    /// By default, this uses the type name. Override if you need a custom name.
191    fn name(&self) -> &'static str {
192        type_name::<Self>()
193    }
194
195    /// Called when the plugin is added to the engine.
196    ///
197    /// Use this to register resources and perform initial setup.
198    fn build(&self, resources: &mut Resources);
199
200    /// Called after all plugins have been built.
201    ///
202    /// Use this for cross-plugin setup that requires other plugins
203    /// to be initialized first.
204    #[allow(unused_variables)]
205    fn finish(&self, resources: &mut Resources) {}
206
207    /// Called when the plugin is removed from the engine.
208    ///
209    /// Use this for cleanup.
210    #[allow(unused_variables)]
211    fn cleanup(&self, resources: &mut Resources) {}
212}
213
214// Blanket implementation of PluginDyn for all Plugin types
215impl<P: Plugin> PluginDyn for P {
216    fn name(&self) -> &'static str {
217        <Self as Plugin>::name(self)
218    }
219
220    fn dependencies(&self) -> Vec<&'static str> {
221        P::Dependencies::names()
222    }
223
224    fn build(&self, resources: &mut Resources) {
225        <Self as Plugin>::build(self, resources)
226    }
227
228    fn finish(&self, resources: &mut Resources) {
229        <Self as Plugin>::finish(self, resources)
230    }
231
232    fn cleanup(&self, resources: &mut Resources) {
233        <Self as Plugin>::cleanup(self, resources)
234    }
235}
236
237/// A plugin group that bundles multiple plugins together.
238///
239/// This is useful for creating plugin bundles that set up
240/// common functionality.
241///
242/// # Example
243///
244/// ```ignore
245/// use astrelis::{Plugin, PluginGroup, Resources};
246///
247/// struct DefaultPlugins;
248///
249/// impl PluginGroup for DefaultPlugins {
250///     fn plugins(&self) -> Vec<Box<dyn Plugin>> {
251///         vec![
252///             Box::new(AssetPlugin),
253///             Box::new(RenderPlugin),
254///             Box::new(InputPlugin),
255///         ]
256///     }
257/// }
258/// ```
259pub trait PluginGroup {
260    /// Returns the plugins in this group.
261    fn plugins(&self) -> Vec<Box<dyn PluginDyn>>;
262
263    /// Returns the name of this plugin group.
264    fn name(&self) -> &'static str {
265        std::any::type_name::<Self>()
266    }
267}
268
269/// Wrapper to make a PluginGroup usable as a single Plugin.
270pub(crate) struct PluginGroupAdapter {
271    /// Plugin group name for debugging
272    #[allow(dead_code)]
273    name: &'static str,
274    plugins: Vec<Box<dyn PluginDyn>>,
275}
276
277impl PluginGroupAdapter {
278    pub fn new(group: impl PluginGroup) -> Self {
279        Self {
280            name: group.name(),
281            plugins: group.plugins(),
282        }
283    }
284
285    pub fn into_plugins(self) -> Vec<Box<dyn PluginDyn>> {
286        self.plugins
287    }
288}
289
290/// A function-based plugin for simple use cases.
291///
292/// # Example
293///
294/// ```
295/// use astrelis::{FnPlugin, Resources, EngineBuilder};
296///
297/// let engine = EngineBuilder::new()
298///     .add_plugin(FnPlugin::new("setup", |resources| {
299///         resources.insert(42i32);
300///     }))
301///     .build();
302/// ```
303pub struct FnPlugin<F>
304where
305    F: Fn(&mut Resources) + Send + Sync + 'static,
306{
307    name: &'static str,
308    build_fn: F,
309}
310
311impl<F> FnPlugin<F>
312where
313    F: Fn(&mut Resources) + Send + Sync + 'static,
314{
315    /// Create a new function-based plugin.
316    pub fn new(name: &'static str, build_fn: F) -> Self {
317        Self { name, build_fn }
318    }
319}
320
321impl<F> Plugin for FnPlugin<F>
322where
323    F: Fn(&mut Resources) + Send + Sync + 'static,
324{
325    type Dependencies = ();
326
327    fn name(&self) -> &'static str {
328        self.name
329    }
330
331    fn build(&self, resources: &mut Resources) {
332        (self.build_fn)(resources);
333    }
334}
335
336#[cfg(test)]
337mod tests {
338    use super::*;
339
340    struct TestPlugin {
341        value: i32,
342    }
343
344    impl Plugin for TestPlugin {
345        type Dependencies = ();
346
347        fn name(&self) -> &'static str {
348            "TestPlugin"
349        }
350
351        fn build(&self, resources: &mut Resources) {
352            resources.insert(self.value);
353        }
354    }
355
356    #[test]
357    fn test_plugin_build() {
358        let plugin = TestPlugin { value: 42 };
359        let mut resources = Resources::new();
360
361        PluginDyn::build(&plugin, &mut resources);
362
363        assert_eq!(*resources.get::<i32>().unwrap(), 42);
364    }
365
366    #[test]
367    fn test_fn_plugin() {
368        let plugin = FnPlugin::new("test", |resources| {
369            resources.insert("hello".to_string());
370        });
371
372        let mut resources = Resources::new();
373        PluginDyn::build(&plugin, &mut resources);
374
375        assert_eq!(resources.get::<String>().unwrap(), "hello");
376    }
377
378    struct DependentPlugin;
379
380    impl Plugin for DependentPlugin {
381        type Dependencies = TestPlugin;
382
383        fn name(&self) -> &'static str {
384            "DependentPlugin"
385        }
386
387        fn build(&self, resources: &mut Resources) {
388            // Double the value set by TestPlugin
389            if let Some(val) = resources.get_mut::<i32>() {
390                *val *= 2;
391            }
392        }
393    }
394
395    #[test]
396    fn test_plugin_dependencies() {
397        let plugin = DependentPlugin;
398        let deps = PluginDyn::dependencies(&plugin);
399        assert_eq!(deps, vec![type_name::<TestPlugin>()]);
400    }
401}