1use alloc::collections::BTreeSet;
2use core::fmt::{Debug, Write};
3
4#[cfg(feature = "trace")]
5use bevy_utils::tracing::info_span;
6use bevy_utils::{
7 default,
8 tracing::{error, info, warn},
9 HashMap, HashSet,
10};
11use derive_more::derive::{Display, Error};
12use disqualified::ShortName;
13use fixedbitset::FixedBitSet;
14use petgraph::{algo::TarjanScc, prelude::*};
15
16use crate::{
17 self as bevy_ecs,
18 component::{ComponentId, Components, Tick},
19 prelude::Component,
20 schedule::*,
21 system::{BoxedSystem, IntoSystem, Resource, System},
22 world::World,
23};
24
25use crate::{query::AccessConflicts, storage::SparseSetIndex};
26pub use stepping::Stepping;
27
28#[derive(Default, Resource)]
30pub struct Schedules {
31 inner: HashMap<InternedScheduleLabel, Schedule>,
32 pub ignored_scheduling_ambiguities: BTreeSet<ComponentId>,
34}
35
36impl Schedules {
37 pub fn new() -> Self {
39 Self {
40 inner: HashMap::new(),
41 ignored_scheduling_ambiguities: BTreeSet::new(),
42 }
43 }
44
45 pub fn insert(&mut self, schedule: Schedule) -> Option<Schedule> {
50 self.inner.insert(schedule.label, schedule)
51 }
52
53 pub fn remove(&mut self, label: impl ScheduleLabel) -> Option<Schedule> {
55 self.inner.remove(&label.intern())
56 }
57
58 pub fn remove_entry(
60 &mut self,
61 label: impl ScheduleLabel,
62 ) -> Option<(InternedScheduleLabel, Schedule)> {
63 self.inner.remove_entry(&label.intern())
64 }
65
66 pub fn contains(&self, label: impl ScheduleLabel) -> bool {
68 self.inner.contains_key(&label.intern())
69 }
70
71 pub fn get(&self, label: impl ScheduleLabel) -> Option<&Schedule> {
73 self.inner.get(&label.intern())
74 }
75
76 pub fn get_mut(&mut self, label: impl ScheduleLabel) -> Option<&mut Schedule> {
78 self.inner.get_mut(&label.intern())
79 }
80
81 pub fn entry(&mut self, label: impl ScheduleLabel) -> &mut Schedule {
83 self.inner
84 .entry(label.intern())
85 .or_insert_with(|| Schedule::new(label))
86 }
87
88 pub fn iter(&self) -> impl Iterator<Item = (&dyn ScheduleLabel, &Schedule)> {
90 self.inner
91 .iter()
92 .map(|(label, schedule)| (&**label, schedule))
93 }
94 pub fn iter_mut(&mut self) -> impl Iterator<Item = (&dyn ScheduleLabel, &mut Schedule)> {
96 self.inner
97 .iter_mut()
98 .map(|(label, schedule)| (&**label, schedule))
99 }
100
101 pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) {
105 #[cfg(feature = "trace")]
106 let _all_span = info_span!("check stored schedule ticks").entered();
107 #[allow(unused_variables)]
109 for (label, schedule) in &mut self.inner {
110 #[cfg(feature = "trace")]
111 let name = format!("{label:?}");
112 #[cfg(feature = "trace")]
113 let _one_span = info_span!("check schedule ticks", name = &name).entered();
114 schedule.check_change_ticks(change_tick);
115 }
116 }
117
118 pub fn configure_schedules(&mut self, schedule_build_settings: ScheduleBuildSettings) {
120 for (_, schedule) in &mut self.inner {
121 schedule.set_build_settings(schedule_build_settings.clone());
122 }
123 }
124
125 pub fn allow_ambiguous_component<T: Component>(&mut self, world: &mut World) {
127 self.ignored_scheduling_ambiguities
128 .insert(world.register_component::<T>());
129 }
130
131 pub fn allow_ambiguous_resource<T: Resource>(&mut self, world: &mut World) {
133 self.ignored_scheduling_ambiguities
134 .insert(world.components.register_resource::<T>());
135 }
136
137 pub fn iter_ignored_ambiguities(&self) -> impl Iterator<Item = &ComponentId> + '_ {
139 self.ignored_scheduling_ambiguities.iter()
140 }
141
142 pub fn print_ignored_ambiguities(&self, components: &Components) {
147 let mut message =
148 "System order ambiguities caused by conflicts on the following types are ignored:\n"
149 .to_string();
150 for id in self.iter_ignored_ambiguities() {
151 writeln!(message, "{}", components.get_name(*id).unwrap()).unwrap();
152 }
153
154 info!("{}", message);
155 }
156
157 pub fn add_systems<M>(
159 &mut self,
160 schedule: impl ScheduleLabel,
161 systems: impl IntoSystemConfigs<M>,
162 ) -> &mut Self {
163 self.entry(schedule).add_systems(systems);
164
165 self
166 }
167
168 #[track_caller]
170 pub fn configure_sets(
171 &mut self,
172 schedule: impl ScheduleLabel,
173 sets: impl IntoSystemSetConfigs,
174 ) -> &mut Self {
175 self.entry(schedule).configure_sets(sets);
176
177 self
178 }
179
180 #[track_caller]
187 pub fn ignore_ambiguity<M1, M2, S1, S2>(
188 &mut self,
189 schedule: impl ScheduleLabel,
190 a: S1,
191 b: S2,
192 ) -> &mut Self
193 where
194 S1: IntoSystemSet<M1>,
195 S2: IntoSystemSet<M2>,
196 {
197 self.entry(schedule).ignore_ambiguity(a, b);
198
199 self
200 }
201}
202
203fn make_executor(kind: ExecutorKind) -> Box<dyn SystemExecutor> {
204 match kind {
205 ExecutorKind::Simple => Box::new(SimpleExecutor::new()),
206 ExecutorKind::SingleThreaded => Box::new(SingleThreadedExecutor::new()),
207 ExecutorKind::MultiThreaded => Box::new(MultiThreadedExecutor::new()),
208 }
209}
210
211#[derive(PartialEq)]
213pub enum Chain {
214 Yes,
217 YesIgnoreDeferred,
219 No,
221}
222
223pub struct Schedule {
261 label: InternedScheduleLabel,
262 graph: ScheduleGraph,
263 executable: SystemSchedule,
264 executor: Box<dyn SystemExecutor>,
265 executor_initialized: bool,
266}
267
268#[derive(ScheduleLabel, Hash, PartialEq, Eq, Debug, Clone)]
269struct DefaultSchedule;
270
271impl Default for Schedule {
272 fn default() -> Self {
277 Self::new(DefaultSchedule)
278 }
279}
280
281impl Schedule {
282 pub fn new(label: impl ScheduleLabel) -> Self {
284 Self {
285 label: label.intern(),
286 graph: ScheduleGraph::new(),
287 executable: SystemSchedule::new(),
288 executor: make_executor(ExecutorKind::default()),
289 executor_initialized: false,
290 }
291 }
292
293 pub fn label(&self) -> InternedScheduleLabel {
295 self.label
296 }
297
298 pub fn add_systems<M>(&mut self, systems: impl IntoSystemConfigs<M>) -> &mut Self {
300 self.graph.process_configs(systems.into_configs(), false);
301 self
302 }
303
304 #[track_caller]
307 pub fn ignore_ambiguity<M1, M2, S1, S2>(&mut self, a: S1, b: S2) -> &mut Self
308 where
309 S1: IntoSystemSet<M1>,
310 S2: IntoSystemSet<M2>,
311 {
312 let a = a.into_system_set();
313 let b = b.into_system_set();
314
315 let Some(&a_id) = self.graph.system_set_ids.get(&a.intern()) else {
316 panic!(
317 "Could not mark system as ambiguous, `{:?}` was not found in the schedule.
318 Did you try to call `ambiguous_with` before adding the system to the world?",
319 a
320 );
321 };
322 let Some(&b_id) = self.graph.system_set_ids.get(&b.intern()) else {
323 panic!(
324 "Could not mark system as ambiguous, `{:?}` was not found in the schedule.
325 Did you try to call `ambiguous_with` before adding the system to the world?",
326 b
327 );
328 };
329
330 self.graph.ambiguous_with.add_edge(a_id, b_id, ());
331
332 self
333 }
334
335 #[track_caller]
337 pub fn configure_sets(&mut self, sets: impl IntoSystemSetConfigs) -> &mut Self {
338 self.graph.configure_sets(sets);
339 self
340 }
341
342 pub fn set_build_settings(&mut self, settings: ScheduleBuildSettings) -> &mut Self {
344 self.graph.settings = settings;
345 self
346 }
347
348 pub fn get_build_settings(&self) -> ScheduleBuildSettings {
350 self.graph.settings.clone()
351 }
352
353 pub fn get_executor_kind(&self) -> ExecutorKind {
355 self.executor.kind()
356 }
357
358 pub fn set_executor_kind(&mut self, executor: ExecutorKind) -> &mut Self {
360 if executor != self.executor.kind() {
361 self.executor = make_executor(executor);
362 self.executor_initialized = false;
363 }
364 self
365 }
366
367 pub fn set_apply_final_deferred(&mut self, apply_final_deferred: bool) -> &mut Self {
372 self.executor.set_apply_final_deferred(apply_final_deferred);
373 self
374 }
375
376 pub fn run(&mut self, world: &mut World) {
378 #[cfg(feature = "trace")]
379 let _span = info_span!("schedule", name = ?self.label).entered();
380
381 world.check_change_ticks();
382 self.initialize(world)
383 .unwrap_or_else(|e| panic!("Error when initializing schedule {:?}: {e}", self.label));
384
385 #[cfg(not(feature = "bevy_debug_stepping"))]
386 self.executor.run(&mut self.executable, world, None);
387
388 #[cfg(feature = "bevy_debug_stepping")]
389 {
390 let skip_systems = match world.get_resource_mut::<Stepping>() {
391 None => None,
392 Some(mut stepping) => stepping.skipped_systems(self),
393 };
394
395 self.executor
396 .run(&mut self.executable, world, skip_systems.as_ref());
397 }
398 }
399
400 pub fn initialize(&mut self, world: &mut World) -> Result<(), ScheduleBuildError> {
405 if self.graph.changed {
406 self.graph.initialize(world);
407 let ignored_ambiguities = world
408 .get_resource_or_init::<Schedules>()
409 .ignored_scheduling_ambiguities
410 .clone();
411 self.graph.update_schedule(
412 &mut self.executable,
413 world.components(),
414 &ignored_ambiguities,
415 self.label,
416 )?;
417 self.graph.changed = false;
418 self.executor_initialized = false;
419 }
420
421 if !self.executor_initialized {
422 self.executor.init(&self.executable);
423 self.executor_initialized = true;
424 }
425
426 Ok(())
427 }
428
429 pub fn graph(&self) -> &ScheduleGraph {
431 &self.graph
432 }
433
434 pub fn graph_mut(&mut self) -> &mut ScheduleGraph {
436 &mut self.graph
437 }
438
439 pub(crate) fn executable(&self) -> &SystemSchedule {
441 &self.executable
442 }
443
444 pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) {
448 for system in &mut self.executable.systems {
449 if !is_apply_deferred(system) {
450 system.check_change_tick(change_tick);
451 }
452 }
453
454 for conditions in &mut self.executable.system_conditions {
455 for system in conditions {
456 system.check_change_tick(change_tick);
457 }
458 }
459
460 for conditions in &mut self.executable.set_conditions {
461 for system in conditions {
462 system.check_change_tick(change_tick);
463 }
464 }
465 }
466
467 pub fn apply_deferred(&mut self, world: &mut World) {
476 for system in &mut self.executable.systems {
477 system.apply_deferred(world);
478 }
479 }
480
481 pub fn systems(
486 &self,
487 ) -> Result<impl Iterator<Item = (NodeId, &BoxedSystem)> + Sized, ScheduleNotInitialized> {
488 if !self.executor_initialized {
489 return Err(ScheduleNotInitialized);
490 }
491
492 let iter = self
493 .executable
494 .system_ids
495 .iter()
496 .zip(&self.executable.systems)
497 .map(|(node_id, system)| (*node_id, system));
498
499 Ok(iter)
500 }
501
502 pub fn systems_len(&self) -> usize {
504 if !self.executor_initialized {
505 self.graph.systems.len()
506 } else {
507 self.executable.systems.len()
508 }
509 }
510}
511
512#[derive(Default)]
514pub struct Dag {
515 graph: DiGraphMap<NodeId, ()>,
517 topsort: Vec<NodeId>,
519}
520
521impl Dag {
522 fn new() -> Self {
523 Self {
524 graph: DiGraphMap::new(),
525 topsort: Vec::new(),
526 }
527 }
528
529 pub fn graph(&self) -> &DiGraphMap<NodeId, ()> {
531 &self.graph
532 }
533
534 pub fn cached_topsort(&self) -> &[NodeId] {
538 &self.topsort
539 }
540}
541
542struct SystemSetNode {
544 inner: InternedSystemSet,
545}
546
547impl SystemSetNode {
548 pub fn new(set: InternedSystemSet) -> Self {
549 Self { inner: set }
550 }
551
552 pub fn name(&self) -> String {
553 format!("{:?}", &self.inner)
554 }
555
556 pub fn is_system_type(&self) -> bool {
557 self.inner.system_type().is_some()
558 }
559
560 pub fn is_anonymous(&self) -> bool {
561 self.inner.is_anonymous()
562 }
563}
564
565struct SystemNode {
567 inner: Option<BoxedSystem>,
568}
569
570impl SystemNode {
571 pub fn new(system: BoxedSystem) -> Self {
572 Self {
573 inner: Some(system),
574 }
575 }
576
577 pub fn get(&self) -> Option<&BoxedSystem> {
578 self.inner.as_ref()
579 }
580
581 pub fn get_mut(&mut self) -> Option<&mut BoxedSystem> {
582 self.inner.as_mut()
583 }
584}
585
586#[derive(Default)]
591pub struct ScheduleGraph {
592 systems: Vec<SystemNode>,
594 system_conditions: Vec<Vec<BoxedCondition>>,
596 system_sets: Vec<SystemSetNode>,
598 system_set_conditions: Vec<Vec<BoxedCondition>>,
600 system_set_ids: HashMap<InternedSystemSet, NodeId>,
602 uninit: Vec<(NodeId, usize)>,
605 hierarchy: Dag,
607 dependency: Dag,
609 ambiguous_with: UnGraphMap<NodeId, ()>,
610 ambiguous_with_all: HashSet<NodeId>,
611 conflicting_systems: Vec<(NodeId, NodeId, Vec<ComponentId>)>,
612 anonymous_sets: usize,
613 changed: bool,
614 settings: ScheduleBuildSettings,
615 no_sync_edges: BTreeSet<(NodeId, NodeId)>,
617 auto_sync_node_ids: HashMap<u32, NodeId>,
618}
619
620impl ScheduleGraph {
621 pub fn new() -> Self {
623 Self {
624 systems: Vec::new(),
625 system_conditions: Vec::new(),
626 system_sets: Vec::new(),
627 system_set_conditions: Vec::new(),
628 system_set_ids: HashMap::new(),
629 uninit: Vec::new(),
630 hierarchy: Dag::new(),
631 dependency: Dag::new(),
632 ambiguous_with: UnGraphMap::new(),
633 ambiguous_with_all: HashSet::new(),
634 conflicting_systems: Vec::new(),
635 anonymous_sets: 0,
636 changed: false,
637 settings: default(),
638 no_sync_edges: BTreeSet::new(),
639 auto_sync_node_ids: HashMap::new(),
640 }
641 }
642
643 pub fn get_system_at(&self, id: NodeId) -> Option<&dyn System<In = (), Out = ()>> {
645 if !id.is_system() {
646 return None;
647 }
648 self.systems
649 .get(id.index())
650 .and_then(|system| system.inner.as_deref())
651 }
652
653 pub fn contains_set(&self, set: impl SystemSet) -> bool {
655 self.system_set_ids.contains_key(&set.intern())
656 }
657
658 #[track_caller]
662 pub fn system_at(&self, id: NodeId) -> &dyn System<In = (), Out = ()> {
663 self.get_system_at(id)
664 .ok_or_else(|| format!("system with id {id:?} does not exist in this Schedule"))
665 .unwrap()
666 }
667
668 pub fn get_set_at(&self, id: NodeId) -> Option<&dyn SystemSet> {
670 if !id.is_set() {
671 return None;
672 }
673 self.system_sets.get(id.index()).map(|set| &*set.inner)
674 }
675
676 #[track_caller]
680 pub fn set_at(&self, id: NodeId) -> &dyn SystemSet {
681 self.get_set_at(id)
682 .ok_or_else(|| format!("set with id {id:?} does not exist in this Schedule"))
683 .unwrap()
684 }
685
686 pub fn systems(
688 &self,
689 ) -> impl Iterator<Item = (NodeId, &dyn System<In = (), Out = ()>, &[BoxedCondition])> {
690 self.systems
691 .iter()
692 .zip(self.system_conditions.iter())
693 .enumerate()
694 .filter_map(|(i, (system_node, condition))| {
695 let system = system_node.inner.as_deref()?;
696 Some((NodeId::System(i), system, condition.as_slice()))
697 })
698 }
699
700 pub fn system_sets(&self) -> impl Iterator<Item = (NodeId, &dyn SystemSet, &[BoxedCondition])> {
703 self.system_set_ids.iter().map(|(_, &node_id)| {
704 let set_node = &self.system_sets[node_id.index()];
705 let set = &*set_node.inner;
706 let conditions = self.system_set_conditions[node_id.index()].as_slice();
707 (node_id, set, conditions)
708 })
709 }
710
711 pub fn hierarchy(&self) -> &Dag {
716 &self.hierarchy
717 }
718
719 pub fn dependency(&self) -> &Dag {
724 &self.dependency
725 }
726
727 pub fn conflicting_systems(&self) -> &[(NodeId, NodeId, Vec<ComponentId>)] {
732 &self.conflicting_systems
733 }
734
735 fn process_config<T: ProcessNodeConfig>(
736 &mut self,
737 config: NodeConfig<T>,
738 collect_nodes: bool,
739 ) -> ProcessConfigsResult {
740 ProcessConfigsResult {
741 densely_chained: true,
742 nodes: collect_nodes
743 .then_some(T::process_config(self, config))
744 .into_iter()
745 .collect(),
746 }
747 }
748
749 fn apply_collective_conditions<T: ProcessNodeConfig>(
750 &mut self,
751 configs: &mut [NodeConfigs<T>],
752 collective_conditions: Vec<BoxedCondition>,
753 ) {
754 if !collective_conditions.is_empty() {
755 if let [config] = configs {
756 for condition in collective_conditions {
757 config.run_if_dyn(condition);
758 }
759 } else {
760 let set = self.create_anonymous_set();
761 for config in configs.iter_mut() {
762 config.in_set_inner(set.intern());
763 }
764 let mut set_config = SystemSetConfig::new(set.intern());
765 set_config.conditions.extend(collective_conditions);
766 self.configure_set_inner(set_config).unwrap();
767 }
768 }
769 }
770
771 #[track_caller]
780 fn process_configs<T: ProcessNodeConfig>(
781 &mut self,
782 configs: NodeConfigs<T>,
783 collect_nodes: bool,
784 ) -> ProcessConfigsResult {
785 match configs {
786 NodeConfigs::NodeConfig(config) => self.process_config(config, collect_nodes),
787 NodeConfigs::Configs {
788 mut configs,
789 collective_conditions,
790 chained,
791 } => {
792 self.apply_collective_conditions(&mut configs, collective_conditions);
793
794 let ignore_deferred = matches!(chained, Chain::YesIgnoreDeferred);
795 let chained = matches!(chained, Chain::Yes | Chain::YesIgnoreDeferred);
796
797 let mut densely_chained = chained || configs.len() == 1;
801 let mut configs = configs.into_iter();
802 let mut nodes = Vec::new();
803
804 let Some(first) = configs.next() else {
805 return ProcessConfigsResult {
806 nodes: Vec::new(),
807 densely_chained,
808 };
809 };
810 let mut previous_result = self.process_configs(first, collect_nodes || chained);
811 densely_chained &= previous_result.densely_chained;
812
813 for current in configs {
814 let current_result = self.process_configs(current, collect_nodes || chained);
815 densely_chained &= current_result.densely_chained;
816
817 if chained {
818 let current_nodes = if current_result.densely_chained {
820 ¤t_result.nodes[..1]
821 } else {
822 ¤t_result.nodes
823 };
824 let previous_nodes = if previous_result.densely_chained {
826 &previous_result.nodes[previous_result.nodes.len() - 1..]
827 } else {
828 &previous_result.nodes
829 };
830
831 for previous_node in previous_nodes {
832 for current_node in current_nodes {
833 self.dependency
834 .graph
835 .add_edge(*previous_node, *current_node, ());
836
837 if ignore_deferred {
838 self.no_sync_edges.insert((*previous_node, *current_node));
839 }
840 }
841 }
842 }
843 if collect_nodes {
844 nodes.append(&mut previous_result.nodes);
845 }
846
847 previous_result = current_result;
848 }
849 if collect_nodes {
850 nodes.append(&mut previous_result.nodes);
851 }
852
853 ProcessConfigsResult {
854 nodes,
855 densely_chained,
856 }
857 }
858 }
859 }
860
861 fn add_system_inner(&mut self, config: SystemConfig) -> Result<NodeId, ScheduleBuildError> {
863 let id = NodeId::System(self.systems.len());
864
865 self.update_graphs(id, config.graph_info)?;
867
868 self.uninit.push((id, 0));
870 self.systems.push(SystemNode::new(config.node));
871 self.system_conditions.push(config.conditions);
872
873 Ok(id)
874 }
875
876 #[track_caller]
877 fn configure_sets(&mut self, sets: impl IntoSystemSetConfigs) {
878 self.process_configs(sets.into_configs(), false);
879 }
880
881 fn configure_set_inner(&mut self, set: SystemSetConfig) -> Result<NodeId, ScheduleBuildError> {
883 let SystemSetConfig {
884 node: set,
885 graph_info,
886 mut conditions,
887 } = set;
888
889 let id = match self.system_set_ids.get(&set) {
890 Some(&id) => id,
891 None => self.add_set(set),
892 };
893
894 self.update_graphs(id, graph_info)?;
896
897 let system_set_conditions = &mut self.system_set_conditions[id.index()];
899 self.uninit.push((id, system_set_conditions.len()));
900 system_set_conditions.append(&mut conditions);
901
902 Ok(id)
903 }
904
905 fn add_set(&mut self, set: InternedSystemSet) -> NodeId {
906 let id = NodeId::Set(self.system_sets.len());
907 self.system_sets.push(SystemSetNode::new(set));
908 self.system_set_conditions.push(Vec::new());
909 self.system_set_ids.insert(set, id);
910 id
911 }
912
913 fn check_hierarchy_set(
916 &mut self,
917 id: &NodeId,
918 set: InternedSystemSet,
919 ) -> Result<(), ScheduleBuildError> {
920 match self.system_set_ids.get(&set) {
921 Some(set_id) => {
922 if id == set_id {
923 return Err(ScheduleBuildError::HierarchyLoop(self.get_node_name(id)));
924 }
925 }
926 None => {
927 self.add_set(set);
928 }
929 }
930
931 Ok(())
932 }
933
934 fn create_anonymous_set(&mut self) -> AnonymousSet {
935 let id = self.anonymous_sets;
936 self.anonymous_sets += 1;
937 AnonymousSet::new(id)
938 }
939
940 fn check_hierarchy_sets(
943 &mut self,
944 id: &NodeId,
945 graph_info: &GraphInfo,
946 ) -> Result<(), ScheduleBuildError> {
947 for &set in &graph_info.hierarchy {
948 self.check_hierarchy_set(id, set)?;
949 }
950
951 Ok(())
952 }
953
954 fn check_edges(
957 &mut self,
958 id: &NodeId,
959 graph_info: &GraphInfo,
960 ) -> Result<(), ScheduleBuildError> {
961 for Dependency { kind: _, set } in &graph_info.dependencies {
962 match self.system_set_ids.get(set) {
963 Some(set_id) => {
964 if id == set_id {
965 return Err(ScheduleBuildError::DependencyLoop(self.get_node_name(id)));
966 }
967 }
968 None => {
969 self.add_set(*set);
970 }
971 }
972 }
973
974 if let Ambiguity::IgnoreWithSet(ambiguous_with) = &graph_info.ambiguous_with {
975 for set in ambiguous_with {
976 if !self.system_set_ids.contains_key(set) {
977 self.add_set(*set);
978 }
979 }
980 }
981
982 Ok(())
983 }
984
985 fn update_graphs(
987 &mut self,
988 id: NodeId,
989 graph_info: GraphInfo,
990 ) -> Result<(), ScheduleBuildError> {
991 self.check_hierarchy_sets(&id, &graph_info)?;
992 self.check_edges(&id, &graph_info)?;
993 self.changed = true;
994
995 let GraphInfo {
996 hierarchy: sets,
997 dependencies,
998 ambiguous_with,
999 ..
1000 } = graph_info;
1001
1002 self.hierarchy.graph.add_node(id);
1003 self.dependency.graph.add_node(id);
1004
1005 for set in sets.into_iter().map(|set| self.system_set_ids[&set]) {
1006 self.hierarchy.graph.add_edge(set, id, ());
1007
1008 self.dependency.graph.add_node(set);
1010 }
1011
1012 for (kind, set) in dependencies
1013 .into_iter()
1014 .map(|Dependency { kind, set }| (kind, self.system_set_ids[&set]))
1015 {
1016 let (lhs, rhs) = match kind {
1017 DependencyKind::Before => (id, set),
1018 DependencyKind::BeforeNoSync => {
1019 self.no_sync_edges.insert((id, set));
1020 (id, set)
1021 }
1022 DependencyKind::After => (set, id),
1023 DependencyKind::AfterNoSync => {
1024 self.no_sync_edges.insert((set, id));
1025 (set, id)
1026 }
1027 };
1028 self.dependency.graph.add_edge(lhs, rhs, ());
1029
1030 self.hierarchy.graph.add_node(set);
1032 }
1033
1034 match ambiguous_with {
1035 Ambiguity::Check => (),
1036 Ambiguity::IgnoreWithSet(ambiguous_with) => {
1037 for set in ambiguous_with
1038 .into_iter()
1039 .map(|set| self.system_set_ids[&set])
1040 {
1041 self.ambiguous_with.add_edge(id, set, ());
1042 }
1043 }
1044 Ambiguity::IgnoreAll => {
1045 self.ambiguous_with_all.insert(id);
1046 }
1047 }
1048
1049 Ok(())
1050 }
1051
1052 pub fn initialize(&mut self, world: &mut World) {
1054 for (id, i) in self.uninit.drain(..) {
1055 match id {
1056 NodeId::System(index) => {
1057 self.systems[index].get_mut().unwrap().initialize(world);
1058 for condition in &mut self.system_conditions[index] {
1059 condition.initialize(world);
1060 }
1061 }
1062 NodeId::Set(index) => {
1063 for condition in self.system_set_conditions[index].iter_mut().skip(i) {
1064 condition.initialize(world);
1065 }
1066 }
1067 }
1068 }
1069 }
1070
1071 pub fn build_schedule(
1077 &mut self,
1078 components: &Components,
1079 schedule_label: InternedScheduleLabel,
1080 ignored_ambiguities: &BTreeSet<ComponentId>,
1081 ) -> Result<SystemSchedule, ScheduleBuildError> {
1082 self.hierarchy.topsort =
1084 self.topsort_graph(&self.hierarchy.graph, ReportCycles::Hierarchy)?;
1085
1086 let hier_results = check_graph(&self.hierarchy.graph, &self.hierarchy.topsort);
1087 self.optionally_check_hierarchy_conflicts(&hier_results.transitive_edges, schedule_label)?;
1088
1089 self.hierarchy.graph = hier_results.transitive_reduction;
1091
1092 self.dependency.topsort =
1094 self.topsort_graph(&self.dependency.graph, ReportCycles::Dependency)?;
1095
1096 let dep_results = check_graph(&self.dependency.graph, &self.dependency.topsort);
1098 self.check_for_cross_dependencies(&dep_results, &hier_results.connected)?;
1099
1100 let (set_systems, set_system_bitsets) =
1103 self.map_sets_to_systems(&self.hierarchy.topsort, &self.hierarchy.graph);
1104 self.check_order_but_intersect(&dep_results.connected, &set_system_bitsets)?;
1105
1106 self.check_system_type_set_ambiguity(&set_systems)?;
1108
1109 let mut dependency_flattened = self.get_dependency_flattened(&set_systems);
1110
1111 if self.settings.auto_insert_apply_deferred {
1113 dependency_flattened = self.auto_insert_apply_deferred(&mut dependency_flattened)?;
1114 }
1115
1116 let mut dependency_flattened_dag = Dag {
1118 topsort: self.topsort_graph(&dependency_flattened, ReportCycles::Dependency)?,
1119 graph: dependency_flattened,
1120 };
1121
1122 let flat_results = check_graph(
1123 &dependency_flattened_dag.graph,
1124 &dependency_flattened_dag.topsort,
1125 );
1126
1127 dependency_flattened_dag.graph = flat_results.transitive_reduction;
1129
1130 let ambiguous_with_flattened = self.get_ambiguous_with_flattened(&set_systems);
1132
1133 let conflicting_systems = self.get_conflicting_systems(
1135 &flat_results.disconnected,
1136 &ambiguous_with_flattened,
1137 ignored_ambiguities,
1138 );
1139 self.optionally_check_conflicts(&conflicting_systems, components, schedule_label)?;
1140 self.conflicting_systems = conflicting_systems;
1141
1142 Ok(self.build_schedule_inner(dependency_flattened_dag, hier_results.reachable))
1144 }
1145
1146 fn auto_insert_apply_deferred(
1148 &mut self,
1149 dependency_flattened: &mut GraphMap<NodeId, (), Directed>,
1150 ) -> Result<GraphMap<NodeId, (), Directed>, ScheduleBuildError> {
1151 let mut sync_point_graph = dependency_flattened.clone();
1152 let topo = self.topsort_graph(dependency_flattened, ReportCycles::Dependency)?;
1153
1154 let mut distances: HashMap<usize, Option<u32>> = HashMap::with_capacity(topo.len());
1157 for node in &topo {
1158 let add_sync_after = self.systems[node.index()].get().unwrap().has_deferred();
1159
1160 for target in dependency_flattened.neighbors_directed(*node, Outgoing) {
1161 let add_sync_on_edge = add_sync_after
1162 && !is_apply_deferred(self.systems[target.index()].get().unwrap())
1163 && !self.no_sync_edges.contains(&(*node, target));
1164
1165 let weight = if add_sync_on_edge { 1 } else { 0 };
1166
1167 let distance = distances
1168 .get(&target.index())
1169 .unwrap_or(&None)
1170 .or(Some(0))
1171 .map(|distance| {
1172 distance.max(
1173 distances.get(&node.index()).unwrap_or(&None).unwrap_or(0) + weight,
1174 )
1175 });
1176
1177 distances.insert(target.index(), distance);
1178
1179 if add_sync_on_edge {
1180 let sync_point = self.get_sync_point(distances[&target.index()].unwrap());
1181 sync_point_graph.add_edge(*node, sync_point, ());
1182 sync_point_graph.add_edge(sync_point, target, ());
1183
1184 sync_point_graph.remove_edge(*node, target);
1186 }
1187 }
1188 }
1189
1190 Ok(sync_point_graph)
1191 }
1192
1193 fn add_auto_sync(&mut self) -> NodeId {
1195 let id = NodeId::System(self.systems.len());
1196
1197 self.systems
1198 .push(SystemNode::new(Box::new(IntoSystem::into_system(
1199 apply_deferred,
1200 ))));
1201 self.system_conditions.push(Vec::new());
1202
1203 self.ambiguous_with_all.insert(id);
1206
1207 id
1208 }
1209
1210 fn get_sync_point(&mut self, distance: u32) -> NodeId {
1213 self.auto_sync_node_ids
1214 .get(&distance)
1215 .copied()
1216 .or_else(|| {
1217 let node_id = self.add_auto_sync();
1218 self.auto_sync_node_ids.insert(distance, node_id);
1219 Some(node_id)
1220 })
1221 .unwrap()
1222 }
1223
1224 fn map_sets_to_systems(
1228 &self,
1229 hierarchy_topsort: &[NodeId],
1230 hierarchy_graph: &GraphMap<NodeId, (), Directed>,
1231 ) -> (HashMap<NodeId, Vec<NodeId>>, HashMap<NodeId, FixedBitSet>) {
1232 let mut set_systems: HashMap<NodeId, Vec<NodeId>> =
1233 HashMap::with_capacity(self.system_sets.len());
1234 let mut set_system_bitsets = HashMap::with_capacity(self.system_sets.len());
1235 for &id in hierarchy_topsort.iter().rev() {
1236 if id.is_system() {
1237 continue;
1238 }
1239
1240 let mut systems = Vec::new();
1241 let mut system_bitset = FixedBitSet::with_capacity(self.systems.len());
1242
1243 for child in hierarchy_graph.neighbors_directed(id, Outgoing) {
1244 match child {
1245 NodeId::System(_) => {
1246 systems.push(child);
1247 system_bitset.insert(child.index());
1248 }
1249 NodeId::Set(_) => {
1250 let child_systems = set_systems.get(&child).unwrap();
1251 let child_system_bitset = set_system_bitsets.get(&child).unwrap();
1252 systems.extend_from_slice(child_systems);
1253 system_bitset.union_with(child_system_bitset);
1254 }
1255 }
1256 }
1257
1258 set_systems.insert(id, systems);
1259 set_system_bitsets.insert(id, system_bitset);
1260 }
1261 (set_systems, set_system_bitsets)
1262 }
1263
1264 fn get_dependency_flattened(
1265 &mut self,
1266 set_systems: &HashMap<NodeId, Vec<NodeId>>,
1267 ) -> GraphMap<NodeId, (), Directed> {
1268 let mut dependency_flattened = self.dependency.graph.clone();
1271 let mut temp = Vec::new();
1272 for (&set, systems) in set_systems {
1273 if systems.is_empty() {
1274 for a in dependency_flattened.neighbors_directed(set, Incoming) {
1276 for b in dependency_flattened.neighbors_directed(set, Outgoing) {
1277 if self.no_sync_edges.contains(&(a, set))
1278 && self.no_sync_edges.contains(&(set, b))
1279 {
1280 self.no_sync_edges.insert((a, b));
1281 }
1282
1283 temp.push((a, b));
1284 }
1285 }
1286 } else {
1287 for a in dependency_flattened.neighbors_directed(set, Incoming) {
1288 for &sys in systems {
1289 if self.no_sync_edges.contains(&(a, set)) {
1290 self.no_sync_edges.insert((a, sys));
1291 }
1292 temp.push((a, sys));
1293 }
1294 }
1295
1296 for b in dependency_flattened.neighbors_directed(set, Outgoing) {
1297 for &sys in systems {
1298 if self.no_sync_edges.contains(&(set, b)) {
1299 self.no_sync_edges.insert((sys, b));
1300 }
1301 temp.push((sys, b));
1302 }
1303 }
1304 }
1305
1306 dependency_flattened.remove_node(set);
1307 for (a, b) in temp.drain(..) {
1308 dependency_flattened.add_edge(a, b, ());
1309 }
1310 }
1311
1312 dependency_flattened
1313 }
1314
1315 fn get_ambiguous_with_flattened(
1316 &self,
1317 set_systems: &HashMap<NodeId, Vec<NodeId>>,
1318 ) -> GraphMap<NodeId, (), Undirected> {
1319 let mut ambiguous_with_flattened = UnGraphMap::new();
1320 for (lhs, rhs, _) in self.ambiguous_with.all_edges() {
1321 match (lhs, rhs) {
1322 (NodeId::System(_), NodeId::System(_)) => {
1323 ambiguous_with_flattened.add_edge(lhs, rhs, ());
1324 }
1325 (NodeId::Set(_), NodeId::System(_)) => {
1326 for &lhs_ in set_systems.get(&lhs).unwrap_or(&Vec::new()) {
1327 ambiguous_with_flattened.add_edge(lhs_, rhs, ());
1328 }
1329 }
1330 (NodeId::System(_), NodeId::Set(_)) => {
1331 for &rhs_ in set_systems.get(&rhs).unwrap_or(&Vec::new()) {
1332 ambiguous_with_flattened.add_edge(lhs, rhs_, ());
1333 }
1334 }
1335 (NodeId::Set(_), NodeId::Set(_)) => {
1336 for &lhs_ in set_systems.get(&lhs).unwrap_or(&Vec::new()) {
1337 for &rhs_ in set_systems.get(&rhs).unwrap_or(&vec![]) {
1338 ambiguous_with_flattened.add_edge(lhs_, rhs_, ());
1339 }
1340 }
1341 }
1342 }
1343 }
1344
1345 ambiguous_with_flattened
1346 }
1347
1348 fn get_conflicting_systems(
1349 &self,
1350 flat_results_disconnected: &Vec<(NodeId, NodeId)>,
1351 ambiguous_with_flattened: &GraphMap<NodeId, (), Undirected>,
1352 ignored_ambiguities: &BTreeSet<ComponentId>,
1353 ) -> Vec<(NodeId, NodeId, Vec<ComponentId>)> {
1354 let mut conflicting_systems = Vec::new();
1355 for &(a, b) in flat_results_disconnected {
1356 if ambiguous_with_flattened.contains_edge(a, b)
1357 || self.ambiguous_with_all.contains(&a)
1358 || self.ambiguous_with_all.contains(&b)
1359 {
1360 continue;
1361 }
1362
1363 let system_a = self.systems[a.index()].get().unwrap();
1364 let system_b = self.systems[b.index()].get().unwrap();
1365 if system_a.is_exclusive() || system_b.is_exclusive() {
1366 conflicting_systems.push((a, b, Vec::new()));
1367 } else {
1368 let access_a = system_a.component_access();
1369 let access_b = system_b.component_access();
1370 if !access_a.is_compatible(access_b) {
1371 match access_a.get_conflicts(access_b) {
1372 AccessConflicts::Individual(conflicts) => {
1373 let conflicts: Vec<_> = conflicts
1374 .ones()
1375 .map(ComponentId::get_sparse_set_index)
1376 .filter(|id| !ignored_ambiguities.contains(id))
1377 .collect();
1378 if !conflicts.is_empty() {
1379 conflicting_systems.push((a, b, conflicts));
1380 }
1381 }
1382 AccessConflicts::All => {
1383 conflicting_systems.push((a, b, Vec::new()));
1386 }
1387 }
1388 }
1389 }
1390 }
1391
1392 conflicting_systems
1393 }
1394
1395 fn build_schedule_inner(
1396 &self,
1397 dependency_flattened_dag: Dag,
1398 hier_results_reachable: FixedBitSet,
1399 ) -> SystemSchedule {
1400 let dg_system_ids = dependency_flattened_dag.topsort.clone();
1401 let dg_system_idx_map = dg_system_ids
1402 .iter()
1403 .cloned()
1404 .enumerate()
1405 .map(|(i, id)| (id, i))
1406 .collect::<HashMap<_, _>>();
1407
1408 let hg_systems = self
1409 .hierarchy
1410 .topsort
1411 .iter()
1412 .cloned()
1413 .enumerate()
1414 .filter(|&(_i, id)| id.is_system())
1415 .collect::<Vec<_>>();
1416
1417 let (hg_set_with_conditions_idxs, hg_set_ids): (Vec<_>, Vec<_>) = self
1418 .hierarchy
1419 .topsort
1420 .iter()
1421 .cloned()
1422 .enumerate()
1423 .filter(|&(_i, id)| {
1424 id.is_set() && !self.system_set_conditions[id.index()].is_empty()
1427 })
1428 .unzip();
1429
1430 let sys_count = self.systems.len();
1431 let set_with_conditions_count = hg_set_ids.len();
1432 let hg_node_count = self.hierarchy.graph.node_count();
1433
1434 let mut system_dependencies = Vec::with_capacity(sys_count);
1437 let mut system_dependents = Vec::with_capacity(sys_count);
1438 for &sys_id in &dg_system_ids {
1439 let num_dependencies = dependency_flattened_dag
1440 .graph
1441 .neighbors_directed(sys_id, Incoming)
1442 .count();
1443
1444 let dependents = dependency_flattened_dag
1445 .graph
1446 .neighbors_directed(sys_id, Outgoing)
1447 .map(|dep_id| dg_system_idx_map[&dep_id])
1448 .collect::<Vec<_>>();
1449
1450 system_dependencies.push(num_dependencies);
1451 system_dependents.push(dependents);
1452 }
1453
1454 let mut systems_in_sets_with_conditions =
1457 vec![FixedBitSet::with_capacity(sys_count); set_with_conditions_count];
1458 for (i, &row) in hg_set_with_conditions_idxs.iter().enumerate() {
1459 let bitset = &mut systems_in_sets_with_conditions[i];
1460 for &(col, sys_id) in &hg_systems {
1461 let idx = dg_system_idx_map[&sys_id];
1462 let is_descendant = hier_results_reachable[index(row, col, hg_node_count)];
1463 bitset.set(idx, is_descendant);
1464 }
1465 }
1466
1467 let mut sets_with_conditions_of_systems =
1468 vec![FixedBitSet::with_capacity(set_with_conditions_count); sys_count];
1469 for &(col, sys_id) in &hg_systems {
1470 let i = dg_system_idx_map[&sys_id];
1471 let bitset = &mut sets_with_conditions_of_systems[i];
1472 for (idx, &row) in hg_set_with_conditions_idxs
1473 .iter()
1474 .enumerate()
1475 .take_while(|&(_idx, &row)| row < col)
1476 {
1477 let is_ancestor = hier_results_reachable[index(row, col, hg_node_count)];
1478 bitset.set(idx, is_ancestor);
1479 }
1480 }
1481
1482 SystemSchedule {
1483 systems: Vec::with_capacity(sys_count),
1484 system_conditions: Vec::with_capacity(sys_count),
1485 set_conditions: Vec::with_capacity(set_with_conditions_count),
1486 system_ids: dg_system_ids,
1487 set_ids: hg_set_ids,
1488 system_dependencies,
1489 system_dependents,
1490 sets_with_conditions_of_systems,
1491 systems_in_sets_with_conditions,
1492 }
1493 }
1494
1495 fn update_schedule(
1497 &mut self,
1498 schedule: &mut SystemSchedule,
1499 components: &Components,
1500 ignored_ambiguities: &BTreeSet<ComponentId>,
1501 schedule_label: InternedScheduleLabel,
1502 ) -> Result<(), ScheduleBuildError> {
1503 if !self.uninit.is_empty() {
1504 return Err(ScheduleBuildError::Uninitialized);
1505 }
1506
1507 for ((id, system), conditions) in schedule
1509 .system_ids
1510 .drain(..)
1511 .zip(schedule.systems.drain(..))
1512 .zip(schedule.system_conditions.drain(..))
1513 {
1514 self.systems[id.index()].inner = Some(system);
1515 self.system_conditions[id.index()] = conditions;
1516 }
1517
1518 for (id, conditions) in schedule
1519 .set_ids
1520 .drain(..)
1521 .zip(schedule.set_conditions.drain(..))
1522 {
1523 self.system_set_conditions[id.index()] = conditions;
1524 }
1525
1526 *schedule = self.build_schedule(components, schedule_label, ignored_ambiguities)?;
1527
1528 for &id in &schedule.system_ids {
1530 let system = self.systems[id.index()].inner.take().unwrap();
1531 let conditions = core::mem::take(&mut self.system_conditions[id.index()]);
1532 schedule.systems.push(system);
1533 schedule.system_conditions.push(conditions);
1534 }
1535
1536 for &id in &schedule.set_ids {
1537 let conditions = core::mem::take(&mut self.system_set_conditions[id.index()]);
1538 schedule.set_conditions.push(conditions);
1539 }
1540
1541 Ok(())
1542 }
1543}
1544
1545struct ProcessConfigsResult {
1547 nodes: Vec<NodeId>,
1550 densely_chained: bool,
1554}
1555
1556trait ProcessNodeConfig: Sized {
1558 fn process_config(schedule_graph: &mut ScheduleGraph, config: NodeConfig<Self>) -> NodeId;
1560}
1561
1562impl ProcessNodeConfig for BoxedSystem {
1563 fn process_config(schedule_graph: &mut ScheduleGraph, config: NodeConfig<Self>) -> NodeId {
1564 schedule_graph.add_system_inner(config).unwrap()
1565 }
1566}
1567
1568impl ProcessNodeConfig for InternedSystemSet {
1569 fn process_config(schedule_graph: &mut ScheduleGraph, config: NodeConfig<Self>) -> NodeId {
1570 schedule_graph.configure_set_inner(config).unwrap()
1571 }
1572}
1573
1574enum ReportCycles {
1576 Hierarchy,
1577 Dependency,
1578}
1579
1580impl ScheduleGraph {
1582 fn get_node_name(&self, id: &NodeId) -> String {
1583 self.get_node_name_inner(id, self.settings.report_sets)
1584 }
1585
1586 #[inline]
1587 fn get_node_name_inner(&self, id: &NodeId, report_sets: bool) -> String {
1588 let name = match id {
1589 NodeId::System(_) => {
1590 let name = self.systems[id.index()].get().unwrap().name().to_string();
1591 if report_sets {
1592 let sets = self.names_of_sets_containing_node(id);
1593 if sets.is_empty() {
1594 name
1595 } else if sets.len() == 1 {
1596 format!("{name} (in set {})", sets[0])
1597 } else {
1598 format!("{name} (in sets {})", sets.join(", "))
1599 }
1600 } else {
1601 name
1602 }
1603 }
1604 NodeId::Set(_) => {
1605 let set = &self.system_sets[id.index()];
1606 if set.is_anonymous() {
1607 self.anonymous_set_name(id)
1608 } else {
1609 set.name()
1610 }
1611 }
1612 };
1613 if self.settings.use_shortnames {
1614 ShortName(&name).to_string()
1615 } else {
1616 name
1617 }
1618 }
1619
1620 fn anonymous_set_name(&self, id: &NodeId) -> String {
1621 format!(
1622 "({})",
1623 self.hierarchy
1624 .graph
1625 .edges_directed(*id, Outgoing)
1626 .map(|(_, member_id, _)| self.get_node_name_inner(&member_id, false))
1628 .reduce(|a, b| format!("{a}, {b}"))
1629 .unwrap_or_default()
1630 )
1631 }
1632
1633 fn get_node_kind(&self, id: &NodeId) -> &'static str {
1634 match id {
1635 NodeId::System(_) => "system",
1636 NodeId::Set(_) => "system set",
1637 }
1638 }
1639
1640 fn optionally_check_hierarchy_conflicts(
1643 &self,
1644 transitive_edges: &[(NodeId, NodeId)],
1645 schedule_label: InternedScheduleLabel,
1646 ) -> Result<(), ScheduleBuildError> {
1647 if self.settings.hierarchy_detection == LogLevel::Ignore || transitive_edges.is_empty() {
1648 return Ok(());
1649 }
1650
1651 let message = self.get_hierarchy_conflicts_error_message(transitive_edges);
1652 match self.settings.hierarchy_detection {
1653 LogLevel::Ignore => unreachable!(),
1654 LogLevel::Warn => {
1655 error!(
1656 "Schedule {schedule_label:?} has redundant edges:\n {}",
1657 message
1658 );
1659 Ok(())
1660 }
1661 LogLevel::Error => Err(ScheduleBuildError::HierarchyRedundancy(message)),
1662 }
1663 }
1664
1665 fn get_hierarchy_conflicts_error_message(
1666 &self,
1667 transitive_edges: &[(NodeId, NodeId)],
1668 ) -> String {
1669 let mut message = String::from("hierarchy contains redundant edge(s)");
1670 for (parent, child) in transitive_edges {
1671 writeln!(
1672 message,
1673 " -- {} `{}` cannot be child of set `{}`, longer path exists",
1674 self.get_node_kind(child),
1675 self.get_node_name(child),
1676 self.get_node_name(parent),
1677 )
1678 .unwrap();
1679 }
1680
1681 message
1682 }
1683
1684 fn topsort_graph(
1694 &self,
1695 graph: &DiGraphMap<NodeId, ()>,
1696 report: ReportCycles,
1697 ) -> Result<Vec<NodeId>, ScheduleBuildError> {
1698 let mut tarjan_scc = TarjanScc::new();
1700 let mut top_sorted_nodes = Vec::with_capacity(graph.node_count());
1701 let mut sccs_with_cycles = Vec::new();
1702
1703 tarjan_scc.run(graph, |scc| {
1704 if scc.len() > 1 {
1708 sccs_with_cycles.push(scc.to_vec());
1709 }
1710 top_sorted_nodes.extend_from_slice(scc);
1711 });
1712
1713 if sccs_with_cycles.is_empty() {
1714 top_sorted_nodes.reverse();
1716 Ok(top_sorted_nodes)
1717 } else {
1718 let mut cycles = Vec::new();
1719 for scc in &sccs_with_cycles {
1720 cycles.append(&mut simple_cycles_in_component(graph, scc));
1721 }
1722
1723 let error = match report {
1724 ReportCycles::Hierarchy => ScheduleBuildError::HierarchyCycle(
1725 self.get_hierarchy_cycles_error_message(&cycles),
1726 ),
1727 ReportCycles::Dependency => ScheduleBuildError::DependencyCycle(
1728 self.get_dependency_cycles_error_message(&cycles),
1729 ),
1730 };
1731
1732 Err(error)
1733 }
1734 }
1735
1736 fn get_hierarchy_cycles_error_message(&self, cycles: &[Vec<NodeId>]) -> String {
1738 let mut message = format!("schedule has {} in_set cycle(s):\n", cycles.len());
1739 for (i, cycle) in cycles.iter().enumerate() {
1740 let mut names = cycle.iter().map(|id| self.get_node_name(id));
1741 let first_name = names.next().unwrap();
1742 writeln!(
1743 message,
1744 "cycle {}: set `{first_name}` contains itself",
1745 i + 1,
1746 )
1747 .unwrap();
1748 writeln!(message, "set `{first_name}`").unwrap();
1749 for name in names.chain(core::iter::once(first_name)) {
1750 writeln!(message, " ... which contains set `{name}`").unwrap();
1751 }
1752 writeln!(message).unwrap();
1753 }
1754
1755 message
1756 }
1757
1758 fn get_dependency_cycles_error_message(&self, cycles: &[Vec<NodeId>]) -> String {
1760 let mut message = format!("schedule has {} before/after cycle(s):\n", cycles.len());
1761 for (i, cycle) in cycles.iter().enumerate() {
1762 let mut names = cycle
1763 .iter()
1764 .map(|id| (self.get_node_kind(id), self.get_node_name(id)));
1765 let (first_kind, first_name) = names.next().unwrap();
1766 writeln!(
1767 message,
1768 "cycle {}: {first_kind} `{first_name}` must run before itself",
1769 i + 1,
1770 )
1771 .unwrap();
1772 writeln!(message, "{first_kind} `{first_name}`").unwrap();
1773 for (kind, name) in names.chain(core::iter::once((first_kind, first_name))) {
1774 writeln!(message, " ... which must run before {kind} `{name}`").unwrap();
1775 }
1776 writeln!(message).unwrap();
1777 }
1778
1779 message
1780 }
1781
1782 fn check_for_cross_dependencies(
1783 &self,
1784 dep_results: &CheckGraphResults<NodeId>,
1785 hier_results_connected: &HashSet<(NodeId, NodeId)>,
1786 ) -> Result<(), ScheduleBuildError> {
1787 for &(a, b) in &dep_results.connected {
1788 if hier_results_connected.contains(&(a, b)) || hier_results_connected.contains(&(b, a))
1789 {
1790 let name_a = self.get_node_name(&a);
1791 let name_b = self.get_node_name(&b);
1792 return Err(ScheduleBuildError::CrossDependency(name_a, name_b));
1793 }
1794 }
1795
1796 Ok(())
1797 }
1798
1799 fn check_order_but_intersect(
1800 &self,
1801 dep_results_connected: &HashSet<(NodeId, NodeId)>,
1802 set_system_bitsets: &HashMap<NodeId, FixedBitSet>,
1803 ) -> Result<(), ScheduleBuildError> {
1804 for (a, b) in dep_results_connected {
1806 if !(a.is_set() && b.is_set()) {
1807 continue;
1808 }
1809
1810 let a_systems = set_system_bitsets.get(a).unwrap();
1811 let b_systems = set_system_bitsets.get(b).unwrap();
1812
1813 if !a_systems.is_disjoint(b_systems) {
1814 return Err(ScheduleBuildError::SetsHaveOrderButIntersect(
1815 self.get_node_name(a),
1816 self.get_node_name(b),
1817 ));
1818 }
1819 }
1820
1821 Ok(())
1822 }
1823
1824 fn check_system_type_set_ambiguity(
1825 &self,
1826 set_systems: &HashMap<NodeId, Vec<NodeId>>,
1827 ) -> Result<(), ScheduleBuildError> {
1828 for (&id, systems) in set_systems {
1829 let set = &self.system_sets[id.index()];
1830 if set.is_system_type() {
1831 let instances = systems.len();
1832 let ambiguous_with = self.ambiguous_with.edges(id);
1833 let before = self.dependency.graph.edges_directed(id, Incoming);
1834 let after = self.dependency.graph.edges_directed(id, Outgoing);
1835 let relations = before.count() + after.count() + ambiguous_with.count();
1836 if instances > 1 && relations > 0 {
1837 return Err(ScheduleBuildError::SystemTypeSetAmbiguity(
1838 self.get_node_name(&id),
1839 ));
1840 }
1841 }
1842 }
1843 Ok(())
1844 }
1845
1846 fn optionally_check_conflicts(
1848 &self,
1849 conflicts: &[(NodeId, NodeId, Vec<ComponentId>)],
1850 components: &Components,
1851 schedule_label: InternedScheduleLabel,
1852 ) -> Result<(), ScheduleBuildError> {
1853 if self.settings.ambiguity_detection == LogLevel::Ignore || conflicts.is_empty() {
1854 return Ok(());
1855 }
1856
1857 let message = self.get_conflicts_error_message(conflicts, components);
1858 match self.settings.ambiguity_detection {
1859 LogLevel::Ignore => Ok(()),
1860 LogLevel::Warn => {
1861 warn!("Schedule {schedule_label:?} has ambiguities.\n{}", message);
1862 Ok(())
1863 }
1864 LogLevel::Error => Err(ScheduleBuildError::Ambiguity(message)),
1865 }
1866 }
1867
1868 fn get_conflicts_error_message(
1869 &self,
1870 ambiguities: &[(NodeId, NodeId, Vec<ComponentId>)],
1871 components: &Components,
1872 ) -> String {
1873 let n_ambiguities = ambiguities.len();
1874
1875 let mut message = format!(
1876 "{n_ambiguities} pairs of systems with conflicting data access have indeterminate execution order. \
1877 Consider adding `before`, `after`, or `ambiguous_with` relationships between these:\n",
1878 );
1879
1880 for (name_a, name_b, conflicts) in self.conflicts_to_string(ambiguities, components) {
1881 writeln!(message, " -- {name_a} and {name_b}").unwrap();
1882
1883 if !conflicts.is_empty() {
1884 writeln!(message, " conflict on: {conflicts:?}").unwrap();
1885 } else {
1886 let world = core::any::type_name::<World>();
1888 writeln!(message, " conflict on: {world}").unwrap();
1889 }
1890 }
1891
1892 message
1893 }
1894
1895 pub fn conflicts_to_string<'a>(
1897 &'a self,
1898 ambiguities: &'a [(NodeId, NodeId, Vec<ComponentId>)],
1899 components: &'a Components,
1900 ) -> impl Iterator<Item = (String, String, Vec<&'a str>)> + 'a {
1901 ambiguities
1902 .iter()
1903 .map(move |(system_a, system_b, conflicts)| {
1904 let name_a = self.get_node_name(system_a);
1905 let name_b = self.get_node_name(system_b);
1906
1907 debug_assert!(system_a.is_system(), "{name_a} is not a system.");
1908 debug_assert!(system_b.is_system(), "{name_b} is not a system.");
1909
1910 let conflict_names: Vec<_> = conflicts
1911 .iter()
1912 .map(|id| components.get_name(*id).unwrap())
1913 .collect();
1914
1915 (name_a, name_b, conflict_names)
1916 })
1917 }
1918
1919 fn traverse_sets_containing_node(&self, id: NodeId, f: &mut impl FnMut(NodeId) -> bool) {
1920 for (set_id, _, _) in self.hierarchy.graph.edges_directed(id, Incoming) {
1921 if f(set_id) {
1922 self.traverse_sets_containing_node(set_id, f);
1923 }
1924 }
1925 }
1926
1927 fn names_of_sets_containing_node(&self, id: &NodeId) -> Vec<String> {
1928 let mut sets = HashSet::new();
1929 self.traverse_sets_containing_node(*id, &mut |set_id| {
1930 !self.system_sets[set_id.index()].is_system_type() && sets.insert(set_id)
1931 });
1932 let mut sets: Vec<_> = sets
1933 .into_iter()
1934 .map(|set_id| self.get_node_name(&set_id))
1935 .collect();
1936 sets.sort();
1937 sets
1938 }
1939}
1940
1941#[derive(Error, Display, Debug)]
1943#[error(ignore)]
1944#[non_exhaustive]
1945pub enum ScheduleBuildError {
1946 #[display("System set `{_0}` contains itself.")]
1948 HierarchyLoop(String),
1949 #[display("System set hierarchy contains cycle(s).\n{_0}")]
1951 HierarchyCycle(String),
1952 #[display("System set hierarchy contains redundant edges.\n{_0}")]
1956 HierarchyRedundancy(String),
1957 #[display("System set `{_0}` depends on itself.")]
1959 DependencyLoop(String),
1960 #[display("System dependencies contain cycle(s).\n{_0}")]
1962 DependencyCycle(String),
1963 #[display("`{_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.")]
1965 CrossDependency(String, String),
1966 #[display("`{_0}` and `{_1}` have a `before`-`after` relationship (which may be transitive) but share systems.")]
1968 SetsHaveOrderButIntersect(String, String),
1969 #[display("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.")]
1971 SystemTypeSetAmbiguity(String),
1972 #[display("Systems with conflicting access have indeterminate run order.\n{_0}")]
1976 Ambiguity(String),
1977 #[display("Systems in schedule have not been initialized.")]
1979 Uninitialized,
1980}
1981
1982#[derive(Debug, Clone, PartialEq)]
1984pub enum LogLevel {
1985 Ignore,
1987 Warn,
1989 Error,
1991}
1992
1993#[derive(Clone, Debug)]
1995pub struct ScheduleBuildSettings {
1996 pub ambiguity_detection: LogLevel,
2001 pub hierarchy_detection: LogLevel,
2007 pub auto_insert_apply_deferred: bool,
2017 pub use_shortnames: bool,
2021 pub report_sets: bool,
2025}
2026
2027impl Default for ScheduleBuildSettings {
2028 fn default() -> Self {
2029 Self::new()
2030 }
2031}
2032
2033impl ScheduleBuildSettings {
2034 pub const fn new() -> Self {
2037 Self {
2038 ambiguity_detection: LogLevel::Ignore,
2039 hierarchy_detection: LogLevel::Warn,
2040 auto_insert_apply_deferred: true,
2041 use_shortnames: true,
2042 report_sets: true,
2043 }
2044 }
2045}
2046
2047#[derive(Error, Display, Debug)]
2050#[display("executable schedule has not been built")]
2051pub struct ScheduleNotInitialized;
2052
2053#[cfg(test)]
2054mod tests {
2055 use bevy_ecs_macros::ScheduleLabel;
2056
2057 use crate::{
2058 self as bevy_ecs,
2059 prelude::{Res, Resource},
2060 schedule::{
2061 tests::ResMut, IntoSystemConfigs, IntoSystemSetConfigs, Schedule,
2062 ScheduleBuildSettings, SystemSet,
2063 },
2064 system::Commands,
2065 world::World,
2066 };
2067
2068 use super::Schedules;
2069
2070 #[derive(Resource)]
2071 struct Resource1;
2072
2073 #[derive(Resource)]
2074 struct Resource2;
2075
2076 #[test]
2078 fn ambiguous_with_not_breaking_run_conditions() {
2079 #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
2080 struct Set;
2081
2082 let mut world = World::new();
2083 let mut schedule = Schedule::default();
2084
2085 schedule.configure_sets(Set.run_if(|| false));
2086 schedule.add_systems(
2087 (|| panic!("This system must not run"))
2088 .ambiguous_with(|| ())
2089 .in_set(Set),
2090 );
2091 schedule.run(&mut world);
2092 }
2093
2094 #[test]
2095 fn inserts_a_sync_point() {
2096 let mut schedule = Schedule::default();
2097 let mut world = World::default();
2098 schedule.add_systems(
2099 (
2100 |mut commands: Commands| commands.insert_resource(Resource1),
2101 |_: Res<Resource1>| {},
2102 )
2103 .chain(),
2104 );
2105 schedule.run(&mut world);
2106
2107 assert_eq!(schedule.executable.systems.len(), 3);
2109 }
2110
2111 #[test]
2112 fn merges_sync_points_into_one() {
2113 let mut schedule = Schedule::default();
2114 let mut world = World::default();
2115 schedule.add_systems(
2117 (
2118 (
2119 |mut commands: Commands| commands.insert_resource(Resource1),
2120 |mut commands: Commands| commands.insert_resource(Resource2),
2121 ),
2122 |_: Res<Resource1>, _: Res<Resource2>| {},
2123 )
2124 .chain(),
2125 );
2126 schedule.run(&mut world);
2127
2128 assert_eq!(schedule.executable.systems.len(), 4);
2130
2131 schedule.add_systems(((
2133 (
2134 |mut commands: Commands| commands.insert_resource(Resource1),
2135 |mut commands: Commands| commands.insert_resource(Resource2),
2136 ),
2137 |_: Res<Resource1>, _: Res<Resource2>| {},
2138 )
2139 .chain(),));
2140 schedule.run(&mut world);
2141
2142 assert_eq!(schedule.executable.systems.len(), 7);
2143 }
2144
2145 #[test]
2146 fn adds_multiple_consecutive_syncs() {
2147 let mut schedule = Schedule::default();
2148 let mut world = World::default();
2149 schedule.add_systems(
2151 (
2152 |mut commands: Commands| commands.insert_resource(Resource1),
2153 |mut commands: Commands| commands.insert_resource(Resource2),
2154 |_: Res<Resource1>, _: Res<Resource2>| {},
2155 )
2156 .chain(),
2157 );
2158 schedule.run(&mut world);
2159
2160 assert_eq!(schedule.executable.systems.len(), 5);
2161 }
2162
2163 #[test]
2164 fn disable_auto_sync_points() {
2165 let mut schedule = Schedule::default();
2166 schedule.set_build_settings(ScheduleBuildSettings {
2167 auto_insert_apply_deferred: false,
2168 ..Default::default()
2169 });
2170 let mut world = World::default();
2171 schedule.add_systems(
2172 (
2173 |mut commands: Commands| commands.insert_resource(Resource1),
2174 |res: Option<Res<Resource1>>| assert!(res.is_none()),
2175 )
2176 .chain(),
2177 );
2178 schedule.run(&mut world);
2179
2180 assert_eq!(schedule.executable.systems.len(), 2);
2181 }
2182
2183 mod no_sync_edges {
2184 use super::*;
2185
2186 fn insert_resource(mut commands: Commands) {
2187 commands.insert_resource(Resource1);
2188 }
2189
2190 fn resource_does_not_exist(res: Option<Res<Resource1>>) {
2191 assert!(res.is_none());
2192 }
2193
2194 #[derive(SystemSet, Hash, PartialEq, Eq, Debug, Clone)]
2195 enum Sets {
2196 A,
2197 B,
2198 }
2199
2200 fn check_no_sync_edges(add_systems: impl FnOnce(&mut Schedule)) {
2201 let mut schedule = Schedule::default();
2202 let mut world = World::default();
2203 add_systems(&mut schedule);
2204
2205 schedule.run(&mut world);
2206
2207 assert_eq!(schedule.executable.systems.len(), 2);
2208 }
2209
2210 #[test]
2211 fn system_to_system_after() {
2212 check_no_sync_edges(|schedule| {
2213 schedule.add_systems((
2214 insert_resource,
2215 resource_does_not_exist.after_ignore_deferred(insert_resource),
2216 ));
2217 });
2218 }
2219
2220 #[test]
2221 fn system_to_system_before() {
2222 check_no_sync_edges(|schedule| {
2223 schedule.add_systems((
2224 insert_resource.before_ignore_deferred(resource_does_not_exist),
2225 resource_does_not_exist,
2226 ));
2227 });
2228 }
2229
2230 #[test]
2231 fn set_to_system_after() {
2232 check_no_sync_edges(|schedule| {
2233 schedule
2234 .add_systems((insert_resource, resource_does_not_exist.in_set(Sets::A)))
2235 .configure_sets(Sets::A.after_ignore_deferred(insert_resource));
2236 });
2237 }
2238
2239 #[test]
2240 fn set_to_system_before() {
2241 check_no_sync_edges(|schedule| {
2242 schedule
2243 .add_systems((insert_resource.in_set(Sets::A), resource_does_not_exist))
2244 .configure_sets(Sets::A.before_ignore_deferred(resource_does_not_exist));
2245 });
2246 }
2247
2248 #[test]
2249 fn set_to_set_after() {
2250 check_no_sync_edges(|schedule| {
2251 schedule
2252 .add_systems((
2253 insert_resource.in_set(Sets::A),
2254 resource_does_not_exist.in_set(Sets::B),
2255 ))
2256 .configure_sets(Sets::B.after_ignore_deferred(Sets::A));
2257 });
2258 }
2259
2260 #[test]
2261 fn set_to_set_before() {
2262 check_no_sync_edges(|schedule| {
2263 schedule
2264 .add_systems((
2265 insert_resource.in_set(Sets::A),
2266 resource_does_not_exist.in_set(Sets::B),
2267 ))
2268 .configure_sets(Sets::A.before_ignore_deferred(Sets::B));
2269 });
2270 }
2271 }
2272
2273 mod no_sync_chain {
2274 use super::*;
2275
2276 #[derive(Resource)]
2277 struct Ra;
2278
2279 #[derive(Resource)]
2280 struct Rb;
2281
2282 #[derive(Resource)]
2283 struct Rc;
2284
2285 fn run_schedule(expected_num_systems: usize, add_systems: impl FnOnce(&mut Schedule)) {
2286 let mut schedule = Schedule::default();
2287 let mut world = World::default();
2288 add_systems(&mut schedule);
2289
2290 schedule.run(&mut world);
2291
2292 assert_eq!(schedule.executable.systems.len(), expected_num_systems);
2293 }
2294
2295 #[test]
2296 fn only_chain_outside() {
2297 run_schedule(5, |schedule: &mut Schedule| {
2298 schedule.add_systems(
2299 (
2300 (
2301 |mut commands: Commands| commands.insert_resource(Ra),
2302 |mut commands: Commands| commands.insert_resource(Rb),
2303 ),
2304 (
2305 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2306 assert!(res_a.is_some());
2307 assert!(res_b.is_some());
2308 },
2309 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2310 assert!(res_a.is_some());
2311 assert!(res_b.is_some());
2312 },
2313 ),
2314 )
2315 .chain(),
2316 );
2317 });
2318
2319 run_schedule(4, |schedule: &mut Schedule| {
2320 schedule.add_systems(
2321 (
2322 (
2323 |mut commands: Commands| commands.insert_resource(Ra),
2324 |mut commands: Commands| commands.insert_resource(Rb),
2325 ),
2326 (
2327 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2328 assert!(res_a.is_none());
2329 assert!(res_b.is_none());
2330 },
2331 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2332 assert!(res_a.is_none());
2333 assert!(res_b.is_none());
2334 },
2335 ),
2336 )
2337 .chain_ignore_deferred(),
2338 );
2339 });
2340 }
2341
2342 #[test]
2343 fn chain_first() {
2344 run_schedule(6, |schedule: &mut Schedule| {
2345 schedule.add_systems(
2346 (
2347 (
2348 |mut commands: Commands| commands.insert_resource(Ra),
2349 |mut commands: Commands, res_a: Option<Res<Ra>>| {
2350 commands.insert_resource(Rb);
2351 assert!(res_a.is_some());
2352 },
2353 )
2354 .chain(),
2355 (
2356 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2357 assert!(res_a.is_some());
2358 assert!(res_b.is_some());
2359 },
2360 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2361 assert!(res_a.is_some());
2362 assert!(res_b.is_some());
2363 },
2364 ),
2365 )
2366 .chain(),
2367 );
2368 });
2369
2370 run_schedule(5, |schedule: &mut Schedule| {
2371 schedule.add_systems(
2372 (
2373 (
2374 |mut commands: Commands| commands.insert_resource(Ra),
2375 |mut commands: Commands, res_a: Option<Res<Ra>>| {
2376 commands.insert_resource(Rb);
2377 assert!(res_a.is_some());
2378 },
2379 )
2380 .chain(),
2381 (
2382 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2383 assert!(res_a.is_some());
2384 assert!(res_b.is_none());
2385 },
2386 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2387 assert!(res_a.is_some());
2388 assert!(res_b.is_none());
2389 },
2390 ),
2391 )
2392 .chain_ignore_deferred(),
2393 );
2394 });
2395 }
2396
2397 #[test]
2398 fn chain_second() {
2399 run_schedule(6, |schedule: &mut Schedule| {
2400 schedule.add_systems(
2401 (
2402 (
2403 |mut commands: Commands| commands.insert_resource(Ra),
2404 |mut commands: Commands| commands.insert_resource(Rb),
2405 ),
2406 (
2407 |mut commands: Commands,
2408 res_a: Option<Res<Ra>>,
2409 res_b: Option<Res<Rb>>| {
2410 commands.insert_resource(Rc);
2411 assert!(res_a.is_some());
2412 assert!(res_b.is_some());
2413 },
2414 |res_a: Option<Res<Ra>>,
2415 res_b: Option<Res<Rb>>,
2416 res_c: Option<Res<Rc>>| {
2417 assert!(res_a.is_some());
2418 assert!(res_b.is_some());
2419 assert!(res_c.is_some());
2420 },
2421 )
2422 .chain(),
2423 )
2424 .chain(),
2425 );
2426 });
2427
2428 run_schedule(5, |schedule: &mut Schedule| {
2429 schedule.add_systems(
2430 (
2431 (
2432 |mut commands: Commands| commands.insert_resource(Ra),
2433 |mut commands: Commands| commands.insert_resource(Rb),
2434 ),
2435 (
2436 |mut commands: Commands,
2437 res_a: Option<Res<Ra>>,
2438 res_b: Option<Res<Rb>>| {
2439 commands.insert_resource(Rc);
2440 assert!(res_a.is_none());
2441 assert!(res_b.is_none());
2442 },
2443 |res_a: Option<Res<Ra>>,
2444 res_b: Option<Res<Rb>>,
2445 res_c: Option<Res<Rc>>| {
2446 assert!(res_a.is_some());
2447 assert!(res_b.is_some());
2448 assert!(res_c.is_some());
2449 },
2450 )
2451 .chain(),
2452 )
2453 .chain_ignore_deferred(),
2454 );
2455 });
2456 }
2457
2458 #[test]
2459 fn chain_all() {
2460 run_schedule(7, |schedule: &mut Schedule| {
2461 schedule.add_systems(
2462 (
2463 (
2464 |mut commands: Commands| commands.insert_resource(Ra),
2465 |mut commands: Commands, res_a: Option<Res<Ra>>| {
2466 commands.insert_resource(Rb);
2467 assert!(res_a.is_some());
2468 },
2469 )
2470 .chain(),
2471 (
2472 |mut commands: Commands,
2473 res_a: Option<Res<Ra>>,
2474 res_b: Option<Res<Rb>>| {
2475 commands.insert_resource(Rc);
2476 assert!(res_a.is_some());
2477 assert!(res_b.is_some());
2478 },
2479 |res_a: Option<Res<Ra>>,
2480 res_b: Option<Res<Rb>>,
2481 res_c: Option<Res<Rc>>| {
2482 assert!(res_a.is_some());
2483 assert!(res_b.is_some());
2484 assert!(res_c.is_some());
2485 },
2486 )
2487 .chain(),
2488 )
2489 .chain(),
2490 );
2491 });
2492
2493 run_schedule(6, |schedule: &mut Schedule| {
2494 schedule.add_systems(
2495 (
2496 (
2497 |mut commands: Commands| commands.insert_resource(Ra),
2498 |mut commands: Commands, res_a: Option<Res<Ra>>| {
2499 commands.insert_resource(Rb);
2500 assert!(res_a.is_some());
2501 },
2502 )
2503 .chain(),
2504 (
2505 |mut commands: Commands,
2506 res_a: Option<Res<Ra>>,
2507 res_b: Option<Res<Rb>>| {
2508 commands.insert_resource(Rc);
2509 assert!(res_a.is_some());
2510 assert!(res_b.is_none());
2511 },
2512 |res_a: Option<Res<Ra>>,
2513 res_b: Option<Res<Rb>>,
2514 res_c: Option<Res<Rc>>| {
2515 assert!(res_a.is_some());
2516 assert!(res_b.is_some());
2517 assert!(res_c.is_some());
2518 },
2519 )
2520 .chain(),
2521 )
2522 .chain_ignore_deferred(),
2523 );
2524 });
2525 }
2526 }
2527
2528 #[derive(ScheduleLabel, Hash, Debug, Clone, PartialEq, Eq)]
2529 struct TestSchedule;
2530
2531 #[derive(Resource)]
2532 struct CheckSystemRan(usize);
2533
2534 #[test]
2535 fn add_systems_to_existing_schedule() {
2536 let mut schedules = Schedules::default();
2537 let schedule = Schedule::new(TestSchedule);
2538
2539 schedules.insert(schedule);
2540 schedules.add_systems(TestSchedule, |mut ran: ResMut<CheckSystemRan>| ran.0 += 1);
2541
2542 let mut world = World::new();
2543
2544 world.insert_resource(CheckSystemRan(0));
2545 world.insert_resource(schedules);
2546 world.run_schedule(TestSchedule);
2547
2548 let value = world
2549 .get_resource::<CheckSystemRan>()
2550 .expect("CheckSystemRan Resource Should Exist");
2551 assert_eq!(value.0, 1);
2552 }
2553
2554 #[test]
2555 fn add_systems_to_non_existing_schedule() {
2556 let mut schedules = Schedules::default();
2557
2558 schedules.add_systems(TestSchedule, |mut ran: ResMut<CheckSystemRan>| ran.0 += 1);
2559
2560 let mut world = World::new();
2561
2562 world.insert_resource(CheckSystemRan(0));
2563 world.insert_resource(schedules);
2564 world.run_schedule(TestSchedule);
2565
2566 let value = world
2567 .get_resource::<CheckSystemRan>()
2568 .expect("CheckSystemRan Resource Should Exist");
2569 assert_eq!(value.0, 1);
2570 }
2571
2572 #[derive(SystemSet, Debug, Hash, Clone, PartialEq, Eq)]
2573 enum TestSet {
2574 First,
2575 Second,
2576 }
2577
2578 #[test]
2579 fn configure_set_on_existing_schedule() {
2580 let mut schedules = Schedules::default();
2581 let schedule = Schedule::new(TestSchedule);
2582
2583 schedules.insert(schedule);
2584
2585 schedules.configure_sets(TestSchedule, (TestSet::First, TestSet::Second).chain());
2586 schedules.add_systems(
2587 TestSchedule,
2588 (|mut ran: ResMut<CheckSystemRan>| {
2589 assert_eq!(ran.0, 0);
2590 ran.0 += 1;
2591 })
2592 .in_set(TestSet::First),
2593 );
2594
2595 schedules.add_systems(
2596 TestSchedule,
2597 (|mut ran: ResMut<CheckSystemRan>| {
2598 assert_eq!(ran.0, 1);
2599 ran.0 += 1;
2600 })
2601 .in_set(TestSet::Second),
2602 );
2603
2604 let mut world = World::new();
2605
2606 world.insert_resource(CheckSystemRan(0));
2607 world.insert_resource(schedules);
2608 world.run_schedule(TestSchedule);
2609
2610 let value = world
2611 .get_resource::<CheckSystemRan>()
2612 .expect("CheckSystemRan Resource Should Exist");
2613 assert_eq!(value.0, 2);
2614 }
2615
2616 #[test]
2617 fn configure_set_on_new_schedule() {
2618 let mut schedules = Schedules::default();
2619
2620 schedules.configure_sets(TestSchedule, (TestSet::First, TestSet::Second).chain());
2621 schedules.add_systems(
2622 TestSchedule,
2623 (|mut ran: ResMut<CheckSystemRan>| {
2624 assert_eq!(ran.0, 0);
2625 ran.0 += 1;
2626 })
2627 .in_set(TestSet::First),
2628 );
2629
2630 schedules.add_systems(
2631 TestSchedule,
2632 (|mut ran: ResMut<CheckSystemRan>| {
2633 assert_eq!(ran.0, 1);
2634 ran.0 += 1;
2635 })
2636 .in_set(TestSet::Second),
2637 );
2638
2639 let mut world = World::new();
2640
2641 world.insert_resource(CheckSystemRan(0));
2642 world.insert_resource(schedules);
2643 world.run_schedule(TestSchedule);
2644
2645 let value = world
2646 .get_resource::<CheckSystemRan>()
2647 .expect("CheckSystemRan Resource Should Exist");
2648 assert_eq!(value.0, 2);
2649 }
2650}