1use crate::{App, AppError, Plugin};
2use alloc::{
3 boxed::Box,
4 string::{String, ToString},
5 vec::Vec,
6};
7use bevy_utils::{TypeIdHashMap, TypeIdHashMapEntry as Entry};
8use core::any::TypeId;
9use log::{debug, warn};
10
11#[macro_export]
120macro_rules! plugin_group {
121 {
122 $(#[$group_meta:meta])*
123 $vis:vis struct $group:ident {
124 $(
125 $(#[cfg(feature = $plugin_feature:literal)])?
126 $(#[custom($plugin_meta:meta)])*
127 $($plugin_path:ident::)* : $plugin_name:ident
128 ),*
129 $(
130 $(,)?$(
131 #[plugin_group]
132 $(#[cfg(feature = $plugin_group_feature:literal)])?
133 $(#[custom($plugin_group_meta:meta)])*
134 $($plugin_group_path:ident::)* : $plugin_group_name:ident
135 ),+
136 )?
137 $(
138 $(,)?$(
139 #[doc(hidden)]
140 $(#[cfg(feature = $hidden_plugin_feature:literal)])?
141 $(#[custom($hidden_plugin_meta:meta)])*
142 $($hidden_plugin_path:ident::)* : $hidden_plugin_name:ident
143 ),+
144 )?
145 $(
146 $(,)? @fn {
147 $(
148 $(#[cfg(feature = $fn_plugin_feature:literal)])?
149 $(#[custom($fn_plugin_meta:meta)])*
150 $($fn_plugin_path:ident::)* : $fn_plugin_name:ident
151 ),*
152 $(
153 $(,)?$(
154 #[doc(hidden)]
155 $(#[cfg(feature = $fn_hidden_plugin_feature:literal)])?
156 $(#[custom($fn_hidden_plugin_meta:meta)])*
157 $($fn_hidden_plugin_path:ident::)* : $fn_hidden_plugin_name:ident
158 ),+
159 )?
160 $(,)?
161 }
162 )?
163
164 $(,)?
165 }
166 $($(#[doc = $post_doc:literal])+)?
167 } => {
168 $(#[$group_meta])*
169 $(#[doc = ::core::concat!(
171 " - [`", ::core::stringify!($plugin_name), "`](" $(, ::core::stringify!($plugin_path), "::")*, ::core::stringify!($plugin_name), ")"
172 $(, " - with feature `", $plugin_feature, "`")?
173 )])*
174 $($(#[doc = ::core::concat!(
175 " - [`", ::core::stringify!($plugin_group_name), "`](" $(, ::core::stringify!($plugin_group_path), "::")*, ::core::stringify!($plugin_group_name), ")"
176 $(, " - with feature `", $plugin_group_feature, "`")?
177 )])+)?
178 $($(#[doc = ::core::concat!(
179 " - [`", ::core::stringify!($fn_plugin_name), "`](" $(, ::core::stringify!($fn_plugin_path), "::")*, ::core::stringify!($fn_plugin_name), ")"
180 $(, " - with feature `", $fn_plugin_feature, "`")?
181 )])*)?
182 $(
183 $(#[doc = $post_doc])+
185 )?
186 $vis struct $group;
187
188 impl $crate::PluginGroup for $group {
189 fn build(self) -> $crate::PluginGroupBuilder {
190 let mut group = $crate::PluginGroupBuilder::start::<Self>();
191
192 $(
193 $(#[cfg(feature = $plugin_feature)])?
194 $(#[$plugin_meta])*
195 {
196 const _: () = {
197 const fn check_default<T: ::core::default::Default>() {}
198 check_default::<$($plugin_path::)*$plugin_name>();
199 };
200
201 group = group.add(<$($plugin_path::)*$plugin_name>::default());
202 }
203 )*
204 $($(
205 $(#[cfg(feature = $plugin_group_feature)])?
206 $(#[$plugin_group_meta])*
207 {
208 const _: () = {
209 const fn check_default<T: ::core::default::Default>() {}
210 check_default::<$($plugin_group_path::)*$plugin_group_name>();
211 };
212
213 group = group.add_group(<$($plugin_group_path::)*$plugin_group_name>::default());
214 }
215 )+)?
216 $($(
217 $(#[cfg(feature = $hidden_plugin_feature)])?
218 $(#[$hidden_plugin_meta])*
219 {
220 const _: () = {
221 const fn check_default<T: ::core::default::Default>() {}
222 check_default::<$($hidden_plugin_path::)*$hidden_plugin_name>();
223 };
224
225 group = group.add(<$($hidden_plugin_path::)*$hidden_plugin_name>::default());
226 }
227 )+)?
228 $($(
229 $(#[cfg(feature = $fn_plugin_feature)])?
230 $(#[$fn_plugin_meta])*
231 {
232 group = group.add($($fn_plugin_path::)*$fn_plugin_name);
233 }
234 )*)?
235 $($($(
236 $(#[cfg(feature = $fn_hidden_plugin_feature)])?
237 $(#[$fn_hidden_plugin_meta])*
238 {
239 group = group.add($($fn_hidden_plugin_path::)*$fn_hidden_plugin_name);
240 }
241 )+)?)?
242
243 group
244 }
245 }
246 };
247}
248
249pub trait PluginGroup: Sized {
254 fn build(self) -> PluginGroupBuilder;
256 fn name() -> String {
258 core::any::type_name::<Self>().to_string()
259 }
260 fn set<T: Plugin>(self, plugin: T) -> PluginGroupBuilder {
262 self.build().set(plugin)
263 }
264}
265
266struct PluginEntry {
267 plugin: Box<dyn Plugin>,
268 enabled: bool,
269}
270
271impl PluginGroup for PluginGroupBuilder {
272 fn build(self) -> PluginGroupBuilder {
273 self
274 }
275}
276
277pub struct PluginGroupBuilder {
283 group_name: String,
284 plugins: TypeIdHashMap<PluginEntry>,
285 order: Vec<TypeId>,
286}
287
288impl PluginGroupBuilder {
289 pub fn start<PG: PluginGroup>() -> Self {
291 Self {
292 group_name: PG::name(),
293 plugins: Default::default(),
294 order: Default::default(),
295 }
296 }
297
298 pub fn contains<T: Plugin>(&self) -> bool {
300 self.plugins.contains_key(&TypeId::of::<T>())
301 }
302
303 pub fn enabled<T: Plugin>(&self) -> bool {
305 self.plugins
306 .get(&TypeId::of::<T>())
307 .is_some_and(|e| e.enabled)
308 }
309
310 fn index_of<Target: Plugin>(&self) -> Option<usize> {
312 self.order
313 .iter()
314 .position(|&ty| ty == TypeId::of::<Target>())
315 }
316
317 fn upsert_plugin_state<T: Plugin>(&mut self, plugin: T, added_at_index: usize) {
320 self.upsert_plugin_entry_state(
321 TypeId::of::<T>(),
322 PluginEntry {
323 plugin: Box::new(plugin),
324 enabled: true,
325 },
326 added_at_index,
327 );
328 }
329
330 fn upsert_plugin_entry_state(
333 &mut self,
334 key: TypeId,
335 plugin: PluginEntry,
336 added_at_index: usize,
337 ) {
338 if let Some(entry) = self.plugins.insert(key, plugin) {
339 if entry.enabled {
340 warn!(
341 "You are replacing plugin '{}' that was not disabled.",
342 entry.plugin.name()
343 );
344 }
345 if let Some(to_remove) = self
346 .order
347 .iter()
348 .enumerate()
349 .find(|(i, ty)| *i != added_at_index && **ty == key)
350 .map(|(i, _)| i)
351 {
352 self.order.remove(to_remove);
353 }
354 }
355 }
356
357 pub fn set<T: Plugin>(self, plugin: T) -> Self {
363 self.try_set(plugin).unwrap_or_else(|_| {
364 panic!(
365 "{} does not exist in this PluginGroup",
366 core::any::type_name::<T>(),
367 )
368 })
369 }
370
371 pub fn try_set<T: Plugin>(mut self, plugin: T) -> Result<Self, (Self, T)> {
375 match self.plugins.entry(TypeId::of::<T>()) {
376 Entry::Occupied(mut entry) => {
377 entry.get_mut().plugin = Box::new(plugin);
378
379 Ok(self)
380 }
381 Entry::Vacant(_) => Err((self, plugin)),
382 }
383 }
384
385 #[expect(
389 clippy::should_implement_trait,
390 reason = "This does not emulate the `+` operator, but is more akin to pushing to a stack."
391 )]
392 pub fn add<T: Plugin>(mut self, plugin: T) -> Self {
393 let target_index = self.order.len();
394 self.order.push(TypeId::of::<T>());
395 self.upsert_plugin_state(plugin, target_index);
396 self
397 }
398
399 pub fn try_add<T: Plugin>(self, plugin: T) -> Result<Self, (Self, T)> {
403 if self.contains::<T>() {
404 return Err((self, plugin));
405 }
406
407 Ok(self.add(plugin))
408 }
409
410 pub fn add_group(mut self, group: impl PluginGroup) -> Self {
413 let Self {
414 mut plugins, order, ..
415 } = group.build();
416
417 for plugin_id in order {
418 self.upsert_plugin_entry_state(
419 plugin_id,
420 plugins.remove(&plugin_id).unwrap(),
421 self.order.len(),
422 );
423
424 self.order.push(plugin_id);
425 }
426
427 self
428 }
429
430 pub fn add_before<Target: Plugin>(self, plugin: impl Plugin) -> Self {
438 self.try_add_before_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_before<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_before_overwrite::<Target, _>(plugin)
460 }
461
462 pub fn try_add_before_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 self.order.insert(target_index, TypeId::of::<Insert>());
476 self.upsert_plugin_state(plugin, target_index);
477 Ok(self)
478 }
479
480 pub fn add_after<Target: Plugin>(self, plugin: impl Plugin) -> Self {
488 self.try_add_after_overwrite::<Target, _>(plugin)
489 .unwrap_or_else(|_| {
490 panic!(
491 "Plugin does not exist in group: {}.",
492 core::any::type_name::<Target>()
493 )
494 })
495 }
496
497 pub fn try_add_after<Target: Plugin, Insert: Plugin>(
502 self,
503 plugin: Insert,
504 ) -> Result<Self, (Self, Insert)> {
505 if self.contains::<Insert>() {
506 return Err((self, plugin));
507 }
508
509 self.try_add_after_overwrite::<Target, _>(plugin)
510 }
511
512 pub fn try_add_after_overwrite<Target: Plugin, Insert: Plugin>(
518 mut self,
519 plugin: Insert,
520 ) -> Result<Self, (Self, Insert)> {
521 let Some(target_index) = self.index_of::<Target>() else {
522 return Err((self, plugin));
523 };
524
525 let target_index = target_index + 1;
526
527 self.order.insert(target_index, TypeId::of::<Insert>());
528 self.upsert_plugin_state(plugin, target_index);
529 Ok(self)
530 }
531
532 pub fn enable<T: Plugin>(mut self) -> Self {
538 let plugin_entry = self
539 .plugins
540 .get_mut(&TypeId::of::<T>())
541 .expect("Cannot enable a plugin that does not exist.");
542 plugin_entry.enabled = true;
543 self
544 }
545
546 pub fn disable<T: Plugin>(mut self) -> Self {
552 let plugin_entry = self
553 .plugins
554 .get_mut(&TypeId::of::<T>())
555 .expect("Cannot disable a plugin that does not exist.");
556 plugin_entry.enabled = false;
557 self
558 }
559
560 #[track_caller]
567 pub fn finish(mut self, app: &mut App) {
568 for ty in &self.order {
569 if let Some(entry) = self.plugins.remove(ty)
570 && entry.enabled
571 {
572 debug!("added plugin: {}", entry.plugin.name());
573 if let Err(AppError::DuplicatePlugin { plugin_name }) =
574 app.add_boxed_plugin(entry.plugin)
575 {
576 panic!(
577 "Error adding plugin {} in group {}: plugin was already added in application",
578 plugin_name,
579 self.group_name
580 );
581 }
582 }
583 }
584 }
585}
586
587#[doc(hidden)]
597pub struct NoopPluginGroup;
598
599impl PluginGroup for NoopPluginGroup {
600 fn build(self) -> PluginGroupBuilder {
601 PluginGroupBuilder::start::<Self>()
602 }
603}
604
605#[cfg(test)]
606mod tests {
607 use alloc::vec;
608 use core::{any::TypeId, fmt::Debug};
609
610 use super::PluginGroupBuilder;
611 use crate::{App, NoopPluginGroup, Plugin, PluginGroup};
612
613 #[derive(Default)]
614 struct PluginA;
615 impl Plugin for PluginA {
616 fn build(&self, _: &mut App) {}
617 }
618
619 #[derive(Default)]
620 struct PluginB;
621 impl Plugin for PluginB {
622 fn build(&self, _: &mut App) {}
623 }
624
625 #[derive(Default)]
626 struct PluginC;
627 impl Plugin for PluginC {
628 fn build(&self, _: &mut App) {}
629 }
630
631 fn plugin_d(_: &mut App) {}
632 fn plugin_e(_: &mut App) {}
633
634 #[derive(PartialEq, Debug)]
635 struct PluginWithData(u32);
636 impl Plugin for PluginWithData {
637 fn build(&self, _: &mut App) {}
638 }
639
640 fn get_plugin<T: Debug + 'static>(group: &PluginGroupBuilder, id: TypeId) -> &T {
641 group.plugins[&id]
642 .plugin
643 .as_any()
644 .downcast_ref::<T>()
645 .unwrap()
646 }
647
648 #[test]
649 fn contains() {
650 let group = PluginGroupBuilder::start::<NoopPluginGroup>()
651 .add(PluginA)
652 .add(PluginB);
653
654 assert!(group.contains::<PluginA>());
655 assert!(!group.contains::<PluginC>());
656
657 let group = group.disable::<PluginA>();
658
659 assert!(group.enabled::<PluginB>());
660 assert!(!group.enabled::<PluginA>());
661 }
662
663 #[test]
664 fn basic_ordering() {
665 let group = PluginGroupBuilder::start::<NoopPluginGroup>()
666 .add(PluginA)
667 .add(PluginB)
668 .add(PluginC);
669
670 assert_eq!(
671 group.order,
672 vec![
673 TypeId::of::<PluginA>(),
674 TypeId::of::<PluginB>(),
675 TypeId::of::<PluginC>(),
676 ]
677 );
678 }
679
680 #[test]
681 fn add_before() {
682 let group = PluginGroupBuilder::start::<NoopPluginGroup>()
683 .add(PluginA)
684 .add(PluginB)
685 .add_before::<PluginB>(PluginC);
686
687 assert_eq!(
688 group.order,
689 vec![
690 TypeId::of::<PluginA>(),
691 TypeId::of::<PluginC>(),
692 TypeId::of::<PluginB>(),
693 ]
694 );
695 }
696
697 #[test]
698 fn try_add_before() {
699 let group = PluginGroupBuilder::start::<NoopPluginGroup>().add(PluginA);
700
701 let Ok(group) = group.try_add_before::<PluginA, _>(PluginC) else {
702 panic!("PluginA wasn't in group");
703 };
704
705 assert_eq!(
706 group.order,
707 vec![TypeId::of::<PluginC>(), TypeId::of::<PluginA>(),]
708 );
709
710 assert!(group.try_add_before::<PluginA, _>(PluginC).is_err());
711 }
712
713 #[test]
714 #[should_panic(
715 expected = "Plugin does not exist in group: bevy_app::plugin_group::tests::PluginB."
716 )]
717 fn add_before_nonexistent() {
718 PluginGroupBuilder::start::<NoopPluginGroup>()
719 .add(PluginA)
720 .add_before::<PluginB>(PluginC);
721 }
722
723 #[test]
724 fn add_after() {
725 let group = PluginGroupBuilder::start::<NoopPluginGroup>()
726 .add(PluginA)
727 .add(PluginB)
728 .add_after::<PluginA>(PluginC);
729
730 assert_eq!(
731 group.order,
732 vec![
733 TypeId::of::<PluginA>(),
734 TypeId::of::<PluginC>(),
735 TypeId::of::<PluginB>(),
736 ]
737 );
738 }
739
740 #[test]
741 fn try_add_after() {
742 let group = PluginGroupBuilder::start::<NoopPluginGroup>()
743 .add(PluginA)
744 .add(PluginB);
745
746 let Ok(group) = group.try_add_after::<PluginA, _>(PluginC) else {
747 panic!("PluginA wasn't in group");
748 };
749
750 assert_eq!(
751 group.order,
752 vec![
753 TypeId::of::<PluginA>(),
754 TypeId::of::<PluginC>(),
755 TypeId::of::<PluginB>(),
756 ]
757 );
758
759 assert!(group.try_add_after::<PluginA, _>(PluginC).is_err());
760 }
761
762 #[test]
763 #[should_panic(
764 expected = "Plugin does not exist in group: bevy_app::plugin_group::tests::PluginB."
765 )]
766 fn add_after_nonexistent() {
767 PluginGroupBuilder::start::<NoopPluginGroup>()
768 .add(PluginA)
769 .add_after::<PluginB>(PluginC);
770 }
771
772 #[test]
773 fn add_overwrite() {
774 let group = PluginGroupBuilder::start::<NoopPluginGroup>()
775 .add(PluginA)
776 .add(PluginWithData(0x0F))
777 .add(PluginC);
778
779 let id = TypeId::of::<PluginWithData>();
780 assert_eq!(
781 get_plugin::<PluginWithData>(&group, id),
782 &PluginWithData(0x0F)
783 );
784
785 let group = group.add(PluginWithData(0xA0));
786
787 assert_eq!(
788 get_plugin::<PluginWithData>(&group, id),
789 &PluginWithData(0xA0)
790 );
791 assert_eq!(
792 group.order,
793 vec![
794 TypeId::of::<PluginA>(),
795 TypeId::of::<PluginC>(),
796 TypeId::of::<PluginWithData>(),
797 ]
798 );
799
800 let Ok(group) = group.try_add_before_overwrite::<PluginA, _>(PluginWithData(0x01)) else {
801 panic!("PluginA wasn't in group");
802 };
803 assert_eq!(
804 get_plugin::<PluginWithData>(&group, id),
805 &PluginWithData(0x01)
806 );
807 assert_eq!(
808 group.order,
809 vec![
810 TypeId::of::<PluginWithData>(),
811 TypeId::of::<PluginA>(),
812 TypeId::of::<PluginC>(),
813 ]
814 );
815
816 let Ok(group) = group.try_add_after_overwrite::<PluginA, _>(PluginWithData(0xdeadbeef))
817 else {
818 panic!("PluginA wasn't in group");
819 };
820 assert_eq!(
821 get_plugin::<PluginWithData>(&group, id),
822 &PluginWithData(0xdeadbeef)
823 );
824 assert_eq!(
825 group.order,
826 vec![
827 TypeId::of::<PluginA>(),
828 TypeId::of::<PluginWithData>(),
829 TypeId::of::<PluginC>(),
830 ]
831 );
832 }
833
834 #[test]
835 fn readd() {
836 let group = PluginGroupBuilder::start::<NoopPluginGroup>()
837 .add(PluginA)
838 .add(PluginB)
839 .add(PluginC)
840 .add(PluginB);
841
842 assert_eq!(
843 group.order,
844 vec![
845 TypeId::of::<PluginA>(),
846 TypeId::of::<PluginC>(),
847 TypeId::of::<PluginB>(),
848 ]
849 );
850 }
851
852 #[test]
853 fn readd_before() {
854 let group = PluginGroupBuilder::start::<NoopPluginGroup>()
855 .add(PluginA)
856 .add(PluginB)
857 .add(PluginC)
858 .add_before::<PluginB>(PluginC);
859
860 assert_eq!(
861 group.order,
862 vec![
863 TypeId::of::<PluginA>(),
864 TypeId::of::<PluginC>(),
865 TypeId::of::<PluginB>(),
866 ]
867 );
868 }
869
870 #[test]
871 fn readd_after() {
872 let group = PluginGroupBuilder::start::<NoopPluginGroup>()
873 .add(PluginA)
874 .add(PluginB)
875 .add(PluginC)
876 .add_after::<PluginA>(PluginC);
877
878 assert_eq!(
879 group.order,
880 vec![
881 TypeId::of::<PluginA>(),
882 TypeId::of::<PluginC>(),
883 TypeId::of::<PluginB>(),
884 ]
885 );
886 }
887
888 #[test]
889 fn add_basic_subgroup() {
890 let group_a = PluginGroupBuilder::start::<NoopPluginGroup>()
891 .add(PluginA)
892 .add(PluginB);
893
894 let group_b = PluginGroupBuilder::start::<NoopPluginGroup>()
895 .add_group(group_a)
896 .add(PluginC);
897
898 assert_eq!(
899 group_b.order,
900 vec![
901 TypeId::of::<PluginA>(),
902 TypeId::of::<PluginB>(),
903 TypeId::of::<PluginC>(),
904 ]
905 );
906 }
907
908 #[test]
909 fn add_conflicting_subgroup() {
910 let group_a = PluginGroupBuilder::start::<NoopPluginGroup>()
911 .add(PluginA)
912 .add(PluginC);
913
914 let group_b = PluginGroupBuilder::start::<NoopPluginGroup>()
915 .add(PluginB)
916 .add(PluginC);
917
918 let group = PluginGroupBuilder::start::<NoopPluginGroup>()
919 .add_group(group_a)
920 .add_group(group_b);
921
922 assert_eq!(
923 group.order,
924 vec![
925 TypeId::of::<PluginA>(),
926 TypeId::of::<PluginB>(),
927 TypeId::of::<PluginC>(),
928 ]
929 );
930 }
931
932 plugin_group! {
933 #[derive(Default)]
934 struct PluginGroupA {
935 :PluginA
936 }
937 }
938 plugin_group! {
939 #[derive(Default)]
940 struct PluginGroupB {
941 :PluginB
942 }
943 }
944 plugin_group! {
945 struct PluginGroupC {
946 :PluginC
947 #[plugin_group]
948 :PluginGroupA,
949 #[plugin_group]
950 :PluginGroupB,
951 }
952 }
953 #[test]
954 fn construct_nested_plugin_groups() {
955 PluginGroupC {}.build();
956 }
957 plugin_group! {
958 #[derive(Default)]
959 struct PluginGroupD {
960 :PluginA
961 @fn {
962 #[doc(hidden)]
963 :plugin_d
964 }
965 }
966 }
967 plugin_group! {
968 #[derive(Default)]
969 struct PluginGroupE {
970 @fn {
971 :plugin_e
972 }
973 }
974 }
975 plugin_group! {
976 struct PluginGroupF {
977 :PluginB
978 #[plugin_group]
979 :PluginGroupD,
980 #[plugin_group]
981 :PluginGroupE,
982 }
983 }
984 #[test]
985 fn construct_nested_plugin_groups_with_plugin_functions() {
986 fn type_id_of<T: Plugin>(_: T) -> TypeId {
987 TypeId::of::<T>()
988 }
989
990 let group = PluginGroupF {}.build();
991
992 assert_eq!(
993 group.order,
994 vec![
995 TypeId::of::<PluginB>(),
996 TypeId::of::<PluginA>(),
997 type_id_of(plugin_d),
998 type_id_of(plugin_e),
999 ]
1000 );
1001 }
1002}