bevy_app/
plugin_group.rs

1use crate::{App, AppError, Plugin};
2use bevy_utils::{
3    tracing::{debug, warn},
4    TypeIdMap,
5};
6use core::any::TypeId;
7
8/// A macro for generating a well-documented [`PluginGroup`] from a list of [`Plugin`] paths.
9///
10/// Every plugin must implement the [`Default`] trait.
11///
12/// # Example
13///
14/// ```
15/// # use bevy_app::*;
16/// #
17/// # mod velocity {
18/// #     use bevy_app::*;
19/// #     #[derive(Default)]
20/// #     pub struct VelocityPlugin;
21/// #     impl Plugin for VelocityPlugin { fn build(&self, _: &mut App) {} }
22/// # }
23/// #
24/// # mod collision {
25/// #     pub mod capsule {
26/// #         use bevy_app::*;
27/// #         #[derive(Default)]
28/// #         pub struct CapsuleCollisionPlugin;
29/// #         impl Plugin for CapsuleCollisionPlugin { fn build(&self, _: &mut App) {} }
30/// #     }
31/// # }
32/// #
33/// # #[derive(Default)]
34/// # pub struct TickratePlugin;
35/// # impl Plugin for TickratePlugin { fn build(&self, _: &mut App) {} }
36/// #
37/// # mod features {
38/// #   use bevy_app::*;
39/// #   #[derive(Default)]
40/// #   pub struct ForcePlugin;
41/// #   impl Plugin for ForcePlugin { fn build(&self, _: &mut App) {} }
42/// # }
43/// #
44/// # mod web {
45/// #   use bevy_app::*;
46/// #   #[derive(Default)]
47/// #   pub struct WebCompatibilityPlugin;
48/// #   impl Plugin for WebCompatibilityPlugin { fn build(&self, _: &mut App) {} }
49/// # }
50/// #
51/// # mod audio {
52/// #   use bevy_app::*;
53/// #   #[derive(Default)]
54/// #   pub struct AudioPlugins;
55/// #   impl PluginGroup for AudioPlugins {
56/// #     fn build(self) -> PluginGroupBuilder {
57/// #       PluginGroupBuilder::start::<Self>()
58/// #     }
59/// #   }
60/// # }
61/// #
62/// # mod internal {
63/// #   use bevy_app::*;
64/// #   #[derive(Default)]
65/// #   pub struct InternalPlugin;
66/// #   impl Plugin for InternalPlugin { fn build(&self, _: &mut App) {} }
67/// # }
68/// #
69/// plugin_group! {
70///     /// Doc comments and annotations are supported: they will be added to the generated plugin
71///     /// group.
72///     #[derive(Debug)]
73///     pub struct PhysicsPlugins {
74///         // If referencing a plugin within the same module, you must prefix it with a colon `:`.
75///         :TickratePlugin,
76///         // If referencing a plugin within a different module, there must be three colons `:::`
77///         // between the final module and the plugin name.
78///         collision::capsule:::CapsuleCollisionPlugin,
79///         velocity:::VelocityPlugin,
80///         // If you feature-flag a plugin, it will be automatically documented. There can only be
81///         // one automatically documented feature flag, and it must be first. All other
82///         // `#[cfg()]` attributes must be wrapped by `#[custom()]`.
83///         #[cfg(feature = "external_forces")]
84///         features:::ForcePlugin,
85///         // More complicated `#[cfg()]`s and annotations are not supported by automatic doc
86///         // generation, in which case you must wrap it in `#[custom()]`.
87///         #[custom(cfg(target_arch = "wasm32"))]
88///         web:::WebCompatibilityPlugin,
89///         // You can nest `PluginGroup`s within other `PluginGroup`s, you just need the
90///         // `#[plugin_group]` attribute.
91///         #[plugin_group]
92///         audio:::AudioPlugins,
93///         // You can hide plugins from documentation. Due to macro limitations, hidden plugins
94///         // must be last.
95///         #[doc(hidden)]
96///         internal:::InternalPlugin
97///     }
98///     /// You may add doc comments after the plugin group as well. They will be appended after
99///     /// the documented list of plugins.
100/// }
101/// ```
102#[macro_export]
103macro_rules! plugin_group {
104    {
105        $(#[$group_meta:meta])*
106        $vis:vis struct $group:ident {
107            $(
108                $(#[cfg(feature = $plugin_feature:literal)])?
109                $(#[custom($plugin_meta:meta)])*
110                $($plugin_path:ident::)* : $plugin_name:ident
111            ),*
112            $(
113                $(,)?$(
114                    #[plugin_group]
115                    $(#[cfg(feature = $plugin_group_feature:literal)])?
116                    $(#[custom($plugin_group_meta:meta)])*
117                    $($plugin_group_path:ident::)* : $plugin_group_name:ident
118                ),+
119            )?
120            $(
121                $(,)?$(
122                    #[doc(hidden)]
123                    $(#[cfg(feature = $hidden_plugin_feature:literal)])?
124                    $(#[custom($hidden_plugin_meta:meta)])*
125                    $($hidden_plugin_path:ident::)* : $hidden_plugin_name:ident
126                ),+
127            )?
128
129            $(,)?
130        }
131        $($(#[doc = $post_doc:literal])+)?
132    } => {
133        $(#[$group_meta])*
134        ///
135        $(#[doc = concat!(
136            " - [`", stringify!($plugin_name), "`](" $(, stringify!($plugin_path), "::")*, stringify!($plugin_name), ")"
137            $(, " - with feature `", $plugin_feature, "`")?
138        )])*
139       $($(#[doc = concat!(
140            " - [`", stringify!($plugin_group_name), "`](" $(, stringify!($plugin_group_path), "::")*, stringify!($plugin_group_name), ")"
141            $(, " - with feature `", $plugin_group_feature, "`")?
142        )]),+)?
143        $(
144            ///
145            $(#[doc = $post_doc])+
146        )?
147        $vis struct $group;
148
149        impl $crate::PluginGroup for $group {
150            fn build(self) -> $crate::PluginGroupBuilder {
151                let mut group = $crate::PluginGroupBuilder::start::<Self>();
152
153                $(
154                    $(#[cfg(feature = $plugin_feature)])?
155                    $(#[$plugin_meta])*
156                    {
157                        const _: () = {
158                            const fn check_default<T: Default>() {}
159                            check_default::<$($plugin_path::)*$plugin_name>();
160                        };
161
162                        group = group.add(<$($plugin_path::)*$plugin_name>::default());
163                    }
164                )*
165                $($(
166                    $(#[cfg(feature = $plugin_group_feature)])?
167                    $(#[$plugin_group_meta])*
168                    {
169                        const _: () = {
170                            const fn check_default<T: Default>() {}
171                            check_default::<$($plugin_group_path::)*$plugin_group_name>();
172                        };
173
174                        group = group.add_group(<$($plugin_group_path::)*$plugin_group_name>::default());
175                    }
176                )+)?
177                $($(
178                    $(#[cfg(feature = $hidden_plugin_feature)])?
179                    $(#[$hidden_plugin_meta])*
180                    {
181                        const _: () = {
182                            const fn check_default<T: Default>() {}
183                            check_default::<$($hidden_plugin_path::)*$hidden_plugin_name>();
184                        };
185
186                        group = group.add(<$($hidden_plugin_path::)*$hidden_plugin_name>::default());
187                    }
188                )+)?
189
190                group
191            }
192        }
193    };
194}
195
196/// Combines multiple [`Plugin`]s into a single unit.
197///
198/// If you want an easier, but slightly more restrictive, method of implementing this trait, you
199/// may be interested in the [`plugin_group!`] macro.
200pub trait PluginGroup: Sized {
201    /// Configures the [`Plugin`]s that are to be added.
202    fn build(self) -> PluginGroupBuilder;
203    /// Configures a name for the [`PluginGroup`] which is primarily used for debugging.
204    fn name() -> String {
205        core::any::type_name::<Self>().to_string()
206    }
207    /// Sets the value of the given [`Plugin`], if it exists
208    fn set<T: Plugin>(self, plugin: T) -> PluginGroupBuilder {
209        self.build().set(plugin)
210    }
211}
212
213struct PluginEntry {
214    plugin: Box<dyn Plugin>,
215    enabled: bool,
216}
217
218impl PluginGroup for PluginGroupBuilder {
219    fn build(self) -> PluginGroupBuilder {
220        self
221    }
222}
223
224/// Helper method to get the [`TypeId`] of a value without having to name its type.
225fn type_id_of_val<T: 'static>(_: &T) -> TypeId {
226    TypeId::of::<T>()
227}
228
229/// Facilitates the creation and configuration of a [`PluginGroup`].
230///
231/// Provides a build ordering to ensure that [`Plugin`]s which produce/require a [`Resource`](bevy_ecs::system::Resource)
232/// are built before/after dependent/depending [`Plugin`]s. [`Plugin`]s inside the group
233/// can be disabled, enabled or reordered.
234pub struct PluginGroupBuilder {
235    group_name: String,
236    plugins: TypeIdMap<PluginEntry>,
237    order: Vec<TypeId>,
238}
239
240impl PluginGroupBuilder {
241    /// Start a new builder for the [`PluginGroup`].
242    pub fn start<PG: PluginGroup>() -> Self {
243        Self {
244            group_name: PG::name(),
245            plugins: Default::default(),
246            order: Default::default(),
247        }
248    }
249
250    /// Finds the index of a target [`Plugin`]. Panics if the target's [`TypeId`] is not found.
251    fn index_of<Target: Plugin>(&self) -> usize {
252        let index = self
253            .order
254            .iter()
255            .position(|&ty| ty == TypeId::of::<Target>());
256
257        match index {
258            Some(i) => i,
259            None => panic!(
260                "Plugin does not exist in group: {}.",
261                core::any::type_name::<Target>()
262            ),
263        }
264    }
265
266    // Insert the new plugin as enabled, and removes its previous ordering if it was
267    // already present
268    fn upsert_plugin_state<T: Plugin>(&mut self, plugin: T, added_at_index: usize) {
269        self.upsert_plugin_entry_state(
270            TypeId::of::<T>(),
271            PluginEntry {
272                plugin: Box::new(plugin),
273                enabled: true,
274            },
275            added_at_index,
276        );
277    }
278
279    // Insert the new plugin entry as enabled, and removes its previous ordering if it was
280    // already present
281    fn upsert_plugin_entry_state(
282        &mut self,
283        key: TypeId,
284        plugin: PluginEntry,
285        added_at_index: usize,
286    ) {
287        if let Some(entry) = self.plugins.insert(key, plugin) {
288            if entry.enabled {
289                warn!(
290                    "You are replacing plugin '{}' that was not disabled.",
291                    entry.plugin.name()
292                );
293            }
294            if let Some(to_remove) = self
295                .order
296                .iter()
297                .enumerate()
298                .find(|(i, ty)| *i != added_at_index && **ty == key)
299                .map(|(i, _)| i)
300            {
301                self.order.remove(to_remove);
302            }
303        }
304    }
305
306    /// Sets the value of the given [`Plugin`], if it exists.
307    ///
308    /// # Panics
309    ///
310    /// Panics if the [`Plugin`] does not exist.
311    pub fn set<T: Plugin>(mut self, plugin: T) -> Self {
312        let entry = self.plugins.get_mut(&TypeId::of::<T>()).unwrap_or_else(|| {
313            panic!(
314                "{} does not exist in this PluginGroup",
315                core::any::type_name::<T>(),
316            )
317        });
318        entry.plugin = Box::new(plugin);
319        self
320    }
321
322    /// Adds the plugin [`Plugin`] at the end of this [`PluginGroupBuilder`]. If the plugin was
323    /// already in the group, it is removed from its previous place.
324    // This is not confusing, clippy!
325    #[expect(
326        clippy::should_implement_trait,
327        reason = "This does not emulate the `+` operator, but is more akin to pushing to a stack."
328    )]
329    pub fn add<T: Plugin>(mut self, plugin: T) -> Self {
330        let target_index = self.order.len();
331        self.order.push(TypeId::of::<T>());
332        self.upsert_plugin_state(plugin, target_index);
333        self
334    }
335
336    /// Adds a [`PluginGroup`] at the end of this [`PluginGroupBuilder`]. If the plugin was
337    /// already in the group, it is removed from its previous place.
338    pub fn add_group(mut self, group: impl PluginGroup) -> Self {
339        let Self {
340            mut plugins, order, ..
341        } = group.build();
342
343        for plugin_id in order {
344            self.upsert_plugin_entry_state(
345                plugin_id,
346                plugins.remove(&plugin_id).unwrap(),
347                self.order.len(),
348            );
349
350            self.order.push(plugin_id);
351        }
352
353        self
354    }
355
356    /// Adds a [`Plugin`] in this [`PluginGroupBuilder`] before the plugin of type `Target`.
357    /// If the plugin was already the group, it is removed from its previous place. There must
358    /// be a plugin of type `Target` in the group or it will panic.
359    pub fn add_before<Target: Plugin>(mut self, plugin: impl Plugin) -> Self {
360        let target_index = self.index_of::<Target>();
361        self.order.insert(target_index, type_id_of_val(&plugin));
362        self.upsert_plugin_state(plugin, target_index);
363        self
364    }
365
366    /// Adds a [`Plugin`] in this [`PluginGroupBuilder`] after the plugin of type `Target`.
367    /// If the plugin was already the group, it is removed from its previous place. There must
368    /// be a plugin of type `Target` in the group or it will panic.
369    pub fn add_after<Target: Plugin>(mut self, plugin: impl Plugin) -> Self {
370        let target_index = self.index_of::<Target>() + 1;
371        self.order.insert(target_index, type_id_of_val(&plugin));
372        self.upsert_plugin_state(plugin, target_index);
373        self
374    }
375
376    /// Enables a [`Plugin`].
377    ///
378    /// [`Plugin`]s within a [`PluginGroup`] are enabled by default. This function is used to
379    /// opt back in to a [`Plugin`] after [disabling](Self::disable) it. If there are no plugins
380    /// of type `T` in this group, it will panic.
381    pub fn enable<T: Plugin>(mut self) -> Self {
382        let plugin_entry = self
383            .plugins
384            .get_mut(&TypeId::of::<T>())
385            .expect("Cannot enable a plugin that does not exist.");
386        plugin_entry.enabled = true;
387        self
388    }
389
390    /// Disables a [`Plugin`], preventing it from being added to the [`App`] with the rest of the
391    /// [`PluginGroup`]. The disabled [`Plugin`] keeps its place in the [`PluginGroup`], so it can
392    /// still be used for ordering with [`add_before`](Self::add_before) or
393    /// [`add_after`](Self::add_after), or it can be [re-enabled](Self::enable). If there are no
394    /// plugins of type `T` in this group, it will panic.
395    pub fn disable<T: Plugin>(mut self) -> Self {
396        let plugin_entry = self
397            .plugins
398            .get_mut(&TypeId::of::<T>())
399            .expect("Cannot disable a plugin that does not exist.");
400        plugin_entry.enabled = false;
401        self
402    }
403
404    /// Consumes the [`PluginGroupBuilder`] and [builds](Plugin::build) the contained [`Plugin`]s
405    /// in the order specified.
406    ///
407    /// # Panics
408    ///
409    /// Panics if one of the plugin in the group was already added to the application.
410    #[track_caller]
411    pub fn finish(mut self, app: &mut App) {
412        for ty in &self.order {
413            if let Some(entry) = self.plugins.remove(ty) {
414                if entry.enabled {
415                    debug!("added plugin: {}", entry.plugin.name());
416                    if let Err(AppError::DuplicatePlugin { plugin_name }) =
417                        app.add_boxed_plugin(entry.plugin)
418                    {
419                        panic!(
420                            "Error adding plugin {} in group {}: plugin was already added in application",
421                            plugin_name,
422                            self.group_name
423                        );
424                    }
425                }
426            }
427        }
428    }
429}
430
431/// A plugin group which doesn't do anything. Useful for examples:
432/// ```
433/// # use bevy_app::prelude::*;
434/// use bevy_app::NoopPluginGroup as MinimalPlugins;
435///
436/// fn main(){
437///     App::new().add_plugins(MinimalPlugins).run();
438/// }
439/// ```
440#[doc(hidden)]
441pub struct NoopPluginGroup;
442
443impl PluginGroup for NoopPluginGroup {
444    fn build(self) -> PluginGroupBuilder {
445        PluginGroupBuilder::start::<Self>()
446    }
447}
448
449#[cfg(test)]
450mod tests {
451    use super::PluginGroupBuilder;
452    use crate::{App, NoopPluginGroup, Plugin};
453
454    struct PluginA;
455    impl Plugin for PluginA {
456        fn build(&self, _: &mut App) {}
457    }
458
459    struct PluginB;
460    impl Plugin for PluginB {
461        fn build(&self, _: &mut App) {}
462    }
463
464    struct PluginC;
465    impl Plugin for PluginC {
466        fn build(&self, _: &mut App) {}
467    }
468
469    #[test]
470    fn basic_ordering() {
471        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
472            .add(PluginA)
473            .add(PluginB)
474            .add(PluginC);
475
476        assert_eq!(
477            group.order,
478            vec![
479                core::any::TypeId::of::<PluginA>(),
480                core::any::TypeId::of::<PluginB>(),
481                core::any::TypeId::of::<PluginC>(),
482            ]
483        );
484    }
485
486    #[test]
487    fn add_after() {
488        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
489            .add(PluginA)
490            .add(PluginB)
491            .add_after::<PluginA>(PluginC);
492
493        assert_eq!(
494            group.order,
495            vec![
496                core::any::TypeId::of::<PluginA>(),
497                core::any::TypeId::of::<PluginC>(),
498                core::any::TypeId::of::<PluginB>(),
499            ]
500        );
501    }
502
503    #[test]
504    fn add_before() {
505        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
506            .add(PluginA)
507            .add(PluginB)
508            .add_before::<PluginB>(PluginC);
509
510        assert_eq!(
511            group.order,
512            vec![
513                core::any::TypeId::of::<PluginA>(),
514                core::any::TypeId::of::<PluginC>(),
515                core::any::TypeId::of::<PluginB>(),
516            ]
517        );
518    }
519
520    #[test]
521    fn readd() {
522        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
523            .add(PluginA)
524            .add(PluginB)
525            .add(PluginC)
526            .add(PluginB);
527
528        assert_eq!(
529            group.order,
530            vec![
531                core::any::TypeId::of::<PluginA>(),
532                core::any::TypeId::of::<PluginC>(),
533                core::any::TypeId::of::<PluginB>(),
534            ]
535        );
536    }
537
538    #[test]
539    fn readd_after() {
540        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
541            .add(PluginA)
542            .add(PluginB)
543            .add(PluginC)
544            .add_after::<PluginA>(PluginC);
545
546        assert_eq!(
547            group.order,
548            vec![
549                core::any::TypeId::of::<PluginA>(),
550                core::any::TypeId::of::<PluginC>(),
551                core::any::TypeId::of::<PluginB>(),
552            ]
553        );
554    }
555
556    #[test]
557    fn readd_before() {
558        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
559            .add(PluginA)
560            .add(PluginB)
561            .add(PluginC)
562            .add_before::<PluginB>(PluginC);
563
564        assert_eq!(
565            group.order,
566            vec![
567                core::any::TypeId::of::<PluginA>(),
568                core::any::TypeId::of::<PluginC>(),
569                core::any::TypeId::of::<PluginB>(),
570            ]
571        );
572    }
573
574    #[test]
575    fn add_basic_subgroup() {
576        let group_a = PluginGroupBuilder::start::<NoopPluginGroup>()
577            .add(PluginA)
578            .add(PluginB);
579
580        let group_b = PluginGroupBuilder::start::<NoopPluginGroup>()
581            .add_group(group_a)
582            .add(PluginC);
583
584        assert_eq!(
585            group_b.order,
586            vec![
587                core::any::TypeId::of::<PluginA>(),
588                core::any::TypeId::of::<PluginB>(),
589                core::any::TypeId::of::<PluginC>(),
590            ]
591        );
592    }
593
594    #[test]
595    fn add_conflicting_subgroup() {
596        let group_a = PluginGroupBuilder::start::<NoopPluginGroup>()
597            .add(PluginA)
598            .add(PluginC);
599
600        let group_b = PluginGroupBuilder::start::<NoopPluginGroup>()
601            .add(PluginB)
602            .add(PluginC);
603
604        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
605            .add_group(group_a)
606            .add_group(group_b);
607
608        assert_eq!(
609            group.order,
610            vec![
611                core::any::TypeId::of::<PluginA>(),
612                core::any::TypeId::of::<PluginB>(),
613                core::any::TypeId::of::<PluginC>(),
614            ]
615        );
616    }
617}