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