Skip to main content

bevy_app/
plugin_group.rs

1use crate::{App, AppError, Plugin};
2use alloc::{
3    boxed::Box,
4    string::{String, ToString},
5    vec::Vec,
6};
7use bevy_utils::{TypeIdHashMap, TypeIdHashMapEntry as Entry};
8use core::any::TypeId;
9use log::{debug, warn};
10
11/// A macro for generating a well-documented [`PluginGroup`] from a list of [`Plugin`] paths.
12///
13/// Every plugin must implement the [`Default`] trait.
14///
15/// # Example
16///
17/// ```
18/// # use bevy_app::*;
19/// #
20/// # mod velocity {
21/// #     use bevy_app::*;
22/// #     #[derive(Default)]
23/// #     pub struct VelocityPlugin;
24/// #     impl Plugin for VelocityPlugin { fn build(&self, _: &mut App) {} }
25/// # }
26/// #
27/// # mod collision {
28/// #     pub mod capsule {
29/// #         use bevy_app::*;
30/// #         #[derive(Default)]
31/// #         pub struct CapsuleCollisionPlugin;
32/// #         impl Plugin for CapsuleCollisionPlugin { fn build(&self, _: &mut App) {} }
33/// #     }
34/// # }
35/// #
36/// # #[derive(Default)]
37/// # pub struct TickratePlugin;
38/// # impl Plugin for TickratePlugin { fn build(&self, _: &mut App) {} }
39/// #
40/// # pub fn bullet_time_plugin(_: &mut App) {}
41/// #
42/// # mod features {
43/// #   use bevy_app::*;
44/// #   #[derive(Default)]
45/// #   pub struct ForcePlugin;
46/// #   impl Plugin for ForcePlugin { fn build(&self, _: &mut App) {} }
47/// #
48/// #   pub fn force_debug_plugin(_: &mut App) {}
49/// # }
50/// #
51/// # mod web {
52/// #   use bevy_app::*;
53/// #   #[derive(Default)]
54/// #   pub struct WebCompatibilityPlugin;
55/// #   impl Plugin for WebCompatibilityPlugin { fn build(&self, _: &mut App) {} }
56/// # }
57/// #
58/// # mod audio {
59/// #   use bevy_app::*;
60/// #   #[derive(Default)]
61/// #   pub struct AudioPlugins;
62/// #   impl PluginGroup for AudioPlugins {
63/// #     fn build(self) -> PluginGroupBuilder {
64/// #       PluginGroupBuilder::start::<Self>()
65/// #     }
66/// #   }
67/// # }
68/// #
69/// # mod internal {
70/// #   use bevy_app::*;
71/// #   #[derive(Default)]
72/// #   pub struct InternalPlugin;
73/// #   impl Plugin for InternalPlugin { fn build(&self, _: &mut App) {} }
74/// #
75/// #   pub fn internal_plugin(_: &mut App) {}
76/// # }
77/// #
78/// plugin_group! {
79///     /// Doc comments and annotations are supported: they will be added to the generated plugin
80///     /// group.
81///     #[derive(Debug)]
82///     pub struct PhysicsPlugins {
83///         // If referencing a plugin within the same module, you must prefix it with a colon `:`.
84///         :TickratePlugin,
85///         // If referencing a plugin within a different module, there must be three colons `:::`
86///         // between the final module and the plugin name.
87///         collision::capsule:::CapsuleCollisionPlugin,
88///         velocity:::VelocityPlugin,
89///         // If you feature-flag a plugin, it will be automatically documented. There can only be
90///         // one automatically documented feature flag, and it must be first. All other
91///         // `#[cfg()]` attributes must be wrapped by `#[custom()]`.
92///         #[cfg(feature = "external_forces")]
93///         features:::ForcePlugin,
94///         // More complicated `#[cfg()]`s and annotations are not supported by automatic doc
95///         // generation, in which case you must wrap it in `#[custom()]`.
96///         #[custom(cfg(target_arch = "wasm32"))]
97///         web:::WebCompatibilityPlugin,
98///         // You can nest `PluginGroup`s within other `PluginGroup`s, you just need the
99///         // `#[plugin_group]` attribute.
100///         #[plugin_group]
101///         audio:::AudioPlugins,
102///         // You can hide plugins from documentation. Due to macro limitations, hidden plugins
103///         // must be after all other plugins.
104///         #[doc(hidden)]
105///         internal:::InternalPlugin,
106///         // Can end with a "fn block" for plugin function members of the group.
107///         @fn {
108///             :bullet_time_plugin,
109///             #[cfg(feature = "external_forces")]
110///             features:::force_debug_plugin,
111///             #[doc(hidden)]
112///             internal:::internal_plugin,
113///         }
114///     }
115///     /// You may add doc comments after the plugin group as well. They will be appended after
116///     /// the documented list of plugins.
117/// }
118/// ```
119#[macro_export]
120macro_rules! plugin_group {
121    {
122        $(#[$group_meta:meta])*
123        $vis:vis struct $group:ident {
124            $(
125                $(#[cfg(feature = $plugin_feature:literal)])?
126                $(#[custom($plugin_meta:meta)])*
127                $($plugin_path:ident::)* : $plugin_name:ident
128            ),*
129            $(
130                $(,)?$(
131                    #[plugin_group]
132                    $(#[cfg(feature = $plugin_group_feature:literal)])?
133                    $(#[custom($plugin_group_meta:meta)])*
134                    $($plugin_group_path:ident::)* : $plugin_group_name:ident
135                ),+
136            )?
137            $(
138                $(,)?$(
139                    #[doc(hidden)]
140                    $(#[cfg(feature = $hidden_plugin_feature:literal)])?
141                    $(#[custom($hidden_plugin_meta:meta)])*
142                    $($hidden_plugin_path:ident::)* : $hidden_plugin_name:ident
143                ),+
144            )?
145            $(
146                $(,)? @fn {
147                    $(
148                        $(#[cfg(feature = $fn_plugin_feature:literal)])?
149                        $(#[custom($fn_plugin_meta:meta)])*
150                        $($fn_plugin_path:ident::)* : $fn_plugin_name:ident
151                    ),*
152                    $(
153                        $(,)?$(
154                            #[doc(hidden)]
155                            $(#[cfg(feature = $fn_hidden_plugin_feature:literal)])?
156                            $(#[custom($fn_hidden_plugin_meta:meta)])*
157                            $($fn_hidden_plugin_path:ident::)* : $fn_hidden_plugin_name:ident
158                        ),+
159                    )?
160                    $(,)?
161                }
162            )?
163
164            $(,)?
165        }
166        $($(#[doc = $post_doc:literal])+)?
167    } => {
168        $(#[$group_meta])*
169        ///
170        $(#[doc = ::core::concat!(
171            " - [`", ::core::stringify!($plugin_name), "`](" $(, ::core::stringify!($plugin_path), "::")*, ::core::stringify!($plugin_name), ")"
172            $(, " - with feature `", $plugin_feature, "`")?
173        )])*
174       $($(#[doc = ::core::concat!(
175            " - [`", ::core::stringify!($plugin_group_name), "`](" $(, ::core::stringify!($plugin_group_path), "::")*, ::core::stringify!($plugin_group_name), ")"
176            $(, " - with feature `", $plugin_group_feature, "`")?
177        )])+)?
178        $($(#[doc = ::core::concat!(
179            " - [`", ::core::stringify!($fn_plugin_name), "`](" $(, ::core::stringify!($fn_plugin_path), "::")*, ::core::stringify!($fn_plugin_name), ")"
180            $(, " - with feature `", $fn_plugin_feature, "`")?
181        )])*)?
182        $(
183            ///
184            $(#[doc = $post_doc])+
185        )?
186        $vis struct $group;
187
188        impl $crate::PluginGroup for $group {
189            fn build(self) -> $crate::PluginGroupBuilder {
190                let mut group = $crate::PluginGroupBuilder::start::<Self>();
191
192                $(
193                    $(#[cfg(feature = $plugin_feature)])?
194                    $(#[$plugin_meta])*
195                    {
196                        const _: () = {
197                            const fn check_default<T: ::core::default::Default>() {}
198                            check_default::<$($plugin_path::)*$plugin_name>();
199                        };
200
201                        group = group.add(<$($plugin_path::)*$plugin_name>::default());
202                    }
203                )*
204                $($(
205                    $(#[cfg(feature = $plugin_group_feature)])?
206                    $(#[$plugin_group_meta])*
207                    {
208                        const _: () = {
209                            const fn check_default<T: ::core::default::Default>() {}
210                            check_default::<$($plugin_group_path::)*$plugin_group_name>();
211                        };
212
213                        group = group.add_group(<$($plugin_group_path::)*$plugin_group_name>::default());
214                    }
215                )+)?
216                $($(
217                    $(#[cfg(feature = $hidden_plugin_feature)])?
218                    $(#[$hidden_plugin_meta])*
219                    {
220                        const _: () = {
221                            const fn check_default<T: ::core::default::Default>() {}
222                            check_default::<$($hidden_plugin_path::)*$hidden_plugin_name>();
223                        };
224
225                        group = group.add(<$($hidden_plugin_path::)*$hidden_plugin_name>::default());
226                    }
227                )+)?
228                $($(
229                    $(#[cfg(feature = $fn_plugin_feature)])?
230                    $(#[$fn_plugin_meta])*
231                    {
232                        group = group.add($($fn_plugin_path::)*$fn_plugin_name);
233                    }
234                )*)?
235                $($($(
236                    $(#[cfg(feature = $fn_hidden_plugin_feature)])?
237                    $(#[$fn_hidden_plugin_meta])*
238                    {
239                        group = group.add($($fn_hidden_plugin_path::)*$fn_hidden_plugin_name);
240                    }
241                )+)?)?
242
243                group
244            }
245        }
246    };
247}
248
249/// Combines multiple [`Plugin`]s into a single unit.
250///
251/// If you want an easier, but slightly more restrictive, method of implementing this trait, you
252/// may be interested in the [`plugin_group!`] macro.
253pub trait PluginGroup: Sized {
254    /// Configures the [`Plugin`]s that are to be added.
255    fn build(self) -> PluginGroupBuilder;
256    /// Configures a name for the [`PluginGroup`] which is primarily used for debugging.
257    fn name() -> String {
258        core::any::type_name::<Self>().to_string()
259    }
260    /// Sets the value of the given [`Plugin`], if it exists
261    fn set<T: Plugin>(self, plugin: T) -> PluginGroupBuilder {
262        self.build().set(plugin)
263    }
264}
265
266struct PluginEntry {
267    plugin: Box<dyn Plugin>,
268    enabled: bool,
269}
270
271impl PluginGroup for PluginGroupBuilder {
272    fn build(self) -> PluginGroupBuilder {
273        self
274    }
275}
276
277/// Facilitates the creation and configuration of a [`PluginGroup`].
278///
279/// Provides a build ordering to ensure that [`Plugin`]s which produce/require a [`Resource`](bevy_ecs::resource::Resource)
280/// are built before/after dependent/depending [`Plugin`]s. [`Plugin`]s inside the group
281/// can be disabled, enabled or reordered.
282pub struct PluginGroupBuilder {
283    group_name: String,
284    plugins: TypeIdHashMap<PluginEntry>,
285    order: Vec<TypeId>,
286}
287
288impl PluginGroupBuilder {
289    /// Start a new builder for the [`PluginGroup`].
290    pub fn start<PG: PluginGroup>() -> Self {
291        Self {
292            group_name: PG::name(),
293            plugins: Default::default(),
294            order: Default::default(),
295        }
296    }
297
298    /// Checks if the [`PluginGroupBuilder`] contains the given [`Plugin`].
299    pub fn contains<T: Plugin>(&self) -> bool {
300        self.plugins.contains_key(&TypeId::of::<T>())
301    }
302
303    /// Returns `true` if the [`PluginGroupBuilder`] contains the given [`Plugin`] and it's enabled.
304    pub fn enabled<T: Plugin>(&self) -> bool {
305        self.plugins
306            .get(&TypeId::of::<T>())
307            .is_some_and(|e| e.enabled)
308    }
309
310    /// Finds the index of a target [`Plugin`].
311    fn index_of<Target: Plugin>(&self) -> Option<usize> {
312        self.order
313            .iter()
314            .position(|&ty| ty == TypeId::of::<Target>())
315    }
316
317    // Insert the new plugin as enabled, and removes its previous ordering if it was
318    // already present
319    fn upsert_plugin_state<T: Plugin>(&mut self, plugin: T, added_at_index: usize) {
320        self.upsert_plugin_entry_state(
321            TypeId::of::<T>(),
322            PluginEntry {
323                plugin: Box::new(plugin),
324                enabled: true,
325            },
326            added_at_index,
327        );
328    }
329
330    // Insert the new plugin entry as enabled, and removes its previous ordering if it was
331    // already present
332    fn upsert_plugin_entry_state(
333        &mut self,
334        key: TypeId,
335        plugin: PluginEntry,
336        added_at_index: usize,
337    ) {
338        if let Some(entry) = self.plugins.insert(key, plugin) {
339            if entry.enabled {
340                warn!(
341                    "You are replacing plugin '{}' that was not disabled.",
342                    entry.plugin.name()
343                );
344            }
345            if let Some(to_remove) = self
346                .order
347                .iter()
348                .enumerate()
349                .find(|(i, ty)| *i != added_at_index && **ty == key)
350                .map(|(i, _)| i)
351            {
352                self.order.remove(to_remove);
353            }
354        }
355    }
356
357    /// Sets the value of the given [`Plugin`], if it exists.
358    ///
359    /// # Panics
360    ///
361    /// Panics if the [`Plugin`] does not exist.
362    pub fn set<T: Plugin>(self, plugin: T) -> Self {
363        self.try_set(plugin).unwrap_or_else(|_| {
364            panic!(
365                "{} does not exist in this PluginGroup",
366                core::any::type_name::<T>(),
367            )
368        })
369    }
370
371    /// Tries to set the value of the given [`Plugin`], if it exists.
372    ///
373    /// If the given plugin doesn't exist returns self and the passed in [`Plugin`].
374    pub fn try_set<T: Plugin>(mut self, plugin: T) -> Result<Self, (Self, T)> {
375        match self.plugins.entry(TypeId::of::<T>()) {
376            Entry::Occupied(mut entry) => {
377                entry.get_mut().plugin = Box::new(plugin);
378
379                Ok(self)
380            }
381            Entry::Vacant(_) => Err((self, plugin)),
382        }
383    }
384
385    /// Adds the plugin [`Plugin`] at the end of this [`PluginGroupBuilder`]. If the plugin was
386    /// already in the group, it is removed from its previous place.
387    // This is not confusing, clippy!
388    #[expect(
389        clippy::should_implement_trait,
390        reason = "This does not emulate the `+` operator, but is more akin to pushing to a stack."
391    )]
392    pub fn add<T: Plugin>(mut self, plugin: T) -> Self {
393        let target_index = self.order.len();
394        self.order.push(TypeId::of::<T>());
395        self.upsert_plugin_state(plugin, target_index);
396        self
397    }
398
399    /// Attempts to add the plugin [`Plugin`] at the end of this [`PluginGroupBuilder`].
400    ///
401    /// If the plugin was already in the group the addition fails.
402    pub fn try_add<T: Plugin>(self, plugin: T) -> Result<Self, (Self, T)> {
403        if self.contains::<T>() {
404            return Err((self, plugin));
405        }
406
407        Ok(self.add(plugin))
408    }
409
410    /// Adds a [`PluginGroup`] at the end of this [`PluginGroupBuilder`]. If the plugin was
411    /// already in the group, it is removed from its previous place.
412    pub fn add_group(mut self, group: impl PluginGroup) -> Self {
413        let Self {
414            mut plugins, order, ..
415        } = group.build();
416
417        for plugin_id in order {
418            self.upsert_plugin_entry_state(
419                plugin_id,
420                plugins.remove(&plugin_id).unwrap(),
421                self.order.len(),
422            );
423
424            self.order.push(plugin_id);
425        }
426
427        self
428    }
429
430    /// Adds a [`Plugin`] in this [`PluginGroupBuilder`] before the plugin of type `Target`.
431    ///
432    /// If the plugin was already the group, it is removed from its previous place.
433    ///
434    /// # Panics
435    ///
436    /// Panics if `Target` is not already in this [`PluginGroupBuilder`].
437    pub fn add_before<Target: Plugin>(self, plugin: impl Plugin) -> Self {
438        self.try_add_before_overwrite::<Target, _>(plugin)
439            .unwrap_or_else(|_| {
440                panic!(
441                    "Plugin does not exist in group: {}.",
442                    core::any::type_name::<Target>()
443                )
444            })
445    }
446
447    /// Adds a [`Plugin`] in this [`PluginGroupBuilder`] before the plugin of type `Target`.
448    ///
449    /// If the plugin was already in the group the add fails. If there isn't a plugin
450    /// of type `Target` in the group the plugin we're trying to insert is returned.
451    pub fn try_add_before<Target: Plugin, Insert: Plugin>(
452        self,
453        plugin: Insert,
454    ) -> Result<Self, (Self, Insert)> {
455        if self.contains::<Insert>() {
456            return Err((self, plugin));
457        }
458
459        self.try_add_before_overwrite::<Target, _>(plugin)
460    }
461
462    /// Adds a [`Plugin`] in this [`PluginGroupBuilder`] before the plugin of type `Target`.
463    ///
464    /// If the plugin was already in the group, it is removed from its previous places.
465    /// If there isn't a plugin of type `Target` in the group the plugin we're trying to insert
466    /// is returned.
467    pub fn try_add_before_overwrite<Target: Plugin, Insert: Plugin>(
468        mut self,
469        plugin: Insert,
470    ) -> Result<Self, (Self, Insert)> {
471        let Some(target_index) = self.index_of::<Target>() else {
472            return Err((self, plugin));
473        };
474
475        self.order.insert(target_index, TypeId::of::<Insert>());
476        self.upsert_plugin_state(plugin, target_index);
477        Ok(self)
478    }
479
480    /// Adds a [`Plugin`] in this [`PluginGroupBuilder`] after the plugin of type `Target`.
481    ///
482    /// If the plugin was already the group, it is removed from its previous place.
483    ///
484    /// # Panics
485    ///
486    /// Panics if `Target` is not already in this [`PluginGroupBuilder`].
487    pub fn add_after<Target: Plugin>(self, plugin: impl Plugin) -> Self {
488        self.try_add_after_overwrite::<Target, _>(plugin)
489            .unwrap_or_else(|_| {
490                panic!(
491                    "Plugin does not exist in group: {}.",
492                    core::any::type_name::<Target>()
493                )
494            })
495    }
496
497    /// Adds a [`Plugin`] in this [`PluginGroupBuilder`] after the plugin of type `Target`.
498    ///
499    /// If the plugin was already in the group the add fails. If there isn't a plugin
500    /// of type `Target` in the group the plugin we're trying to insert is returned.
501    pub fn try_add_after<Target: Plugin, Insert: Plugin>(
502        self,
503        plugin: Insert,
504    ) -> Result<Self, (Self, Insert)> {
505        if self.contains::<Insert>() {
506            return Err((self, plugin));
507        }
508
509        self.try_add_after_overwrite::<Target, _>(plugin)
510    }
511
512    /// Adds a [`Plugin`] in this [`PluginGroupBuilder`] after the plugin of type `Target`.
513    ///
514    /// If the plugin was already in the group, it is removed from its previous places.
515    /// If there isn't a plugin of type `Target` in the group the plugin we're trying to insert
516    /// is returned.
517    pub fn try_add_after_overwrite<Target: Plugin, Insert: Plugin>(
518        mut self,
519        plugin: Insert,
520    ) -> Result<Self, (Self, Insert)> {
521        let Some(target_index) = self.index_of::<Target>() else {
522            return Err((self, plugin));
523        };
524
525        let target_index = target_index + 1;
526
527        self.order.insert(target_index, TypeId::of::<Insert>());
528        self.upsert_plugin_state(plugin, target_index);
529        Ok(self)
530    }
531
532    /// Enables a [`Plugin`].
533    ///
534    /// [`Plugin`]s within a [`PluginGroup`] are enabled by default. This function is used to
535    /// opt back in to a [`Plugin`] after [disabling](Self::disable) it. If there are no plugins
536    /// of type `T` in this group, it will panic.
537    pub fn enable<T: Plugin>(mut self) -> Self {
538        let plugin_entry = self
539            .plugins
540            .get_mut(&TypeId::of::<T>())
541            .expect("Cannot enable a plugin that does not exist.");
542        plugin_entry.enabled = true;
543        self
544    }
545
546    /// Disables a [`Plugin`], preventing it from being added to the [`App`] with the rest of the
547    /// [`PluginGroup`]. The disabled [`Plugin`] keeps its place in the [`PluginGroup`], so it can
548    /// still be used for ordering with [`add_before`](Self::add_before) or
549    /// [`add_after`](Self::add_after), or it can be [re-enabled](Self::enable). If there are no
550    /// plugins of type `T` in this group, it will panic.
551    pub fn disable<T: Plugin>(mut self) -> Self {
552        let plugin_entry = self
553            .plugins
554            .get_mut(&TypeId::of::<T>())
555            .expect("Cannot disable a plugin that does not exist.");
556        plugin_entry.enabled = false;
557        self
558    }
559
560    /// Consumes the [`PluginGroupBuilder`] and [builds](Plugin::build) the contained [`Plugin`]s
561    /// in the order specified.
562    ///
563    /// # Panics
564    ///
565    /// Panics if one of the plugin in the group was already added to the application.
566    #[track_caller]
567    pub fn finish(mut self, app: &mut App) {
568        for ty in &self.order {
569            if let Some(entry) = self.plugins.remove(ty)
570                && entry.enabled
571            {
572                debug!("added plugin: {}", entry.plugin.name());
573                if let Err(AppError::DuplicatePlugin { plugin_name }) =
574                    app.add_boxed_plugin(entry.plugin)
575                {
576                    panic!(
577                        "Error adding plugin {} in group {}: plugin was already added in application",
578                        plugin_name,
579                        self.group_name
580                    );
581                }
582            }
583        }
584    }
585}
586
587/// A plugin group which doesn't do anything. Useful for examples:
588/// ```
589/// # use bevy_app::prelude::*;
590/// use bevy_app::NoopPluginGroup as MinimalPlugins;
591///
592/// fn main(){
593///     App::new().add_plugins(MinimalPlugins).run();
594/// }
595/// ```
596#[doc(hidden)]
597pub struct NoopPluginGroup;
598
599impl PluginGroup for NoopPluginGroup {
600    fn build(self) -> PluginGroupBuilder {
601        PluginGroupBuilder::start::<Self>()
602    }
603}
604
605#[cfg(test)]
606mod tests {
607    use alloc::vec;
608    use core::{any::TypeId, fmt::Debug};
609
610    use super::PluginGroupBuilder;
611    use crate::{App, NoopPluginGroup, Plugin, PluginGroup};
612
613    #[derive(Default)]
614    struct PluginA;
615    impl Plugin for PluginA {
616        fn build(&self, _: &mut App) {}
617    }
618
619    #[derive(Default)]
620    struct PluginB;
621    impl Plugin for PluginB {
622        fn build(&self, _: &mut App) {}
623    }
624
625    #[derive(Default)]
626    struct PluginC;
627    impl Plugin for PluginC {
628        fn build(&self, _: &mut App) {}
629    }
630
631    fn plugin_d(_: &mut App) {}
632    fn plugin_e(_: &mut App) {}
633
634    #[derive(PartialEq, Debug)]
635    struct PluginWithData(u32);
636    impl Plugin for PluginWithData {
637        fn build(&self, _: &mut App) {}
638    }
639
640    fn get_plugin<T: Debug + 'static>(group: &PluginGroupBuilder, id: TypeId) -> &T {
641        group.plugins[&id]
642            .plugin
643            .as_any()
644            .downcast_ref::<T>()
645            .unwrap()
646    }
647
648    #[test]
649    fn contains() {
650        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
651            .add(PluginA)
652            .add(PluginB);
653
654        assert!(group.contains::<PluginA>());
655        assert!(!group.contains::<PluginC>());
656
657        let group = group.disable::<PluginA>();
658
659        assert!(group.enabled::<PluginB>());
660        assert!(!group.enabled::<PluginA>());
661    }
662
663    #[test]
664    fn basic_ordering() {
665        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
666            .add(PluginA)
667            .add(PluginB)
668            .add(PluginC);
669
670        assert_eq!(
671            group.order,
672            vec![
673                TypeId::of::<PluginA>(),
674                TypeId::of::<PluginB>(),
675                TypeId::of::<PluginC>(),
676            ]
677        );
678    }
679
680    #[test]
681    fn add_before() {
682        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
683            .add(PluginA)
684            .add(PluginB)
685            .add_before::<PluginB>(PluginC);
686
687        assert_eq!(
688            group.order,
689            vec![
690                TypeId::of::<PluginA>(),
691                TypeId::of::<PluginC>(),
692                TypeId::of::<PluginB>(),
693            ]
694        );
695    }
696
697    #[test]
698    fn try_add_before() {
699        let group = PluginGroupBuilder::start::<NoopPluginGroup>().add(PluginA);
700
701        let Ok(group) = group.try_add_before::<PluginA, _>(PluginC) else {
702            panic!("PluginA wasn't in group");
703        };
704
705        assert_eq!(
706            group.order,
707            vec![TypeId::of::<PluginC>(), TypeId::of::<PluginA>(),]
708        );
709
710        assert!(group.try_add_before::<PluginA, _>(PluginC).is_err());
711    }
712
713    #[test]
714    #[should_panic(
715        expected = "Plugin does not exist in group: bevy_app::plugin_group::tests::PluginB."
716    )]
717    fn add_before_nonexistent() {
718        PluginGroupBuilder::start::<NoopPluginGroup>()
719            .add(PluginA)
720            .add_before::<PluginB>(PluginC);
721    }
722
723    #[test]
724    fn add_after() {
725        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
726            .add(PluginA)
727            .add(PluginB)
728            .add_after::<PluginA>(PluginC);
729
730        assert_eq!(
731            group.order,
732            vec![
733                TypeId::of::<PluginA>(),
734                TypeId::of::<PluginC>(),
735                TypeId::of::<PluginB>(),
736            ]
737        );
738    }
739
740    #[test]
741    fn try_add_after() {
742        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
743            .add(PluginA)
744            .add(PluginB);
745
746        let Ok(group) = group.try_add_after::<PluginA, _>(PluginC) else {
747            panic!("PluginA wasn't in group");
748        };
749
750        assert_eq!(
751            group.order,
752            vec![
753                TypeId::of::<PluginA>(),
754                TypeId::of::<PluginC>(),
755                TypeId::of::<PluginB>(),
756            ]
757        );
758
759        assert!(group.try_add_after::<PluginA, _>(PluginC).is_err());
760    }
761
762    #[test]
763    #[should_panic(
764        expected = "Plugin does not exist in group: bevy_app::plugin_group::tests::PluginB."
765    )]
766    fn add_after_nonexistent() {
767        PluginGroupBuilder::start::<NoopPluginGroup>()
768            .add(PluginA)
769            .add_after::<PluginB>(PluginC);
770    }
771
772    #[test]
773    fn add_overwrite() {
774        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
775            .add(PluginA)
776            .add(PluginWithData(0x0F))
777            .add(PluginC);
778
779        let id = TypeId::of::<PluginWithData>();
780        assert_eq!(
781            get_plugin::<PluginWithData>(&group, id),
782            &PluginWithData(0x0F)
783        );
784
785        let group = group.add(PluginWithData(0xA0));
786
787        assert_eq!(
788            get_plugin::<PluginWithData>(&group, id),
789            &PluginWithData(0xA0)
790        );
791        assert_eq!(
792            group.order,
793            vec![
794                TypeId::of::<PluginA>(),
795                TypeId::of::<PluginC>(),
796                TypeId::of::<PluginWithData>(),
797            ]
798        );
799
800        let Ok(group) = group.try_add_before_overwrite::<PluginA, _>(PluginWithData(0x01)) else {
801            panic!("PluginA wasn't in group");
802        };
803        assert_eq!(
804            get_plugin::<PluginWithData>(&group, id),
805            &PluginWithData(0x01)
806        );
807        assert_eq!(
808            group.order,
809            vec![
810                TypeId::of::<PluginWithData>(),
811                TypeId::of::<PluginA>(),
812                TypeId::of::<PluginC>(),
813            ]
814        );
815
816        let Ok(group) = group.try_add_after_overwrite::<PluginA, _>(PluginWithData(0xdeadbeef))
817        else {
818            panic!("PluginA wasn't in group");
819        };
820        assert_eq!(
821            get_plugin::<PluginWithData>(&group, id),
822            &PluginWithData(0xdeadbeef)
823        );
824        assert_eq!(
825            group.order,
826            vec![
827                TypeId::of::<PluginA>(),
828                TypeId::of::<PluginWithData>(),
829                TypeId::of::<PluginC>(),
830            ]
831        );
832    }
833
834    #[test]
835    fn readd() {
836        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
837            .add(PluginA)
838            .add(PluginB)
839            .add(PluginC)
840            .add(PluginB);
841
842        assert_eq!(
843            group.order,
844            vec![
845                TypeId::of::<PluginA>(),
846                TypeId::of::<PluginC>(),
847                TypeId::of::<PluginB>(),
848            ]
849        );
850    }
851
852    #[test]
853    fn readd_before() {
854        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
855            .add(PluginA)
856            .add(PluginB)
857            .add(PluginC)
858            .add_before::<PluginB>(PluginC);
859
860        assert_eq!(
861            group.order,
862            vec![
863                TypeId::of::<PluginA>(),
864                TypeId::of::<PluginC>(),
865                TypeId::of::<PluginB>(),
866            ]
867        );
868    }
869
870    #[test]
871    fn readd_after() {
872        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
873            .add(PluginA)
874            .add(PluginB)
875            .add(PluginC)
876            .add_after::<PluginA>(PluginC);
877
878        assert_eq!(
879            group.order,
880            vec![
881                TypeId::of::<PluginA>(),
882                TypeId::of::<PluginC>(),
883                TypeId::of::<PluginB>(),
884            ]
885        );
886    }
887
888    #[test]
889    fn add_basic_subgroup() {
890        let group_a = PluginGroupBuilder::start::<NoopPluginGroup>()
891            .add(PluginA)
892            .add(PluginB);
893
894        let group_b = PluginGroupBuilder::start::<NoopPluginGroup>()
895            .add_group(group_a)
896            .add(PluginC);
897
898        assert_eq!(
899            group_b.order,
900            vec![
901                TypeId::of::<PluginA>(),
902                TypeId::of::<PluginB>(),
903                TypeId::of::<PluginC>(),
904            ]
905        );
906    }
907
908    #[test]
909    fn add_conflicting_subgroup() {
910        let group_a = PluginGroupBuilder::start::<NoopPluginGroup>()
911            .add(PluginA)
912            .add(PluginC);
913
914        let group_b = PluginGroupBuilder::start::<NoopPluginGroup>()
915            .add(PluginB)
916            .add(PluginC);
917
918        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
919            .add_group(group_a)
920            .add_group(group_b);
921
922        assert_eq!(
923            group.order,
924            vec![
925                TypeId::of::<PluginA>(),
926                TypeId::of::<PluginB>(),
927                TypeId::of::<PluginC>(),
928            ]
929        );
930    }
931
932    plugin_group! {
933        #[derive(Default)]
934        struct PluginGroupA {
935            :PluginA
936        }
937    }
938    plugin_group! {
939        #[derive(Default)]
940        struct PluginGroupB {
941            :PluginB
942        }
943    }
944    plugin_group! {
945        struct PluginGroupC {
946            :PluginC
947            #[plugin_group]
948            :PluginGroupA,
949            #[plugin_group]
950            :PluginGroupB,
951        }
952    }
953    #[test]
954    fn construct_nested_plugin_groups() {
955        PluginGroupC {}.build();
956    }
957    plugin_group! {
958        #[derive(Default)]
959        struct PluginGroupD {
960            :PluginA
961            @fn {
962                #[doc(hidden)]
963                :plugin_d
964            }
965        }
966    }
967    plugin_group! {
968        #[derive(Default)]
969        struct PluginGroupE {
970            @fn {
971                :plugin_e
972            }
973        }
974    }
975    plugin_group! {
976        struct PluginGroupF {
977            :PluginB
978            #[plugin_group]
979            :PluginGroupD,
980            #[plugin_group]
981            :PluginGroupE,
982        }
983    }
984    #[test]
985    fn construct_nested_plugin_groups_with_plugin_functions() {
986        fn type_id_of<T: Plugin>(_: T) -> TypeId {
987            TypeId::of::<T>()
988        }
989
990        let group = PluginGroupF {}.build();
991
992        assert_eq!(
993            group.order,
994            vec![
995                TypeId::of::<PluginB>(),
996                TypeId::of::<PluginA>(),
997                type_id_of(plugin_d),
998                type_id_of(plugin_e),
999            ]
1000        );
1001    }
1002}