1#![expect(
2 clippy::module_inception,
3 reason = "This instance of module inception is being discussed; see #17344."
4)]
5use alloc::{
6 boxed::Box,
7 collections::{BTreeMap, BTreeSet},
8 format,
9 string::{String, ToString},
10 vec,
11 vec::Vec,
12};
13use bevy_platform::collections::{HashMap, HashSet};
14use bevy_utils::{default, prelude::DebugName, TypeIdMap};
15use core::{
16 any::{Any, TypeId},
17 fmt::{Debug, Write},
18};
19use fixedbitset::FixedBitSet;
20use log::{error, info, warn};
21use pass::ScheduleBuildPassObj;
22use thiserror::Error;
23#[cfg(feature = "trace")]
24use tracing::info_span;
25
26use crate::{component::CheckChangeTicks, system::System};
27use crate::{
28 component::{ComponentId, Components},
29 prelude::Component,
30 resource::Resource,
31 schedule::*,
32 system::ScheduleSystem,
33 world::World,
34};
35
36use crate::{query::AccessConflicts, storage::SparseSetIndex};
37pub use stepping::Stepping;
38use Direction::{Incoming, Outgoing};
39
40#[derive(Default, Resource)]
42pub struct Schedules {
43 inner: HashMap<InternedScheduleLabel, Schedule>,
44 pub ignored_scheduling_ambiguities: BTreeSet<ComponentId>,
46}
47
48impl Schedules {
49 pub fn new() -> Self {
51 Self::default()
52 }
53
54 pub fn insert(&mut self, schedule: Schedule) -> Option<Schedule> {
59 self.inner.insert(schedule.label, schedule)
60 }
61
62 pub fn remove(&mut self, label: impl ScheduleLabel) -> Option<Schedule> {
64 self.inner.remove(&label.intern())
65 }
66
67 pub fn remove_entry(
69 &mut self,
70 label: impl ScheduleLabel,
71 ) -> Option<(InternedScheduleLabel, Schedule)> {
72 self.inner.remove_entry(&label.intern())
73 }
74
75 pub fn contains(&self, label: impl ScheduleLabel) -> bool {
77 self.inner.contains_key(&label.intern())
78 }
79
80 pub fn get(&self, label: impl ScheduleLabel) -> Option<&Schedule> {
82 self.inner.get(&label.intern())
83 }
84
85 pub fn get_mut(&mut self, label: impl ScheduleLabel) -> Option<&mut Schedule> {
87 self.inner.get_mut(&label.intern())
88 }
89
90 pub fn entry(&mut self, label: impl ScheduleLabel) -> &mut Schedule {
92 self.inner
93 .entry(label.intern())
94 .or_insert_with(|| Schedule::new(label))
95 }
96
97 pub fn iter(&self) -> impl Iterator<Item = (&dyn ScheduleLabel, &Schedule)> {
99 self.inner
100 .iter()
101 .map(|(label, schedule)| (&**label, schedule))
102 }
103 pub fn iter_mut(&mut self) -> impl Iterator<Item = (&dyn ScheduleLabel, &mut Schedule)> {
105 self.inner
106 .iter_mut()
107 .map(|(label, schedule)| (&**label, schedule))
108 }
109
110 pub(crate) fn check_change_ticks(&mut self, check: CheckChangeTicks) {
114 #[cfg(feature = "trace")]
115 let _all_span = info_span!("check stored schedule ticks").entered();
116 #[cfg_attr(
117 not(feature = "trace"),
118 expect(
119 unused_variables,
120 reason = "The `label` variable goes unused if the `trace` feature isn't active"
121 )
122 )]
123 for (label, schedule) in &mut self.inner {
124 #[cfg(feature = "trace")]
125 let name = format!("{label:?}");
126 #[cfg(feature = "trace")]
127 let _one_span = info_span!("check schedule ticks", name = &name).entered();
128 schedule.check_change_ticks(check);
129 }
130 }
131
132 pub fn configure_schedules(&mut self, schedule_build_settings: ScheduleBuildSettings) {
134 for (_, schedule) in &mut self.inner {
135 schedule.set_build_settings(schedule_build_settings.clone());
136 }
137 }
138
139 pub fn allow_ambiguous_component<T: Component>(&mut self, world: &mut World) {
141 self.ignored_scheduling_ambiguities
142 .insert(world.register_component::<T>());
143 }
144
145 pub fn allow_ambiguous_resource<T: Resource>(&mut self, world: &mut World) {
147 self.ignored_scheduling_ambiguities
148 .insert(world.components_registrator().register_resource::<T>());
149 }
150
151 pub fn iter_ignored_ambiguities(&self) -> impl Iterator<Item = &ComponentId> + '_ {
153 self.ignored_scheduling_ambiguities.iter()
154 }
155
156 pub fn print_ignored_ambiguities(&self, components: &Components) {
161 let mut message =
162 "System order ambiguities caused by conflicts on the following types are ignored:\n"
163 .to_string();
164 for id in self.iter_ignored_ambiguities() {
165 writeln!(message, "{}", components.get_name(*id).unwrap()).unwrap();
166 }
167
168 info!("{message}");
169 }
170
171 pub fn add_systems<M>(
173 &mut self,
174 schedule: impl ScheduleLabel,
175 systems: impl IntoScheduleConfigs<ScheduleSystem, M>,
176 ) -> &mut Self {
177 self.entry(schedule).add_systems(systems);
178
179 self
180 }
181
182 #[track_caller]
184 pub fn configure_sets<M>(
185 &mut self,
186 schedule: impl ScheduleLabel,
187 sets: impl IntoScheduleConfigs<InternedSystemSet, M>,
188 ) -> &mut Self {
189 self.entry(schedule).configure_sets(sets);
190
191 self
192 }
193
194 #[track_caller]
201 pub fn ignore_ambiguity<M1, M2, S1, S2>(
202 &mut self,
203 schedule: impl ScheduleLabel,
204 a: S1,
205 b: S2,
206 ) -> &mut Self
207 where
208 S1: IntoSystemSet<M1>,
209 S2: IntoSystemSet<M2>,
210 {
211 self.entry(schedule).ignore_ambiguity(a, b);
212
213 self
214 }
215}
216
217fn make_executor(kind: ExecutorKind) -> Box<dyn SystemExecutor> {
218 match kind {
219 #[expect(deprecated, reason = "We still need to support this.")]
220 ExecutorKind::Simple => Box::new(SimpleExecutor::new()),
221 ExecutorKind::SingleThreaded => Box::new(SingleThreadedExecutor::new()),
222 #[cfg(feature = "std")]
223 ExecutorKind::MultiThreaded => Box::new(MultiThreadedExecutor::new()),
224 }
225}
226
227#[derive(Default)]
229pub enum Chain {
230 #[default]
232 Unchained,
233 Chained(TypeIdMap<Box<dyn Any>>),
236}
237
238impl Chain {
239 pub fn set_chained(&mut self) {
241 if matches!(self, Chain::Unchained) {
242 *self = Self::Chained(Default::default());
243 };
244 }
245 pub fn set_chained_with_config<T: 'static>(&mut self, config: T) {
248 self.set_chained();
249 if let Chain::Chained(config_map) = self {
250 config_map.insert(TypeId::of::<T>(), Box::new(config));
251 } else {
252 unreachable!()
253 };
254 }
255}
256
257pub struct Schedule {
330 label: InternedScheduleLabel,
331 graph: ScheduleGraph,
332 executable: SystemSchedule,
333 executor: Box<dyn SystemExecutor>,
334 executor_initialized: bool,
335 warnings: Vec<ScheduleBuildWarning>,
336}
337
338#[derive(ScheduleLabel, Hash, PartialEq, Eq, Debug, Clone)]
339struct DefaultSchedule;
340
341impl Default for Schedule {
342 fn default() -> Self {
347 Self::new(DefaultSchedule)
348 }
349}
350
351impl Schedule {
352 pub fn new(label: impl ScheduleLabel) -> Self {
354 let mut this = Self {
355 label: label.intern(),
356 graph: ScheduleGraph::new(),
357 executable: SystemSchedule::new(),
358 executor: make_executor(ExecutorKind::default()),
359 executor_initialized: false,
360 warnings: Vec::new(),
361 };
362 this.set_build_settings(Default::default());
364 this
365 }
366
367 pub fn label(&self) -> InternedScheduleLabel {
370 self.label
371 }
372
373 pub fn add_systems<M>(
375 &mut self,
376 systems: impl IntoScheduleConfigs<ScheduleSystem, M>,
377 ) -> &mut Self {
378 self.graph.process_configs(systems.into_configs(), false);
379 self
380 }
381
382 #[track_caller]
385 pub fn ignore_ambiguity<M1, M2, S1, S2>(&mut self, a: S1, b: S2) -> &mut Self
386 where
387 S1: IntoSystemSet<M1>,
388 S2: IntoSystemSet<M2>,
389 {
390 let a = a.into_system_set();
391 let b = b.into_system_set();
392
393 let a_id = self.graph.system_sets.get_key_or_insert(a.intern());
394 let b_id = self.graph.system_sets.get_key_or_insert(b.intern());
395
396 self.graph
397 .ambiguous_with
398 .add_edge(NodeId::Set(a_id), NodeId::Set(b_id));
399
400 self
401 }
402
403 #[track_caller]
405 pub fn configure_sets<M>(
406 &mut self,
407 sets: impl IntoScheduleConfigs<InternedSystemSet, M>,
408 ) -> &mut Self {
409 self.graph.configure_sets(sets);
410 self
411 }
412
413 pub fn add_build_pass<T: ScheduleBuildPass>(&mut self, pass: T) -> &mut Self {
415 self.graph.passes.insert(TypeId::of::<T>(), Box::new(pass));
416 self
417 }
418
419 pub fn remove_build_pass<T: ScheduleBuildPass>(&mut self) {
421 self.graph.passes.remove(&TypeId::of::<T>());
422 }
423
424 pub fn set_build_settings(&mut self, settings: ScheduleBuildSettings) -> &mut Self {
432 if settings.auto_insert_apply_deferred {
433 if !self
434 .graph
435 .passes
436 .contains_key(&TypeId::of::<passes::AutoInsertApplyDeferredPass>())
437 {
438 self.add_build_pass(passes::AutoInsertApplyDeferredPass::default());
439 }
440 } else {
441 self.remove_build_pass::<passes::AutoInsertApplyDeferredPass>();
442 }
443 self.graph.settings = settings;
444 self
445 }
446
447 pub fn get_build_settings(&self) -> ScheduleBuildSettings {
449 self.graph.settings.clone()
450 }
451
452 pub fn get_executor_kind(&self) -> ExecutorKind {
454 self.executor.kind()
455 }
456
457 pub fn set_executor_kind(&mut self, executor: ExecutorKind) -> &mut Self {
459 if executor != self.executor.kind() {
460 self.executor = make_executor(executor);
461 self.executor_initialized = false;
462 }
463 self
464 }
465
466 pub fn set_apply_final_deferred(&mut self, apply_final_deferred: bool) -> &mut Self {
471 self.executor.set_apply_final_deferred(apply_final_deferred);
472 self
473 }
474
475 pub fn run(&mut self, world: &mut World) {
477 #[cfg(feature = "trace")]
478 let _span = info_span!("schedule", name = ?self.label).entered();
479
480 world.check_change_ticks();
481 self.initialize(world).unwrap_or_else(|e| {
482 panic!(
483 "Error when initializing schedule {:?}: {}",
484 self.label,
485 e.to_string(self.graph(), world)
486 )
487 });
488
489 let error_handler = world.default_error_handler();
490
491 #[cfg(not(feature = "bevy_debug_stepping"))]
492 self.executor
493 .run(&mut self.executable, world, None, error_handler);
494
495 #[cfg(feature = "bevy_debug_stepping")]
496 {
497 let skip_systems = match world.get_resource_mut::<Stepping>() {
498 None => None,
499 Some(mut stepping) => stepping.skipped_systems(self),
500 };
501
502 self.executor.run(
503 &mut self.executable,
504 world,
505 skip_systems.as_ref(),
506 error_handler,
507 );
508 }
509 }
510
511 pub fn initialize(&mut self, world: &mut World) -> Result<(), ScheduleBuildError> {
516 if self.graph.changed {
517 self.graph.initialize(world);
518 let ignored_ambiguities = world
519 .get_resource_or_init::<Schedules>()
520 .ignored_scheduling_ambiguities
521 .clone();
522 self.warnings = self.graph.update_schedule(
523 world,
524 &mut self.executable,
525 &ignored_ambiguities,
526 self.label,
527 )?;
528 self.graph.changed = false;
529 self.executor_initialized = false;
530 }
531
532 if !self.executor_initialized {
533 self.executor.init(&self.executable);
534 self.executor_initialized = true;
535 }
536
537 Ok(())
538 }
539
540 pub fn graph(&self) -> &ScheduleGraph {
542 &self.graph
543 }
544
545 pub fn graph_mut(&mut self) -> &mut ScheduleGraph {
547 &mut self.graph
548 }
549
550 pub(crate) fn executable(&self) -> &SystemSchedule {
552 &self.executable
553 }
554
555 pub fn check_change_ticks(&mut self, check: CheckChangeTicks) {
559 for system in &mut self.executable.systems {
560 if !is_apply_deferred(system) {
561 system.check_change_tick(check);
562 }
563 }
564
565 for conditions in &mut self.executable.system_conditions {
566 for condition in conditions {
567 condition.check_change_tick(check);
568 }
569 }
570
571 for conditions in &mut self.executable.set_conditions {
572 for condition in conditions {
573 condition.check_change_tick(check);
574 }
575 }
576 }
577
578 pub fn apply_deferred(&mut self, world: &mut World) {
587 for SystemWithAccess { system, .. } in &mut self.executable.systems {
588 system.apply_deferred(world);
589 }
590 }
591
592 pub fn systems(
597 &self,
598 ) -> Result<impl Iterator<Item = (SystemKey, &ScheduleSystem)> + Sized, ScheduleNotInitialized>
599 {
600 if !self.executor_initialized {
601 return Err(ScheduleNotInitialized);
602 }
603
604 let iter = self
605 .executable
606 .system_ids
607 .iter()
608 .zip(&self.executable.systems)
609 .map(|(&node_id, system)| (node_id, &system.system));
610
611 Ok(iter)
612 }
613
614 pub fn systems_len(&self) -> usize {
616 if !self.executor_initialized {
617 self.graph.systems.len()
618 } else {
619 self.executable.systems.len()
620 }
621 }
622
623 pub fn warnings(&self) -> &[ScheduleBuildWarning] {
626 &self.warnings
627 }
628}
629
630pub struct Dag<N: GraphNodeId> {
632 graph: DiGraph<N>,
634 topsort: Vec<N>,
636}
637
638impl<N: GraphNodeId> Dag<N> {
639 fn new() -> Self {
640 Self {
641 graph: DiGraph::default(),
642 topsort: Vec::new(),
643 }
644 }
645
646 pub fn graph(&self) -> &DiGraph<N> {
648 &self.graph
649 }
650
651 pub fn cached_topsort(&self) -> &[N] {
655 &self.topsort
656 }
657}
658
659impl<N: GraphNodeId> Default for Dag<N> {
660 fn default() -> Self {
661 Self {
662 graph: Default::default(),
663 topsort: Default::default(),
664 }
665 }
666}
667
668#[derive(Default)]
673pub struct ScheduleGraph {
674 pub systems: Systems,
676 pub system_sets: SystemSets,
678 hierarchy: Dag<NodeId>,
680 dependency: Dag<NodeId>,
682 ambiguous_with: UnGraph<NodeId>,
683 pub ambiguous_with_all: HashSet<NodeId>,
685 conflicting_systems: Vec<(SystemKey, SystemKey, Vec<ComponentId>)>,
686 anonymous_sets: usize,
687 changed: bool,
688 settings: ScheduleBuildSettings,
689 passes: BTreeMap<TypeId, Box<dyn ScheduleBuildPassObj>>,
690}
691
692impl ScheduleGraph {
693 pub fn new() -> Self {
695 Self {
696 systems: Systems::default(),
697 system_sets: SystemSets::default(),
698 hierarchy: Dag::new(),
699 dependency: Dag::new(),
700 ambiguous_with: UnGraph::default(),
701 ambiguous_with_all: HashSet::default(),
702 conflicting_systems: Vec::new(),
703 anonymous_sets: 0,
704 changed: false,
705 settings: default(),
706 passes: default(),
707 }
708 }
709
710 pub fn hierarchy(&self) -> &Dag<NodeId> {
715 &self.hierarchy
716 }
717
718 pub fn dependency(&self) -> &Dag<NodeId> {
723 &self.dependency
724 }
725
726 pub fn conflicting_systems(&self) -> &[(SystemKey, SystemKey, Vec<ComponentId>)] {
731 &self.conflicting_systems
732 }
733
734 fn process_config<T: ProcessScheduleConfig + Schedulable>(
735 &mut self,
736 config: ScheduleConfig<T>,
737 collect_nodes: bool,
738 ) -> ProcessConfigsResult {
739 ProcessConfigsResult {
740 densely_chained: true,
741 nodes: collect_nodes
742 .then_some(T::process_config(self, config))
743 .into_iter()
744 .collect(),
745 }
746 }
747
748 fn apply_collective_conditions<
749 T: ProcessScheduleConfig + Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>,
750 >(
751 &mut self,
752 configs: &mut [ScheduleConfigs<T>],
753 collective_conditions: Vec<BoxedCondition>,
754 ) {
755 if !collective_conditions.is_empty() {
756 if let [config] = configs {
757 for condition in collective_conditions {
758 config.run_if_dyn(condition);
759 }
760 } else {
761 let set = self.create_anonymous_set();
762 for config in configs.iter_mut() {
763 config.in_set_inner(set.intern());
764 }
765 let mut set_config = InternedSystemSet::into_config(set.intern());
766 set_config.conditions.extend(collective_conditions);
767 self.configure_set_inner(set_config);
768 }
769 }
770 }
771
772 #[track_caller]
781 fn process_configs<
782 T: ProcessScheduleConfig + Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>,
783 >(
784 &mut self,
785 configs: ScheduleConfigs<T>,
786 collect_nodes: bool,
787 ) -> ProcessConfigsResult {
788 match configs {
789 ScheduleConfigs::ScheduleConfig(config) => self.process_config(config, collect_nodes),
790 ScheduleConfigs::Configs {
791 metadata,
792 mut configs,
793 collective_conditions,
794 } => {
795 self.apply_collective_conditions(&mut configs, collective_conditions);
796
797 let is_chained = matches!(metadata, Chain::Chained(_));
798
799 let mut densely_chained = is_chained || configs.len() == 1;
803 let mut configs = configs.into_iter();
804 let mut nodes = Vec::new();
805
806 let Some(first) = configs.next() else {
807 return ProcessConfigsResult {
808 nodes: Vec::new(),
809 densely_chained,
810 };
811 };
812 let mut previous_result = self.process_configs(first, collect_nodes || is_chained);
813 densely_chained &= previous_result.densely_chained;
814
815 for current in configs {
816 let current_result = self.process_configs(current, collect_nodes || is_chained);
817 densely_chained &= current_result.densely_chained;
818
819 if let Chain::Chained(chain_options) = &metadata {
820 let current_nodes = if current_result.densely_chained {
822 ¤t_result.nodes[..1]
823 } else {
824 ¤t_result.nodes
825 };
826 let previous_nodes = if previous_result.densely_chained {
828 &previous_result.nodes[previous_result.nodes.len() - 1..]
829 } else {
830 &previous_result.nodes
831 };
832
833 for previous_node in previous_nodes {
834 for current_node in current_nodes {
835 self.dependency
836 .graph
837 .add_edge(*previous_node, *current_node);
838
839 for pass in self.passes.values_mut() {
840 pass.add_dependency(
841 *previous_node,
842 *current_node,
843 chain_options,
844 );
845 }
846 }
847 }
848 }
849 if collect_nodes {
850 nodes.append(&mut previous_result.nodes);
851 }
852
853 previous_result = current_result;
854 }
855 if collect_nodes {
856 nodes.append(&mut previous_result.nodes);
857 }
858
859 ProcessConfigsResult {
860 nodes,
861 densely_chained,
862 }
863 }
864 }
865 }
866
867 fn add_system_inner(&mut self, config: ScheduleConfig<ScheduleSystem>) -> SystemKey {
869 let key = self.systems.insert(config.node, config.conditions);
870
871 self.update_graphs(NodeId::System(key), config.metadata);
873
874 key
875 }
876
877 #[track_caller]
878 fn configure_sets<M>(&mut self, sets: impl IntoScheduleConfigs<InternedSystemSet, M>) {
879 self.process_configs(sets.into_configs(), false);
880 }
881
882 fn configure_set_inner(&mut self, config: ScheduleConfig<InternedSystemSet>) -> SystemSetKey {
884 let key = self.system_sets.insert(config.node, config.conditions);
885
886 self.update_graphs(NodeId::Set(key), config.metadata);
888
889 key
890 }
891
892 fn create_anonymous_set(&mut self) -> AnonymousSet {
893 let id = self.anonymous_sets;
894 self.anonymous_sets += 1;
895 AnonymousSet::new(id)
896 }
897
898 fn update_graphs(&mut self, id: NodeId, graph_info: GraphInfo) {
900 self.changed = true;
901
902 let GraphInfo {
903 hierarchy: sets,
904 dependencies,
905 ambiguous_with,
906 ..
907 } = graph_info;
908
909 self.hierarchy.graph.add_node(id);
910 self.dependency.graph.add_node(id);
911
912 for key in sets
913 .into_iter()
914 .map(|set| self.system_sets.get_key_or_insert(set))
915 {
916 self.hierarchy.graph.add_edge(NodeId::Set(key), id);
917
918 self.dependency.graph.add_node(NodeId::Set(key));
920 }
921
922 for (kind, key, options) in
923 dependencies
924 .into_iter()
925 .map(|Dependency { kind, set, options }| {
926 (kind, self.system_sets.get_key_or_insert(set), options)
927 })
928 {
929 let (lhs, rhs) = match kind {
930 DependencyKind::Before => (id, NodeId::Set(key)),
931 DependencyKind::After => (NodeId::Set(key), id),
932 };
933 self.dependency.graph.add_edge(lhs, rhs);
934 for pass in self.passes.values_mut() {
935 pass.add_dependency(lhs, rhs, &options);
936 }
937
938 self.hierarchy.graph.add_node(NodeId::Set(key));
940 }
941
942 match ambiguous_with {
943 Ambiguity::Check => (),
944 Ambiguity::IgnoreWithSet(ambiguous_with) => {
945 for key in ambiguous_with
946 .into_iter()
947 .map(|set| self.system_sets.get_key_or_insert(set))
948 {
949 self.ambiguous_with.add_edge(id, NodeId::Set(key));
950 }
951 }
952 Ambiguity::IgnoreAll => {
953 self.ambiguous_with_all.insert(id);
954 }
955 }
956 }
957
958 pub fn initialize(&mut self, world: &mut World) {
961 self.systems.initialize(world);
962 self.system_sets.initialize(world);
963 }
964
965 pub fn build_schedule(
973 &mut self,
974 world: &mut World,
975 ignored_ambiguities: &BTreeSet<ComponentId>,
976 ) -> Result<(SystemSchedule, Vec<ScheduleBuildWarning>), ScheduleBuildError> {
977 let mut warnings = Vec::new();
978
979 self.hierarchy.topsort =
981 self.topsort_graph(&self.hierarchy.graph, ReportCycles::Hierarchy)?;
982
983 let hier_results = check_graph(&self.hierarchy.graph, &self.hierarchy.topsort);
984 if let Some(warning) =
985 self.optionally_check_hierarchy_conflicts(&hier_results.transitive_edges)?
986 {
987 warnings.push(warning);
988 }
989
990 self.hierarchy.graph = hier_results.transitive_reduction;
992
993 self.dependency.topsort =
995 self.topsort_graph(&self.dependency.graph, ReportCycles::Dependency)?;
996
997 let dep_results = check_graph(&self.dependency.graph, &self.dependency.topsort);
999 self.check_for_cross_dependencies(&dep_results, &hier_results.connected)?;
1000
1001 let (set_systems, set_system_bitsets) =
1004 self.map_sets_to_systems(&self.hierarchy.topsort, &self.hierarchy.graph);
1005 self.check_order_but_intersect(&dep_results.connected, &set_system_bitsets)?;
1006
1007 self.check_system_type_set_ambiguity(&set_systems)?;
1009
1010 let mut dependency_flattened = self.get_dependency_flattened(&set_systems);
1011
1012 let mut passes = core::mem::take(&mut self.passes);
1014 for pass in passes.values_mut() {
1015 pass.build(world, self, &mut dependency_flattened)?;
1016 }
1017 self.passes = passes;
1018
1019 let mut dependency_flattened_dag = Dag {
1021 topsort: self.topsort_graph(&dependency_flattened, ReportCycles::Dependency)?,
1022 graph: dependency_flattened,
1023 };
1024
1025 let flat_results = check_graph(
1026 &dependency_flattened_dag.graph,
1027 &dependency_flattened_dag.topsort,
1028 );
1029
1030 dependency_flattened_dag.graph = flat_results.transitive_reduction;
1032
1033 let ambiguous_with_flattened = self.get_ambiguous_with_flattened(&set_systems);
1035
1036 let conflicting_systems = self.get_conflicting_systems(
1038 &flat_results.disconnected,
1039 &ambiguous_with_flattened,
1040 ignored_ambiguities,
1041 );
1042 if let Some(warning) = self.optionally_check_conflicts(&conflicting_systems)? {
1043 warnings.push(warning);
1044 }
1045 self.conflicting_systems = conflicting_systems;
1046
1047 Ok((
1049 self.build_schedule_inner(dependency_flattened_dag, hier_results.reachable),
1050 warnings,
1051 ))
1052 }
1053
1054 fn map_sets_to_systems(
1058 &self,
1059 hierarchy_topsort: &[NodeId],
1060 hierarchy_graph: &DiGraph<NodeId>,
1061 ) -> (
1062 HashMap<SystemSetKey, Vec<SystemKey>>,
1063 HashMap<SystemSetKey, HashSet<SystemKey>>,
1064 ) {
1065 let mut set_systems: HashMap<SystemSetKey, Vec<SystemKey>> =
1066 HashMap::with_capacity_and_hasher(self.system_sets.len(), Default::default());
1067 let mut set_system_sets: HashMap<SystemSetKey, HashSet<SystemKey>> =
1068 HashMap::with_capacity_and_hasher(self.system_sets.len(), Default::default());
1069 for &id in hierarchy_topsort.iter().rev() {
1070 let NodeId::Set(set_key) = id else {
1071 continue;
1072 };
1073
1074 let mut systems = Vec::new();
1075 let mut system_set = HashSet::with_capacity(self.systems.len());
1076
1077 for child in hierarchy_graph.neighbors_directed(id, Outgoing) {
1078 match child {
1079 NodeId::System(key) => {
1080 systems.push(key);
1081 system_set.insert(key);
1082 }
1083 NodeId::Set(key) => {
1084 let child_systems = set_systems.get(&key).unwrap();
1085 let child_system_set = set_system_sets.get(&key).unwrap();
1086 systems.extend_from_slice(child_systems);
1087 system_set.extend(child_system_set.iter());
1088 }
1089 }
1090 }
1091
1092 set_systems.insert(set_key, systems);
1093 set_system_sets.insert(set_key, system_set);
1094 }
1095 (set_systems, set_system_sets)
1096 }
1097
1098 fn get_dependency_flattened(
1099 &mut self,
1100 set_systems: &HashMap<SystemSetKey, Vec<SystemKey>>,
1101 ) -> DiGraph<SystemKey> {
1102 let mut dependency_flattening = self.dependency.graph.clone();
1105 let mut temp = Vec::new();
1106 for (&set, systems) in set_systems {
1107 for pass in self.passes.values_mut() {
1108 pass.collapse_set(set, systems, &dependency_flattening, &mut temp);
1109 }
1110 if systems.is_empty() {
1111 for a in dependency_flattening.neighbors_directed(NodeId::Set(set), Incoming) {
1113 for b in dependency_flattening.neighbors_directed(NodeId::Set(set), Outgoing) {
1114 temp.push((a, b));
1115 }
1116 }
1117 } else {
1118 for a in dependency_flattening.neighbors_directed(NodeId::Set(set), Incoming) {
1119 for &sys in systems {
1120 temp.push((a, NodeId::System(sys)));
1121 }
1122 }
1123
1124 for b in dependency_flattening.neighbors_directed(NodeId::Set(set), Outgoing) {
1125 for &sys in systems {
1126 temp.push((NodeId::System(sys), b));
1127 }
1128 }
1129 }
1130
1131 dependency_flattening.remove_node(NodeId::Set(set));
1132 for (a, b) in temp.drain(..) {
1133 dependency_flattening.add_edge(a, b);
1134 }
1135 }
1136
1137 dependency_flattening
1140 .try_into::<SystemKey>()
1141 .unwrap_or_else(|n| {
1142 unreachable!(
1143 "Flattened dependency graph has a leftover system set {}",
1144 self.get_node_name(&NodeId::Set(n))
1145 )
1146 })
1147 }
1148
1149 fn get_ambiguous_with_flattened(
1150 &self,
1151 set_systems: &HashMap<SystemSetKey, Vec<SystemKey>>,
1152 ) -> UnGraph<NodeId> {
1153 let mut ambiguous_with_flattened = UnGraph::default();
1154 for (lhs, rhs) in self.ambiguous_with.all_edges() {
1155 match (lhs, rhs) {
1156 (NodeId::System(_), NodeId::System(_)) => {
1157 ambiguous_with_flattened.add_edge(lhs, rhs);
1158 }
1159 (NodeId::Set(lhs), NodeId::System(_)) => {
1160 for &lhs_ in set_systems.get(&lhs).unwrap_or(&Vec::new()) {
1161 ambiguous_with_flattened.add_edge(NodeId::System(lhs_), rhs);
1162 }
1163 }
1164 (NodeId::System(_), NodeId::Set(rhs)) => {
1165 for &rhs_ in set_systems.get(&rhs).unwrap_or(&Vec::new()) {
1166 ambiguous_with_flattened.add_edge(lhs, NodeId::System(rhs_));
1167 }
1168 }
1169 (NodeId::Set(lhs), NodeId::Set(rhs)) => {
1170 for &lhs_ in set_systems.get(&lhs).unwrap_or(&Vec::new()) {
1171 for &rhs_ in set_systems.get(&rhs).unwrap_or(&vec![]) {
1172 ambiguous_with_flattened
1173 .add_edge(NodeId::System(lhs_), NodeId::System(rhs_));
1174 }
1175 }
1176 }
1177 }
1178 }
1179
1180 ambiguous_with_flattened
1181 }
1182
1183 fn get_conflicting_systems(
1184 &self,
1185 flat_results_disconnected: &Vec<(SystemKey, SystemKey)>,
1186 ambiguous_with_flattened: &UnGraph<NodeId>,
1187 ignored_ambiguities: &BTreeSet<ComponentId>,
1188 ) -> Vec<(SystemKey, SystemKey, Vec<ComponentId>)> {
1189 let mut conflicting_systems = Vec::new();
1190 for &(a, b) in flat_results_disconnected {
1191 if ambiguous_with_flattened.contains_edge(NodeId::System(a), NodeId::System(b))
1192 || self.ambiguous_with_all.contains(&NodeId::System(a))
1193 || self.ambiguous_with_all.contains(&NodeId::System(b))
1194 {
1195 continue;
1196 }
1197
1198 let system_a = &self.systems[a];
1199 let system_b = &self.systems[b];
1200 if system_a.is_exclusive() || system_b.is_exclusive() {
1201 conflicting_systems.push((a, b, Vec::new()));
1202 } else {
1203 let access_a = &system_a.access;
1204 let access_b = &system_b.access;
1205 if !access_a.is_compatible(access_b) {
1206 match access_a.get_conflicts(access_b) {
1207 AccessConflicts::Individual(conflicts) => {
1208 let conflicts: Vec<_> = conflicts
1209 .ones()
1210 .map(ComponentId::get_sparse_set_index)
1211 .filter(|id| !ignored_ambiguities.contains(id))
1212 .collect();
1213 if !conflicts.is_empty() {
1214 conflicting_systems.push((a, b, conflicts));
1215 }
1216 }
1217 AccessConflicts::All => {
1218 conflicting_systems.push((a, b, Vec::new()));
1221 }
1222 }
1223 }
1224 }
1225 }
1226
1227 conflicting_systems
1228 }
1229
1230 fn build_schedule_inner(
1231 &self,
1232 dependency_flattened_dag: Dag<SystemKey>,
1233 hier_results_reachable: FixedBitSet,
1234 ) -> SystemSchedule {
1235 let dg_system_ids = dependency_flattened_dag.topsort;
1236 let dg_system_idx_map = dg_system_ids
1237 .iter()
1238 .cloned()
1239 .enumerate()
1240 .map(|(i, id)| (id, i))
1241 .collect::<HashMap<_, _>>();
1242
1243 let hg_systems = self
1244 .hierarchy
1245 .topsort
1246 .iter()
1247 .cloned()
1248 .enumerate()
1249 .filter_map(|(i, id)| Some((i, id.as_system()?)))
1250 .collect::<Vec<_>>();
1251
1252 let (hg_set_with_conditions_idxs, hg_set_ids): (Vec<_>, Vec<_>) = self
1253 .hierarchy
1254 .topsort
1255 .iter()
1256 .cloned()
1257 .enumerate()
1258 .filter_map(|(i, id)| {
1259 let key = id.as_set()?;
1262 self.system_sets.has_conditions(key).then_some((i, key))
1263 })
1264 .unzip();
1265
1266 let sys_count = self.systems.len();
1267 let set_with_conditions_count = hg_set_ids.len();
1268 let hg_node_count = self.hierarchy.graph.node_count();
1269
1270 let mut system_dependencies = Vec::with_capacity(sys_count);
1273 let mut system_dependents = Vec::with_capacity(sys_count);
1274 for &sys_key in &dg_system_ids {
1275 let num_dependencies = dependency_flattened_dag
1276 .graph
1277 .neighbors_directed(sys_key, Incoming)
1278 .count();
1279
1280 let dependents = dependency_flattened_dag
1281 .graph
1282 .neighbors_directed(sys_key, Outgoing)
1283 .map(|dep_id| dg_system_idx_map[&dep_id])
1284 .collect::<Vec<_>>();
1285
1286 system_dependencies.push(num_dependencies);
1287 system_dependents.push(dependents);
1288 }
1289
1290 let mut systems_in_sets_with_conditions =
1293 vec![FixedBitSet::with_capacity(sys_count); set_with_conditions_count];
1294 for (i, &row) in hg_set_with_conditions_idxs.iter().enumerate() {
1295 let bitset = &mut systems_in_sets_with_conditions[i];
1296 for &(col, sys_key) in &hg_systems {
1297 let idx = dg_system_idx_map[&sys_key];
1298 let is_descendant = hier_results_reachable[index(row, col, hg_node_count)];
1299 bitset.set(idx, is_descendant);
1300 }
1301 }
1302
1303 let mut sets_with_conditions_of_systems =
1304 vec![FixedBitSet::with_capacity(set_with_conditions_count); sys_count];
1305 for &(col, sys_key) in &hg_systems {
1306 let i = dg_system_idx_map[&sys_key];
1307 let bitset = &mut sets_with_conditions_of_systems[i];
1308 for (idx, &row) in hg_set_with_conditions_idxs
1309 .iter()
1310 .enumerate()
1311 .take_while(|&(_idx, &row)| row < col)
1312 {
1313 let is_ancestor = hier_results_reachable[index(row, col, hg_node_count)];
1314 bitset.set(idx, is_ancestor);
1315 }
1316 }
1317
1318 SystemSchedule {
1319 systems: Vec::with_capacity(sys_count),
1320 system_conditions: Vec::with_capacity(sys_count),
1321 set_conditions: Vec::with_capacity(set_with_conditions_count),
1322 system_ids: dg_system_ids,
1323 set_ids: hg_set_ids,
1324 system_dependencies,
1325 system_dependents,
1326 sets_with_conditions_of_systems,
1327 systems_in_sets_with_conditions,
1328 }
1329 }
1330
1331 fn update_schedule(
1333 &mut self,
1334 world: &mut World,
1335 schedule: &mut SystemSchedule,
1336 ignored_ambiguities: &BTreeSet<ComponentId>,
1337 schedule_label: InternedScheduleLabel,
1338 ) -> Result<Vec<ScheduleBuildWarning>, ScheduleBuildError> {
1339 if !self.systems.is_initialized() || !self.system_sets.is_initialized() {
1340 return Err(ScheduleBuildError::Uninitialized);
1341 }
1342
1343 for ((key, system), conditions) in schedule
1345 .system_ids
1346 .drain(..)
1347 .zip(schedule.systems.drain(..))
1348 .zip(schedule.system_conditions.drain(..))
1349 {
1350 self.systems.node_mut(key).inner = Some(system);
1351 *self.systems.get_conditions_mut(key).unwrap() = conditions;
1352 }
1353
1354 for (key, conditions) in schedule
1355 .set_ids
1356 .drain(..)
1357 .zip(schedule.set_conditions.drain(..))
1358 {
1359 *self.system_sets.get_conditions_mut(key).unwrap() = conditions;
1360 }
1361
1362 let (new_schedule, warnings) = self.build_schedule(world, ignored_ambiguities)?;
1363 *schedule = new_schedule;
1364
1365 for warning in &warnings {
1366 warn!(
1367 "{:?} schedule built successfully, however: {}",
1368 schedule_label,
1369 warning.to_string(self, world)
1370 );
1371 }
1372
1373 for &key in &schedule.system_ids {
1375 let system = self.systems.node_mut(key).inner.take().unwrap();
1376 let conditions = core::mem::take(self.systems.get_conditions_mut(key).unwrap());
1377 schedule.systems.push(system);
1378 schedule.system_conditions.push(conditions);
1379 }
1380
1381 for &key in &schedule.set_ids {
1382 let conditions = core::mem::take(self.system_sets.get_conditions_mut(key).unwrap());
1383 schedule.set_conditions.push(conditions);
1384 }
1385
1386 Ok(warnings)
1387 }
1388}
1389
1390struct ProcessConfigsResult {
1392 nodes: Vec<NodeId>,
1395 densely_chained: bool,
1399}
1400
1401trait ProcessScheduleConfig: Schedulable + Sized {
1403 fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig<Self>) -> NodeId;
1405}
1406
1407impl ProcessScheduleConfig for ScheduleSystem {
1408 fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig<Self>) -> NodeId {
1409 NodeId::System(schedule_graph.add_system_inner(config))
1410 }
1411}
1412
1413impl ProcessScheduleConfig for InternedSystemSet {
1414 fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig<Self>) -> NodeId {
1415 NodeId::Set(schedule_graph.configure_set_inner(config))
1416 }
1417}
1418
1419pub enum ReportCycles {
1421 Hierarchy,
1423 Dependency,
1425}
1426
1427impl ScheduleGraph {
1429 pub fn get_node_name(&self, id: &NodeId) -> String {
1436 self.get_node_name_inner(id, self.settings.report_sets)
1437 }
1438
1439 #[inline]
1440 fn get_node_name_inner(&self, id: &NodeId, report_sets: bool) -> String {
1441 match *id {
1442 NodeId::System(key) => {
1443 let name = self.systems[key].name();
1444 let name = if self.settings.use_shortnames {
1445 name.shortname().to_string()
1446 } else {
1447 name.to_string()
1448 };
1449 if report_sets {
1450 let sets = self.names_of_sets_containing_node(id);
1451 if sets.is_empty() {
1452 name
1453 } else if sets.len() == 1 {
1454 format!("{name} (in set {})", sets[0])
1455 } else {
1456 format!("{name} (in sets {})", sets.join(", "))
1457 }
1458 } else {
1459 name
1460 }
1461 }
1462 NodeId::Set(key) => {
1463 let set = &self.system_sets[key];
1464 if set.is_anonymous() {
1465 self.anonymous_set_name(id)
1466 } else {
1467 format!("{set:?}")
1468 }
1469 }
1470 }
1471 }
1472
1473 fn anonymous_set_name(&self, id: &NodeId) -> String {
1474 format!(
1475 "({})",
1476 self.hierarchy
1477 .graph
1478 .edges_directed(*id, Outgoing)
1479 .map(|(_, member_id)| self.get_node_name_inner(&member_id, false))
1481 .reduce(|a, b| format!("{a}, {b}"))
1482 .unwrap_or_default()
1483 )
1484 }
1485
1486 fn optionally_check_hierarchy_conflicts(
1489 &self,
1490 transitive_edges: &[(NodeId, NodeId)],
1491 ) -> Result<Option<ScheduleBuildWarning>, ScheduleBuildError> {
1492 match (
1493 self.settings.hierarchy_detection,
1494 !transitive_edges.is_empty(),
1495 ) {
1496 (LogLevel::Warn, true) => Ok(Some(ScheduleBuildWarning::HierarchyRedundancy(
1497 transitive_edges.to_vec(),
1498 ))),
1499 (LogLevel::Error, true) => {
1500 Err(ScheduleBuildWarning::HierarchyRedundancy(transitive_edges.to_vec()).into())
1501 }
1502 _ => Ok(None),
1503 }
1504 }
1505
1506 pub fn topsort_graph<N: GraphNodeId + Into<NodeId>>(
1516 &self,
1517 graph: &DiGraph<N>,
1518 report: ReportCycles,
1519 ) -> Result<Vec<N>, ScheduleBuildError> {
1520 if let Some((node, _)) = graph.all_edges().find(|(left, right)| left == right) {
1523 let error = match report {
1524 ReportCycles::Hierarchy => ScheduleBuildError::HierarchyLoop(node.into()),
1525 ReportCycles::Dependency => ScheduleBuildError::DependencyLoop(node.into()),
1526 };
1527 return Err(error);
1528 }
1529
1530 let mut top_sorted_nodes = Vec::with_capacity(graph.node_count());
1532 let mut sccs_with_cycles = Vec::new();
1533
1534 for scc in graph.iter_sccs() {
1535 top_sorted_nodes.extend_from_slice(&scc);
1539 if scc.len() > 1 {
1540 sccs_with_cycles.push(scc);
1541 }
1542 }
1543
1544 if sccs_with_cycles.is_empty() {
1545 top_sorted_nodes.reverse();
1547 Ok(top_sorted_nodes)
1548 } else {
1549 let mut cycles = Vec::new();
1550 for scc in &sccs_with_cycles {
1551 cycles.append(&mut simple_cycles_in_component(graph, scc));
1552 }
1553
1554 let error = match report {
1555 ReportCycles::Hierarchy => ScheduleBuildError::HierarchyCycle(
1556 cycles
1557 .into_iter()
1558 .map(|c| c.into_iter().map(Into::into).collect())
1559 .collect(),
1560 ),
1561 ReportCycles::Dependency => ScheduleBuildError::DependencyCycle(
1562 cycles
1563 .into_iter()
1564 .map(|c| c.into_iter().map(Into::into).collect())
1565 .collect(),
1566 ),
1567 };
1568
1569 Err(error)
1570 }
1571 }
1572
1573 fn check_for_cross_dependencies(
1574 &self,
1575 dep_results: &CheckGraphResults<NodeId>,
1576 hier_results_connected: &HashSet<(NodeId, NodeId)>,
1577 ) -> Result<(), ScheduleBuildError> {
1578 for &(a, b) in &dep_results.connected {
1579 if hier_results_connected.contains(&(a, b)) || hier_results_connected.contains(&(b, a))
1580 {
1581 return Err(ScheduleBuildError::CrossDependency(a, b));
1582 }
1583 }
1584
1585 Ok(())
1586 }
1587
1588 fn check_order_but_intersect(
1589 &self,
1590 dep_results_connected: &HashSet<(NodeId, NodeId)>,
1591 set_system_sets: &HashMap<SystemSetKey, HashSet<SystemKey>>,
1592 ) -> Result<(), ScheduleBuildError> {
1593 for &(a, b) in dep_results_connected {
1595 let (NodeId::Set(a_key), NodeId::Set(b_key)) = (a, b) else {
1596 continue;
1597 };
1598
1599 let a_systems = set_system_sets.get(&a_key).unwrap();
1600 let b_systems = set_system_sets.get(&b_key).unwrap();
1601
1602 if !a_systems.is_disjoint(b_systems) {
1603 return Err(ScheduleBuildError::SetsHaveOrderButIntersect(a_key, b_key));
1604 }
1605 }
1606
1607 Ok(())
1608 }
1609
1610 fn check_system_type_set_ambiguity(
1611 &self,
1612 set_systems: &HashMap<SystemSetKey, Vec<SystemKey>>,
1613 ) -> Result<(), ScheduleBuildError> {
1614 for (&key, systems) in set_systems {
1615 let set = &self.system_sets[key];
1616 if set.system_type().is_some() {
1617 let instances = systems.len();
1618 let ambiguous_with = self.ambiguous_with.edges(NodeId::Set(key));
1619 let before = self
1620 .dependency
1621 .graph
1622 .edges_directed(NodeId::Set(key), Incoming);
1623 let after = self
1624 .dependency
1625 .graph
1626 .edges_directed(NodeId::Set(key), Outgoing);
1627 let relations = before.count() + after.count() + ambiguous_with.count();
1628 if instances > 1 && relations > 0 {
1629 return Err(ScheduleBuildError::SystemTypeSetAmbiguity(key));
1630 }
1631 }
1632 }
1633 Ok(())
1634 }
1635
1636 fn optionally_check_conflicts(
1638 &self,
1639 conflicts: &[(SystemKey, SystemKey, Vec<ComponentId>)],
1640 ) -> Result<Option<ScheduleBuildWarning>, ScheduleBuildError> {
1641 match (self.settings.ambiguity_detection, !conflicts.is_empty()) {
1642 (LogLevel::Warn, true) => Ok(Some(ScheduleBuildWarning::Ambiguity(conflicts.to_vec()))),
1643 (LogLevel::Error, true) => {
1644 Err(ScheduleBuildWarning::Ambiguity(conflicts.to_vec()).into())
1645 }
1646 _ => Ok(None),
1647 }
1648 }
1649
1650 pub fn conflicts_to_string<'a>(
1652 &'a self,
1653 ambiguities: &'a [(SystemKey, SystemKey, Vec<ComponentId>)],
1654 components: &'a Components,
1655 ) -> impl Iterator<Item = (String, String, Vec<DebugName>)> + 'a {
1656 ambiguities
1657 .iter()
1658 .map(move |(system_a, system_b, conflicts)| {
1659 let name_a = self.get_node_name(&NodeId::System(*system_a));
1660 let name_b = self.get_node_name(&NodeId::System(*system_b));
1661
1662 let conflict_names: Vec<_> = conflicts
1663 .iter()
1664 .map(|id| components.get_name(*id).unwrap())
1665 .collect();
1666
1667 (name_a, name_b, conflict_names)
1668 })
1669 }
1670
1671 fn traverse_sets_containing_node(&self, id: NodeId, f: &mut impl FnMut(SystemSetKey) -> bool) {
1672 for (set_id, _) in self.hierarchy.graph.edges_directed(id, Incoming) {
1673 let NodeId::Set(set_key) = set_id else {
1674 continue;
1675 };
1676 if f(set_key) {
1677 self.traverse_sets_containing_node(NodeId::Set(set_key), f);
1678 }
1679 }
1680 }
1681
1682 fn names_of_sets_containing_node(&self, id: &NodeId) -> Vec<String> {
1683 let mut sets = <HashSet<_>>::default();
1684 self.traverse_sets_containing_node(*id, &mut |key| {
1685 self.system_sets[key].system_type().is_none() && sets.insert(key)
1686 });
1687 let mut sets: Vec<_> = sets
1688 .into_iter()
1689 .map(|key| self.get_node_name(&NodeId::Set(key)))
1690 .collect();
1691 sets.sort();
1692 sets
1693 }
1694}
1695
1696#[derive(Debug, Clone, Copy, PartialEq)]
1698pub enum LogLevel {
1699 Ignore,
1701 Warn,
1703 Error,
1705}
1706
1707#[derive(Clone, Debug)]
1709pub struct ScheduleBuildSettings {
1710 pub ambiguity_detection: LogLevel,
1716 pub hierarchy_detection: LogLevel,
1722 pub auto_insert_apply_deferred: bool,
1732 pub use_shortnames: bool,
1736 pub report_sets: bool,
1740}
1741
1742impl Default for ScheduleBuildSettings {
1743 fn default() -> Self {
1744 Self::new()
1745 }
1746}
1747
1748impl ScheduleBuildSettings {
1749 pub const fn new() -> Self {
1752 Self {
1753 ambiguity_detection: LogLevel::Ignore,
1754 hierarchy_detection: LogLevel::Warn,
1755 auto_insert_apply_deferred: true,
1756 use_shortnames: true,
1757 report_sets: true,
1758 }
1759 }
1760}
1761
1762#[derive(Error, Debug)]
1765#[error("executable schedule has not been built")]
1766pub struct ScheduleNotInitialized;
1767
1768#[cfg(test)]
1769mod tests {
1770 use bevy_ecs_macros::ScheduleLabel;
1771
1772 use crate::{
1773 error::{ignore, panic, DefaultErrorHandler, Result},
1774 prelude::{ApplyDeferred, Res, Resource},
1775 schedule::{
1776 tests::ResMut, IntoScheduleConfigs, Schedule, ScheduleBuildSettings, SystemSet,
1777 },
1778 system::Commands,
1779 world::World,
1780 };
1781
1782 use super::Schedules;
1783
1784 #[derive(Resource)]
1785 struct Resource1;
1786
1787 #[derive(Resource)]
1788 struct Resource2;
1789
1790 #[test]
1791 fn unchanged_auto_insert_apply_deferred_has_no_effect() {
1792 use alloc::{vec, vec::Vec};
1793
1794 #[derive(PartialEq, Debug)]
1795 enum Entry {
1796 System(usize),
1797 SyncPoint(usize),
1798 }
1799
1800 #[derive(Resource, Default)]
1801 struct Log(Vec<Entry>);
1802
1803 fn system<const N: usize>(mut res: ResMut<Log>, mut commands: Commands) {
1804 res.0.push(Entry::System(N));
1805 commands
1806 .queue(|world: &mut World| world.resource_mut::<Log>().0.push(Entry::SyncPoint(N)));
1807 }
1808
1809 let mut world = World::default();
1810 world.init_resource::<Log>();
1811 let mut schedule = Schedule::default();
1812 schedule.add_systems((system::<1>, system::<2>).chain_ignore_deferred());
1813 schedule.set_build_settings(ScheduleBuildSettings {
1814 auto_insert_apply_deferred: true,
1815 ..Default::default()
1816 });
1817 schedule.run(&mut world);
1818 let actual = world.remove_resource::<Log>().unwrap().0;
1819
1820 let expected = vec![
1821 Entry::System(1),
1822 Entry::System(2),
1823 Entry::SyncPoint(1),
1824 Entry::SyncPoint(2),
1825 ];
1826
1827 assert_eq!(actual, expected);
1828 }
1829
1830 #[test]
1832 fn ambiguous_with_not_breaking_run_conditions() {
1833 #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
1834 struct Set;
1835
1836 let mut world = World::new();
1837 let mut schedule = Schedule::default();
1838
1839 let system: fn() = || {
1840 panic!("This system must not run");
1841 };
1842
1843 schedule.configure_sets(Set.run_if(|| false));
1844 schedule.add_systems(system.ambiguous_with(|| ()).in_set(Set));
1845 schedule.run(&mut world);
1846 }
1847
1848 #[test]
1849 fn inserts_a_sync_point() {
1850 let mut schedule = Schedule::default();
1851 let mut world = World::default();
1852 schedule.add_systems(
1853 (
1854 |mut commands: Commands| commands.insert_resource(Resource1),
1855 |_: Res<Resource1>| {},
1856 )
1857 .chain(),
1858 );
1859 schedule.run(&mut world);
1860
1861 assert_eq!(schedule.executable.systems.len(), 3);
1863 }
1864
1865 #[test]
1866 fn explicit_sync_point_used_as_auto_sync_point() {
1867 let mut schedule = Schedule::default();
1868 let mut world = World::default();
1869 schedule.add_systems(
1870 (
1871 |mut commands: Commands| commands.insert_resource(Resource1),
1872 |_: Res<Resource1>| {},
1873 )
1874 .chain(),
1875 );
1876 schedule.add_systems((|| {}, ApplyDeferred, || {}).chain());
1877 schedule.run(&mut world);
1878
1879 assert_eq!(schedule.executable.systems.len(), 5);
1881 }
1882
1883 #[test]
1884 fn conditional_explicit_sync_point_not_used_as_auto_sync_point() {
1885 let mut schedule = Schedule::default();
1886 let mut world = World::default();
1887 schedule.add_systems(
1888 (
1889 |mut commands: Commands| commands.insert_resource(Resource1),
1890 |_: Res<Resource1>| {},
1891 )
1892 .chain(),
1893 );
1894 schedule.add_systems((|| {}, ApplyDeferred.run_if(|| false), || {}).chain());
1895 schedule.run(&mut world);
1896
1897 assert_eq!(schedule.executable.systems.len(), 6);
1899 }
1900
1901 #[test]
1902 fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_chain() {
1903 let mut schedule = Schedule::default();
1904 let mut world = World::default();
1905 schedule.add_systems(
1906 (
1907 |mut commands: Commands| commands.insert_resource(Resource1),
1908 |_: Res<Resource1>| {},
1909 )
1910 .chain(),
1911 );
1912 schedule.add_systems((|| {}, ApplyDeferred, || {}).chain().run_if(|| false));
1913 schedule.run(&mut world);
1914
1915 assert_eq!(schedule.executable.systems.len(), 6);
1917 }
1918
1919 #[test]
1920 fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_system_set() {
1921 #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
1922 struct Set;
1923
1924 let mut schedule = Schedule::default();
1925 let mut world = World::default();
1926 schedule.configure_sets(Set.run_if(|| false));
1927 schedule.add_systems(
1928 (
1929 |mut commands: Commands| commands.insert_resource(Resource1),
1930 |_: Res<Resource1>| {},
1931 )
1932 .chain(),
1933 );
1934 schedule.add_systems((|| {}, ApplyDeferred.in_set(Set), || {}).chain());
1935 schedule.run(&mut world);
1936
1937 assert_eq!(schedule.executable.systems.len(), 6);
1939 }
1940
1941 #[test]
1942 fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_nested_system_set()
1943 {
1944 #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
1945 struct Set1;
1946 #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
1947 struct Set2;
1948
1949 let mut schedule = Schedule::default();
1950 let mut world = World::default();
1951 schedule.configure_sets(Set2.run_if(|| false));
1952 schedule.configure_sets(Set1.in_set(Set2));
1953 schedule.add_systems(
1954 (
1955 |mut commands: Commands| commands.insert_resource(Resource1),
1956 |_: Res<Resource1>| {},
1957 )
1958 .chain(),
1959 );
1960 schedule.add_systems((|| {}, ApplyDeferred, || {}).chain().in_set(Set1));
1961 schedule.run(&mut world);
1962
1963 assert_eq!(schedule.executable.systems.len(), 6);
1965 }
1966
1967 #[test]
1968 fn merges_sync_points_into_one() {
1969 let mut schedule = Schedule::default();
1970 let mut world = World::default();
1971 schedule.add_systems(
1973 (
1974 (
1975 |mut commands: Commands| commands.insert_resource(Resource1),
1976 |mut commands: Commands| commands.insert_resource(Resource2),
1977 ),
1978 |_: Res<Resource1>, _: Res<Resource2>| {},
1979 )
1980 .chain(),
1981 );
1982 schedule.run(&mut world);
1983
1984 assert_eq!(schedule.executable.systems.len(), 4);
1986
1987 schedule.add_systems(((
1989 (
1990 |mut commands: Commands| commands.insert_resource(Resource1),
1991 |mut commands: Commands| commands.insert_resource(Resource2),
1992 ),
1993 |_: Res<Resource1>, _: Res<Resource2>| {},
1994 )
1995 .chain(),));
1996 schedule.run(&mut world);
1997
1998 assert_eq!(schedule.executable.systems.len(), 7);
1999 }
2000
2001 #[test]
2002 fn adds_multiple_consecutive_syncs() {
2003 let mut schedule = Schedule::default();
2004 let mut world = World::default();
2005 schedule.add_systems(
2007 (
2008 |mut commands: Commands| commands.insert_resource(Resource1),
2009 |mut commands: Commands| commands.insert_resource(Resource2),
2010 |_: Res<Resource1>, _: Res<Resource2>| {},
2011 )
2012 .chain(),
2013 );
2014 schedule.run(&mut world);
2015
2016 assert_eq!(schedule.executable.systems.len(), 5);
2017 }
2018
2019 #[test]
2020 fn do_not_consider_ignore_deferred_before_exclusive_system() {
2021 let mut schedule = Schedule::default();
2022 let mut world = World::default();
2023 schedule.add_systems(
2025 (
2026 |_: Commands| {},
2027 |mut commands: Commands| commands.insert_resource(Resource1),
2029 |world: &mut World| assert!(world.contains_resource::<Resource1>()),
2031 |_: &mut World| {},
2033 |_: Commands| {},
2035 )
2036 .chain_ignore_deferred(),
2037 );
2038 schedule.run(&mut world);
2039
2040 assert_eq!(schedule.executable.systems.len(), 6); }
2042
2043 #[test]
2044 fn bubble_sync_point_through_ignore_deferred_node() {
2045 let mut schedule = Schedule::default();
2046 let mut world = World::default();
2047
2048 let insert_resource_config = (
2049 |mut commands: Commands| commands.insert_resource(Resource1),
2051 || {},
2053 )
2054 .chain_ignore_deferred();
2056
2057 schedule.add_systems(
2058 (
2059 insert_resource_config,
2060 |_: Res<Resource1>| {},
2062 )
2063 .chain(),
2065 );
2066
2067 schedule.run(&mut world);
2072
2073 assert_eq!(schedule.executable.systems.len(), 4); }
2075
2076 #[test]
2077 fn disable_auto_sync_points() {
2078 let mut schedule = Schedule::default();
2079 schedule.set_build_settings(ScheduleBuildSettings {
2080 auto_insert_apply_deferred: false,
2081 ..Default::default()
2082 });
2083 let mut world = World::default();
2084 schedule.add_systems(
2085 (
2086 |mut commands: Commands| commands.insert_resource(Resource1),
2087 |res: Option<Res<Resource1>>| assert!(res.is_none()),
2088 )
2089 .chain(),
2090 );
2091 schedule.run(&mut world);
2092
2093 assert_eq!(schedule.executable.systems.len(), 2);
2094 }
2095
2096 mod no_sync_edges {
2097 use super::*;
2098
2099 fn insert_resource(mut commands: Commands) {
2100 commands.insert_resource(Resource1);
2101 }
2102
2103 fn resource_does_not_exist(res: Option<Res<Resource1>>) {
2104 assert!(res.is_none());
2105 }
2106
2107 #[derive(SystemSet, Hash, PartialEq, Eq, Debug, Clone)]
2108 enum Sets {
2109 A,
2110 B,
2111 }
2112
2113 fn check_no_sync_edges(add_systems: impl FnOnce(&mut Schedule)) {
2114 let mut schedule = Schedule::default();
2115 let mut world = World::default();
2116 add_systems(&mut schedule);
2117
2118 schedule.run(&mut world);
2119
2120 assert_eq!(schedule.executable.systems.len(), 2);
2121 }
2122
2123 #[test]
2124 fn system_to_system_after() {
2125 check_no_sync_edges(|schedule| {
2126 schedule.add_systems((
2127 insert_resource,
2128 resource_does_not_exist.after_ignore_deferred(insert_resource),
2129 ));
2130 });
2131 }
2132
2133 #[test]
2134 fn system_to_system_before() {
2135 check_no_sync_edges(|schedule| {
2136 schedule.add_systems((
2137 insert_resource.before_ignore_deferred(resource_does_not_exist),
2138 resource_does_not_exist,
2139 ));
2140 });
2141 }
2142
2143 #[test]
2144 fn set_to_system_after() {
2145 check_no_sync_edges(|schedule| {
2146 schedule
2147 .add_systems((insert_resource, resource_does_not_exist.in_set(Sets::A)))
2148 .configure_sets(Sets::A.after_ignore_deferred(insert_resource));
2149 });
2150 }
2151
2152 #[test]
2153 fn set_to_system_before() {
2154 check_no_sync_edges(|schedule| {
2155 schedule
2156 .add_systems((insert_resource.in_set(Sets::A), resource_does_not_exist))
2157 .configure_sets(Sets::A.before_ignore_deferred(resource_does_not_exist));
2158 });
2159 }
2160
2161 #[test]
2162 fn set_to_set_after() {
2163 check_no_sync_edges(|schedule| {
2164 schedule
2165 .add_systems((
2166 insert_resource.in_set(Sets::A),
2167 resource_does_not_exist.in_set(Sets::B),
2168 ))
2169 .configure_sets(Sets::B.after_ignore_deferred(Sets::A));
2170 });
2171 }
2172
2173 #[test]
2174 fn set_to_set_before() {
2175 check_no_sync_edges(|schedule| {
2176 schedule
2177 .add_systems((
2178 insert_resource.in_set(Sets::A),
2179 resource_does_not_exist.in_set(Sets::B),
2180 ))
2181 .configure_sets(Sets::A.before_ignore_deferred(Sets::B));
2182 });
2183 }
2184 }
2185
2186 mod no_sync_chain {
2187 use super::*;
2188
2189 #[derive(Resource)]
2190 struct Ra;
2191
2192 #[derive(Resource)]
2193 struct Rb;
2194
2195 #[derive(Resource)]
2196 struct Rc;
2197
2198 fn run_schedule(expected_num_systems: usize, add_systems: impl FnOnce(&mut Schedule)) {
2199 let mut schedule = Schedule::default();
2200 let mut world = World::default();
2201 add_systems(&mut schedule);
2202
2203 schedule.run(&mut world);
2204
2205 assert_eq!(schedule.executable.systems.len(), expected_num_systems);
2206 }
2207
2208 #[test]
2209 fn only_chain_outside() {
2210 run_schedule(5, |schedule: &mut Schedule| {
2211 schedule.add_systems(
2212 (
2213 (
2214 |mut commands: Commands| commands.insert_resource(Ra),
2215 |mut commands: Commands| commands.insert_resource(Rb),
2216 ),
2217 (
2218 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2219 assert!(res_a.is_some());
2220 assert!(res_b.is_some());
2221 },
2222 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2223 assert!(res_a.is_some());
2224 assert!(res_b.is_some());
2225 },
2226 ),
2227 )
2228 .chain(),
2229 );
2230 });
2231
2232 run_schedule(4, |schedule: &mut Schedule| {
2233 schedule.add_systems(
2234 (
2235 (
2236 |mut commands: Commands| commands.insert_resource(Ra),
2237 |mut commands: Commands| commands.insert_resource(Rb),
2238 ),
2239 (
2240 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2241 assert!(res_a.is_none());
2242 assert!(res_b.is_none());
2243 },
2244 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2245 assert!(res_a.is_none());
2246 assert!(res_b.is_none());
2247 },
2248 ),
2249 )
2250 .chain_ignore_deferred(),
2251 );
2252 });
2253 }
2254
2255 #[test]
2256 fn chain_first() {
2257 run_schedule(6, |schedule: &mut Schedule| {
2258 schedule.add_systems(
2259 (
2260 (
2261 |mut commands: Commands| commands.insert_resource(Ra),
2262 |mut commands: Commands, res_a: Option<Res<Ra>>| {
2263 commands.insert_resource(Rb);
2264 assert!(res_a.is_some());
2265 },
2266 )
2267 .chain(),
2268 (
2269 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2270 assert!(res_a.is_some());
2271 assert!(res_b.is_some());
2272 },
2273 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2274 assert!(res_a.is_some());
2275 assert!(res_b.is_some());
2276 },
2277 ),
2278 )
2279 .chain(),
2280 );
2281 });
2282
2283 run_schedule(5, |schedule: &mut Schedule| {
2284 schedule.add_systems(
2285 (
2286 (
2287 |mut commands: Commands| commands.insert_resource(Ra),
2288 |mut commands: Commands, res_a: Option<Res<Ra>>| {
2289 commands.insert_resource(Rb);
2290 assert!(res_a.is_some());
2291 },
2292 )
2293 .chain(),
2294 (
2295 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2296 assert!(res_a.is_some());
2297 assert!(res_b.is_none());
2298 },
2299 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2300 assert!(res_a.is_some());
2301 assert!(res_b.is_none());
2302 },
2303 ),
2304 )
2305 .chain_ignore_deferred(),
2306 );
2307 });
2308 }
2309
2310 #[test]
2311 fn chain_second() {
2312 run_schedule(6, |schedule: &mut Schedule| {
2313 schedule.add_systems(
2314 (
2315 (
2316 |mut commands: Commands| commands.insert_resource(Ra),
2317 |mut commands: Commands| commands.insert_resource(Rb),
2318 ),
2319 (
2320 |mut commands: Commands,
2321 res_a: Option<Res<Ra>>,
2322 res_b: Option<Res<Rb>>| {
2323 commands.insert_resource(Rc);
2324 assert!(res_a.is_some());
2325 assert!(res_b.is_some());
2326 },
2327 |res_a: Option<Res<Ra>>,
2328 res_b: Option<Res<Rb>>,
2329 res_c: Option<Res<Rc>>| {
2330 assert!(res_a.is_some());
2331 assert!(res_b.is_some());
2332 assert!(res_c.is_some());
2333 },
2334 )
2335 .chain(),
2336 )
2337 .chain(),
2338 );
2339 });
2340
2341 run_schedule(5, |schedule: &mut Schedule| {
2342 schedule.add_systems(
2343 (
2344 (
2345 |mut commands: Commands| commands.insert_resource(Ra),
2346 |mut commands: Commands| commands.insert_resource(Rb),
2347 ),
2348 (
2349 |mut commands: Commands,
2350 res_a: Option<Res<Ra>>,
2351 res_b: Option<Res<Rb>>| {
2352 commands.insert_resource(Rc);
2353 assert!(res_a.is_none());
2354 assert!(res_b.is_none());
2355 },
2356 |res_a: Option<Res<Ra>>,
2357 res_b: Option<Res<Rb>>,
2358 res_c: Option<Res<Rc>>| {
2359 assert!(res_a.is_some());
2360 assert!(res_b.is_some());
2361 assert!(res_c.is_some());
2362 },
2363 )
2364 .chain(),
2365 )
2366 .chain_ignore_deferred(),
2367 );
2368 });
2369 }
2370
2371 #[test]
2372 fn chain_all() {
2373 run_schedule(7, |schedule: &mut Schedule| {
2374 schedule.add_systems(
2375 (
2376 (
2377 |mut commands: Commands| commands.insert_resource(Ra),
2378 |mut commands: Commands, res_a: Option<Res<Ra>>| {
2379 commands.insert_resource(Rb);
2380 assert!(res_a.is_some());
2381 },
2382 )
2383 .chain(),
2384 (
2385 |mut commands: Commands,
2386 res_a: Option<Res<Ra>>,
2387 res_b: Option<Res<Rb>>| {
2388 commands.insert_resource(Rc);
2389 assert!(res_a.is_some());
2390 assert!(res_b.is_some());
2391 },
2392 |res_a: Option<Res<Ra>>,
2393 res_b: Option<Res<Rb>>,
2394 res_c: Option<Res<Rc>>| {
2395 assert!(res_a.is_some());
2396 assert!(res_b.is_some());
2397 assert!(res_c.is_some());
2398 },
2399 )
2400 .chain(),
2401 )
2402 .chain(),
2403 );
2404 });
2405
2406 run_schedule(6, |schedule: &mut Schedule| {
2407 schedule.add_systems(
2408 (
2409 (
2410 |mut commands: Commands| commands.insert_resource(Ra),
2411 |mut commands: Commands, res_a: Option<Res<Ra>>| {
2412 commands.insert_resource(Rb);
2413 assert!(res_a.is_some());
2414 },
2415 )
2416 .chain(),
2417 (
2418 |mut commands: Commands,
2419 res_a: Option<Res<Ra>>,
2420 res_b: Option<Res<Rb>>| {
2421 commands.insert_resource(Rc);
2422 assert!(res_a.is_some());
2423 assert!(res_b.is_none());
2424 },
2425 |res_a: Option<Res<Ra>>,
2426 res_b: Option<Res<Rb>>,
2427 res_c: Option<Res<Rc>>| {
2428 assert!(res_a.is_some());
2429 assert!(res_b.is_some());
2430 assert!(res_c.is_some());
2431 },
2432 )
2433 .chain(),
2434 )
2435 .chain_ignore_deferred(),
2436 );
2437 });
2438 }
2439 }
2440
2441 #[derive(ScheduleLabel, Hash, Debug, Clone, PartialEq, Eq)]
2442 struct TestSchedule;
2443
2444 #[derive(Resource)]
2445 struct CheckSystemRan(usize);
2446
2447 #[test]
2448 fn add_systems_to_existing_schedule() {
2449 let mut schedules = Schedules::default();
2450 let schedule = Schedule::new(TestSchedule);
2451
2452 schedules.insert(schedule);
2453 schedules.add_systems(TestSchedule, |mut ran: ResMut<CheckSystemRan>| ran.0 += 1);
2454
2455 let mut world = World::new();
2456
2457 world.insert_resource(CheckSystemRan(0));
2458 world.insert_resource(schedules);
2459 world.run_schedule(TestSchedule);
2460
2461 let value = world
2462 .get_resource::<CheckSystemRan>()
2463 .expect("CheckSystemRan Resource Should Exist");
2464 assert_eq!(value.0, 1);
2465 }
2466
2467 #[test]
2468 fn add_systems_to_non_existing_schedule() {
2469 let mut schedules = Schedules::default();
2470
2471 schedules.add_systems(TestSchedule, |mut ran: ResMut<CheckSystemRan>| ran.0 += 1);
2472
2473 let mut world = World::new();
2474
2475 world.insert_resource(CheckSystemRan(0));
2476 world.insert_resource(schedules);
2477 world.run_schedule(TestSchedule);
2478
2479 let value = world
2480 .get_resource::<CheckSystemRan>()
2481 .expect("CheckSystemRan Resource Should Exist");
2482 assert_eq!(value.0, 1);
2483 }
2484
2485 #[derive(SystemSet, Debug, Hash, Clone, PartialEq, Eq)]
2486 enum TestSet {
2487 First,
2488 Second,
2489 }
2490
2491 #[test]
2492 fn configure_set_on_existing_schedule() {
2493 let mut schedules = Schedules::default();
2494 let schedule = Schedule::new(TestSchedule);
2495
2496 schedules.insert(schedule);
2497
2498 schedules.configure_sets(TestSchedule, (TestSet::First, TestSet::Second).chain());
2499 schedules.add_systems(
2500 TestSchedule,
2501 (|mut ran: ResMut<CheckSystemRan>| {
2502 assert_eq!(ran.0, 0);
2503 ran.0 += 1;
2504 })
2505 .in_set(TestSet::First),
2506 );
2507
2508 schedules.add_systems(
2509 TestSchedule,
2510 (|mut ran: ResMut<CheckSystemRan>| {
2511 assert_eq!(ran.0, 1);
2512 ran.0 += 1;
2513 })
2514 .in_set(TestSet::Second),
2515 );
2516
2517 let mut world = World::new();
2518
2519 world.insert_resource(CheckSystemRan(0));
2520 world.insert_resource(schedules);
2521 world.run_schedule(TestSchedule);
2522
2523 let value = world
2524 .get_resource::<CheckSystemRan>()
2525 .expect("CheckSystemRan Resource Should Exist");
2526 assert_eq!(value.0, 2);
2527 }
2528
2529 #[test]
2530 fn configure_set_on_new_schedule() {
2531 let mut schedules = Schedules::default();
2532
2533 schedules.configure_sets(TestSchedule, (TestSet::First, TestSet::Second).chain());
2534 schedules.add_systems(
2535 TestSchedule,
2536 (|mut ran: ResMut<CheckSystemRan>| {
2537 assert_eq!(ran.0, 0);
2538 ran.0 += 1;
2539 })
2540 .in_set(TestSet::First),
2541 );
2542
2543 schedules.add_systems(
2544 TestSchedule,
2545 (|mut ran: ResMut<CheckSystemRan>| {
2546 assert_eq!(ran.0, 1);
2547 ran.0 += 1;
2548 })
2549 .in_set(TestSet::Second),
2550 );
2551
2552 let mut world = World::new();
2553
2554 world.insert_resource(CheckSystemRan(0));
2555 world.insert_resource(schedules);
2556 world.run_schedule(TestSchedule);
2557
2558 let value = world
2559 .get_resource::<CheckSystemRan>()
2560 .expect("CheckSystemRan Resource Should Exist");
2561 assert_eq!(value.0, 2);
2562 }
2563
2564 #[test]
2565 fn test_default_error_handler() {
2566 #[derive(Resource, Default)]
2567 struct Ran(bool);
2568
2569 fn system(mut ran: ResMut<Ran>) -> Result {
2570 ran.0 = true;
2571 Err("I failed!".into())
2572 }
2573
2574 let mut world = World::default();
2576 world.init_resource::<Ran>();
2577 world.insert_resource(DefaultErrorHandler(ignore));
2578 let mut schedule = Schedule::default();
2579 schedule.add_systems(system).run(&mut world);
2580 assert!(world.resource::<Ran>().0);
2581
2582 schedule.add_systems(
2584 (|world: &mut World| {
2585 world.insert_resource(DefaultErrorHandler(panic));
2586 })
2587 .before(system),
2588 );
2589 schedule.run(&mut world);
2590 }
2591}