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 {
398 if settings.auto_insert_apply_deferred {
399 self.add_build_pass(passes::AutoInsertApplyDeferredPass::default());
400 } else {
401 self.remove_build_pass::<passes::AutoInsertApplyDeferredPass>();
402 }
403 self.graph.settings = settings;
404 self
405 }
406
407 pub fn get_build_settings(&self) -> ScheduleBuildSettings {
409 self.graph.settings.clone()
410 }
411
412 pub fn get_executor_kind(&self) -> ExecutorKind {
414 self.executor.kind()
415 }
416
417 pub fn set_executor_kind(&mut self, executor: ExecutorKind) -> &mut Self {
419 if executor != self.executor.kind() {
420 self.executor = make_executor(executor);
421 self.executor_initialized = false;
422 }
423 self
424 }
425
426 pub fn set_apply_final_deferred(&mut self, apply_final_deferred: bool) -> &mut Self {
431 self.executor.set_apply_final_deferred(apply_final_deferred);
432 self
433 }
434
435 pub fn run(&mut self, world: &mut World) {
437 #[cfg(feature = "trace")]
438 let _span = info_span!("schedule", name = ?self.label).entered();
439
440 world.check_change_ticks();
441 self.initialize(world)
442 .unwrap_or_else(|e| panic!("Error when initializing schedule {:?}: {e}", self.label));
443
444 let error_handler = default_error_handler();
445
446 #[cfg(not(feature = "bevy_debug_stepping"))]
447 self.executor
448 .run(&mut self.executable, world, None, error_handler);
449
450 #[cfg(feature = "bevy_debug_stepping")]
451 {
452 let skip_systems = match world.get_resource_mut::<Stepping>() {
453 None => None,
454 Some(mut stepping) => stepping.skipped_systems(self),
455 };
456
457 self.executor.run(
458 &mut self.executable,
459 world,
460 skip_systems.as_ref(),
461 error_handler,
462 );
463 }
464 }
465
466 pub fn initialize(&mut self, world: &mut World) -> Result<(), ScheduleBuildError> {
471 if self.graph.changed {
472 self.graph.initialize(world);
473 let ignored_ambiguities = world
474 .get_resource_or_init::<Schedules>()
475 .ignored_scheduling_ambiguities
476 .clone();
477 self.graph.update_schedule(
478 world,
479 &mut self.executable,
480 &ignored_ambiguities,
481 self.label,
482 )?;
483 self.graph.changed = false;
484 self.executor_initialized = false;
485 }
486
487 if !self.executor_initialized {
488 self.executor.init(&self.executable);
489 self.executor_initialized = true;
490 }
491
492 Ok(())
493 }
494
495 pub fn graph(&self) -> &ScheduleGraph {
497 &self.graph
498 }
499
500 pub fn graph_mut(&mut self) -> &mut ScheduleGraph {
502 &mut self.graph
503 }
504
505 pub(crate) fn executable(&self) -> &SystemSchedule {
507 &self.executable
508 }
509
510 pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) {
514 for system in &mut self.executable.systems {
515 if !is_apply_deferred(system) {
516 system.check_change_tick(change_tick);
517 }
518 }
519
520 for conditions in &mut self.executable.system_conditions {
521 for system in conditions {
522 system.check_change_tick(change_tick);
523 }
524 }
525
526 for conditions in &mut self.executable.set_conditions {
527 for system in conditions {
528 system.check_change_tick(change_tick);
529 }
530 }
531 }
532
533 pub fn apply_deferred(&mut self, world: &mut World) {
542 for system in &mut self.executable.systems {
543 system.apply_deferred(world);
544 }
545 }
546
547 pub fn systems(
552 &self,
553 ) -> Result<impl Iterator<Item = (NodeId, &ScheduleSystem)> + Sized, ScheduleNotInitialized>
554 {
555 if !self.executor_initialized {
556 return Err(ScheduleNotInitialized);
557 }
558
559 let iter = self
560 .executable
561 .system_ids
562 .iter()
563 .zip(&self.executable.systems)
564 .map(|(node_id, system)| (*node_id, system));
565
566 Ok(iter)
567 }
568
569 pub fn systems_len(&self) -> usize {
571 if !self.executor_initialized {
572 self.graph.systems.len()
573 } else {
574 self.executable.systems.len()
575 }
576 }
577}
578
579#[derive(Default)]
581pub struct Dag {
582 graph: DiGraph,
584 topsort: Vec<NodeId>,
586}
587
588impl Dag {
589 fn new() -> Self {
590 Self {
591 graph: DiGraph::default(),
592 topsort: Vec::new(),
593 }
594 }
595
596 pub fn graph(&self) -> &DiGraph {
598 &self.graph
599 }
600
601 pub fn cached_topsort(&self) -> &[NodeId] {
605 &self.topsort
606 }
607}
608
609struct SystemSetNode {
611 inner: InternedSystemSet,
612}
613
614impl SystemSetNode {
615 pub fn new(set: InternedSystemSet) -> Self {
616 Self { inner: set }
617 }
618
619 pub fn name(&self) -> String {
620 format!("{:?}", &self.inner)
621 }
622
623 pub fn is_system_type(&self) -> bool {
624 self.inner.system_type().is_some()
625 }
626
627 pub fn is_anonymous(&self) -> bool {
628 self.inner.is_anonymous()
629 }
630}
631
632pub struct SystemNode {
634 inner: Option<ScheduleSystem>,
635}
636
637impl SystemNode {
638 pub fn new(system: ScheduleSystem) -> Self {
640 Self {
641 inner: Some(system),
642 }
643 }
644
645 pub fn get(&self) -> Option<&ScheduleSystem> {
647 self.inner.as_ref()
648 }
649
650 pub fn get_mut(&mut self) -> Option<&mut ScheduleSystem> {
652 self.inner.as_mut()
653 }
654}
655
656#[derive(Default)]
661pub struct ScheduleGraph {
662 pub systems: Vec<SystemNode>,
664 pub system_conditions: Vec<Vec<BoxedCondition>>,
666 system_sets: Vec<SystemSetNode>,
668 system_set_conditions: Vec<Vec<BoxedCondition>>,
670 system_set_ids: HashMap<InternedSystemSet, NodeId>,
672 uninit: Vec<(NodeId, usize)>,
675 hierarchy: Dag,
677 dependency: Dag,
679 ambiguous_with: UnGraph,
680 pub ambiguous_with_all: HashSet<NodeId>,
682 conflicting_systems: Vec<(NodeId, NodeId, Vec<ComponentId>)>,
683 anonymous_sets: usize,
684 changed: bool,
685 settings: ScheduleBuildSettings,
686
687 passes: BTreeMap<TypeId, Box<dyn ScheduleBuildPassObj>>,
688}
689
690impl ScheduleGraph {
691 pub fn new() -> Self {
693 Self {
694 systems: Vec::new(),
695 system_conditions: Vec::new(),
696 system_sets: Vec::new(),
697 system_set_conditions: Vec::new(),
698 system_set_ids: HashMap::default(),
699 uninit: Vec::new(),
700 hierarchy: Dag::new(),
701 dependency: Dag::new(),
702 ambiguous_with: UnGraph::default(),
703 ambiguous_with_all: HashSet::default(),
704 conflicting_systems: Vec::new(),
705 anonymous_sets: 0,
706 changed: false,
707 settings: default(),
708 passes: default(),
709 }
710 }
711
712 pub fn get_system_at(&self, id: NodeId) -> Option<&ScheduleSystem> {
714 if !id.is_system() {
715 return None;
716 }
717 self.systems
718 .get(id.index())
719 .and_then(|system| system.inner.as_ref())
720 }
721
722 pub fn contains_set(&self, set: impl SystemSet) -> bool {
724 self.system_set_ids.contains_key(&set.intern())
725 }
726
727 #[track_caller]
731 pub fn system_at(&self, id: NodeId) -> &ScheduleSystem {
732 self.get_system_at(id)
733 .ok_or_else(|| format!("system with id {id:?} does not exist in this Schedule"))
734 .unwrap()
735 }
736
737 pub fn get_set_at(&self, id: NodeId) -> Option<&dyn SystemSet> {
739 if !id.is_set() {
740 return None;
741 }
742 self.system_sets.get(id.index()).map(|set| &*set.inner)
743 }
744
745 #[track_caller]
749 pub fn set_at(&self, id: NodeId) -> &dyn SystemSet {
750 self.get_set_at(id)
751 .ok_or_else(|| format!("set with id {id:?} does not exist in this Schedule"))
752 .unwrap()
753 }
754
755 pub fn get_set_conditions_at(&self, id: NodeId) -> Option<&[BoxedCondition]> {
757 if !id.is_set() {
758 return None;
759 }
760 self.system_set_conditions
761 .get(id.index())
762 .map(Vec::as_slice)
763 }
764
765 #[track_caller]
769 pub fn set_conditions_at(&self, id: NodeId) -> &[BoxedCondition] {
770 self.get_set_conditions_at(id)
771 .ok_or_else(|| format!("set with id {id:?} does not exist in this Schedule"))
772 .unwrap()
773 }
774
775 pub fn systems(&self) -> impl Iterator<Item = (NodeId, &ScheduleSystem, &[BoxedCondition])> {
777 self.systems
778 .iter()
779 .zip(self.system_conditions.iter())
780 .enumerate()
781 .filter_map(|(i, (system_node, condition))| {
782 let system = system_node.inner.as_ref()?;
783 Some((NodeId::System(i), system, condition.as_slice()))
784 })
785 }
786
787 pub fn system_sets(&self) -> impl Iterator<Item = (NodeId, &dyn SystemSet, &[BoxedCondition])> {
790 self.system_set_ids.iter().map(|(_, &node_id)| {
791 let set_node = &self.system_sets[node_id.index()];
792 let set = &*set_node.inner;
793 let conditions = self.system_set_conditions[node_id.index()].as_slice();
794 (node_id, set, conditions)
795 })
796 }
797
798 pub fn hierarchy(&self) -> &Dag {
803 &self.hierarchy
804 }
805
806 pub fn dependency(&self) -> &Dag {
811 &self.dependency
812 }
813
814 pub fn conflicting_systems(&self) -> &[(NodeId, NodeId, Vec<ComponentId>)] {
819 &self.conflicting_systems
820 }
821
822 fn process_config<T: ProcessScheduleConfig + Schedulable>(
823 &mut self,
824 config: ScheduleConfig<T>,
825 collect_nodes: bool,
826 ) -> ProcessConfigsResult {
827 ProcessConfigsResult {
828 densely_chained: true,
829 nodes: collect_nodes
830 .then_some(T::process_config(self, config))
831 .into_iter()
832 .collect(),
833 }
834 }
835
836 fn apply_collective_conditions<
837 T: ProcessScheduleConfig + Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>,
838 >(
839 &mut self,
840 configs: &mut [ScheduleConfigs<T>],
841 collective_conditions: Vec<BoxedCondition>,
842 ) {
843 if !collective_conditions.is_empty() {
844 if let [config] = configs {
845 for condition in collective_conditions {
846 config.run_if_dyn(condition);
847 }
848 } else {
849 let set = self.create_anonymous_set();
850 for config in configs.iter_mut() {
851 config.in_set_inner(set.intern());
852 }
853 let mut set_config = InternedSystemSet::into_config(set.intern());
854 set_config.conditions.extend(collective_conditions);
855 self.configure_set_inner(set_config).unwrap();
856 }
857 }
858 }
859
860 #[track_caller]
869 fn process_configs<
870 T: ProcessScheduleConfig + Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>,
871 >(
872 &mut self,
873 configs: ScheduleConfigs<T>,
874 collect_nodes: bool,
875 ) -> ProcessConfigsResult {
876 match configs {
877 ScheduleConfigs::ScheduleConfig(config) => self.process_config(config, collect_nodes),
878 ScheduleConfigs::Configs {
879 metadata,
880 mut configs,
881 collective_conditions,
882 } => {
883 self.apply_collective_conditions(&mut configs, collective_conditions);
884
885 let is_chained = matches!(metadata, Chain::Chained(_));
886
887 let mut densely_chained = is_chained || configs.len() == 1;
891 let mut configs = configs.into_iter();
892 let mut nodes = Vec::new();
893
894 let Some(first) = configs.next() else {
895 return ProcessConfigsResult {
896 nodes: Vec::new(),
897 densely_chained,
898 };
899 };
900 let mut previous_result = self.process_configs(first, collect_nodes || is_chained);
901 densely_chained &= previous_result.densely_chained;
902
903 for current in configs {
904 let current_result = self.process_configs(current, collect_nodes || is_chained);
905 densely_chained &= current_result.densely_chained;
906
907 if let Chain::Chained(chain_options) = &metadata {
908 let current_nodes = if current_result.densely_chained {
910 ¤t_result.nodes[..1]
911 } else {
912 ¤t_result.nodes
913 };
914 let previous_nodes = if previous_result.densely_chained {
916 &previous_result.nodes[previous_result.nodes.len() - 1..]
917 } else {
918 &previous_result.nodes
919 };
920
921 for previous_node in previous_nodes {
922 for current_node in current_nodes {
923 self.dependency
924 .graph
925 .add_edge(*previous_node, *current_node);
926
927 for pass in self.passes.values_mut() {
928 pass.add_dependency(
929 *previous_node,
930 *current_node,
931 chain_options,
932 );
933 }
934 }
935 }
936 }
937 if collect_nodes {
938 nodes.append(&mut previous_result.nodes);
939 }
940
941 previous_result = current_result;
942 }
943 if collect_nodes {
944 nodes.append(&mut previous_result.nodes);
945 }
946
947 ProcessConfigsResult {
948 nodes,
949 densely_chained,
950 }
951 }
952 }
953 }
954
955 fn add_system_inner(
957 &mut self,
958 config: ScheduleConfig<ScheduleSystem>,
959 ) -> Result<NodeId, ScheduleBuildError> {
960 let id = NodeId::System(self.systems.len());
961
962 self.update_graphs(id, config.metadata)?;
964
965 self.uninit.push((id, 0));
967 self.systems.push(SystemNode::new(config.node));
968 self.system_conditions.push(config.conditions);
969
970 Ok(id)
971 }
972
973 #[track_caller]
974 fn configure_sets<M>(&mut self, sets: impl IntoScheduleConfigs<InternedSystemSet, M>) {
975 self.process_configs(sets.into_configs(), false);
976 }
977
978 fn configure_set_inner(
980 &mut self,
981 set: ScheduleConfig<InternedSystemSet>,
982 ) -> Result<NodeId, ScheduleBuildError> {
983 let ScheduleConfig {
984 node: set,
985 metadata,
986 mut conditions,
987 } = set;
988
989 let id = match self.system_set_ids.get(&set) {
990 Some(&id) => id,
991 None => self.add_set(set),
992 };
993
994 self.update_graphs(id, metadata)?;
996
997 let system_set_conditions = &mut self.system_set_conditions[id.index()];
999 self.uninit.push((id, system_set_conditions.len()));
1000 system_set_conditions.append(&mut conditions);
1001
1002 Ok(id)
1003 }
1004
1005 fn add_set(&mut self, set: InternedSystemSet) -> NodeId {
1006 let id = NodeId::Set(self.system_sets.len());
1007 self.system_sets.push(SystemSetNode::new(set));
1008 self.system_set_conditions.push(Vec::new());
1009 self.system_set_ids.insert(set, id);
1010 id
1011 }
1012
1013 fn check_hierarchy_set(
1016 &mut self,
1017 id: &NodeId,
1018 set: InternedSystemSet,
1019 ) -> Result<(), ScheduleBuildError> {
1020 match self.system_set_ids.get(&set) {
1021 Some(set_id) => {
1022 if id == set_id {
1023 return Err(ScheduleBuildError::HierarchyLoop(self.get_node_name(id)));
1024 }
1025 }
1026 None => {
1027 self.add_set(set);
1028 }
1029 }
1030
1031 Ok(())
1032 }
1033
1034 fn create_anonymous_set(&mut self) -> AnonymousSet {
1035 let id = self.anonymous_sets;
1036 self.anonymous_sets += 1;
1037 AnonymousSet::new(id)
1038 }
1039
1040 fn check_hierarchy_sets(
1043 &mut self,
1044 id: &NodeId,
1045 graph_info: &GraphInfo,
1046 ) -> Result<(), ScheduleBuildError> {
1047 for &set in &graph_info.hierarchy {
1048 self.check_hierarchy_set(id, set)?;
1049 }
1050
1051 Ok(())
1052 }
1053
1054 fn check_edges(
1057 &mut self,
1058 id: &NodeId,
1059 graph_info: &GraphInfo,
1060 ) -> Result<(), ScheduleBuildError> {
1061 for Dependency { set, .. } in &graph_info.dependencies {
1062 match self.system_set_ids.get(set) {
1063 Some(set_id) => {
1064 if id == set_id {
1065 return Err(ScheduleBuildError::DependencyLoop(self.get_node_name(id)));
1066 }
1067 }
1068 None => {
1069 self.add_set(*set);
1070 }
1071 }
1072 }
1073
1074 if let Ambiguity::IgnoreWithSet(ambiguous_with) = &graph_info.ambiguous_with {
1075 for set in ambiguous_with {
1076 if !self.system_set_ids.contains_key(set) {
1077 self.add_set(*set);
1078 }
1079 }
1080 }
1081
1082 Ok(())
1083 }
1084
1085 fn update_graphs(
1087 &mut self,
1088 id: NodeId,
1089 graph_info: GraphInfo,
1090 ) -> Result<(), ScheduleBuildError> {
1091 self.check_hierarchy_sets(&id, &graph_info)?;
1092 self.check_edges(&id, &graph_info)?;
1093 self.changed = true;
1094
1095 let GraphInfo {
1096 hierarchy: sets,
1097 dependencies,
1098 ambiguous_with,
1099 ..
1100 } = graph_info;
1101
1102 self.hierarchy.graph.add_node(id);
1103 self.dependency.graph.add_node(id);
1104
1105 for set in sets.into_iter().map(|set| self.system_set_ids[&set]) {
1106 self.hierarchy.graph.add_edge(set, id);
1107
1108 self.dependency.graph.add_node(set);
1110 }
1111
1112 for (kind, set, options) in dependencies
1113 .into_iter()
1114 .map(|Dependency { kind, set, options }| (kind, self.system_set_ids[&set], options))
1115 {
1116 let (lhs, rhs) = match kind {
1117 DependencyKind::Before => (id, set),
1118 DependencyKind::After => (set, id),
1119 };
1120 self.dependency.graph.add_edge(lhs, rhs);
1121 for pass in self.passes.values_mut() {
1122 pass.add_dependency(lhs, rhs, &options);
1123 }
1124
1125 self.hierarchy.graph.add_node(set);
1127 }
1128
1129 match ambiguous_with {
1130 Ambiguity::Check => (),
1131 Ambiguity::IgnoreWithSet(ambiguous_with) => {
1132 for set in ambiguous_with
1133 .into_iter()
1134 .map(|set| self.system_set_ids[&set])
1135 {
1136 self.ambiguous_with.add_edge(id, set);
1137 }
1138 }
1139 Ambiguity::IgnoreAll => {
1140 self.ambiguous_with_all.insert(id);
1141 }
1142 }
1143
1144 Ok(())
1145 }
1146
1147 pub fn initialize(&mut self, world: &mut World) {
1149 for (id, i) in self.uninit.drain(..) {
1150 match id {
1151 NodeId::System(index) => {
1152 self.systems[index].get_mut().unwrap().initialize(world);
1153 for condition in &mut self.system_conditions[index] {
1154 condition.initialize(world);
1155 }
1156 }
1157 NodeId::Set(index) => {
1158 for condition in self.system_set_conditions[index].iter_mut().skip(i) {
1159 condition.initialize(world);
1160 }
1161 }
1162 }
1163 }
1164 }
1165
1166 pub fn build_schedule(
1172 &mut self,
1173 world: &mut World,
1174 schedule_label: InternedScheduleLabel,
1175 ignored_ambiguities: &BTreeSet<ComponentId>,
1176 ) -> Result<SystemSchedule, ScheduleBuildError> {
1177 self.hierarchy.topsort =
1179 self.topsort_graph(&self.hierarchy.graph, ReportCycles::Hierarchy)?;
1180
1181 let hier_results = check_graph(&self.hierarchy.graph, &self.hierarchy.topsort);
1182 self.optionally_check_hierarchy_conflicts(&hier_results.transitive_edges, schedule_label)?;
1183
1184 self.hierarchy.graph = hier_results.transitive_reduction;
1186
1187 self.dependency.topsort =
1189 self.topsort_graph(&self.dependency.graph, ReportCycles::Dependency)?;
1190
1191 let dep_results = check_graph(&self.dependency.graph, &self.dependency.topsort);
1193 self.check_for_cross_dependencies(&dep_results, &hier_results.connected)?;
1194
1195 let (set_systems, set_system_bitsets) =
1198 self.map_sets_to_systems(&self.hierarchy.topsort, &self.hierarchy.graph);
1199 self.check_order_but_intersect(&dep_results.connected, &set_system_bitsets)?;
1200
1201 self.check_system_type_set_ambiguity(&set_systems)?;
1203
1204 let mut dependency_flattened = self.get_dependency_flattened(&set_systems);
1205
1206 let mut passes = core::mem::take(&mut self.passes);
1208 for pass in passes.values_mut() {
1209 pass.build(world, self, &mut dependency_flattened)?;
1210 }
1211 self.passes = passes;
1212
1213 let mut dependency_flattened_dag = Dag {
1215 topsort: self.topsort_graph(&dependency_flattened, ReportCycles::Dependency)?,
1216 graph: dependency_flattened,
1217 };
1218
1219 let flat_results = check_graph(
1220 &dependency_flattened_dag.graph,
1221 &dependency_flattened_dag.topsort,
1222 );
1223
1224 dependency_flattened_dag.graph = flat_results.transitive_reduction;
1226
1227 let ambiguous_with_flattened = self.get_ambiguous_with_flattened(&set_systems);
1229
1230 let conflicting_systems = self.get_conflicting_systems(
1232 &flat_results.disconnected,
1233 &ambiguous_with_flattened,
1234 ignored_ambiguities,
1235 );
1236 self.optionally_check_conflicts(&conflicting_systems, world.components(), schedule_label)?;
1237 self.conflicting_systems = conflicting_systems;
1238
1239 Ok(self.build_schedule_inner(dependency_flattened_dag, hier_results.reachable))
1241 }
1242
1243 fn map_sets_to_systems(
1247 &self,
1248 hierarchy_topsort: &[NodeId],
1249 hierarchy_graph: &DiGraph,
1250 ) -> (HashMap<NodeId, Vec<NodeId>>, HashMap<NodeId, FixedBitSet>) {
1251 let mut set_systems: HashMap<NodeId, Vec<NodeId>> =
1252 HashMap::with_capacity_and_hasher(self.system_sets.len(), Default::default());
1253 let mut set_system_bitsets =
1254 HashMap::with_capacity_and_hasher(self.system_sets.len(), Default::default());
1255 for &id in hierarchy_topsort.iter().rev() {
1256 if id.is_system() {
1257 continue;
1258 }
1259
1260 let mut systems = Vec::new();
1261 let mut system_bitset = FixedBitSet::with_capacity(self.systems.len());
1262
1263 for child in hierarchy_graph.neighbors_directed(id, Outgoing) {
1264 match child {
1265 NodeId::System(_) => {
1266 systems.push(child);
1267 system_bitset.insert(child.index());
1268 }
1269 NodeId::Set(_) => {
1270 let child_systems = set_systems.get(&child).unwrap();
1271 let child_system_bitset = set_system_bitsets.get(&child).unwrap();
1272 systems.extend_from_slice(child_systems);
1273 system_bitset.union_with(child_system_bitset);
1274 }
1275 }
1276 }
1277
1278 set_systems.insert(id, systems);
1279 set_system_bitsets.insert(id, system_bitset);
1280 }
1281 (set_systems, set_system_bitsets)
1282 }
1283
1284 fn get_dependency_flattened(&mut self, set_systems: &HashMap<NodeId, Vec<NodeId>>) -> DiGraph {
1285 let mut dependency_flattened = self.dependency.graph.clone();
1288 let mut temp = Vec::new();
1289 for (&set, systems) in set_systems {
1290 for pass in self.passes.values_mut() {
1291 pass.collapse_set(set, systems, &dependency_flattened, &mut temp);
1292 }
1293 if systems.is_empty() {
1294 for a in dependency_flattened.neighbors_directed(set, Incoming) {
1296 for b in dependency_flattened.neighbors_directed(set, Outgoing) {
1297 temp.push((a, b));
1298 }
1299 }
1300 } else {
1301 for a in dependency_flattened.neighbors_directed(set, Incoming) {
1302 for &sys in systems {
1303 temp.push((a, sys));
1304 }
1305 }
1306
1307 for b in dependency_flattened.neighbors_directed(set, Outgoing) {
1308 for &sys in systems {
1309 temp.push((sys, b));
1310 }
1311 }
1312 }
1313
1314 dependency_flattened.remove_node(set);
1315 for (a, b) in temp.drain(..) {
1316 dependency_flattened.add_edge(a, b);
1317 }
1318 }
1319
1320 dependency_flattened
1321 }
1322
1323 fn get_ambiguous_with_flattened(&self, set_systems: &HashMap<NodeId, Vec<NodeId>>) -> UnGraph {
1324 let mut ambiguous_with_flattened = UnGraph::default();
1325 for (lhs, rhs) in self.ambiguous_with.all_edges() {
1326 match (lhs, rhs) {
1327 (NodeId::System(_), NodeId::System(_)) => {
1328 ambiguous_with_flattened.add_edge(lhs, rhs);
1329 }
1330 (NodeId::Set(_), NodeId::System(_)) => {
1331 for &lhs_ in set_systems.get(&lhs).unwrap_or(&Vec::new()) {
1332 ambiguous_with_flattened.add_edge(lhs_, rhs);
1333 }
1334 }
1335 (NodeId::System(_), NodeId::Set(_)) => {
1336 for &rhs_ in set_systems.get(&rhs).unwrap_or(&Vec::new()) {
1337 ambiguous_with_flattened.add_edge(lhs, rhs_);
1338 }
1339 }
1340 (NodeId::Set(_), NodeId::Set(_)) => {
1341 for &lhs_ in set_systems.get(&lhs).unwrap_or(&Vec::new()) {
1342 for &rhs_ in set_systems.get(&rhs).unwrap_or(&vec![]) {
1343 ambiguous_with_flattened.add_edge(lhs_, rhs_);
1344 }
1345 }
1346 }
1347 }
1348 }
1349
1350 ambiguous_with_flattened
1351 }
1352
1353 fn get_conflicting_systems(
1354 &self,
1355 flat_results_disconnected: &Vec<(NodeId, NodeId)>,
1356 ambiguous_with_flattened: &UnGraph,
1357 ignored_ambiguities: &BTreeSet<ComponentId>,
1358 ) -> Vec<(NodeId, NodeId, Vec<ComponentId>)> {
1359 let mut conflicting_systems = Vec::new();
1360 for &(a, b) in flat_results_disconnected {
1361 if ambiguous_with_flattened.contains_edge(a, b)
1362 || self.ambiguous_with_all.contains(&a)
1363 || self.ambiguous_with_all.contains(&b)
1364 {
1365 continue;
1366 }
1367
1368 let system_a = self.systems[a.index()].get().unwrap();
1369 let system_b = self.systems[b.index()].get().unwrap();
1370 if system_a.is_exclusive() || system_b.is_exclusive() {
1371 conflicting_systems.push((a, b, Vec::new()));
1372 } else {
1373 let access_a = system_a.component_access();
1374 let access_b = system_b.component_access();
1375 if !access_a.is_compatible(access_b) {
1376 match access_a.get_conflicts(access_b) {
1377 AccessConflicts::Individual(conflicts) => {
1378 let conflicts: Vec<_> = conflicts
1379 .ones()
1380 .map(ComponentId::get_sparse_set_index)
1381 .filter(|id| !ignored_ambiguities.contains(id))
1382 .collect();
1383 if !conflicts.is_empty() {
1384 conflicting_systems.push((a, b, conflicts));
1385 }
1386 }
1387 AccessConflicts::All => {
1388 conflicting_systems.push((a, b, Vec::new()));
1391 }
1392 }
1393 }
1394 }
1395 }
1396
1397 conflicting_systems
1398 }
1399
1400 fn build_schedule_inner(
1401 &self,
1402 dependency_flattened_dag: Dag,
1403 hier_results_reachable: FixedBitSet,
1404 ) -> SystemSchedule {
1405 let dg_system_ids = dependency_flattened_dag.topsort.clone();
1406 let dg_system_idx_map = dg_system_ids
1407 .iter()
1408 .cloned()
1409 .enumerate()
1410 .map(|(i, id)| (id, i))
1411 .collect::<HashMap<_, _>>();
1412
1413 let hg_systems = self
1414 .hierarchy
1415 .topsort
1416 .iter()
1417 .cloned()
1418 .enumerate()
1419 .filter(|&(_i, id)| id.is_system())
1420 .collect::<Vec<_>>();
1421
1422 let (hg_set_with_conditions_idxs, hg_set_ids): (Vec<_>, Vec<_>) = self
1423 .hierarchy
1424 .topsort
1425 .iter()
1426 .cloned()
1427 .enumerate()
1428 .filter(|&(_i, id)| {
1429 id.is_set() && !self.system_set_conditions[id.index()].is_empty()
1432 })
1433 .unzip();
1434
1435 let sys_count = self.systems.len();
1436 let set_with_conditions_count = hg_set_ids.len();
1437 let hg_node_count = self.hierarchy.graph.node_count();
1438
1439 let mut system_dependencies = Vec::with_capacity(sys_count);
1442 let mut system_dependents = Vec::with_capacity(sys_count);
1443 for &sys_id in &dg_system_ids {
1444 let num_dependencies = dependency_flattened_dag
1445 .graph
1446 .neighbors_directed(sys_id, Incoming)
1447 .count();
1448
1449 let dependents = dependency_flattened_dag
1450 .graph
1451 .neighbors_directed(sys_id, Outgoing)
1452 .map(|dep_id| dg_system_idx_map[&dep_id])
1453 .collect::<Vec<_>>();
1454
1455 system_dependencies.push(num_dependencies);
1456 system_dependents.push(dependents);
1457 }
1458
1459 let mut systems_in_sets_with_conditions =
1462 vec![FixedBitSet::with_capacity(sys_count); set_with_conditions_count];
1463 for (i, &row) in hg_set_with_conditions_idxs.iter().enumerate() {
1464 let bitset = &mut systems_in_sets_with_conditions[i];
1465 for &(col, sys_id) in &hg_systems {
1466 let idx = dg_system_idx_map[&sys_id];
1467 let is_descendant = hier_results_reachable[index(row, col, hg_node_count)];
1468 bitset.set(idx, is_descendant);
1469 }
1470 }
1471
1472 let mut sets_with_conditions_of_systems =
1473 vec![FixedBitSet::with_capacity(set_with_conditions_count); sys_count];
1474 for &(col, sys_id) in &hg_systems {
1475 let i = dg_system_idx_map[&sys_id];
1476 let bitset = &mut sets_with_conditions_of_systems[i];
1477 for (idx, &row) in hg_set_with_conditions_idxs
1478 .iter()
1479 .enumerate()
1480 .take_while(|&(_idx, &row)| row < col)
1481 {
1482 let is_ancestor = hier_results_reachable[index(row, col, hg_node_count)];
1483 bitset.set(idx, is_ancestor);
1484 }
1485 }
1486
1487 SystemSchedule {
1488 systems: Vec::with_capacity(sys_count),
1489 system_conditions: Vec::with_capacity(sys_count),
1490 set_conditions: Vec::with_capacity(set_with_conditions_count),
1491 system_ids: dg_system_ids,
1492 set_ids: hg_set_ids,
1493 system_dependencies,
1494 system_dependents,
1495 sets_with_conditions_of_systems,
1496 systems_in_sets_with_conditions,
1497 }
1498 }
1499
1500 fn update_schedule(
1502 &mut self,
1503 world: &mut World,
1504 schedule: &mut SystemSchedule,
1505 ignored_ambiguities: &BTreeSet<ComponentId>,
1506 schedule_label: InternedScheduleLabel,
1507 ) -> Result<(), ScheduleBuildError> {
1508 if !self.uninit.is_empty() {
1509 return Err(ScheduleBuildError::Uninitialized);
1510 }
1511
1512 for ((id, system), conditions) in schedule
1514 .system_ids
1515 .drain(..)
1516 .zip(schedule.systems.drain(..))
1517 .zip(schedule.system_conditions.drain(..))
1518 {
1519 self.systems[id.index()].inner = Some(system);
1520 self.system_conditions[id.index()] = conditions;
1521 }
1522
1523 for (id, conditions) in schedule
1524 .set_ids
1525 .drain(..)
1526 .zip(schedule.set_conditions.drain(..))
1527 {
1528 self.system_set_conditions[id.index()] = conditions;
1529 }
1530
1531 *schedule = self.build_schedule(world, schedule_label, ignored_ambiguities)?;
1532
1533 for &id in &schedule.system_ids {
1535 let system = self.systems[id.index()].inner.take().unwrap();
1536 let conditions = core::mem::take(&mut self.system_conditions[id.index()]);
1537 schedule.systems.push(system);
1538 schedule.system_conditions.push(conditions);
1539 }
1540
1541 for &id in &schedule.set_ids {
1542 let conditions = core::mem::take(&mut self.system_set_conditions[id.index()]);
1543 schedule.set_conditions.push(conditions);
1544 }
1545
1546 Ok(())
1547 }
1548}
1549
1550struct ProcessConfigsResult {
1552 nodes: Vec<NodeId>,
1555 densely_chained: bool,
1559}
1560
1561trait ProcessScheduleConfig: Schedulable + Sized {
1563 fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig<Self>) -> NodeId;
1565}
1566
1567impl ProcessScheduleConfig for ScheduleSystem {
1568 fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig<Self>) -> NodeId {
1569 schedule_graph.add_system_inner(config).unwrap()
1570 }
1571}
1572
1573impl ProcessScheduleConfig for InternedSystemSet {
1574 fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig<Self>) -> NodeId {
1575 schedule_graph.configure_set_inner(config).unwrap()
1576 }
1577}
1578
1579pub enum ReportCycles {
1581 Hierarchy,
1583 Dependency,
1585}
1586
1587impl ScheduleGraph {
1589 fn get_node_name(&self, id: &NodeId) -> String {
1590 self.get_node_name_inner(id, self.settings.report_sets)
1591 }
1592
1593 #[inline]
1594 fn get_node_name_inner(&self, id: &NodeId, report_sets: bool) -> String {
1595 let name = match id {
1596 NodeId::System(_) => {
1597 let name = self.systems[id.index()].get().unwrap().name().to_string();
1598 if report_sets {
1599 let sets = self.names_of_sets_containing_node(id);
1600 if sets.is_empty() {
1601 name
1602 } else if sets.len() == 1 {
1603 format!("{name} (in set {})", sets[0])
1604 } else {
1605 format!("{name} (in sets {})", sets.join(", "))
1606 }
1607 } else {
1608 name
1609 }
1610 }
1611 NodeId::Set(_) => {
1612 let set = &self.system_sets[id.index()];
1613 if set.is_anonymous() {
1614 self.anonymous_set_name(id)
1615 } else {
1616 set.name()
1617 }
1618 }
1619 };
1620 if self.settings.use_shortnames {
1621 ShortName(&name).to_string()
1622 } else {
1623 name
1624 }
1625 }
1626
1627 fn anonymous_set_name(&self, id: &NodeId) -> String {
1628 format!(
1629 "({})",
1630 self.hierarchy
1631 .graph
1632 .edges_directed(*id, Outgoing)
1633 .map(|(_, member_id)| self.get_node_name_inner(&member_id, false))
1635 .reduce(|a, b| format!("{a}, {b}"))
1636 .unwrap_or_default()
1637 )
1638 }
1639
1640 fn get_node_kind(&self, id: &NodeId) -> &'static str {
1641 match id {
1642 NodeId::System(_) => "system",
1643 NodeId::Set(_) => "system set",
1644 }
1645 }
1646
1647 fn optionally_check_hierarchy_conflicts(
1650 &self,
1651 transitive_edges: &[(NodeId, NodeId)],
1652 schedule_label: InternedScheduleLabel,
1653 ) -> Result<(), ScheduleBuildError> {
1654 if self.settings.hierarchy_detection == LogLevel::Ignore || transitive_edges.is_empty() {
1655 return Ok(());
1656 }
1657
1658 let message = self.get_hierarchy_conflicts_error_message(transitive_edges);
1659 match self.settings.hierarchy_detection {
1660 LogLevel::Ignore => unreachable!(),
1661 LogLevel::Warn => {
1662 error!(
1663 "Schedule {schedule_label:?} has redundant edges:\n {}",
1664 message
1665 );
1666 Ok(())
1667 }
1668 LogLevel::Error => Err(ScheduleBuildError::HierarchyRedundancy(message)),
1669 }
1670 }
1671
1672 fn get_hierarchy_conflicts_error_message(
1673 &self,
1674 transitive_edges: &[(NodeId, NodeId)],
1675 ) -> String {
1676 let mut message = String::from("hierarchy contains redundant edge(s)");
1677 for (parent, child) in transitive_edges {
1678 writeln!(
1679 message,
1680 " -- {} `{}` cannot be child of set `{}`, longer path exists",
1681 self.get_node_kind(child),
1682 self.get_node_name(child),
1683 self.get_node_name(parent),
1684 )
1685 .unwrap();
1686 }
1687
1688 message
1689 }
1690
1691 pub fn topsort_graph(
1701 &self,
1702 graph: &DiGraph,
1703 report: ReportCycles,
1704 ) -> Result<Vec<NodeId>, ScheduleBuildError> {
1705 let mut top_sorted_nodes = Vec::with_capacity(graph.node_count());
1707 let mut sccs_with_cycles = Vec::new();
1708
1709 for scc in graph.iter_sccs() {
1710 top_sorted_nodes.extend_from_slice(&scc);
1714 if scc.len() > 1 {
1715 sccs_with_cycles.push(scc);
1716 }
1717 }
1718
1719 if sccs_with_cycles.is_empty() {
1720 top_sorted_nodes.reverse();
1722 Ok(top_sorted_nodes)
1723 } else {
1724 let mut cycles = Vec::new();
1725 for scc in &sccs_with_cycles {
1726 cycles.append(&mut simple_cycles_in_component(graph, scc));
1727 }
1728
1729 let error = match report {
1730 ReportCycles::Hierarchy => ScheduleBuildError::HierarchyCycle(
1731 self.get_hierarchy_cycles_error_message(&cycles),
1732 ),
1733 ReportCycles::Dependency => ScheduleBuildError::DependencyCycle(
1734 self.get_dependency_cycles_error_message(&cycles),
1735 ),
1736 };
1737
1738 Err(error)
1739 }
1740 }
1741
1742 fn get_hierarchy_cycles_error_message(&self, cycles: &[Vec<NodeId>]) -> String {
1744 let mut message = format!("schedule has {} in_set cycle(s):\n", cycles.len());
1745 for (i, cycle) in cycles.iter().enumerate() {
1746 let mut names = cycle.iter().map(|id| self.get_node_name(id));
1747 let first_name = names.next().unwrap();
1748 writeln!(
1749 message,
1750 "cycle {}: set `{first_name}` contains itself",
1751 i + 1,
1752 )
1753 .unwrap();
1754 writeln!(message, "set `{first_name}`").unwrap();
1755 for name in names.chain(core::iter::once(first_name)) {
1756 writeln!(message, " ... which contains set `{name}`").unwrap();
1757 }
1758 writeln!(message).unwrap();
1759 }
1760
1761 message
1762 }
1763
1764 fn get_dependency_cycles_error_message(&self, cycles: &[Vec<NodeId>]) -> String {
1766 let mut message = format!("schedule has {} before/after cycle(s):\n", cycles.len());
1767 for (i, cycle) in cycles.iter().enumerate() {
1768 let mut names = cycle
1769 .iter()
1770 .map(|id| (self.get_node_kind(id), self.get_node_name(id)));
1771 let (first_kind, first_name) = names.next().unwrap();
1772 writeln!(
1773 message,
1774 "cycle {}: {first_kind} `{first_name}` must run before itself",
1775 i + 1,
1776 )
1777 .unwrap();
1778 writeln!(message, "{first_kind} `{first_name}`").unwrap();
1779 for (kind, name) in names.chain(core::iter::once((first_kind, first_name))) {
1780 writeln!(message, " ... which must run before {kind} `{name}`").unwrap();
1781 }
1782 writeln!(message).unwrap();
1783 }
1784
1785 message
1786 }
1787
1788 fn check_for_cross_dependencies(
1789 &self,
1790 dep_results: &CheckGraphResults,
1791 hier_results_connected: &HashSet<(NodeId, NodeId)>,
1792 ) -> Result<(), ScheduleBuildError> {
1793 for &(a, b) in &dep_results.connected {
1794 if hier_results_connected.contains(&(a, b)) || hier_results_connected.contains(&(b, a))
1795 {
1796 let name_a = self.get_node_name(&a);
1797 let name_b = self.get_node_name(&b);
1798 return Err(ScheduleBuildError::CrossDependency(name_a, name_b));
1799 }
1800 }
1801
1802 Ok(())
1803 }
1804
1805 fn check_order_but_intersect(
1806 &self,
1807 dep_results_connected: &HashSet<(NodeId, NodeId)>,
1808 set_system_bitsets: &HashMap<NodeId, FixedBitSet>,
1809 ) -> Result<(), ScheduleBuildError> {
1810 for (a, b) in dep_results_connected {
1812 if !(a.is_set() && b.is_set()) {
1813 continue;
1814 }
1815
1816 let a_systems = set_system_bitsets.get(a).unwrap();
1817 let b_systems = set_system_bitsets.get(b).unwrap();
1818
1819 if !a_systems.is_disjoint(b_systems) {
1820 return Err(ScheduleBuildError::SetsHaveOrderButIntersect(
1821 self.get_node_name(a),
1822 self.get_node_name(b),
1823 ));
1824 }
1825 }
1826
1827 Ok(())
1828 }
1829
1830 fn check_system_type_set_ambiguity(
1831 &self,
1832 set_systems: &HashMap<NodeId, Vec<NodeId>>,
1833 ) -> Result<(), ScheduleBuildError> {
1834 for (&id, systems) in set_systems {
1835 let set = &self.system_sets[id.index()];
1836 if set.is_system_type() {
1837 let instances = systems.len();
1838 let ambiguous_with = self.ambiguous_with.edges(id);
1839 let before = self.dependency.graph.edges_directed(id, Incoming);
1840 let after = self.dependency.graph.edges_directed(id, Outgoing);
1841 let relations = before.count() + after.count() + ambiguous_with.count();
1842 if instances > 1 && relations > 0 {
1843 return Err(ScheduleBuildError::SystemTypeSetAmbiguity(
1844 self.get_node_name(&id),
1845 ));
1846 }
1847 }
1848 }
1849 Ok(())
1850 }
1851
1852 fn optionally_check_conflicts(
1854 &self,
1855 conflicts: &[(NodeId, NodeId, Vec<ComponentId>)],
1856 components: &Components,
1857 schedule_label: InternedScheduleLabel,
1858 ) -> Result<(), ScheduleBuildError> {
1859 if self.settings.ambiguity_detection == LogLevel::Ignore || conflicts.is_empty() {
1860 return Ok(());
1861 }
1862
1863 let message = self.get_conflicts_error_message(conflicts, components);
1864 match self.settings.ambiguity_detection {
1865 LogLevel::Ignore => Ok(()),
1866 LogLevel::Warn => {
1867 warn!("Schedule {schedule_label:?} has ambiguities.\n{}", message);
1868 Ok(())
1869 }
1870 LogLevel::Error => Err(ScheduleBuildError::Ambiguity(message)),
1871 }
1872 }
1873
1874 fn get_conflicts_error_message(
1875 &self,
1876 ambiguities: &[(NodeId, NodeId, Vec<ComponentId>)],
1877 components: &Components,
1878 ) -> String {
1879 let n_ambiguities = ambiguities.len();
1880
1881 let mut message = format!(
1882 "{n_ambiguities} pairs of systems with conflicting data access have indeterminate execution order. \
1883 Consider adding `before`, `after`, or `ambiguous_with` relationships between these:\n",
1884 );
1885
1886 for (name_a, name_b, conflicts) in self.conflicts_to_string(ambiguities, components) {
1887 writeln!(message, " -- {name_a} and {name_b}").unwrap();
1888
1889 if !conflicts.is_empty() {
1890 writeln!(message, " conflict on: {conflicts:?}").unwrap();
1891 } else {
1892 let world = core::any::type_name::<World>();
1894 writeln!(message, " conflict on: {world}").unwrap();
1895 }
1896 }
1897
1898 message
1899 }
1900
1901 pub fn conflicts_to_string<'a>(
1903 &'a self,
1904 ambiguities: &'a [(NodeId, NodeId, Vec<ComponentId>)],
1905 components: &'a Components,
1906 ) -> impl Iterator<Item = (String, String, Vec<Cow<'a, str>>)> + 'a {
1907 ambiguities
1908 .iter()
1909 .map(move |(system_a, system_b, conflicts)| {
1910 let name_a = self.get_node_name(system_a);
1911 let name_b = self.get_node_name(system_b);
1912
1913 debug_assert!(system_a.is_system(), "{name_a} is not a system.");
1914 debug_assert!(system_b.is_system(), "{name_b} is not a system.");
1915
1916 let conflict_names: Vec<_> = conflicts
1917 .iter()
1918 .map(|id| components.get_name(*id).unwrap())
1919 .collect();
1920
1921 (name_a, name_b, conflict_names)
1922 })
1923 }
1924
1925 fn traverse_sets_containing_node(&self, id: NodeId, f: &mut impl FnMut(NodeId) -> bool) {
1926 for (set_id, _) in self.hierarchy.graph.edges_directed(id, Incoming) {
1927 if f(set_id) {
1928 self.traverse_sets_containing_node(set_id, f);
1929 }
1930 }
1931 }
1932
1933 fn names_of_sets_containing_node(&self, id: &NodeId) -> Vec<String> {
1934 let mut sets = <HashSet<_>>::default();
1935 self.traverse_sets_containing_node(*id, &mut |set_id| {
1936 !self.system_sets[set_id.index()].is_system_type() && sets.insert(set_id)
1937 });
1938 let mut sets: Vec<_> = sets
1939 .into_iter()
1940 .map(|set_id| self.get_node_name(&set_id))
1941 .collect();
1942 sets.sort();
1943 sets
1944 }
1945}
1946
1947#[derive(Error, Debug)]
1949#[non_exhaustive]
1950pub enum ScheduleBuildError {
1951 #[error("System set `{0}` contains itself.")]
1953 HierarchyLoop(String),
1954 #[error("System set hierarchy contains cycle(s).\n{0}")]
1956 HierarchyCycle(String),
1957 #[error("System set hierarchy contains redundant edges.\n{0}")]
1961 HierarchyRedundancy(String),
1962 #[error("System set `{0}` depends on itself.")]
1964 DependencyLoop(String),
1965 #[error("System dependencies contain cycle(s).\n{0}")]
1967 DependencyCycle(String),
1968 #[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.")]
1970 CrossDependency(String, String),
1971 #[error("`{0}` and `{1}` have a `before`-`after` relationship (which may be transitive) but share systems.")]
1973 SetsHaveOrderButIntersect(String, String),
1974 #[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.")]
1976 SystemTypeSetAmbiguity(String),
1977 #[error("Systems with conflicting access have indeterminate run order.\n{0}")]
1981 Ambiguity(String),
1982 #[error("Systems in schedule have not been initialized.")]
1984 Uninitialized,
1985}
1986
1987#[derive(Debug, Clone, PartialEq)]
1989pub enum LogLevel {
1990 Ignore,
1992 Warn,
1994 Error,
1996}
1997
1998#[derive(Clone, Debug)]
2000pub struct ScheduleBuildSettings {
2001 pub ambiguity_detection: LogLevel,
2006 pub hierarchy_detection: LogLevel,
2012 pub auto_insert_apply_deferred: bool,
2022 pub use_shortnames: bool,
2026 pub report_sets: bool,
2030}
2031
2032impl Default for ScheduleBuildSettings {
2033 fn default() -> Self {
2034 Self::new()
2035 }
2036}
2037
2038impl ScheduleBuildSettings {
2039 pub const fn new() -> Self {
2042 Self {
2043 ambiguity_detection: LogLevel::Ignore,
2044 hierarchy_detection: LogLevel::Warn,
2045 auto_insert_apply_deferred: true,
2046 use_shortnames: true,
2047 report_sets: true,
2048 }
2049 }
2050}
2051
2052#[derive(Error, Debug)]
2055#[error("executable schedule has not been built")]
2056pub struct ScheduleNotInitialized;
2057
2058#[cfg(test)]
2059mod tests {
2060 use bevy_ecs_macros::ScheduleLabel;
2061
2062 use crate::{
2063 prelude::{ApplyDeferred, Res, Resource},
2064 schedule::{
2065 tests::ResMut, IntoScheduleConfigs, Schedule, ScheduleBuildSettings, SystemSet,
2066 },
2067 system::Commands,
2068 world::World,
2069 };
2070
2071 use super::Schedules;
2072
2073 #[derive(Resource)]
2074 struct Resource1;
2075
2076 #[derive(Resource)]
2077 struct Resource2;
2078
2079 #[test]
2081 fn ambiguous_with_not_breaking_run_conditions() {
2082 #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
2083 struct Set;
2084
2085 let mut world = World::new();
2086 let mut schedule = Schedule::default();
2087
2088 let system: fn() = || {
2089 panic!("This system must not run");
2090 };
2091
2092 schedule.configure_sets(Set.run_if(|| false));
2093 schedule.add_systems(system.ambiguous_with(|| ()).in_set(Set));
2094 schedule.run(&mut world);
2095 }
2096
2097 #[test]
2098 fn inserts_a_sync_point() {
2099 let mut schedule = Schedule::default();
2100 let mut world = World::default();
2101 schedule.add_systems(
2102 (
2103 |mut commands: Commands| commands.insert_resource(Resource1),
2104 |_: Res<Resource1>| {},
2105 )
2106 .chain(),
2107 );
2108 schedule.run(&mut world);
2109
2110 assert_eq!(schedule.executable.systems.len(), 3);
2112 }
2113
2114 #[test]
2115 fn explicit_sync_point_used_as_auto_sync_point() {
2116 let mut schedule = Schedule::default();
2117 let mut world = World::default();
2118 schedule.add_systems(
2119 (
2120 |mut commands: Commands| commands.insert_resource(Resource1),
2121 |_: Res<Resource1>| {},
2122 )
2123 .chain(),
2124 );
2125 schedule.add_systems((|| {}, ApplyDeferred, || {}).chain());
2126 schedule.run(&mut world);
2127
2128 assert_eq!(schedule.executable.systems.len(), 5);
2130 }
2131
2132 #[test]
2133 fn conditional_explicit_sync_point_not_used_as_auto_sync_point() {
2134 let mut schedule = Schedule::default();
2135 let mut world = World::default();
2136 schedule.add_systems(
2137 (
2138 |mut commands: Commands| commands.insert_resource(Resource1),
2139 |_: Res<Resource1>| {},
2140 )
2141 .chain(),
2142 );
2143 schedule.add_systems((|| {}, ApplyDeferred.run_if(|| false), || {}).chain());
2144 schedule.run(&mut world);
2145
2146 assert_eq!(schedule.executable.systems.len(), 6);
2148 }
2149
2150 #[test]
2151 fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_chain() {
2152 let mut schedule = Schedule::default();
2153 let mut world = World::default();
2154 schedule.add_systems(
2155 (
2156 |mut commands: Commands| commands.insert_resource(Resource1),
2157 |_: Res<Resource1>| {},
2158 )
2159 .chain(),
2160 );
2161 schedule.add_systems((|| {}, ApplyDeferred, || {}).chain().run_if(|| false));
2162 schedule.run(&mut world);
2163
2164 assert_eq!(schedule.executable.systems.len(), 6);
2166 }
2167
2168 #[test]
2169 fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_system_set() {
2170 #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
2171 struct Set;
2172
2173 let mut schedule = Schedule::default();
2174 let mut world = World::default();
2175 schedule.configure_sets(Set.run_if(|| false));
2176 schedule.add_systems(
2177 (
2178 |mut commands: Commands| commands.insert_resource(Resource1),
2179 |_: Res<Resource1>| {},
2180 )
2181 .chain(),
2182 );
2183 schedule.add_systems((|| {}, ApplyDeferred.in_set(Set), || {}).chain());
2184 schedule.run(&mut world);
2185
2186 assert_eq!(schedule.executable.systems.len(), 6);
2188 }
2189
2190 #[test]
2191 fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_nested_system_set()
2192 {
2193 #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
2194 struct Set1;
2195 #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
2196 struct Set2;
2197
2198 let mut schedule = Schedule::default();
2199 let mut world = World::default();
2200 schedule.configure_sets(Set2.run_if(|| false));
2201 schedule.configure_sets(Set1.in_set(Set2));
2202 schedule.add_systems(
2203 (
2204 |mut commands: Commands| commands.insert_resource(Resource1),
2205 |_: Res<Resource1>| {},
2206 )
2207 .chain(),
2208 );
2209 schedule.add_systems((|| {}, ApplyDeferred, || {}).chain().in_set(Set1));
2210 schedule.run(&mut world);
2211
2212 assert_eq!(schedule.executable.systems.len(), 6);
2214 }
2215
2216 #[test]
2217 fn merges_sync_points_into_one() {
2218 let mut schedule = Schedule::default();
2219 let mut world = World::default();
2220 schedule.add_systems(
2222 (
2223 (
2224 |mut commands: Commands| commands.insert_resource(Resource1),
2225 |mut commands: Commands| commands.insert_resource(Resource2),
2226 ),
2227 |_: Res<Resource1>, _: Res<Resource2>| {},
2228 )
2229 .chain(),
2230 );
2231 schedule.run(&mut world);
2232
2233 assert_eq!(schedule.executable.systems.len(), 4);
2235
2236 schedule.add_systems(((
2238 (
2239 |mut commands: Commands| commands.insert_resource(Resource1),
2240 |mut commands: Commands| commands.insert_resource(Resource2),
2241 ),
2242 |_: Res<Resource1>, _: Res<Resource2>| {},
2243 )
2244 .chain(),));
2245 schedule.run(&mut world);
2246
2247 assert_eq!(schedule.executable.systems.len(), 7);
2248 }
2249
2250 #[test]
2251 fn adds_multiple_consecutive_syncs() {
2252 let mut schedule = Schedule::default();
2253 let mut world = World::default();
2254 schedule.add_systems(
2256 (
2257 |mut commands: Commands| commands.insert_resource(Resource1),
2258 |mut commands: Commands| commands.insert_resource(Resource2),
2259 |_: Res<Resource1>, _: Res<Resource2>| {},
2260 )
2261 .chain(),
2262 );
2263 schedule.run(&mut world);
2264
2265 assert_eq!(schedule.executable.systems.len(), 5);
2266 }
2267
2268 #[test]
2269 fn do_not_consider_ignore_deferred_before_exclusive_system() {
2270 let mut schedule = Schedule::default();
2271 let mut world = World::default();
2272 schedule.add_systems(
2274 (
2275 |_: Commands| {},
2276 |mut commands: Commands| commands.insert_resource(Resource1),
2278 |world: &mut World| assert!(world.contains_resource::<Resource1>()),
2280 |_: &mut World| {},
2282 |_: Commands| {},
2284 )
2285 .chain_ignore_deferred(),
2286 );
2287 schedule.run(&mut world);
2288
2289 assert_eq!(schedule.executable.systems.len(), 6); }
2291
2292 #[test]
2293 fn bubble_sync_point_through_ignore_deferred_node() {
2294 let mut schedule = Schedule::default();
2295 let mut world = World::default();
2296
2297 let insert_resource_config = (
2298 |mut commands: Commands| commands.insert_resource(Resource1),
2300 || {},
2302 )
2303 .chain_ignore_deferred();
2305
2306 schedule.add_systems(
2307 (
2308 insert_resource_config,
2309 |_: Res<Resource1>| {},
2311 )
2312 .chain(),
2314 );
2315
2316 schedule.run(&mut world);
2321
2322 assert_eq!(schedule.executable.systems.len(), 4); }
2324
2325 #[test]
2326 fn disable_auto_sync_points() {
2327 let mut schedule = Schedule::default();
2328 schedule.set_build_settings(ScheduleBuildSettings {
2329 auto_insert_apply_deferred: false,
2330 ..Default::default()
2331 });
2332 let mut world = World::default();
2333 schedule.add_systems(
2334 (
2335 |mut commands: Commands| commands.insert_resource(Resource1),
2336 |res: Option<Res<Resource1>>| assert!(res.is_none()),
2337 )
2338 .chain(),
2339 );
2340 schedule.run(&mut world);
2341
2342 assert_eq!(schedule.executable.systems.len(), 2);
2343 }
2344
2345 mod no_sync_edges {
2346 use super::*;
2347
2348 fn insert_resource(mut commands: Commands) {
2349 commands.insert_resource(Resource1);
2350 }
2351
2352 fn resource_does_not_exist(res: Option<Res<Resource1>>) {
2353 assert!(res.is_none());
2354 }
2355
2356 #[derive(SystemSet, Hash, PartialEq, Eq, Debug, Clone)]
2357 enum Sets {
2358 A,
2359 B,
2360 }
2361
2362 fn check_no_sync_edges(add_systems: impl FnOnce(&mut Schedule)) {
2363 let mut schedule = Schedule::default();
2364 let mut world = World::default();
2365 add_systems(&mut schedule);
2366
2367 schedule.run(&mut world);
2368
2369 assert_eq!(schedule.executable.systems.len(), 2);
2370 }
2371
2372 #[test]
2373 fn system_to_system_after() {
2374 check_no_sync_edges(|schedule| {
2375 schedule.add_systems((
2376 insert_resource,
2377 resource_does_not_exist.after_ignore_deferred(insert_resource),
2378 ));
2379 });
2380 }
2381
2382 #[test]
2383 fn system_to_system_before() {
2384 check_no_sync_edges(|schedule| {
2385 schedule.add_systems((
2386 insert_resource.before_ignore_deferred(resource_does_not_exist),
2387 resource_does_not_exist,
2388 ));
2389 });
2390 }
2391
2392 #[test]
2393 fn set_to_system_after() {
2394 check_no_sync_edges(|schedule| {
2395 schedule
2396 .add_systems((insert_resource, resource_does_not_exist.in_set(Sets::A)))
2397 .configure_sets(Sets::A.after_ignore_deferred(insert_resource));
2398 });
2399 }
2400
2401 #[test]
2402 fn set_to_system_before() {
2403 check_no_sync_edges(|schedule| {
2404 schedule
2405 .add_systems((insert_resource.in_set(Sets::A), resource_does_not_exist))
2406 .configure_sets(Sets::A.before_ignore_deferred(resource_does_not_exist));
2407 });
2408 }
2409
2410 #[test]
2411 fn set_to_set_after() {
2412 check_no_sync_edges(|schedule| {
2413 schedule
2414 .add_systems((
2415 insert_resource.in_set(Sets::A),
2416 resource_does_not_exist.in_set(Sets::B),
2417 ))
2418 .configure_sets(Sets::B.after_ignore_deferred(Sets::A));
2419 });
2420 }
2421
2422 #[test]
2423 fn set_to_set_before() {
2424 check_no_sync_edges(|schedule| {
2425 schedule
2426 .add_systems((
2427 insert_resource.in_set(Sets::A),
2428 resource_does_not_exist.in_set(Sets::B),
2429 ))
2430 .configure_sets(Sets::A.before_ignore_deferred(Sets::B));
2431 });
2432 }
2433 }
2434
2435 mod no_sync_chain {
2436 use super::*;
2437
2438 #[derive(Resource)]
2439 struct Ra;
2440
2441 #[derive(Resource)]
2442 struct Rb;
2443
2444 #[derive(Resource)]
2445 struct Rc;
2446
2447 fn run_schedule(expected_num_systems: usize, add_systems: impl FnOnce(&mut Schedule)) {
2448 let mut schedule = Schedule::default();
2449 let mut world = World::default();
2450 add_systems(&mut schedule);
2451
2452 schedule.run(&mut world);
2453
2454 assert_eq!(schedule.executable.systems.len(), expected_num_systems);
2455 }
2456
2457 #[test]
2458 fn only_chain_outside() {
2459 run_schedule(5, |schedule: &mut Schedule| {
2460 schedule.add_systems(
2461 (
2462 (
2463 |mut commands: Commands| commands.insert_resource(Ra),
2464 |mut commands: Commands| commands.insert_resource(Rb),
2465 ),
2466 (
2467 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2468 assert!(res_a.is_some());
2469 assert!(res_b.is_some());
2470 },
2471 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2472 assert!(res_a.is_some());
2473 assert!(res_b.is_some());
2474 },
2475 ),
2476 )
2477 .chain(),
2478 );
2479 });
2480
2481 run_schedule(4, |schedule: &mut Schedule| {
2482 schedule.add_systems(
2483 (
2484 (
2485 |mut commands: Commands| commands.insert_resource(Ra),
2486 |mut commands: Commands| commands.insert_resource(Rb),
2487 ),
2488 (
2489 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2490 assert!(res_a.is_none());
2491 assert!(res_b.is_none());
2492 },
2493 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2494 assert!(res_a.is_none());
2495 assert!(res_b.is_none());
2496 },
2497 ),
2498 )
2499 .chain_ignore_deferred(),
2500 );
2501 });
2502 }
2503
2504 #[test]
2505 fn chain_first() {
2506 run_schedule(6, |schedule: &mut Schedule| {
2507 schedule.add_systems(
2508 (
2509 (
2510 |mut commands: Commands| commands.insert_resource(Ra),
2511 |mut commands: Commands, res_a: Option<Res<Ra>>| {
2512 commands.insert_resource(Rb);
2513 assert!(res_a.is_some());
2514 },
2515 )
2516 .chain(),
2517 (
2518 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2519 assert!(res_a.is_some());
2520 assert!(res_b.is_some());
2521 },
2522 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2523 assert!(res_a.is_some());
2524 assert!(res_b.is_some());
2525 },
2526 ),
2527 )
2528 .chain(),
2529 );
2530 });
2531
2532 run_schedule(5, |schedule: &mut Schedule| {
2533 schedule.add_systems(
2534 (
2535 (
2536 |mut commands: Commands| commands.insert_resource(Ra),
2537 |mut commands: Commands, res_a: Option<Res<Ra>>| {
2538 commands.insert_resource(Rb);
2539 assert!(res_a.is_some());
2540 },
2541 )
2542 .chain(),
2543 (
2544 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2545 assert!(res_a.is_some());
2546 assert!(res_b.is_none());
2547 },
2548 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2549 assert!(res_a.is_some());
2550 assert!(res_b.is_none());
2551 },
2552 ),
2553 )
2554 .chain_ignore_deferred(),
2555 );
2556 });
2557 }
2558
2559 #[test]
2560 fn chain_second() {
2561 run_schedule(6, |schedule: &mut Schedule| {
2562 schedule.add_systems(
2563 (
2564 (
2565 |mut commands: Commands| commands.insert_resource(Ra),
2566 |mut commands: Commands| commands.insert_resource(Rb),
2567 ),
2568 (
2569 |mut commands: Commands,
2570 res_a: Option<Res<Ra>>,
2571 res_b: Option<Res<Rb>>| {
2572 commands.insert_resource(Rc);
2573 assert!(res_a.is_some());
2574 assert!(res_b.is_some());
2575 },
2576 |res_a: Option<Res<Ra>>,
2577 res_b: Option<Res<Rb>>,
2578 res_c: Option<Res<Rc>>| {
2579 assert!(res_a.is_some());
2580 assert!(res_b.is_some());
2581 assert!(res_c.is_some());
2582 },
2583 )
2584 .chain(),
2585 )
2586 .chain(),
2587 );
2588 });
2589
2590 run_schedule(5, |schedule: &mut Schedule| {
2591 schedule.add_systems(
2592 (
2593 (
2594 |mut commands: Commands| commands.insert_resource(Ra),
2595 |mut commands: Commands| commands.insert_resource(Rb),
2596 ),
2597 (
2598 |mut commands: Commands,
2599 res_a: Option<Res<Ra>>,
2600 res_b: Option<Res<Rb>>| {
2601 commands.insert_resource(Rc);
2602 assert!(res_a.is_none());
2603 assert!(res_b.is_none());
2604 },
2605 |res_a: Option<Res<Ra>>,
2606 res_b: Option<Res<Rb>>,
2607 res_c: Option<Res<Rc>>| {
2608 assert!(res_a.is_some());
2609 assert!(res_b.is_some());
2610 assert!(res_c.is_some());
2611 },
2612 )
2613 .chain(),
2614 )
2615 .chain_ignore_deferred(),
2616 );
2617 });
2618 }
2619
2620 #[test]
2621 fn chain_all() {
2622 run_schedule(7, |schedule: &mut Schedule| {
2623 schedule.add_systems(
2624 (
2625 (
2626 |mut commands: Commands| commands.insert_resource(Ra),
2627 |mut commands: Commands, res_a: Option<Res<Ra>>| {
2628 commands.insert_resource(Rb);
2629 assert!(res_a.is_some());
2630 },
2631 )
2632 .chain(),
2633 (
2634 |mut commands: Commands,
2635 res_a: Option<Res<Ra>>,
2636 res_b: Option<Res<Rb>>| {
2637 commands.insert_resource(Rc);
2638 assert!(res_a.is_some());
2639 assert!(res_b.is_some());
2640 },
2641 |res_a: Option<Res<Ra>>,
2642 res_b: Option<Res<Rb>>,
2643 res_c: Option<Res<Rc>>| {
2644 assert!(res_a.is_some());
2645 assert!(res_b.is_some());
2646 assert!(res_c.is_some());
2647 },
2648 )
2649 .chain(),
2650 )
2651 .chain(),
2652 );
2653 });
2654
2655 run_schedule(6, |schedule: &mut Schedule| {
2656 schedule.add_systems(
2657 (
2658 (
2659 |mut commands: Commands| commands.insert_resource(Ra),
2660 |mut commands: Commands, res_a: Option<Res<Ra>>| {
2661 commands.insert_resource(Rb);
2662 assert!(res_a.is_some());
2663 },
2664 )
2665 .chain(),
2666 (
2667 |mut commands: Commands,
2668 res_a: Option<Res<Ra>>,
2669 res_b: Option<Res<Rb>>| {
2670 commands.insert_resource(Rc);
2671 assert!(res_a.is_some());
2672 assert!(res_b.is_none());
2673 },
2674 |res_a: Option<Res<Ra>>,
2675 res_b: Option<Res<Rb>>,
2676 res_c: Option<Res<Rc>>| {
2677 assert!(res_a.is_some());
2678 assert!(res_b.is_some());
2679 assert!(res_c.is_some());
2680 },
2681 )
2682 .chain(),
2683 )
2684 .chain_ignore_deferred(),
2685 );
2686 });
2687 }
2688 }
2689
2690 #[derive(ScheduleLabel, Hash, Debug, Clone, PartialEq, Eq)]
2691 struct TestSchedule;
2692
2693 #[derive(Resource)]
2694 struct CheckSystemRan(usize);
2695
2696 #[test]
2697 fn add_systems_to_existing_schedule() {
2698 let mut schedules = Schedules::default();
2699 let schedule = Schedule::new(TestSchedule);
2700
2701 schedules.insert(schedule);
2702 schedules.add_systems(TestSchedule, |mut ran: ResMut<CheckSystemRan>| ran.0 += 1);
2703
2704 let mut world = World::new();
2705
2706 world.insert_resource(CheckSystemRan(0));
2707 world.insert_resource(schedules);
2708 world.run_schedule(TestSchedule);
2709
2710 let value = world
2711 .get_resource::<CheckSystemRan>()
2712 .expect("CheckSystemRan Resource Should Exist");
2713 assert_eq!(value.0, 1);
2714 }
2715
2716 #[test]
2717 fn add_systems_to_non_existing_schedule() {
2718 let mut schedules = Schedules::default();
2719
2720 schedules.add_systems(TestSchedule, |mut ran: ResMut<CheckSystemRan>| ran.0 += 1);
2721
2722 let mut world = World::new();
2723
2724 world.insert_resource(CheckSystemRan(0));
2725 world.insert_resource(schedules);
2726 world.run_schedule(TestSchedule);
2727
2728 let value = world
2729 .get_resource::<CheckSystemRan>()
2730 .expect("CheckSystemRan Resource Should Exist");
2731 assert_eq!(value.0, 1);
2732 }
2733
2734 #[derive(SystemSet, Debug, Hash, Clone, PartialEq, Eq)]
2735 enum TestSet {
2736 First,
2737 Second,
2738 }
2739
2740 #[test]
2741 fn configure_set_on_existing_schedule() {
2742 let mut schedules = Schedules::default();
2743 let schedule = Schedule::new(TestSchedule);
2744
2745 schedules.insert(schedule);
2746
2747 schedules.configure_sets(TestSchedule, (TestSet::First, TestSet::Second).chain());
2748 schedules.add_systems(
2749 TestSchedule,
2750 (|mut ran: ResMut<CheckSystemRan>| {
2751 assert_eq!(ran.0, 0);
2752 ran.0 += 1;
2753 })
2754 .in_set(TestSet::First),
2755 );
2756
2757 schedules.add_systems(
2758 TestSchedule,
2759 (|mut ran: ResMut<CheckSystemRan>| {
2760 assert_eq!(ran.0, 1);
2761 ran.0 += 1;
2762 })
2763 .in_set(TestSet::Second),
2764 );
2765
2766 let mut world = World::new();
2767
2768 world.insert_resource(CheckSystemRan(0));
2769 world.insert_resource(schedules);
2770 world.run_schedule(TestSchedule);
2771
2772 let value = world
2773 .get_resource::<CheckSystemRan>()
2774 .expect("CheckSystemRan Resource Should Exist");
2775 assert_eq!(value.0, 2);
2776 }
2777
2778 #[test]
2779 fn configure_set_on_new_schedule() {
2780 let mut schedules = Schedules::default();
2781
2782 schedules.configure_sets(TestSchedule, (TestSet::First, TestSet::Second).chain());
2783 schedules.add_systems(
2784 TestSchedule,
2785 (|mut ran: ResMut<CheckSystemRan>| {
2786 assert_eq!(ran.0, 0);
2787 ran.0 += 1;
2788 })
2789 .in_set(TestSet::First),
2790 );
2791
2792 schedules.add_systems(
2793 TestSchedule,
2794 (|mut ran: ResMut<CheckSystemRan>| {
2795 assert_eq!(ran.0, 1);
2796 ran.0 += 1;
2797 })
2798 .in_set(TestSet::Second),
2799 );
2800
2801 let mut world = World::new();
2802
2803 world.insert_resource(CheckSystemRan(0));
2804 world.insert_resource(schedules);
2805 world.run_schedule(TestSchedule);
2806
2807 let value = world
2808 .get_resource::<CheckSystemRan>()
2809 .expect("CheckSystemRan Resource Should Exist");
2810 assert_eq!(value.0, 2);
2811 }
2812}