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#[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 $(#[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 $(#[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
199pub trait PluginGroup: Sized {
204 fn build(self) -> PluginGroupBuilder;
206 fn name() -> String {
208 core::any::type_name::<Self>().to_string()
209 }
210 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
227pub struct PluginGroupBuilder {
233 group_name: String,
234 plugins: TypeIdMap<PluginEntry>,
235 order: Vec<TypeId>,
236}
237
238impl PluginGroupBuilder {
239 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 pub fn contains<T: Plugin>(&self) -> bool {
250 self.plugins.contains_key(&TypeId::of::<T>())
251 }
252
253 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 fn index_of<Target: Plugin>(&self) -> Option<usize> {
262 self.order
263 .iter()
264 .position(|&ty| ty == TypeId::of::<Target>())
265 }
266
267 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 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 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 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 #[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 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 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 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 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 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 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 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 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 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 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 #[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#[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}