bevy_app/
plugin_group.rs

1use crate::{App, AppError, Plugin};
2use bevy_utils::{tracing::debug, tracing::warn, TypeIdMap};
3use std::any::TypeId;
4
5/// Combines multiple [`Plugin`]s into a single unit.
6pub trait PluginGroup: Sized {
7    /// Configures the [`Plugin`]s that are to be added.
8    fn build(self) -> PluginGroupBuilder;
9    /// Configures a name for the [`PluginGroup`] which is primarily used for debugging.
10    fn name() -> String {
11        std::any::type_name::<Self>().to_string()
12    }
13    /// Sets the value of the given [`Plugin`], if it exists
14    fn set<T: Plugin>(self, plugin: T) -> PluginGroupBuilder {
15        self.build().set(plugin)
16    }
17}
18
19struct PluginEntry {
20    plugin: Box<dyn Plugin>,
21    enabled: bool,
22}
23
24impl PluginGroup for PluginGroupBuilder {
25    fn build(self) -> PluginGroupBuilder {
26        self
27    }
28}
29
30/// Facilitates the creation and configuration of a [`PluginGroup`].
31/// Provides a build ordering to ensure that [`Plugin`]s which produce/require a [`Resource`](bevy_ecs::system::Resource)
32/// are built before/after dependent/depending [`Plugin`]s. [`Plugin`]s inside the group
33/// can be disabled, enabled or reordered.
34pub struct PluginGroupBuilder {
35    group_name: String,
36    plugins: TypeIdMap<PluginEntry>,
37    order: Vec<TypeId>,
38}
39
40impl PluginGroupBuilder {
41    /// Start a new builder for the [`PluginGroup`].
42    pub fn start<PG: PluginGroup>() -> Self {
43        Self {
44            group_name: PG::name(),
45            plugins: Default::default(),
46            order: Default::default(),
47        }
48    }
49
50    /// Finds the index of a target [`Plugin`]. Panics if the target's [`TypeId`] is not found.
51    fn index_of<Target: Plugin>(&self) -> usize {
52        let index = self
53            .order
54            .iter()
55            .position(|&ty| ty == TypeId::of::<Target>());
56
57        match index {
58            Some(i) => i,
59            None => panic!(
60                "Plugin does not exist in group: {}.",
61                std::any::type_name::<Target>()
62            ),
63        }
64    }
65
66    // Insert the new plugin as enabled, and removes its previous ordering if it was
67    // already present
68    fn upsert_plugin_state<T: Plugin>(&mut self, plugin: T, added_at_index: usize) {
69        self.upsert_plugin_entry_state(
70            TypeId::of::<T>(),
71            PluginEntry {
72                plugin: Box::new(plugin),
73                enabled: true,
74            },
75            added_at_index,
76        );
77    }
78
79    // Insert the new plugin entry as enabled, and removes its previous ordering if it was
80    // already present
81    fn upsert_plugin_entry_state(
82        &mut self,
83        key: TypeId,
84        plugin: PluginEntry,
85        added_at_index: usize,
86    ) {
87        if let Some(entry) = self.plugins.insert(key, plugin) {
88            if entry.enabled {
89                warn!(
90                    "You are replacing plugin '{}' that was not disabled.",
91                    entry.plugin.name()
92                );
93            }
94            if let Some(to_remove) = self
95                .order
96                .iter()
97                .enumerate()
98                .find(|(i, ty)| *i != added_at_index && **ty == key)
99                .map(|(i, _)| i)
100            {
101                self.order.remove(to_remove);
102            }
103        }
104    }
105
106    /// Sets the value of the given [`Plugin`], if it exists.
107    ///
108    /// # Panics
109    ///
110    /// Panics if the [`Plugin`] does not exist.
111    pub fn set<T: Plugin>(mut self, plugin: T) -> Self {
112        let entry = self.plugins.get_mut(&TypeId::of::<T>()).unwrap_or_else(|| {
113            panic!(
114                "{} does not exist in this PluginGroup",
115                std::any::type_name::<T>(),
116            )
117        });
118        entry.plugin = Box::new(plugin);
119        self
120    }
121
122    /// Adds the plugin [`Plugin`] at the end of this [`PluginGroupBuilder`]. If the plugin was
123    /// already in the group, it is removed from its previous place.
124    // This is not confusing, clippy!
125    #[allow(clippy::should_implement_trait)]
126    pub fn add<T: Plugin>(mut self, plugin: T) -> Self {
127        let target_index = self.order.len();
128        self.order.push(TypeId::of::<T>());
129        self.upsert_plugin_state(plugin, target_index);
130        self
131    }
132
133    /// Adds a [`PluginGroup`] at the end of this [`PluginGroupBuilder`]. If the plugin was
134    /// already in the group, it is removed from its previous place.
135    pub fn add_group(mut self, group: impl PluginGroup) -> Self {
136        let Self {
137            mut plugins, order, ..
138        } = group.build();
139
140        for plugin_id in order {
141            self.upsert_plugin_entry_state(
142                plugin_id,
143                plugins.remove(&plugin_id).unwrap(),
144                self.order.len(),
145            );
146
147            self.order.push(plugin_id);
148        }
149
150        self
151    }
152
153    /// Adds a [`Plugin`] in this [`PluginGroupBuilder`] before the plugin of type `Target`.
154    /// If the plugin was already the group, it is removed from its previous place. There must
155    /// be a plugin of type `Target` in the group or it will panic.
156    pub fn add_before<Target: Plugin, T: Plugin>(mut self, plugin: T) -> Self {
157        let target_index = self.index_of::<Target>();
158        self.order.insert(target_index, TypeId::of::<T>());
159        self.upsert_plugin_state(plugin, target_index);
160        self
161    }
162
163    /// Adds a [`Plugin`] in this [`PluginGroupBuilder`] after the plugin of type `Target`.
164    /// If the plugin was already the group, it is removed from its previous place. There must
165    /// be a plugin of type `Target` in the group or it will panic.
166    pub fn add_after<Target: Plugin, T: Plugin>(mut self, plugin: T) -> Self {
167        let target_index = self.index_of::<Target>() + 1;
168        self.order.insert(target_index, TypeId::of::<T>());
169        self.upsert_plugin_state(plugin, target_index);
170        self
171    }
172
173    /// Enables a [`Plugin`].
174    ///
175    /// [`Plugin`]s within a [`PluginGroup`] are enabled by default. This function is used to
176    /// opt back in to a [`Plugin`] after [disabling](Self::disable) it. If there are no plugins
177    /// of type `T` in this group, it will panic.
178    pub fn enable<T: Plugin>(mut self) -> Self {
179        let plugin_entry = self
180            .plugins
181            .get_mut(&TypeId::of::<T>())
182            .expect("Cannot enable a plugin that does not exist.");
183        plugin_entry.enabled = true;
184        self
185    }
186
187    /// Disables a [`Plugin`], preventing it from being added to the [`App`] with the rest of the
188    /// [`PluginGroup`]. The disabled [`Plugin`] keeps its place in the [`PluginGroup`], so it can
189    /// still be used for ordering with [`add_before`](Self::add_before) or
190    /// [`add_after`](Self::add_after), or it can be [re-enabled](Self::enable). If there are no
191    /// plugins of type `T` in this group, it will panic.
192    pub fn disable<T: Plugin>(mut self) -> Self {
193        let plugin_entry = self
194            .plugins
195            .get_mut(&TypeId::of::<T>())
196            .expect("Cannot disable a plugin that does not exist.");
197        plugin_entry.enabled = false;
198        self
199    }
200
201    /// Consumes the [`PluginGroupBuilder`] and [builds](Plugin::build) the contained [`Plugin`]s
202    /// in the order specified.
203    ///
204    /// # Panics
205    ///
206    /// Panics if one of the plugin in the group was already added to the application.
207    #[track_caller]
208    pub fn finish(mut self, app: &mut App) {
209        for ty in &self.order {
210            if let Some(entry) = self.plugins.remove(ty) {
211                if entry.enabled {
212                    debug!("added plugin: {}", entry.plugin.name());
213                    if let Err(AppError::DuplicatePlugin { plugin_name }) =
214                        app.add_boxed_plugin(entry.plugin)
215                    {
216                        panic!(
217                            "Error adding plugin {} in group {}: plugin was already added in application",
218                            plugin_name,
219                            self.group_name
220                        );
221                    }
222                }
223            }
224        }
225    }
226}
227
228/// A plugin group which doesn't do anything. Useful for examples:
229/// ```
230/// # use bevy_app::prelude::*;
231/// use bevy_app::NoopPluginGroup as MinimalPlugins;
232///
233/// fn main(){
234///     App::new().add_plugins(MinimalPlugins).run();
235/// }
236/// ```
237#[doc(hidden)]
238pub struct NoopPluginGroup;
239
240impl PluginGroup for NoopPluginGroup {
241    fn build(self) -> PluginGroupBuilder {
242        PluginGroupBuilder::start::<Self>()
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use super::PluginGroupBuilder;
249    use crate::{App, NoopPluginGroup, Plugin};
250
251    struct PluginA;
252    impl Plugin for PluginA {
253        fn build(&self, _: &mut App) {}
254    }
255
256    struct PluginB;
257    impl Plugin for PluginB {
258        fn build(&self, _: &mut App) {}
259    }
260
261    struct PluginC;
262    impl Plugin for PluginC {
263        fn build(&self, _: &mut App) {}
264    }
265
266    #[test]
267    fn basic_ordering() {
268        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
269            .add(PluginA)
270            .add(PluginB)
271            .add(PluginC);
272
273        assert_eq!(
274            group.order,
275            vec![
276                std::any::TypeId::of::<PluginA>(),
277                std::any::TypeId::of::<PluginB>(),
278                std::any::TypeId::of::<PluginC>(),
279            ]
280        );
281    }
282
283    #[test]
284    fn add_after() {
285        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
286            .add(PluginA)
287            .add(PluginB)
288            .add_after::<PluginA, PluginC>(PluginC);
289
290        assert_eq!(
291            group.order,
292            vec![
293                std::any::TypeId::of::<PluginA>(),
294                std::any::TypeId::of::<PluginC>(),
295                std::any::TypeId::of::<PluginB>(),
296            ]
297        );
298    }
299
300    #[test]
301    fn add_before() {
302        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
303            .add(PluginA)
304            .add(PluginB)
305            .add_before::<PluginB, PluginC>(PluginC);
306
307        assert_eq!(
308            group.order,
309            vec![
310                std::any::TypeId::of::<PluginA>(),
311                std::any::TypeId::of::<PluginC>(),
312                std::any::TypeId::of::<PluginB>(),
313            ]
314        );
315    }
316
317    #[test]
318    fn readd() {
319        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
320            .add(PluginA)
321            .add(PluginB)
322            .add(PluginC)
323            .add(PluginB);
324
325        assert_eq!(
326            group.order,
327            vec![
328                std::any::TypeId::of::<PluginA>(),
329                std::any::TypeId::of::<PluginC>(),
330                std::any::TypeId::of::<PluginB>(),
331            ]
332        );
333    }
334
335    #[test]
336    fn readd_after() {
337        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
338            .add(PluginA)
339            .add(PluginB)
340            .add(PluginC)
341            .add_after::<PluginA, PluginC>(PluginC);
342
343        assert_eq!(
344            group.order,
345            vec![
346                std::any::TypeId::of::<PluginA>(),
347                std::any::TypeId::of::<PluginC>(),
348                std::any::TypeId::of::<PluginB>(),
349            ]
350        );
351    }
352
353    #[test]
354    fn readd_before() {
355        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
356            .add(PluginA)
357            .add(PluginB)
358            .add(PluginC)
359            .add_before::<PluginB, PluginC>(PluginC);
360
361        assert_eq!(
362            group.order,
363            vec![
364                std::any::TypeId::of::<PluginA>(),
365                std::any::TypeId::of::<PluginC>(),
366                std::any::TypeId::of::<PluginB>(),
367            ]
368        );
369    }
370
371    #[test]
372    fn add_basic_subgroup() {
373        let group_a = PluginGroupBuilder::start::<NoopPluginGroup>()
374            .add(PluginA)
375            .add(PluginB);
376
377        let group_b = PluginGroupBuilder::start::<NoopPluginGroup>()
378            .add_group(group_a)
379            .add(PluginC);
380
381        assert_eq!(
382            group_b.order,
383            vec![
384                std::any::TypeId::of::<PluginA>(),
385                std::any::TypeId::of::<PluginB>(),
386                std::any::TypeId::of::<PluginC>(),
387            ]
388        );
389    }
390
391    #[test]
392    fn add_conflicting_subgroup() {
393        let group_a = PluginGroupBuilder::start::<NoopPluginGroup>()
394            .add(PluginA)
395            .add(PluginC);
396
397        let group_b = PluginGroupBuilder::start::<NoopPluginGroup>()
398            .add(PluginB)
399            .add(PluginC);
400
401        let group = PluginGroupBuilder::start::<NoopPluginGroup>()
402            .add_group(group_a)
403            .add_group(group_b);
404
405        assert_eq!(
406            group.order,
407            vec![
408                std::any::TypeId::of::<PluginA>(),
409                std::any::TypeId::of::<PluginB>(),
410                std::any::TypeId::of::<PluginC>(),
411            ]
412        );
413    }
414}