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}