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