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                && entry.enabled
522            {
523                debug!("added plugin: {}", entry.plugin.name());
524                if let Err(AppError::DuplicatePlugin { plugin_name }) =
525                    app.add_boxed_plugin(entry.plugin)
526                {
527                    panic!(
528                        "Error adding plugin {} in group {}: plugin was already added in application",
529                        plugin_name,
530                        self.group_name
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, PluginGroup};
563
564    #[derive(Default)]
565    struct PluginA;
566    impl Plugin for PluginA {
567        fn build(&self, _: &mut App) {}
568    }
569
570    #[derive(Default)]
571    struct PluginB;
572    impl Plugin for PluginB {
573        fn build(&self, _: &mut App) {}
574    }
575
576    #[derive(Default)]
577    struct PluginC;
578    impl Plugin for PluginC {
579        fn build(&self, _: &mut App) {}
580    }
581
582    #[derive(PartialEq, Debug)]
583    struct PluginWithData(u32);
584    impl Plugin for PluginWithData {
585        fn build(&self, _: &mut App) {}
586    }
587
588    fn get_plugin<T: Debug + 'static>(group: &PluginGroupBuilder, id: TypeId) -> &T {
589        group.plugins[&id]
590            .plugin
591            .as_any()
592            .downcast_ref::<T>()
593            .unwrap()
594    }
595
596    #[test]
597    fn contains() {
598        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
599            .add(PluginA)
600            .add(PluginB);
601
602        assert!(group.contains::<PluginA>());
603        assert!(!group.contains::<PluginC>());
604
605        let group = group.disable::<PluginA>();
606
607        assert!(group.enabled::<PluginB>());
608        assert!(!group.enabled::<PluginA>());
609    }
610
611    #[test]
612    fn basic_ordering() {
613        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
614            .add(PluginA)
615            .add(PluginB)
616            .add(PluginC);
617
618        assert_eq!(
619            group.order,
620            vec![
621                TypeId::of::<PluginA>(),
622                TypeId::of::<PluginB>(),
623                TypeId::of::<PluginC>(),
624            ]
625        );
626    }
627
628    #[test]
629    fn add_before() {
630        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
631            .add(PluginA)
632            .add(PluginB)
633            .add_before::<PluginB>(PluginC);
634
635        assert_eq!(
636            group.order,
637            vec![
638                TypeId::of::<PluginA>(),
639                TypeId::of::<PluginC>(),
640                TypeId::of::<PluginB>(),
641            ]
642        );
643    }
644
645    #[test]
646    fn try_add_before() {
647        let group = PluginGroupBuilder::start::<NoopPluginGroup>().add(PluginA);
648
649        let Ok(group) = group.try_add_before::<PluginA, _>(PluginC) else {
650            panic!("PluginA wasn't in group");
651        };
652
653        assert_eq!(
654            group.order,
655            vec![TypeId::of::<PluginC>(), TypeId::of::<PluginA>(),]
656        );
657
658        assert!(group.try_add_before::<PluginA, _>(PluginC).is_err());
659    }
660
661    #[test]
662    #[should_panic(
663        expected = "Plugin does not exist in group: bevy_app::plugin_group::tests::PluginB."
664    )]
665    fn add_before_nonexistent() {
666        PluginGroupBuilder::start::<NoopPluginGroup>()
667            .add(PluginA)
668            .add_before::<PluginB>(PluginC);
669    }
670
671    #[test]
672    fn add_after() {
673        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
674            .add(PluginA)
675            .add(PluginB)
676            .add_after::<PluginA>(PluginC);
677
678        assert_eq!(
679            group.order,
680            vec![
681                TypeId::of::<PluginA>(),
682                TypeId::of::<PluginC>(),
683                TypeId::of::<PluginB>(),
684            ]
685        );
686    }
687
688    #[test]
689    fn try_add_after() {
690        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
691            .add(PluginA)
692            .add(PluginB);
693
694        let Ok(group) = group.try_add_after::<PluginA, _>(PluginC) else {
695            panic!("PluginA wasn't in group");
696        };
697
698        assert_eq!(
699            group.order,
700            vec![
701                TypeId::of::<PluginA>(),
702                TypeId::of::<PluginC>(),
703                TypeId::of::<PluginB>(),
704            ]
705        );
706
707        assert!(group.try_add_after::<PluginA, _>(PluginC).is_err());
708    }
709
710    #[test]
711    #[should_panic(
712        expected = "Plugin does not exist in group: bevy_app::plugin_group::tests::PluginB."
713    )]
714    fn add_after_nonexistent() {
715        PluginGroupBuilder::start::<NoopPluginGroup>()
716            .add(PluginA)
717            .add_after::<PluginB>(PluginC);
718    }
719
720    #[test]
721    fn add_overwrite() {
722        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
723            .add(PluginA)
724            .add(PluginWithData(0x0F))
725            .add(PluginC);
726
727        let id = TypeId::of::<PluginWithData>();
728        assert_eq!(
729            get_plugin::<PluginWithData>(&group, id),
730            &PluginWithData(0x0F)
731        );
732
733        let group = group.add(PluginWithData(0xA0));
734
735        assert_eq!(
736            get_plugin::<PluginWithData>(&group, id),
737            &PluginWithData(0xA0)
738        );
739        assert_eq!(
740            group.order,
741            vec![
742                TypeId::of::<PluginA>(),
743                TypeId::of::<PluginC>(),
744                TypeId::of::<PluginWithData>(),
745            ]
746        );
747
748        let Ok(group) = group.try_add_before_overwrite::<PluginA, _>(PluginWithData(0x01)) else {
749            panic!("PluginA wasn't in group");
750        };
751        assert_eq!(
752            get_plugin::<PluginWithData>(&group, id),
753            &PluginWithData(0x01)
754        );
755        assert_eq!(
756            group.order,
757            vec![
758                TypeId::of::<PluginWithData>(),
759                TypeId::of::<PluginA>(),
760                TypeId::of::<PluginC>(),
761            ]
762        );
763
764        let Ok(group) = group.try_add_after_overwrite::<PluginA, _>(PluginWithData(0xdeadbeef))
765        else {
766            panic!("PluginA wasn't in group");
767        };
768        assert_eq!(
769            get_plugin::<PluginWithData>(&group, id),
770            &PluginWithData(0xdeadbeef)
771        );
772        assert_eq!(
773            group.order,
774            vec![
775                TypeId::of::<PluginA>(),
776                TypeId::of::<PluginWithData>(),
777                TypeId::of::<PluginC>(),
778            ]
779        );
780    }
781
782    #[test]
783    fn readd() {
784        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
785            .add(PluginA)
786            .add(PluginB)
787            .add(PluginC)
788            .add(PluginB);
789
790        assert_eq!(
791            group.order,
792            vec![
793                TypeId::of::<PluginA>(),
794                TypeId::of::<PluginC>(),
795                TypeId::of::<PluginB>(),
796            ]
797        );
798    }
799
800    #[test]
801    fn readd_before() {
802        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
803            .add(PluginA)
804            .add(PluginB)
805            .add(PluginC)
806            .add_before::<PluginB>(PluginC);
807
808        assert_eq!(
809            group.order,
810            vec![
811                TypeId::of::<PluginA>(),
812                TypeId::of::<PluginC>(),
813                TypeId::of::<PluginB>(),
814            ]
815        );
816    }
817
818    #[test]
819    fn readd_after() {
820        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
821            .add(PluginA)
822            .add(PluginB)
823            .add(PluginC)
824            .add_after::<PluginA>(PluginC);
825
826        assert_eq!(
827            group.order,
828            vec![
829                TypeId::of::<PluginA>(),
830                TypeId::of::<PluginC>(),
831                TypeId::of::<PluginB>(),
832            ]
833        );
834    }
835
836    #[test]
837    fn add_basic_subgroup() {
838        let group_a = PluginGroupBuilder::start::<NoopPluginGroup>()
839            .add(PluginA)
840            .add(PluginB);
841
842        let group_b = PluginGroupBuilder::start::<NoopPluginGroup>()
843            .add_group(group_a)
844            .add(PluginC);
845
846        assert_eq!(
847            group_b.order,
848            vec![
849                TypeId::of::<PluginA>(),
850                TypeId::of::<PluginB>(),
851                TypeId::of::<PluginC>(),
852            ]
853        );
854    }
855
856    #[test]
857    fn add_conflicting_subgroup() {
858        let group_a = PluginGroupBuilder::start::<NoopPluginGroup>()
859            .add(PluginA)
860            .add(PluginC);
861
862        let group_b = PluginGroupBuilder::start::<NoopPluginGroup>()
863            .add(PluginB)
864            .add(PluginC);
865
866        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
867            .add_group(group_a)
868            .add_group(group_b);
869
870        assert_eq!(
871            group.order,
872            vec![
873                TypeId::of::<PluginA>(),
874                TypeId::of::<PluginB>(),
875                TypeId::of::<PluginC>(),
876            ]
877        );
878    }
879
880    plugin_group! {
881        #[derive(Default)]
882        struct PluginGroupA {
883            :PluginA
884        }
885    }
886    plugin_group! {
887        #[derive(Default)]
888        struct PluginGroupB {
889            :PluginB
890        }
891    }
892    plugin_group! {
893        struct PluginGroupC {
894            :PluginC
895            #[plugin_group]
896            :PluginGroupA,
897            #[plugin_group]
898            :PluginGroupB,
899        }
900    }
901    #[test]
902    fn construct_nested_plugin_groups() {
903        PluginGroupC {}.build();
904    }
905}