1#![expect(
2 clippy::module_inception,
3 reason = "This instance of module inception is being discussed; see #17344."
4)]
5use alloc::borrow::Cow;
6use alloc::{
7 boxed::Box,
8 collections::{BTreeMap, BTreeSet},
9 format,
10 string::{String, ToString},
11 vec,
12 vec::Vec,
13};
14use bevy_platform::collections::{HashMap, HashSet};
15use bevy_utils::{default, TypeIdMap};
16use core::{
17 any::{Any, TypeId},
18 fmt::{Debug, Write},
19};
20use disqualified::ShortName;
21use fixedbitset::FixedBitSet;
22use log::{error, info, warn};
23use pass::ScheduleBuildPassObj;
24use thiserror::Error;
25#[cfg(feature = "trace")]
26use tracing::info_span;
27
28use crate::{
29 component::{ComponentId, Components, Tick},
30 error::default_error_handler,
31 prelude::Component,
32 resource::Resource,
33 schedule::*,
34 system::ScheduleSystem,
35 world::World,
36};
37
38use crate::{query::AccessConflicts, storage::SparseSetIndex};
39pub use stepping::Stepping;
40use Direction::{Incoming, Outgoing};
41
42#[derive(Default, Resource)]
44pub struct Schedules {
45 inner: HashMap<InternedScheduleLabel, Schedule>,
46 pub ignored_scheduling_ambiguities: BTreeSet<ComponentId>,
48}
49
50impl Schedules {
51 pub fn new() -> Self {
53 Self::default()
54 }
55
56 pub fn insert(&mut self, schedule: Schedule) -> Option<Schedule> {
61 self.inner.insert(schedule.label, schedule)
62 }
63
64 pub fn remove(&mut self, label: impl ScheduleLabel) -> Option<Schedule> {
66 self.inner.remove(&label.intern())
67 }
68
69 pub fn remove_entry(
71 &mut self,
72 label: impl ScheduleLabel,
73 ) -> Option<(InternedScheduleLabel, Schedule)> {
74 self.inner.remove_entry(&label.intern())
75 }
76
77 pub fn contains(&self, label: impl ScheduleLabel) -> bool {
79 self.inner.contains_key(&label.intern())
80 }
81
82 pub fn get(&self, label: impl ScheduleLabel) -> Option<&Schedule> {
84 self.inner.get(&label.intern())
85 }
86
87 pub fn get_mut(&mut self, label: impl ScheduleLabel) -> Option<&mut Schedule> {
89 self.inner.get_mut(&label.intern())
90 }
91
92 pub fn entry(&mut self, label: impl ScheduleLabel) -> &mut Schedule {
94 self.inner
95 .entry(label.intern())
96 .or_insert_with(|| Schedule::new(label))
97 }
98
99 pub fn iter(&self) -> impl Iterator<Item = (&dyn ScheduleLabel, &Schedule)> {
101 self.inner
102 .iter()
103 .map(|(label, schedule)| (&**label, schedule))
104 }
105 pub fn iter_mut(&mut self) -> impl Iterator<Item = (&dyn ScheduleLabel, &mut Schedule)> {
107 self.inner
108 .iter_mut()
109 .map(|(label, schedule)| (&**label, schedule))
110 }
111
112 pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) {
116 #[cfg(feature = "trace")]
117 let _all_span = info_span!("check stored schedule ticks").entered();
118 #[cfg_attr(
119 not(feature = "trace"),
120 expect(
121 unused_variables,
122 reason = "The `label` variable goes unused if the `trace` feature isn't active"
123 )
124 )]
125 for (label, schedule) in &mut self.inner {
126 #[cfg(feature = "trace")]
127 let name = format!("{label:?}");
128 #[cfg(feature = "trace")]
129 let _one_span = info_span!("check schedule ticks", name = &name).entered();
130 schedule.check_change_ticks(change_tick);
131 }
132 }
133
134 pub fn configure_schedules(&mut self, schedule_build_settings: ScheduleBuildSettings) {
136 for (_, schedule) in &mut self.inner {
137 schedule.set_build_settings(schedule_build_settings.clone());
138 }
139 }
140
141 pub fn allow_ambiguous_component<T: Component>(&mut self, world: &mut World) {
143 self.ignored_scheduling_ambiguities
144 .insert(world.register_component::<T>());
145 }
146
147 pub fn allow_ambiguous_resource<T: Resource>(&mut self, world: &mut World) {
149 self.ignored_scheduling_ambiguities
150 .insert(world.components_registrator().register_resource::<T>());
151 }
152
153 pub fn iter_ignored_ambiguities(&self) -> impl Iterator<Item = &ComponentId> + '_ {
155 self.ignored_scheduling_ambiguities.iter()
156 }
157
158 pub fn print_ignored_ambiguities(&self, components: &Components) {
163 let mut message =
164 "System order ambiguities caused by conflicts on the following types are ignored:\n"
165 .to_string();
166 for id in self.iter_ignored_ambiguities() {
167 writeln!(message, "{}", components.get_name(*id).unwrap()).unwrap();
168 }
169
170 info!("{}", message);
171 }
172
173 pub fn add_systems<M>(
175 &mut self,
176 schedule: impl ScheduleLabel,
177 systems: impl IntoScheduleConfigs<ScheduleSystem, M>,
178 ) -> &mut Self {
179 self.entry(schedule).add_systems(systems);
180
181 self
182 }
183
184 #[track_caller]
186 pub fn configure_sets<M>(
187 &mut self,
188 schedule: impl ScheduleLabel,
189 sets: impl IntoScheduleConfigs<InternedSystemSet, M>,
190 ) -> &mut Self {
191 self.entry(schedule).configure_sets(sets);
192
193 self
194 }
195
196 #[track_caller]
203 pub fn ignore_ambiguity<M1, M2, S1, S2>(
204 &mut self,
205 schedule: impl ScheduleLabel,
206 a: S1,
207 b: S2,
208 ) -> &mut Self
209 where
210 S1: IntoSystemSet<M1>,
211 S2: IntoSystemSet<M2>,
212 {
213 self.entry(schedule).ignore_ambiguity(a, b);
214
215 self
216 }
217}
218
219fn make_executor(kind: ExecutorKind) -> Box<dyn SystemExecutor> {
220 match kind {
221 ExecutorKind::Simple => Box::new(SimpleExecutor::new()),
222 ExecutorKind::SingleThreaded => Box::new(SingleThreadedExecutor::new()),
223 #[cfg(feature = "std")]
224 ExecutorKind::MultiThreaded => Box::new(MultiThreadedExecutor::new()),
225 }
226}
227
228#[derive(Default)]
230pub enum Chain {
231 #[default]
233 Unchained,
234 Chained(TypeIdMap<Box<dyn Any>>),
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 {
295 label: InternedScheduleLabel,
296 graph: ScheduleGraph,
297 executable: SystemSchedule,
298 executor: Box<dyn SystemExecutor>,
299 executor_initialized: bool,
300}
301
302#[derive(ScheduleLabel, Hash, PartialEq, Eq, Debug, Clone)]
303struct DefaultSchedule;
304
305impl Default for Schedule {
306 fn default() -> Self {
311 Self::new(DefaultSchedule)
312 }
313}
314
315impl Schedule {
316 pub fn new(label: impl ScheduleLabel) -> Self {
318 let mut this = Self {
319 label: label.intern(),
320 graph: ScheduleGraph::new(),
321 executable: SystemSchedule::new(),
322 executor: make_executor(ExecutorKind::default()),
323 executor_initialized: false,
324 };
325 this.set_build_settings(Default::default());
327 this
328 }
329
330 pub fn label(&self) -> InternedScheduleLabel {
332 self.label
333 }
334
335 pub fn add_systems<M>(
337 &mut self,
338 systems: impl IntoScheduleConfigs<ScheduleSystem, M>,
339 ) -> &mut Self {
340 self.graph.process_configs(systems.into_configs(), false);
341 self
342 }
343
344 #[track_caller]
347 pub fn ignore_ambiguity<M1, M2, S1, S2>(&mut self, a: S1, b: S2) -> &mut Self
348 where
349 S1: IntoSystemSet<M1>,
350 S2: IntoSystemSet<M2>,
351 {
352 let a = a.into_system_set();
353 let b = b.into_system_set();
354
355 let Some(&a_id) = self.graph.system_set_ids.get(&a.intern()) else {
356 panic!(
357 "Could not mark system as ambiguous, `{:?}` was not found in the schedule.
358 Did you try to call `ambiguous_with` before adding the system to the world?",
359 a
360 );
361 };
362 let Some(&b_id) = self.graph.system_set_ids.get(&b.intern()) else {
363 panic!(
364 "Could not mark system as ambiguous, `{:?}` was not found in the schedule.
365 Did you try to call `ambiguous_with` before adding the system to the world?",
366 b
367 );
368 };
369
370 self.graph.ambiguous_with.add_edge(a_id, b_id);
371
372 self
373 }
374
375 #[track_caller]
377 pub fn configure_sets<M>(
378 &mut self,
379 sets: impl IntoScheduleConfigs<InternedSystemSet, M>,
380 ) -> &mut Self {
381 self.graph.configure_sets(sets);
382 self
383 }
384
385 pub fn add_build_pass<T: ScheduleBuildPass>(&mut self, pass: T) -> &mut Self {
387 self.graph.passes.insert(TypeId::of::<T>(), Box::new(pass));
388 self
389 }
390
391 pub fn remove_build_pass<T: ScheduleBuildPass>(&mut self) {
393 self.graph.passes.remove(&TypeId::of::<T>());
394 }
395
396 pub fn set_build_settings(&mut self, settings: ScheduleBuildSettings) -> &mut Self {
404 if settings.auto_insert_apply_deferred {
405 if !self
406 .graph
407 .passes
408 .contains_key(&TypeId::of::<passes::AutoInsertApplyDeferredPass>())
409 {
410 self.add_build_pass(passes::AutoInsertApplyDeferredPass::default());
411 }
412 } else {
413 self.remove_build_pass::<passes::AutoInsertApplyDeferredPass>();
414 }
415 self.graph.settings = settings;
416 self
417 }
418
419 pub fn get_build_settings(&self) -> ScheduleBuildSettings {
421 self.graph.settings.clone()
422 }
423
424 pub fn get_executor_kind(&self) -> ExecutorKind {
426 self.executor.kind()
427 }
428
429 pub fn set_executor_kind(&mut self, executor: ExecutorKind) -> &mut Self {
431 if executor != self.executor.kind() {
432 self.executor = make_executor(executor);
433 self.executor_initialized = false;
434 }
435 self
436 }
437
438 pub fn set_apply_final_deferred(&mut self, apply_final_deferred: bool) -> &mut Self {
443 self.executor.set_apply_final_deferred(apply_final_deferred);
444 self
445 }
446
447 pub fn run(&mut self, world: &mut World) {
449 #[cfg(feature = "trace")]
450 let _span = info_span!("schedule", name = ?self.label).entered();
451
452 world.check_change_ticks();
453 self.initialize(world)
454 .unwrap_or_else(|e| panic!("Error when initializing schedule {:?}: {e}", self.label));
455
456 let error_handler = default_error_handler();
457
458 #[cfg(not(feature = "bevy_debug_stepping"))]
459 self.executor
460 .run(&mut self.executable, world, None, error_handler);
461
462 #[cfg(feature = "bevy_debug_stepping")]
463 {
464 let skip_systems = match world.get_resource_mut::<Stepping>() {
465 None => None,
466 Some(mut stepping) => stepping.skipped_systems(self),
467 };
468
469 self.executor.run(
470 &mut self.executable,
471 world,
472 skip_systems.as_ref(),
473 error_handler,
474 );
475 }
476 }
477
478 pub fn initialize(&mut self, world: &mut World) -> Result<(), ScheduleBuildError> {
483 if self.graph.changed {
484 self.graph.initialize(world);
485 let ignored_ambiguities = world
486 .get_resource_or_init::<Schedules>()
487 .ignored_scheduling_ambiguities
488 .clone();
489 self.graph.update_schedule(
490 world,
491 &mut self.executable,
492 &ignored_ambiguities,
493 self.label,
494 )?;
495 self.graph.changed = false;
496 self.executor_initialized = false;
497 }
498
499 if !self.executor_initialized {
500 self.executor.init(&self.executable);
501 self.executor_initialized = true;
502 }
503
504 Ok(())
505 }
506
507 pub fn graph(&self) -> &ScheduleGraph {
509 &self.graph
510 }
511
512 pub fn graph_mut(&mut self) -> &mut ScheduleGraph {
514 &mut self.graph
515 }
516
517 pub(crate) fn executable(&self) -> &SystemSchedule {
519 &self.executable
520 }
521
522 pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) {
526 for system in &mut self.executable.systems {
527 if !is_apply_deferred(system) {
528 system.check_change_tick(change_tick);
529 }
530 }
531
532 for conditions in &mut self.executable.system_conditions {
533 for system in conditions {
534 system.check_change_tick(change_tick);
535 }
536 }
537
538 for conditions in &mut self.executable.set_conditions {
539 for system in conditions {
540 system.check_change_tick(change_tick);
541 }
542 }
543 }
544
545 pub fn apply_deferred(&mut self, world: &mut World) {
554 for system in &mut self.executable.systems {
555 system.apply_deferred(world);
556 }
557 }
558
559 pub fn systems(
564 &self,
565 ) -> Result<impl Iterator<Item = (NodeId, &ScheduleSystem)> + Sized, ScheduleNotInitialized>
566 {
567 if !self.executor_initialized {
568 return Err(ScheduleNotInitialized);
569 }
570
571 let iter = self
572 .executable
573 .system_ids
574 .iter()
575 .zip(&self.executable.systems)
576 .map(|(node_id, system)| (*node_id, system));
577
578 Ok(iter)
579 }
580
581 pub fn systems_len(&self) -> usize {
583 if !self.executor_initialized {
584 self.graph.systems.len()
585 } else {
586 self.executable.systems.len()
587 }
588 }
589}
590
591#[derive(Default)]
593pub struct Dag {
594 graph: DiGraph,
596 topsort: Vec<NodeId>,
598}
599
600impl Dag {
601 fn new() -> Self {
602 Self {
603 graph: DiGraph::default(),
604 topsort: Vec::new(),
605 }
606 }
607
608 pub fn graph(&self) -> &DiGraph {
610 &self.graph
611 }
612
613 pub fn cached_topsort(&self) -> &[NodeId] {
617 &self.topsort
618 }
619}
620
621struct SystemSetNode {
623 inner: InternedSystemSet,
624}
625
626impl SystemSetNode {
627 pub fn new(set: InternedSystemSet) -> Self {
628 Self { inner: set }
629 }
630
631 pub fn name(&self) -> String {
632 format!("{:?}", &self.inner)
633 }
634
635 pub fn is_system_type(&self) -> bool {
636 self.inner.system_type().is_some()
637 }
638
639 pub fn is_anonymous(&self) -> bool {
640 self.inner.is_anonymous()
641 }
642}
643
644pub struct SystemNode {
646 inner: Option<ScheduleSystem>,
647}
648
649impl SystemNode {
650 pub fn new(system: ScheduleSystem) -> Self {
652 Self {
653 inner: Some(system),
654 }
655 }
656
657 pub fn get(&self) -> Option<&ScheduleSystem> {
659 self.inner.as_ref()
660 }
661
662 pub fn get_mut(&mut self) -> Option<&mut ScheduleSystem> {
664 self.inner.as_mut()
665 }
666}
667
668#[derive(Default)]
673pub struct ScheduleGraph {
674 pub systems: Vec<SystemNode>,
676 pub system_conditions: Vec<Vec<BoxedCondition>>,
678 system_sets: Vec<SystemSetNode>,
680 system_set_conditions: Vec<Vec<BoxedCondition>>,
682 system_set_ids: HashMap<InternedSystemSet, NodeId>,
684 uninit: Vec<(NodeId, usize)>,
687 hierarchy: Dag,
689 dependency: Dag,
691 ambiguous_with: UnGraph,
692 pub ambiguous_with_all: HashSet<NodeId>,
694 conflicting_systems: Vec<(NodeId, NodeId, Vec<ComponentId>)>,
695 anonymous_sets: usize,
696 changed: bool,
697 settings: ScheduleBuildSettings,
698
699 passes: BTreeMap<TypeId, Box<dyn ScheduleBuildPassObj>>,
700}
701
702impl ScheduleGraph {
703 pub fn new() -> Self {
705 Self {
706 systems: Vec::new(),
707 system_conditions: Vec::new(),
708 system_sets: Vec::new(),
709 system_set_conditions: Vec::new(),
710 system_set_ids: HashMap::default(),
711 uninit: Vec::new(),
712 hierarchy: Dag::new(),
713 dependency: Dag::new(),
714 ambiguous_with: UnGraph::default(),
715 ambiguous_with_all: HashSet::default(),
716 conflicting_systems: Vec::new(),
717 anonymous_sets: 0,
718 changed: false,
719 settings: default(),
720 passes: default(),
721 }
722 }
723
724 pub fn get_system_at(&self, id: NodeId) -> Option<&ScheduleSystem> {
726 if !id.is_system() {
727 return None;
728 }
729 self.systems
730 .get(id.index())
731 .and_then(|system| system.inner.as_ref())
732 }
733
734 pub fn contains_set(&self, set: impl SystemSet) -> bool {
736 self.system_set_ids.contains_key(&set.intern())
737 }
738
739 #[track_caller]
743 pub fn system_at(&self, id: NodeId) -> &ScheduleSystem {
744 self.get_system_at(id)
745 .ok_or_else(|| format!("system with id {id:?} does not exist in this Schedule"))
746 .unwrap()
747 }
748
749 pub fn get_set_at(&self, id: NodeId) -> Option<&dyn SystemSet> {
751 if !id.is_set() {
752 return None;
753 }
754 self.system_sets.get(id.index()).map(|set| &*set.inner)
755 }
756
757 #[track_caller]
761 pub fn set_at(&self, id: NodeId) -> &dyn SystemSet {
762 self.get_set_at(id)
763 .ok_or_else(|| format!("set with id {id:?} does not exist in this Schedule"))
764 .unwrap()
765 }
766
767 pub fn get_set_conditions_at(&self, id: NodeId) -> Option<&[BoxedCondition]> {
769 if !id.is_set() {
770 return None;
771 }
772 self.system_set_conditions
773 .get(id.index())
774 .map(Vec::as_slice)
775 }
776
777 #[track_caller]
781 pub fn set_conditions_at(&self, id: NodeId) -> &[BoxedCondition] {
782 self.get_set_conditions_at(id)
783 .ok_or_else(|| format!("set with id {id:?} does not exist in this Schedule"))
784 .unwrap()
785 }
786
787 pub fn systems(&self) -> impl Iterator<Item = (NodeId, &ScheduleSystem, &[BoxedCondition])> {
789 self.systems
790 .iter()
791 .zip(self.system_conditions.iter())
792 .enumerate()
793 .filter_map(|(i, (system_node, condition))| {
794 let system = system_node.inner.as_ref()?;
795 Some((NodeId::System(i), system, condition.as_slice()))
796 })
797 }
798
799 pub fn system_sets(&self) -> impl Iterator<Item = (NodeId, &dyn SystemSet, &[BoxedCondition])> {
802 self.system_set_ids.iter().map(|(_, &node_id)| {
803 let set_node = &self.system_sets[node_id.index()];
804 let set = &*set_node.inner;
805 let conditions = self.system_set_conditions[node_id.index()].as_slice();
806 (node_id, set, conditions)
807 })
808 }
809
810 pub fn hierarchy(&self) -> &Dag {
815 &self.hierarchy
816 }
817
818 pub fn dependency(&self) -> &Dag {
823 &self.dependency
824 }
825
826 pub fn conflicting_systems(&self) -> &[(NodeId, NodeId, Vec<ComponentId>)] {
831 &self.conflicting_systems
832 }
833
834 fn process_config<T: ProcessScheduleConfig + Schedulable>(
835 &mut self,
836 config: ScheduleConfig<T>,
837 collect_nodes: bool,
838 ) -> ProcessConfigsResult {
839 ProcessConfigsResult {
840 densely_chained: true,
841 nodes: collect_nodes
842 .then_some(T::process_config(self, config))
843 .into_iter()
844 .collect(),
845 }
846 }
847
848 fn apply_collective_conditions<
849 T: ProcessScheduleConfig + Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>,
850 >(
851 &mut self,
852 configs: &mut [ScheduleConfigs<T>],
853 collective_conditions: Vec<BoxedCondition>,
854 ) {
855 if !collective_conditions.is_empty() {
856 if let [config] = configs {
857 for condition in collective_conditions {
858 config.run_if_dyn(condition);
859 }
860 } else {
861 let set = self.create_anonymous_set();
862 for config in configs.iter_mut() {
863 config.in_set_inner(set.intern());
864 }
865 let mut set_config = InternedSystemSet::into_config(set.intern());
866 set_config.conditions.extend(collective_conditions);
867 self.configure_set_inner(set_config).unwrap();
868 }
869 }
870 }
871
872 #[track_caller]
881 fn process_configs<
882 T: ProcessScheduleConfig + Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>,
883 >(
884 &mut self,
885 configs: ScheduleConfigs<T>,
886 collect_nodes: bool,
887 ) -> ProcessConfigsResult {
888 match configs {
889 ScheduleConfigs::ScheduleConfig(config) => self.process_config(config, collect_nodes),
890 ScheduleConfigs::Configs {
891 metadata,
892 mut configs,
893 collective_conditions,
894 } => {
895 self.apply_collective_conditions(&mut configs, collective_conditions);
896
897 let is_chained = matches!(metadata, Chain::Chained(_));
898
899 let mut densely_chained = is_chained || configs.len() == 1;
903 let mut configs = configs.into_iter();
904 let mut nodes = Vec::new();
905
906 let Some(first) = configs.next() else {
907 return ProcessConfigsResult {
908 nodes: Vec::new(),
909 densely_chained,
910 };
911 };
912 let mut previous_result = self.process_configs(first, collect_nodes || is_chained);
913 densely_chained &= previous_result.densely_chained;
914
915 for current in configs {
916 let current_result = self.process_configs(current, collect_nodes || is_chained);
917 densely_chained &= current_result.densely_chained;
918
919 if let Chain::Chained(chain_options) = &metadata {
920 let current_nodes = if current_result.densely_chained {
922 ¤t_result.nodes[..1]
923 } else {
924 ¤t_result.nodes
925 };
926 let previous_nodes = if previous_result.densely_chained {
928 &previous_result.nodes[previous_result.nodes.len() - 1..]
929 } else {
930 &previous_result.nodes
931 };
932
933 for previous_node in previous_nodes {
934 for current_node in current_nodes {
935 self.dependency
936 .graph
937 .add_edge(*previous_node, *current_node);
938
939 for pass in self.passes.values_mut() {
940 pass.add_dependency(
941 *previous_node,
942 *current_node,
943 chain_options,
944 );
945 }
946 }
947 }
948 }
949 if collect_nodes {
950 nodes.append(&mut previous_result.nodes);
951 }
952
953 previous_result = current_result;
954 }
955 if collect_nodes {
956 nodes.append(&mut previous_result.nodes);
957 }
958
959 ProcessConfigsResult {
960 nodes,
961 densely_chained,
962 }
963 }
964 }
965 }
966
967 fn add_system_inner(
969 &mut self,
970 config: ScheduleConfig<ScheduleSystem>,
971 ) -> Result<NodeId, ScheduleBuildError> {
972 let id = NodeId::System(self.systems.len());
973
974 self.update_graphs(id, config.metadata)?;
976
977 self.uninit.push((id, 0));
979 self.systems.push(SystemNode::new(config.node));
980 self.system_conditions.push(config.conditions);
981
982 Ok(id)
983 }
984
985 #[track_caller]
986 fn configure_sets<M>(&mut self, sets: impl IntoScheduleConfigs<InternedSystemSet, M>) {
987 self.process_configs(sets.into_configs(), false);
988 }
989
990 fn configure_set_inner(
992 &mut self,
993 set: ScheduleConfig<InternedSystemSet>,
994 ) -> Result<NodeId, ScheduleBuildError> {
995 let ScheduleConfig {
996 node: set,
997 metadata,
998 mut conditions,
999 } = set;
1000
1001 let id = match self.system_set_ids.get(&set) {
1002 Some(&id) => id,
1003 None => self.add_set(set),
1004 };
1005
1006 self.update_graphs(id, metadata)?;
1008
1009 let system_set_conditions = &mut self.system_set_conditions[id.index()];
1011 self.uninit.push((id, system_set_conditions.len()));
1012 system_set_conditions.append(&mut conditions);
1013
1014 Ok(id)
1015 }
1016
1017 fn add_set(&mut self, set: InternedSystemSet) -> NodeId {
1018 let id = NodeId::Set(self.system_sets.len());
1019 self.system_sets.push(SystemSetNode::new(set));
1020 self.system_set_conditions.push(Vec::new());
1021 self.system_set_ids.insert(set, id);
1022 id
1023 }
1024
1025 fn check_hierarchy_set(
1028 &mut self,
1029 id: &NodeId,
1030 set: InternedSystemSet,
1031 ) -> Result<(), ScheduleBuildError> {
1032 match self.system_set_ids.get(&set) {
1033 Some(set_id) => {
1034 if id == set_id {
1035 return Err(ScheduleBuildError::HierarchyLoop(self.get_node_name(id)));
1036 }
1037 }
1038 None => {
1039 self.add_set(set);
1040 }
1041 }
1042
1043 Ok(())
1044 }
1045
1046 fn create_anonymous_set(&mut self) -> AnonymousSet {
1047 let id = self.anonymous_sets;
1048 self.anonymous_sets += 1;
1049 AnonymousSet::new(id)
1050 }
1051
1052 fn check_hierarchy_sets(
1055 &mut self,
1056 id: &NodeId,
1057 graph_info: &GraphInfo,
1058 ) -> Result<(), ScheduleBuildError> {
1059 for &set in &graph_info.hierarchy {
1060 self.check_hierarchy_set(id, set)?;
1061 }
1062
1063 Ok(())
1064 }
1065
1066 fn check_edges(
1069 &mut self,
1070 id: &NodeId,
1071 graph_info: &GraphInfo,
1072 ) -> Result<(), ScheduleBuildError> {
1073 for Dependency { set, .. } in &graph_info.dependencies {
1074 match self.system_set_ids.get(set) {
1075 Some(set_id) => {
1076 if id == set_id {
1077 return Err(ScheduleBuildError::DependencyLoop(self.get_node_name(id)));
1078 }
1079 }
1080 None => {
1081 self.add_set(*set);
1082 }
1083 }
1084 }
1085
1086 if let Ambiguity::IgnoreWithSet(ambiguous_with) = &graph_info.ambiguous_with {
1087 for set in ambiguous_with {
1088 if !self.system_set_ids.contains_key(set) {
1089 self.add_set(*set);
1090 }
1091 }
1092 }
1093
1094 Ok(())
1095 }
1096
1097 fn update_graphs(
1099 &mut self,
1100 id: NodeId,
1101 graph_info: GraphInfo,
1102 ) -> Result<(), ScheduleBuildError> {
1103 self.check_hierarchy_sets(&id, &graph_info)?;
1104 self.check_edges(&id, &graph_info)?;
1105 self.changed = true;
1106
1107 let GraphInfo {
1108 hierarchy: sets,
1109 dependencies,
1110 ambiguous_with,
1111 ..
1112 } = graph_info;
1113
1114 self.hierarchy.graph.add_node(id);
1115 self.dependency.graph.add_node(id);
1116
1117 for set in sets.into_iter().map(|set| self.system_set_ids[&set]) {
1118 self.hierarchy.graph.add_edge(set, id);
1119
1120 self.dependency.graph.add_node(set);
1122 }
1123
1124 for (kind, set, options) in dependencies
1125 .into_iter()
1126 .map(|Dependency { kind, set, options }| (kind, self.system_set_ids[&set], options))
1127 {
1128 let (lhs, rhs) = match kind {
1129 DependencyKind::Before => (id, set),
1130 DependencyKind::After => (set, id),
1131 };
1132 self.dependency.graph.add_edge(lhs, rhs);
1133 for pass in self.passes.values_mut() {
1134 pass.add_dependency(lhs, rhs, &options);
1135 }
1136
1137 self.hierarchy.graph.add_node(set);
1139 }
1140
1141 match ambiguous_with {
1142 Ambiguity::Check => (),
1143 Ambiguity::IgnoreWithSet(ambiguous_with) => {
1144 for set in ambiguous_with
1145 .into_iter()
1146 .map(|set| self.system_set_ids[&set])
1147 {
1148 self.ambiguous_with.add_edge(id, set);
1149 }
1150 }
1151 Ambiguity::IgnoreAll => {
1152 self.ambiguous_with_all.insert(id);
1153 }
1154 }
1155
1156 Ok(())
1157 }
1158
1159 pub fn initialize(&mut self, world: &mut World) {
1161 for (id, i) in self.uninit.drain(..) {
1162 match id {
1163 NodeId::System(index) => {
1164 self.systems[index].get_mut().unwrap().initialize(world);
1165 for condition in &mut self.system_conditions[index] {
1166 condition.initialize(world);
1167 }
1168 }
1169 NodeId::Set(index) => {
1170 for condition in self.system_set_conditions[index].iter_mut().skip(i) {
1171 condition.initialize(world);
1172 }
1173 }
1174 }
1175 }
1176 }
1177
1178 pub fn build_schedule(
1184 &mut self,
1185 world: &mut World,
1186 schedule_label: InternedScheduleLabel,
1187 ignored_ambiguities: &BTreeSet<ComponentId>,
1188 ) -> Result<SystemSchedule, ScheduleBuildError> {
1189 self.hierarchy.topsort =
1191 self.topsort_graph(&self.hierarchy.graph, ReportCycles::Hierarchy)?;
1192
1193 let hier_results = check_graph(&self.hierarchy.graph, &self.hierarchy.topsort);
1194 self.optionally_check_hierarchy_conflicts(&hier_results.transitive_edges, schedule_label)?;
1195
1196 self.hierarchy.graph = hier_results.transitive_reduction;
1198
1199 self.dependency.topsort =
1201 self.topsort_graph(&self.dependency.graph, ReportCycles::Dependency)?;
1202
1203 let dep_results = check_graph(&self.dependency.graph, &self.dependency.topsort);
1205 self.check_for_cross_dependencies(&dep_results, &hier_results.connected)?;
1206
1207 let (set_systems, set_system_bitsets) =
1210 self.map_sets_to_systems(&self.hierarchy.topsort, &self.hierarchy.graph);
1211 self.check_order_but_intersect(&dep_results.connected, &set_system_bitsets)?;
1212
1213 self.check_system_type_set_ambiguity(&set_systems)?;
1215
1216 let mut dependency_flattened = self.get_dependency_flattened(&set_systems);
1217
1218 let mut passes = core::mem::take(&mut self.passes);
1220 for pass in passes.values_mut() {
1221 pass.build(world, self, &mut dependency_flattened)?;
1222 }
1223 self.passes = passes;
1224
1225 let mut dependency_flattened_dag = Dag {
1227 topsort: self.topsort_graph(&dependency_flattened, ReportCycles::Dependency)?,
1228 graph: dependency_flattened,
1229 };
1230
1231 let flat_results = check_graph(
1232 &dependency_flattened_dag.graph,
1233 &dependency_flattened_dag.topsort,
1234 );
1235
1236 dependency_flattened_dag.graph = flat_results.transitive_reduction;
1238
1239 let ambiguous_with_flattened = self.get_ambiguous_with_flattened(&set_systems);
1241
1242 let conflicting_systems = self.get_conflicting_systems(
1244 &flat_results.disconnected,
1245 &ambiguous_with_flattened,
1246 ignored_ambiguities,
1247 );
1248 self.optionally_check_conflicts(&conflicting_systems, world.components(), schedule_label)?;
1249 self.conflicting_systems = conflicting_systems;
1250
1251 Ok(self.build_schedule_inner(dependency_flattened_dag, hier_results.reachable))
1253 }
1254
1255 fn map_sets_to_systems(
1259 &self,
1260 hierarchy_topsort: &[NodeId],
1261 hierarchy_graph: &DiGraph,
1262 ) -> (HashMap<NodeId, Vec<NodeId>>, HashMap<NodeId, FixedBitSet>) {
1263 let mut set_systems: HashMap<NodeId, Vec<NodeId>> =
1264 HashMap::with_capacity_and_hasher(self.system_sets.len(), Default::default());
1265 let mut set_system_bitsets =
1266 HashMap::with_capacity_and_hasher(self.system_sets.len(), Default::default());
1267 for &id in hierarchy_topsort.iter().rev() {
1268 if id.is_system() {
1269 continue;
1270 }
1271
1272 let mut systems = Vec::new();
1273 let mut system_bitset = FixedBitSet::with_capacity(self.systems.len());
1274
1275 for child in hierarchy_graph.neighbors_directed(id, Outgoing) {
1276 match child {
1277 NodeId::System(_) => {
1278 systems.push(child);
1279 system_bitset.insert(child.index());
1280 }
1281 NodeId::Set(_) => {
1282 let child_systems = set_systems.get(&child).unwrap();
1283 let child_system_bitset = set_system_bitsets.get(&child).unwrap();
1284 systems.extend_from_slice(child_systems);
1285 system_bitset.union_with(child_system_bitset);
1286 }
1287 }
1288 }
1289
1290 set_systems.insert(id, systems);
1291 set_system_bitsets.insert(id, system_bitset);
1292 }
1293 (set_systems, set_system_bitsets)
1294 }
1295
1296 fn get_dependency_flattened(&mut self, set_systems: &HashMap<NodeId, Vec<NodeId>>) -> DiGraph {
1297 let mut dependency_flattened = self.dependency.graph.clone();
1300 let mut temp = Vec::new();
1301 for (&set, systems) in set_systems {
1302 for pass in self.passes.values_mut() {
1303 pass.collapse_set(set, systems, &dependency_flattened, &mut temp);
1304 }
1305 if systems.is_empty() {
1306 for a in dependency_flattened.neighbors_directed(set, Incoming) {
1308 for b in dependency_flattened.neighbors_directed(set, Outgoing) {
1309 temp.push((a, b));
1310 }
1311 }
1312 } else {
1313 for a in dependency_flattened.neighbors_directed(set, Incoming) {
1314 for &sys in systems {
1315 temp.push((a, sys));
1316 }
1317 }
1318
1319 for b in dependency_flattened.neighbors_directed(set, Outgoing) {
1320 for &sys in systems {
1321 temp.push((sys, b));
1322 }
1323 }
1324 }
1325
1326 dependency_flattened.remove_node(set);
1327 for (a, b) in temp.drain(..) {
1328 dependency_flattened.add_edge(a, b);
1329 }
1330 }
1331
1332 dependency_flattened
1333 }
1334
1335 fn get_ambiguous_with_flattened(&self, set_systems: &HashMap<NodeId, Vec<NodeId>>) -> UnGraph {
1336 let mut ambiguous_with_flattened = UnGraph::default();
1337 for (lhs, rhs) in self.ambiguous_with.all_edges() {
1338 match (lhs, rhs) {
1339 (NodeId::System(_), NodeId::System(_)) => {
1340 ambiguous_with_flattened.add_edge(lhs, rhs);
1341 }
1342 (NodeId::Set(_), NodeId::System(_)) => {
1343 for &lhs_ in set_systems.get(&lhs).unwrap_or(&Vec::new()) {
1344 ambiguous_with_flattened.add_edge(lhs_, rhs);
1345 }
1346 }
1347 (NodeId::System(_), NodeId::Set(_)) => {
1348 for &rhs_ in set_systems.get(&rhs).unwrap_or(&Vec::new()) {
1349 ambiguous_with_flattened.add_edge(lhs, rhs_);
1350 }
1351 }
1352 (NodeId::Set(_), NodeId::Set(_)) => {
1353 for &lhs_ in set_systems.get(&lhs).unwrap_or(&Vec::new()) {
1354 for &rhs_ in set_systems.get(&rhs).unwrap_or(&vec![]) {
1355 ambiguous_with_flattened.add_edge(lhs_, rhs_);
1356 }
1357 }
1358 }
1359 }
1360 }
1361
1362 ambiguous_with_flattened
1363 }
1364
1365 fn get_conflicting_systems(
1366 &self,
1367 flat_results_disconnected: &Vec<(NodeId, NodeId)>,
1368 ambiguous_with_flattened: &UnGraph,
1369 ignored_ambiguities: &BTreeSet<ComponentId>,
1370 ) -> Vec<(NodeId, NodeId, Vec<ComponentId>)> {
1371 let mut conflicting_systems = Vec::new();
1372 for &(a, b) in flat_results_disconnected {
1373 if ambiguous_with_flattened.contains_edge(a, b)
1374 || self.ambiguous_with_all.contains(&a)
1375 || self.ambiguous_with_all.contains(&b)
1376 {
1377 continue;
1378 }
1379
1380 let system_a = self.systems[a.index()].get().unwrap();
1381 let system_b = self.systems[b.index()].get().unwrap();
1382 if system_a.is_exclusive() || system_b.is_exclusive() {
1383 conflicting_systems.push((a, b, Vec::new()));
1384 } else {
1385 let access_a = system_a.component_access();
1386 let access_b = system_b.component_access();
1387 if !access_a.is_compatible(access_b) {
1388 match access_a.get_conflicts(access_b) {
1389 AccessConflicts::Individual(conflicts) => {
1390 let conflicts: Vec<_> = conflicts
1391 .ones()
1392 .map(ComponentId::get_sparse_set_index)
1393 .filter(|id| !ignored_ambiguities.contains(id))
1394 .collect();
1395 if !conflicts.is_empty() {
1396 conflicting_systems.push((a, b, conflicts));
1397 }
1398 }
1399 AccessConflicts::All => {
1400 conflicting_systems.push((a, b, Vec::new()));
1403 }
1404 }
1405 }
1406 }
1407 }
1408
1409 conflicting_systems
1410 }
1411
1412 fn build_schedule_inner(
1413 &self,
1414 dependency_flattened_dag: Dag,
1415 hier_results_reachable: FixedBitSet,
1416 ) -> SystemSchedule {
1417 let dg_system_ids = dependency_flattened_dag.topsort.clone();
1418 let dg_system_idx_map = dg_system_ids
1419 .iter()
1420 .cloned()
1421 .enumerate()
1422 .map(|(i, id)| (id, i))
1423 .collect::<HashMap<_, _>>();
1424
1425 let hg_systems = self
1426 .hierarchy
1427 .topsort
1428 .iter()
1429 .cloned()
1430 .enumerate()
1431 .filter(|&(_i, id)| id.is_system())
1432 .collect::<Vec<_>>();
1433
1434 let (hg_set_with_conditions_idxs, hg_set_ids): (Vec<_>, Vec<_>) = self
1435 .hierarchy
1436 .topsort
1437 .iter()
1438 .cloned()
1439 .enumerate()
1440 .filter(|&(_i, id)| {
1441 id.is_set() && !self.system_set_conditions[id.index()].is_empty()
1444 })
1445 .unzip();
1446
1447 let sys_count = self.systems.len();
1448 let set_with_conditions_count = hg_set_ids.len();
1449 let hg_node_count = self.hierarchy.graph.node_count();
1450
1451 let mut system_dependencies = Vec::with_capacity(sys_count);
1454 let mut system_dependents = Vec::with_capacity(sys_count);
1455 for &sys_id in &dg_system_ids {
1456 let num_dependencies = dependency_flattened_dag
1457 .graph
1458 .neighbors_directed(sys_id, Incoming)
1459 .count();
1460
1461 let dependents = dependency_flattened_dag
1462 .graph
1463 .neighbors_directed(sys_id, Outgoing)
1464 .map(|dep_id| dg_system_idx_map[&dep_id])
1465 .collect::<Vec<_>>();
1466
1467 system_dependencies.push(num_dependencies);
1468 system_dependents.push(dependents);
1469 }
1470
1471 let mut systems_in_sets_with_conditions =
1474 vec![FixedBitSet::with_capacity(sys_count); set_with_conditions_count];
1475 for (i, &row) in hg_set_with_conditions_idxs.iter().enumerate() {
1476 let bitset = &mut systems_in_sets_with_conditions[i];
1477 for &(col, sys_id) in &hg_systems {
1478 let idx = dg_system_idx_map[&sys_id];
1479 let is_descendant = hier_results_reachable[index(row, col, hg_node_count)];
1480 bitset.set(idx, is_descendant);
1481 }
1482 }
1483
1484 let mut sets_with_conditions_of_systems =
1485 vec![FixedBitSet::with_capacity(set_with_conditions_count); sys_count];
1486 for &(col, sys_id) in &hg_systems {
1487 let i = dg_system_idx_map[&sys_id];
1488 let bitset = &mut sets_with_conditions_of_systems[i];
1489 for (idx, &row) in hg_set_with_conditions_idxs
1490 .iter()
1491 .enumerate()
1492 .take_while(|&(_idx, &row)| row < col)
1493 {
1494 let is_ancestor = hier_results_reachable[index(row, col, hg_node_count)];
1495 bitset.set(idx, is_ancestor);
1496 }
1497 }
1498
1499 SystemSchedule {
1500 systems: Vec::with_capacity(sys_count),
1501 system_conditions: Vec::with_capacity(sys_count),
1502 set_conditions: Vec::with_capacity(set_with_conditions_count),
1503 system_ids: dg_system_ids,
1504 set_ids: hg_set_ids,
1505 system_dependencies,
1506 system_dependents,
1507 sets_with_conditions_of_systems,
1508 systems_in_sets_with_conditions,
1509 }
1510 }
1511
1512 fn update_schedule(
1514 &mut self,
1515 world: &mut World,
1516 schedule: &mut SystemSchedule,
1517 ignored_ambiguities: &BTreeSet<ComponentId>,
1518 schedule_label: InternedScheduleLabel,
1519 ) -> Result<(), ScheduleBuildError> {
1520 if !self.uninit.is_empty() {
1521 return Err(ScheduleBuildError::Uninitialized);
1522 }
1523
1524 for ((id, system), conditions) in schedule
1526 .system_ids
1527 .drain(..)
1528 .zip(schedule.systems.drain(..))
1529 .zip(schedule.system_conditions.drain(..))
1530 {
1531 self.systems[id.index()].inner = Some(system);
1532 self.system_conditions[id.index()] = conditions;
1533 }
1534
1535 for (id, conditions) in schedule
1536 .set_ids
1537 .drain(..)
1538 .zip(schedule.set_conditions.drain(..))
1539 {
1540 self.system_set_conditions[id.index()] = conditions;
1541 }
1542
1543 *schedule = self.build_schedule(world, schedule_label, ignored_ambiguities)?;
1544
1545 for &id in &schedule.system_ids {
1547 let system = self.systems[id.index()].inner.take().unwrap();
1548 let conditions = core::mem::take(&mut self.system_conditions[id.index()]);
1549 schedule.systems.push(system);
1550 schedule.system_conditions.push(conditions);
1551 }
1552
1553 for &id in &schedule.set_ids {
1554 let conditions = core::mem::take(&mut self.system_set_conditions[id.index()]);
1555 schedule.set_conditions.push(conditions);
1556 }
1557
1558 Ok(())
1559 }
1560}
1561
1562struct ProcessConfigsResult {
1564 nodes: Vec<NodeId>,
1567 densely_chained: bool,
1571}
1572
1573trait ProcessScheduleConfig: Schedulable + Sized {
1575 fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig<Self>) -> NodeId;
1577}
1578
1579impl ProcessScheduleConfig for ScheduleSystem {
1580 fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig<Self>) -> NodeId {
1581 schedule_graph.add_system_inner(config).unwrap()
1582 }
1583}
1584
1585impl ProcessScheduleConfig for InternedSystemSet {
1586 fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig<Self>) -> NodeId {
1587 schedule_graph.configure_set_inner(config).unwrap()
1588 }
1589}
1590
1591pub enum ReportCycles {
1593 Hierarchy,
1595 Dependency,
1597}
1598
1599impl ScheduleGraph {
1601 fn get_node_name(&self, id: &NodeId) -> String {
1602 self.get_node_name_inner(id, self.settings.report_sets)
1603 }
1604
1605 #[inline]
1606 fn get_node_name_inner(&self, id: &NodeId, report_sets: bool) -> String {
1607 let name = match id {
1608 NodeId::System(_) => {
1609 let name = self.systems[id.index()].get().unwrap().name().to_string();
1610 if report_sets {
1611 let sets = self.names_of_sets_containing_node(id);
1612 if sets.is_empty() {
1613 name
1614 } else if sets.len() == 1 {
1615 format!("{name} (in set {})", sets[0])
1616 } else {
1617 format!("{name} (in sets {})", sets.join(", "))
1618 }
1619 } else {
1620 name
1621 }
1622 }
1623 NodeId::Set(_) => {
1624 let set = &self.system_sets[id.index()];
1625 if set.is_anonymous() {
1626 self.anonymous_set_name(id)
1627 } else {
1628 set.name()
1629 }
1630 }
1631 };
1632 if self.settings.use_shortnames {
1633 ShortName(&name).to_string()
1634 } else {
1635 name
1636 }
1637 }
1638
1639 fn anonymous_set_name(&self, id: &NodeId) -> String {
1640 format!(
1641 "({})",
1642 self.hierarchy
1643 .graph
1644 .edges_directed(*id, Outgoing)
1645 .map(|(_, member_id)| self.get_node_name_inner(&member_id, false))
1647 .reduce(|a, b| format!("{a}, {b}"))
1648 .unwrap_or_default()
1649 )
1650 }
1651
1652 fn get_node_kind(&self, id: &NodeId) -> &'static str {
1653 match id {
1654 NodeId::System(_) => "system",
1655 NodeId::Set(_) => "system set",
1656 }
1657 }
1658
1659 fn optionally_check_hierarchy_conflicts(
1662 &self,
1663 transitive_edges: &[(NodeId, NodeId)],
1664 schedule_label: InternedScheduleLabel,
1665 ) -> Result<(), ScheduleBuildError> {
1666 if self.settings.hierarchy_detection == LogLevel::Ignore || transitive_edges.is_empty() {
1667 return Ok(());
1668 }
1669
1670 let message = self.get_hierarchy_conflicts_error_message(transitive_edges);
1671 match self.settings.hierarchy_detection {
1672 LogLevel::Ignore => unreachable!(),
1673 LogLevel::Warn => {
1674 error!(
1675 "Schedule {schedule_label:?} has redundant edges:\n {}",
1676 message
1677 );
1678 Ok(())
1679 }
1680 LogLevel::Error => Err(ScheduleBuildError::HierarchyRedundancy(message)),
1681 }
1682 }
1683
1684 fn get_hierarchy_conflicts_error_message(
1685 &self,
1686 transitive_edges: &[(NodeId, NodeId)],
1687 ) -> String {
1688 let mut message = String::from("hierarchy contains redundant edge(s)");
1689 for (parent, child) in transitive_edges {
1690 writeln!(
1691 message,
1692 " -- {} `{}` cannot be child of set `{}`, longer path exists",
1693 self.get_node_kind(child),
1694 self.get_node_name(child),
1695 self.get_node_name(parent),
1696 )
1697 .unwrap();
1698 }
1699
1700 message
1701 }
1702
1703 pub fn topsort_graph(
1713 &self,
1714 graph: &DiGraph,
1715 report: ReportCycles,
1716 ) -> Result<Vec<NodeId>, ScheduleBuildError> {
1717 let mut top_sorted_nodes = Vec::with_capacity(graph.node_count());
1719 let mut sccs_with_cycles = Vec::new();
1720
1721 for scc in graph.iter_sccs() {
1722 top_sorted_nodes.extend_from_slice(&scc);
1726 if scc.len() > 1 {
1727 sccs_with_cycles.push(scc);
1728 }
1729 }
1730
1731 if sccs_with_cycles.is_empty() {
1732 top_sorted_nodes.reverse();
1734 Ok(top_sorted_nodes)
1735 } else {
1736 let mut cycles = Vec::new();
1737 for scc in &sccs_with_cycles {
1738 cycles.append(&mut simple_cycles_in_component(graph, scc));
1739 }
1740
1741 let error = match report {
1742 ReportCycles::Hierarchy => ScheduleBuildError::HierarchyCycle(
1743 self.get_hierarchy_cycles_error_message(&cycles),
1744 ),
1745 ReportCycles::Dependency => ScheduleBuildError::DependencyCycle(
1746 self.get_dependency_cycles_error_message(&cycles),
1747 ),
1748 };
1749
1750 Err(error)
1751 }
1752 }
1753
1754 fn get_hierarchy_cycles_error_message(&self, cycles: &[Vec<NodeId>]) -> String {
1756 let mut message = format!("schedule has {} in_set cycle(s):\n", cycles.len());
1757 for (i, cycle) in cycles.iter().enumerate() {
1758 let mut names = cycle.iter().map(|id| self.get_node_name(id));
1759 let first_name = names.next().unwrap();
1760 writeln!(
1761 message,
1762 "cycle {}: set `{first_name}` contains itself",
1763 i + 1,
1764 )
1765 .unwrap();
1766 writeln!(message, "set `{first_name}`").unwrap();
1767 for name in names.chain(core::iter::once(first_name)) {
1768 writeln!(message, " ... which contains set `{name}`").unwrap();
1769 }
1770 writeln!(message).unwrap();
1771 }
1772
1773 message
1774 }
1775
1776 fn get_dependency_cycles_error_message(&self, cycles: &[Vec<NodeId>]) -> String {
1778 let mut message = format!("schedule has {} before/after cycle(s):\n", cycles.len());
1779 for (i, cycle) in cycles.iter().enumerate() {
1780 let mut names = cycle
1781 .iter()
1782 .map(|id| (self.get_node_kind(id), self.get_node_name(id)));
1783 let (first_kind, first_name) = names.next().unwrap();
1784 writeln!(
1785 message,
1786 "cycle {}: {first_kind} `{first_name}` must run before itself",
1787 i + 1,
1788 )
1789 .unwrap();
1790 writeln!(message, "{first_kind} `{first_name}`").unwrap();
1791 for (kind, name) in names.chain(core::iter::once((first_kind, first_name))) {
1792 writeln!(message, " ... which must run before {kind} `{name}`").unwrap();
1793 }
1794 writeln!(message).unwrap();
1795 }
1796
1797 message
1798 }
1799
1800 fn check_for_cross_dependencies(
1801 &self,
1802 dep_results: &CheckGraphResults,
1803 hier_results_connected: &HashSet<(NodeId, NodeId)>,
1804 ) -> Result<(), ScheduleBuildError> {
1805 for &(a, b) in &dep_results.connected {
1806 if hier_results_connected.contains(&(a, b)) || hier_results_connected.contains(&(b, a))
1807 {
1808 let name_a = self.get_node_name(&a);
1809 let name_b = self.get_node_name(&b);
1810 return Err(ScheduleBuildError::CrossDependency(name_a, name_b));
1811 }
1812 }
1813
1814 Ok(())
1815 }
1816
1817 fn check_order_but_intersect(
1818 &self,
1819 dep_results_connected: &HashSet<(NodeId, NodeId)>,
1820 set_system_bitsets: &HashMap<NodeId, FixedBitSet>,
1821 ) -> Result<(), ScheduleBuildError> {
1822 for (a, b) in dep_results_connected {
1824 if !(a.is_set() && b.is_set()) {
1825 continue;
1826 }
1827
1828 let a_systems = set_system_bitsets.get(a).unwrap();
1829 let b_systems = set_system_bitsets.get(b).unwrap();
1830
1831 if !a_systems.is_disjoint(b_systems) {
1832 return Err(ScheduleBuildError::SetsHaveOrderButIntersect(
1833 self.get_node_name(a),
1834 self.get_node_name(b),
1835 ));
1836 }
1837 }
1838
1839 Ok(())
1840 }
1841
1842 fn check_system_type_set_ambiguity(
1843 &self,
1844 set_systems: &HashMap<NodeId, Vec<NodeId>>,
1845 ) -> Result<(), ScheduleBuildError> {
1846 for (&id, systems) in set_systems {
1847 let set = &self.system_sets[id.index()];
1848 if set.is_system_type() {
1849 let instances = systems.len();
1850 let ambiguous_with = self.ambiguous_with.edges(id);
1851 let before = self.dependency.graph.edges_directed(id, Incoming);
1852 let after = self.dependency.graph.edges_directed(id, Outgoing);
1853 let relations = before.count() + after.count() + ambiguous_with.count();
1854 if instances > 1 && relations > 0 {
1855 return Err(ScheduleBuildError::SystemTypeSetAmbiguity(
1856 self.get_node_name(&id),
1857 ));
1858 }
1859 }
1860 }
1861 Ok(())
1862 }
1863
1864 fn optionally_check_conflicts(
1866 &self,
1867 conflicts: &[(NodeId, NodeId, Vec<ComponentId>)],
1868 components: &Components,
1869 schedule_label: InternedScheduleLabel,
1870 ) -> Result<(), ScheduleBuildError> {
1871 if self.settings.ambiguity_detection == LogLevel::Ignore || conflicts.is_empty() {
1872 return Ok(());
1873 }
1874
1875 let message = self.get_conflicts_error_message(conflicts, components);
1876 match self.settings.ambiguity_detection {
1877 LogLevel::Ignore => Ok(()),
1878 LogLevel::Warn => {
1879 warn!("Schedule {schedule_label:?} has ambiguities.\n{}", message);
1880 Ok(())
1881 }
1882 LogLevel::Error => Err(ScheduleBuildError::Ambiguity(message)),
1883 }
1884 }
1885
1886 fn get_conflicts_error_message(
1887 &self,
1888 ambiguities: &[(NodeId, NodeId, Vec<ComponentId>)],
1889 components: &Components,
1890 ) -> String {
1891 let n_ambiguities = ambiguities.len();
1892
1893 let mut message = format!(
1894 "{n_ambiguities} pairs of systems with conflicting data access have indeterminate execution order. \
1895 Consider adding `before`, `after`, or `ambiguous_with` relationships between these:\n",
1896 );
1897
1898 for (name_a, name_b, conflicts) in self.conflicts_to_string(ambiguities, components) {
1899 writeln!(message, " -- {name_a} and {name_b}").unwrap();
1900
1901 if !conflicts.is_empty() {
1902 writeln!(message, " conflict on: {conflicts:?}").unwrap();
1903 } else {
1904 let world = core::any::type_name::<World>();
1906 writeln!(message, " conflict on: {world}").unwrap();
1907 }
1908 }
1909
1910 message
1911 }
1912
1913 pub fn conflicts_to_string<'a>(
1915 &'a self,
1916 ambiguities: &'a [(NodeId, NodeId, Vec<ComponentId>)],
1917 components: &'a Components,
1918 ) -> impl Iterator<Item = (String, String, Vec<Cow<'a, str>>)> + 'a {
1919 ambiguities
1920 .iter()
1921 .map(move |(system_a, system_b, conflicts)| {
1922 let name_a = self.get_node_name(system_a);
1923 let name_b = self.get_node_name(system_b);
1924
1925 debug_assert!(system_a.is_system(), "{name_a} is not a system.");
1926 debug_assert!(system_b.is_system(), "{name_b} is not a system.");
1927
1928 let conflict_names: Vec<_> = conflicts
1929 .iter()
1930 .map(|id| components.get_name(*id).unwrap())
1931 .collect();
1932
1933 (name_a, name_b, conflict_names)
1934 })
1935 }
1936
1937 fn traverse_sets_containing_node(&self, id: NodeId, f: &mut impl FnMut(NodeId) -> bool) {
1938 for (set_id, _) in self.hierarchy.graph.edges_directed(id, Incoming) {
1939 if f(set_id) {
1940 self.traverse_sets_containing_node(set_id, f);
1941 }
1942 }
1943 }
1944
1945 fn names_of_sets_containing_node(&self, id: &NodeId) -> Vec<String> {
1946 let mut sets = <HashSet<_>>::default();
1947 self.traverse_sets_containing_node(*id, &mut |set_id| {
1948 !self.system_sets[set_id.index()].is_system_type() && sets.insert(set_id)
1949 });
1950 let mut sets: Vec<_> = sets
1951 .into_iter()
1952 .map(|set_id| self.get_node_name(&set_id))
1953 .collect();
1954 sets.sort();
1955 sets
1956 }
1957}
1958
1959#[derive(Error, Debug)]
1961#[non_exhaustive]
1962pub enum ScheduleBuildError {
1963 #[error("System set `{0}` contains itself.")]
1965 HierarchyLoop(String),
1966 #[error("System set hierarchy contains cycle(s).\n{0}")]
1968 HierarchyCycle(String),
1969 #[error("System set hierarchy contains redundant edges.\n{0}")]
1973 HierarchyRedundancy(String),
1974 #[error("System set `{0}` depends on itself.")]
1976 DependencyLoop(String),
1977 #[error("System dependencies contain cycle(s).\n{0}")]
1979 DependencyCycle(String),
1980 #[error("`{0}` and `{1}` have both `in_set` and `before`-`after` relationships (these might be transitive). This combination is unsolvable as a system cannot run before or after a set it belongs to.")]
1982 CrossDependency(String, String),
1983 #[error("`{0}` and `{1}` have a `before`-`after` relationship (which may be transitive) but share systems.")]
1985 SetsHaveOrderButIntersect(String, String),
1986 #[error("Tried to order against `{0}` in a schedule that has more than one `{0}` instance. `{0}` is a `SystemTypeSet` and cannot be used for ordering if ambiguous. Use a different set without this restriction.")]
1988 SystemTypeSetAmbiguity(String),
1989 #[error("Systems with conflicting access have indeterminate run order.\n{0}")]
1993 Ambiguity(String),
1994 #[error("Systems in schedule have not been initialized.")]
1996 Uninitialized,
1997}
1998
1999#[derive(Debug, Clone, PartialEq)]
2001pub enum LogLevel {
2002 Ignore,
2004 Warn,
2006 Error,
2008}
2009
2010#[derive(Clone, Debug)]
2012pub struct ScheduleBuildSettings {
2013 pub ambiguity_detection: LogLevel,
2018 pub hierarchy_detection: LogLevel,
2024 pub auto_insert_apply_deferred: bool,
2034 pub use_shortnames: bool,
2038 pub report_sets: bool,
2042}
2043
2044impl Default for ScheduleBuildSettings {
2045 fn default() -> Self {
2046 Self::new()
2047 }
2048}
2049
2050impl ScheduleBuildSettings {
2051 pub const fn new() -> Self {
2054 Self {
2055 ambiguity_detection: LogLevel::Ignore,
2056 hierarchy_detection: LogLevel::Warn,
2057 auto_insert_apply_deferred: true,
2058 use_shortnames: true,
2059 report_sets: true,
2060 }
2061 }
2062}
2063
2064#[derive(Error, Debug)]
2067#[error("executable schedule has not been built")]
2068pub struct ScheduleNotInitialized;
2069
2070#[cfg(test)]
2071mod tests {
2072 use bevy_ecs_macros::ScheduleLabel;
2073
2074 use crate::{
2075 prelude::{ApplyDeferred, Res, Resource},
2076 schedule::{
2077 tests::ResMut, IntoScheduleConfigs, Schedule, ScheduleBuildSettings, SystemSet,
2078 },
2079 system::Commands,
2080 world::World,
2081 };
2082
2083 use super::Schedules;
2084
2085 #[derive(Resource)]
2086 struct Resource1;
2087
2088 #[derive(Resource)]
2089 struct Resource2;
2090
2091 #[test]
2092 fn unchanged_auto_insert_apply_deferred_has_no_effect() {
2093 use alloc::{vec, vec::Vec};
2094
2095 #[derive(PartialEq, Debug)]
2096 enum Entry {
2097 System(usize),
2098 SyncPoint(usize),
2099 }
2100
2101 #[derive(Resource, Default)]
2102 struct Log(Vec<Entry>);
2103
2104 fn system<const N: usize>(mut res: ResMut<Log>, mut commands: Commands) {
2105 res.0.push(Entry::System(N));
2106 commands
2107 .queue(|world: &mut World| world.resource_mut::<Log>().0.push(Entry::SyncPoint(N)));
2108 }
2109
2110 let mut world = World::default();
2111 world.init_resource::<Log>();
2112 let mut schedule = Schedule::default();
2113 schedule.add_systems((system::<1>, system::<2>).chain_ignore_deferred());
2114 schedule.set_build_settings(ScheduleBuildSettings {
2115 auto_insert_apply_deferred: true,
2116 ..Default::default()
2117 });
2118 schedule.run(&mut world);
2119 let actual = world.remove_resource::<Log>().unwrap().0;
2120
2121 let expected = vec![
2122 Entry::System(1),
2123 Entry::System(2),
2124 Entry::SyncPoint(1),
2125 Entry::SyncPoint(2),
2126 ];
2127
2128 assert_eq!(actual, expected);
2129 }
2130
2131 #[test]
2133 fn ambiguous_with_not_breaking_run_conditions() {
2134 #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
2135 struct Set;
2136
2137 let mut world = World::new();
2138 let mut schedule = Schedule::default();
2139
2140 let system: fn() = || {
2141 panic!("This system must not run");
2142 };
2143
2144 schedule.configure_sets(Set.run_if(|| false));
2145 schedule.add_systems(system.ambiguous_with(|| ()).in_set(Set));
2146 schedule.run(&mut world);
2147 }
2148
2149 #[test]
2150 fn inserts_a_sync_point() {
2151 let mut schedule = Schedule::default();
2152 let mut world = World::default();
2153 schedule.add_systems(
2154 (
2155 |mut commands: Commands| commands.insert_resource(Resource1),
2156 |_: Res<Resource1>| {},
2157 )
2158 .chain(),
2159 );
2160 schedule.run(&mut world);
2161
2162 assert_eq!(schedule.executable.systems.len(), 3);
2164 }
2165
2166 #[test]
2167 fn explicit_sync_point_used_as_auto_sync_point() {
2168 let mut schedule = Schedule::default();
2169 let mut world = World::default();
2170 schedule.add_systems(
2171 (
2172 |mut commands: Commands| commands.insert_resource(Resource1),
2173 |_: Res<Resource1>| {},
2174 )
2175 .chain(),
2176 );
2177 schedule.add_systems((|| {}, ApplyDeferred, || {}).chain());
2178 schedule.run(&mut world);
2179
2180 assert_eq!(schedule.executable.systems.len(), 5);
2182 }
2183
2184 #[test]
2185 fn conditional_explicit_sync_point_not_used_as_auto_sync_point() {
2186 let mut schedule = Schedule::default();
2187 let mut world = World::default();
2188 schedule.add_systems(
2189 (
2190 |mut commands: Commands| commands.insert_resource(Resource1),
2191 |_: Res<Resource1>| {},
2192 )
2193 .chain(),
2194 );
2195 schedule.add_systems((|| {}, ApplyDeferred.run_if(|| false), || {}).chain());
2196 schedule.run(&mut world);
2197
2198 assert_eq!(schedule.executable.systems.len(), 6);
2200 }
2201
2202 #[test]
2203 fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_chain() {
2204 let mut schedule = Schedule::default();
2205 let mut world = World::default();
2206 schedule.add_systems(
2207 (
2208 |mut commands: Commands| commands.insert_resource(Resource1),
2209 |_: Res<Resource1>| {},
2210 )
2211 .chain(),
2212 );
2213 schedule.add_systems((|| {}, ApplyDeferred, || {}).chain().run_if(|| false));
2214 schedule.run(&mut world);
2215
2216 assert_eq!(schedule.executable.systems.len(), 6);
2218 }
2219
2220 #[test]
2221 fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_system_set() {
2222 #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
2223 struct Set;
2224
2225 let mut schedule = Schedule::default();
2226 let mut world = World::default();
2227 schedule.configure_sets(Set.run_if(|| false));
2228 schedule.add_systems(
2229 (
2230 |mut commands: Commands| commands.insert_resource(Resource1),
2231 |_: Res<Resource1>| {},
2232 )
2233 .chain(),
2234 );
2235 schedule.add_systems((|| {}, ApplyDeferred.in_set(Set), || {}).chain());
2236 schedule.run(&mut world);
2237
2238 assert_eq!(schedule.executable.systems.len(), 6);
2240 }
2241
2242 #[test]
2243 fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_nested_system_set()
2244 {
2245 #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
2246 struct Set1;
2247 #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
2248 struct Set2;
2249
2250 let mut schedule = Schedule::default();
2251 let mut world = World::default();
2252 schedule.configure_sets(Set2.run_if(|| false));
2253 schedule.configure_sets(Set1.in_set(Set2));
2254 schedule.add_systems(
2255 (
2256 |mut commands: Commands| commands.insert_resource(Resource1),
2257 |_: Res<Resource1>| {},
2258 )
2259 .chain(),
2260 );
2261 schedule.add_systems((|| {}, ApplyDeferred, || {}).chain().in_set(Set1));
2262 schedule.run(&mut world);
2263
2264 assert_eq!(schedule.executable.systems.len(), 6);
2266 }
2267
2268 #[test]
2269 fn merges_sync_points_into_one() {
2270 let mut schedule = Schedule::default();
2271 let mut world = World::default();
2272 schedule.add_systems(
2274 (
2275 (
2276 |mut commands: Commands| commands.insert_resource(Resource1),
2277 |mut commands: Commands| commands.insert_resource(Resource2),
2278 ),
2279 |_: Res<Resource1>, _: Res<Resource2>| {},
2280 )
2281 .chain(),
2282 );
2283 schedule.run(&mut world);
2284
2285 assert_eq!(schedule.executable.systems.len(), 4);
2287
2288 schedule.add_systems(((
2290 (
2291 |mut commands: Commands| commands.insert_resource(Resource1),
2292 |mut commands: Commands| commands.insert_resource(Resource2),
2293 ),
2294 |_: Res<Resource1>, _: Res<Resource2>| {},
2295 )
2296 .chain(),));
2297 schedule.run(&mut world);
2298
2299 assert_eq!(schedule.executable.systems.len(), 7);
2300 }
2301
2302 #[test]
2303 fn adds_multiple_consecutive_syncs() {
2304 let mut schedule = Schedule::default();
2305 let mut world = World::default();
2306 schedule.add_systems(
2308 (
2309 |mut commands: Commands| commands.insert_resource(Resource1),
2310 |mut commands: Commands| commands.insert_resource(Resource2),
2311 |_: Res<Resource1>, _: Res<Resource2>| {},
2312 )
2313 .chain(),
2314 );
2315 schedule.run(&mut world);
2316
2317 assert_eq!(schedule.executable.systems.len(), 5);
2318 }
2319
2320 #[test]
2321 fn do_not_consider_ignore_deferred_before_exclusive_system() {
2322 let mut schedule = Schedule::default();
2323 let mut world = World::default();
2324 schedule.add_systems(
2326 (
2327 |_: Commands| {},
2328 |mut commands: Commands| commands.insert_resource(Resource1),
2330 |world: &mut World| assert!(world.contains_resource::<Resource1>()),
2332 |_: &mut World| {},
2334 |_: Commands| {},
2336 )
2337 .chain_ignore_deferred(),
2338 );
2339 schedule.run(&mut world);
2340
2341 assert_eq!(schedule.executable.systems.len(), 6); }
2343
2344 #[test]
2345 fn bubble_sync_point_through_ignore_deferred_node() {
2346 let mut schedule = Schedule::default();
2347 let mut world = World::default();
2348
2349 let insert_resource_config = (
2350 |mut commands: Commands| commands.insert_resource(Resource1),
2352 || {},
2354 )
2355 .chain_ignore_deferred();
2357
2358 schedule.add_systems(
2359 (
2360 insert_resource_config,
2361 |_: Res<Resource1>| {},
2363 )
2364 .chain(),
2366 );
2367
2368 schedule.run(&mut world);
2373
2374 assert_eq!(schedule.executable.systems.len(), 4); }
2376
2377 #[test]
2378 fn disable_auto_sync_points() {
2379 let mut schedule = Schedule::default();
2380 schedule.set_build_settings(ScheduleBuildSettings {
2381 auto_insert_apply_deferred: false,
2382 ..Default::default()
2383 });
2384 let mut world = World::default();
2385 schedule.add_systems(
2386 (
2387 |mut commands: Commands| commands.insert_resource(Resource1),
2388 |res: Option<Res<Resource1>>| assert!(res.is_none()),
2389 )
2390 .chain(),
2391 );
2392 schedule.run(&mut world);
2393
2394 assert_eq!(schedule.executable.systems.len(), 2);
2395 }
2396
2397 mod no_sync_edges {
2398 use super::*;
2399
2400 fn insert_resource(mut commands: Commands) {
2401 commands.insert_resource(Resource1);
2402 }
2403
2404 fn resource_does_not_exist(res: Option<Res<Resource1>>) {
2405 assert!(res.is_none());
2406 }
2407
2408 #[derive(SystemSet, Hash, PartialEq, Eq, Debug, Clone)]
2409 enum Sets {
2410 A,
2411 B,
2412 }
2413
2414 fn check_no_sync_edges(add_systems: impl FnOnce(&mut Schedule)) {
2415 let mut schedule = Schedule::default();
2416 let mut world = World::default();
2417 add_systems(&mut schedule);
2418
2419 schedule.run(&mut world);
2420
2421 assert_eq!(schedule.executable.systems.len(), 2);
2422 }
2423
2424 #[test]
2425 fn system_to_system_after() {
2426 check_no_sync_edges(|schedule| {
2427 schedule.add_systems((
2428 insert_resource,
2429 resource_does_not_exist.after_ignore_deferred(insert_resource),
2430 ));
2431 });
2432 }
2433
2434 #[test]
2435 fn system_to_system_before() {
2436 check_no_sync_edges(|schedule| {
2437 schedule.add_systems((
2438 insert_resource.before_ignore_deferred(resource_does_not_exist),
2439 resource_does_not_exist,
2440 ));
2441 });
2442 }
2443
2444 #[test]
2445 fn set_to_system_after() {
2446 check_no_sync_edges(|schedule| {
2447 schedule
2448 .add_systems((insert_resource, resource_does_not_exist.in_set(Sets::A)))
2449 .configure_sets(Sets::A.after_ignore_deferred(insert_resource));
2450 });
2451 }
2452
2453 #[test]
2454 fn set_to_system_before() {
2455 check_no_sync_edges(|schedule| {
2456 schedule
2457 .add_systems((insert_resource.in_set(Sets::A), resource_does_not_exist))
2458 .configure_sets(Sets::A.before_ignore_deferred(resource_does_not_exist));
2459 });
2460 }
2461
2462 #[test]
2463 fn set_to_set_after() {
2464 check_no_sync_edges(|schedule| {
2465 schedule
2466 .add_systems((
2467 insert_resource.in_set(Sets::A),
2468 resource_does_not_exist.in_set(Sets::B),
2469 ))
2470 .configure_sets(Sets::B.after_ignore_deferred(Sets::A));
2471 });
2472 }
2473
2474 #[test]
2475 fn set_to_set_before() {
2476 check_no_sync_edges(|schedule| {
2477 schedule
2478 .add_systems((
2479 insert_resource.in_set(Sets::A),
2480 resource_does_not_exist.in_set(Sets::B),
2481 ))
2482 .configure_sets(Sets::A.before_ignore_deferred(Sets::B));
2483 });
2484 }
2485 }
2486
2487 mod no_sync_chain {
2488 use super::*;
2489
2490 #[derive(Resource)]
2491 struct Ra;
2492
2493 #[derive(Resource)]
2494 struct Rb;
2495
2496 #[derive(Resource)]
2497 struct Rc;
2498
2499 fn run_schedule(expected_num_systems: usize, add_systems: impl FnOnce(&mut Schedule)) {
2500 let mut schedule = Schedule::default();
2501 let mut world = World::default();
2502 add_systems(&mut schedule);
2503
2504 schedule.run(&mut world);
2505
2506 assert_eq!(schedule.executable.systems.len(), expected_num_systems);
2507 }
2508
2509 #[test]
2510 fn only_chain_outside() {
2511 run_schedule(5, |schedule: &mut Schedule| {
2512 schedule.add_systems(
2513 (
2514 (
2515 |mut commands: Commands| commands.insert_resource(Ra),
2516 |mut commands: Commands| commands.insert_resource(Rb),
2517 ),
2518 (
2519 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2520 assert!(res_a.is_some());
2521 assert!(res_b.is_some());
2522 },
2523 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2524 assert!(res_a.is_some());
2525 assert!(res_b.is_some());
2526 },
2527 ),
2528 )
2529 .chain(),
2530 );
2531 });
2532
2533 run_schedule(4, |schedule: &mut Schedule| {
2534 schedule.add_systems(
2535 (
2536 (
2537 |mut commands: Commands| commands.insert_resource(Ra),
2538 |mut commands: Commands| commands.insert_resource(Rb),
2539 ),
2540 (
2541 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2542 assert!(res_a.is_none());
2543 assert!(res_b.is_none());
2544 },
2545 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2546 assert!(res_a.is_none());
2547 assert!(res_b.is_none());
2548 },
2549 ),
2550 )
2551 .chain_ignore_deferred(),
2552 );
2553 });
2554 }
2555
2556 #[test]
2557 fn chain_first() {
2558 run_schedule(6, |schedule: &mut Schedule| {
2559 schedule.add_systems(
2560 (
2561 (
2562 |mut commands: Commands| commands.insert_resource(Ra),
2563 |mut commands: Commands, res_a: Option<Res<Ra>>| {
2564 commands.insert_resource(Rb);
2565 assert!(res_a.is_some());
2566 },
2567 )
2568 .chain(),
2569 (
2570 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2571 assert!(res_a.is_some());
2572 assert!(res_b.is_some());
2573 },
2574 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2575 assert!(res_a.is_some());
2576 assert!(res_b.is_some());
2577 },
2578 ),
2579 )
2580 .chain(),
2581 );
2582 });
2583
2584 run_schedule(5, |schedule: &mut Schedule| {
2585 schedule.add_systems(
2586 (
2587 (
2588 |mut commands: Commands| commands.insert_resource(Ra),
2589 |mut commands: Commands, res_a: Option<Res<Ra>>| {
2590 commands.insert_resource(Rb);
2591 assert!(res_a.is_some());
2592 },
2593 )
2594 .chain(),
2595 (
2596 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2597 assert!(res_a.is_some());
2598 assert!(res_b.is_none());
2599 },
2600 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2601 assert!(res_a.is_some());
2602 assert!(res_b.is_none());
2603 },
2604 ),
2605 )
2606 .chain_ignore_deferred(),
2607 );
2608 });
2609 }
2610
2611 #[test]
2612 fn chain_second() {
2613 run_schedule(6, |schedule: &mut Schedule| {
2614 schedule.add_systems(
2615 (
2616 (
2617 |mut commands: Commands| commands.insert_resource(Ra),
2618 |mut commands: Commands| commands.insert_resource(Rb),
2619 ),
2620 (
2621 |mut commands: Commands,
2622 res_a: Option<Res<Ra>>,
2623 res_b: Option<Res<Rb>>| {
2624 commands.insert_resource(Rc);
2625 assert!(res_a.is_some());
2626 assert!(res_b.is_some());
2627 },
2628 |res_a: Option<Res<Ra>>,
2629 res_b: Option<Res<Rb>>,
2630 res_c: Option<Res<Rc>>| {
2631 assert!(res_a.is_some());
2632 assert!(res_b.is_some());
2633 assert!(res_c.is_some());
2634 },
2635 )
2636 .chain(),
2637 )
2638 .chain(),
2639 );
2640 });
2641
2642 run_schedule(5, |schedule: &mut Schedule| {
2643 schedule.add_systems(
2644 (
2645 (
2646 |mut commands: Commands| commands.insert_resource(Ra),
2647 |mut commands: Commands| commands.insert_resource(Rb),
2648 ),
2649 (
2650 |mut commands: Commands,
2651 res_a: Option<Res<Ra>>,
2652 res_b: Option<Res<Rb>>| {
2653 commands.insert_resource(Rc);
2654 assert!(res_a.is_none());
2655 assert!(res_b.is_none());
2656 },
2657 |res_a: Option<Res<Ra>>,
2658 res_b: Option<Res<Rb>>,
2659 res_c: Option<Res<Rc>>| {
2660 assert!(res_a.is_some());
2661 assert!(res_b.is_some());
2662 assert!(res_c.is_some());
2663 },
2664 )
2665 .chain(),
2666 )
2667 .chain_ignore_deferred(),
2668 );
2669 });
2670 }
2671
2672 #[test]
2673 fn chain_all() {
2674 run_schedule(7, |schedule: &mut Schedule| {
2675 schedule.add_systems(
2676 (
2677 (
2678 |mut commands: Commands| commands.insert_resource(Ra),
2679 |mut commands: Commands, res_a: Option<Res<Ra>>| {
2680 commands.insert_resource(Rb);
2681 assert!(res_a.is_some());
2682 },
2683 )
2684 .chain(),
2685 (
2686 |mut commands: Commands,
2687 res_a: Option<Res<Ra>>,
2688 res_b: Option<Res<Rb>>| {
2689 commands.insert_resource(Rc);
2690 assert!(res_a.is_some());
2691 assert!(res_b.is_some());
2692 },
2693 |res_a: Option<Res<Ra>>,
2694 res_b: Option<Res<Rb>>,
2695 res_c: Option<Res<Rc>>| {
2696 assert!(res_a.is_some());
2697 assert!(res_b.is_some());
2698 assert!(res_c.is_some());
2699 },
2700 )
2701 .chain(),
2702 )
2703 .chain(),
2704 );
2705 });
2706
2707 run_schedule(6, |schedule: &mut Schedule| {
2708 schedule.add_systems(
2709 (
2710 (
2711 |mut commands: Commands| commands.insert_resource(Ra),
2712 |mut commands: Commands, res_a: Option<Res<Ra>>| {
2713 commands.insert_resource(Rb);
2714 assert!(res_a.is_some());
2715 },
2716 )
2717 .chain(),
2718 (
2719 |mut commands: Commands,
2720 res_a: Option<Res<Ra>>,
2721 res_b: Option<Res<Rb>>| {
2722 commands.insert_resource(Rc);
2723 assert!(res_a.is_some());
2724 assert!(res_b.is_none());
2725 },
2726 |res_a: Option<Res<Ra>>,
2727 res_b: Option<Res<Rb>>,
2728 res_c: Option<Res<Rc>>| {
2729 assert!(res_a.is_some());
2730 assert!(res_b.is_some());
2731 assert!(res_c.is_some());
2732 },
2733 )
2734 .chain(),
2735 )
2736 .chain_ignore_deferred(),
2737 );
2738 });
2739 }
2740 }
2741
2742 #[derive(ScheduleLabel, Hash, Debug, Clone, PartialEq, Eq)]
2743 struct TestSchedule;
2744
2745 #[derive(Resource)]
2746 struct CheckSystemRan(usize);
2747
2748 #[test]
2749 fn add_systems_to_existing_schedule() {
2750 let mut schedules = Schedules::default();
2751 let schedule = Schedule::new(TestSchedule);
2752
2753 schedules.insert(schedule);
2754 schedules.add_systems(TestSchedule, |mut ran: ResMut<CheckSystemRan>| ran.0 += 1);
2755
2756 let mut world = World::new();
2757
2758 world.insert_resource(CheckSystemRan(0));
2759 world.insert_resource(schedules);
2760 world.run_schedule(TestSchedule);
2761
2762 let value = world
2763 .get_resource::<CheckSystemRan>()
2764 .expect("CheckSystemRan Resource Should Exist");
2765 assert_eq!(value.0, 1);
2766 }
2767
2768 #[test]
2769 fn add_systems_to_non_existing_schedule() {
2770 let mut schedules = Schedules::default();
2771
2772 schedules.add_systems(TestSchedule, |mut ran: ResMut<CheckSystemRan>| ran.0 += 1);
2773
2774 let mut world = World::new();
2775
2776 world.insert_resource(CheckSystemRan(0));
2777 world.insert_resource(schedules);
2778 world.run_schedule(TestSchedule);
2779
2780 let value = world
2781 .get_resource::<CheckSystemRan>()
2782 .expect("CheckSystemRan Resource Should Exist");
2783 assert_eq!(value.0, 1);
2784 }
2785
2786 #[derive(SystemSet, Debug, Hash, Clone, PartialEq, Eq)]
2787 enum TestSet {
2788 First,
2789 Second,
2790 }
2791
2792 #[test]
2793 fn configure_set_on_existing_schedule() {
2794 let mut schedules = Schedules::default();
2795 let schedule = Schedule::new(TestSchedule);
2796
2797 schedules.insert(schedule);
2798
2799 schedules.configure_sets(TestSchedule, (TestSet::First, TestSet::Second).chain());
2800 schedules.add_systems(
2801 TestSchedule,
2802 (|mut ran: ResMut<CheckSystemRan>| {
2803 assert_eq!(ran.0, 0);
2804 ran.0 += 1;
2805 })
2806 .in_set(TestSet::First),
2807 );
2808
2809 schedules.add_systems(
2810 TestSchedule,
2811 (|mut ran: ResMut<CheckSystemRan>| {
2812 assert_eq!(ran.0, 1);
2813 ran.0 += 1;
2814 })
2815 .in_set(TestSet::Second),
2816 );
2817
2818 let mut world = World::new();
2819
2820 world.insert_resource(CheckSystemRan(0));
2821 world.insert_resource(schedules);
2822 world.run_schedule(TestSchedule);
2823
2824 let value = world
2825 .get_resource::<CheckSystemRan>()
2826 .expect("CheckSystemRan Resource Should Exist");
2827 assert_eq!(value.0, 2);
2828 }
2829
2830 #[test]
2831 fn configure_set_on_new_schedule() {
2832 let mut schedules = Schedules::default();
2833
2834 schedules.configure_sets(TestSchedule, (TestSet::First, TestSet::Second).chain());
2835 schedules.add_systems(
2836 TestSchedule,
2837 (|mut ran: ResMut<CheckSystemRan>| {
2838 assert_eq!(ran.0, 0);
2839 ran.0 += 1;
2840 })
2841 .in_set(TestSet::First),
2842 );
2843
2844 schedules.add_systems(
2845 TestSchedule,
2846 (|mut ran: ResMut<CheckSystemRan>| {
2847 assert_eq!(ran.0, 1);
2848 ran.0 += 1;
2849 })
2850 .in_set(TestSet::Second),
2851 );
2852
2853 let mut world = World::new();
2854
2855 world.insert_resource(CheckSystemRan(0));
2856 world.insert_resource(schedules);
2857 world.run_schedule(TestSchedule);
2858
2859 let value = world
2860 .get_resource::<CheckSystemRan>()
2861 .expect("CheckSystemRan Resource Should Exist");
2862 assert_eq!(value.0, 2);
2863 }
2864}