bevy_ecs/schedule/
schedule.rs

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/// Resource that stores [`Schedule`]s mapped to [`ScheduleLabel`]s excluding the current running [`Schedule`].
29#[derive(Default, Resource)]
30pub struct Schedules {
31    inner: HashMap<InternedScheduleLabel, Schedule>,
32    /// List of [`ComponentId`]s to ignore when reporting system order ambiguity conflicts
33    pub ignored_scheduling_ambiguities: BTreeSet<ComponentId>,
34}
35
36impl Schedules {
37    /// Constructs an empty `Schedules` with zero initial capacity.
38    pub fn new() -> Self {
39        Self {
40            inner: HashMap::new(),
41            ignored_scheduling_ambiguities: BTreeSet::new(),
42        }
43    }
44
45    /// Inserts a labeled schedule into the map.
46    ///
47    /// If the map already had an entry for `label`, `schedule` is inserted,
48    /// and the old schedule is returned. Otherwise, `None` is returned.
49    pub fn insert(&mut self, schedule: Schedule) -> Option<Schedule> {
50        self.inner.insert(schedule.label, schedule)
51    }
52
53    /// Removes the schedule corresponding to the `label` from the map, returning it if it existed.
54    pub fn remove(&mut self, label: impl ScheduleLabel) -> Option<Schedule> {
55        self.inner.remove(&label.intern())
56    }
57
58    /// Removes the (schedule, label) pair corresponding to the `label` from the map, returning it if it existed.
59    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    /// Does a schedule with the provided label already exist?
67    pub fn contains(&self, label: impl ScheduleLabel) -> bool {
68        self.inner.contains_key(&label.intern())
69    }
70
71    /// Returns a reference to the schedule associated with `label`, if it exists.
72    pub fn get(&self, label: impl ScheduleLabel) -> Option<&Schedule> {
73        self.inner.get(&label.intern())
74    }
75
76    /// Returns a mutable reference to the schedule associated with `label`, if it exists.
77    pub fn get_mut(&mut self, label: impl ScheduleLabel) -> Option<&mut Schedule> {
78        self.inner.get_mut(&label.intern())
79    }
80
81    /// Returns a mutable reference to the schedules associated with `label`, creating one if it doesn't already exist.
82    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    /// Returns an iterator over all schedules. Iteration order is undefined.
89    pub fn iter(&self) -> impl Iterator<Item = (&dyn ScheduleLabel, &Schedule)> {
90        self.inner
91            .iter()
92            .map(|(label, schedule)| (&**label, schedule))
93    }
94    /// Returns an iterator over mutable references to all schedules. Iteration order is undefined.
95    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    /// Iterates the change ticks of all systems in all stored schedules and clamps any older than
102    /// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE).
103    /// This prevents overflow and thus prevents false positives.
104    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        // label used when trace feature is enabled
108        #[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    /// Applies the provided [`ScheduleBuildSettings`] to all schedules.
119    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    /// Ignore system order ambiguities caused by conflicts on [`Component`]s of type `T`.
126    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    /// Ignore system order ambiguities caused by conflicts on [`Resource`]s of type `T`.
132    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    /// Iterate through the [`ComponentId`]'s that will be ignored.
138    pub fn iter_ignored_ambiguities(&self) -> impl Iterator<Item = &ComponentId> + '_ {
139        self.ignored_scheduling_ambiguities.iter()
140    }
141
142    /// Prints the names of the components and resources with [`info`]
143    ///
144    /// May panic or retrieve incorrect names if [`Components`] is not from the same
145    /// world
146    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    /// Adds one or more systems to the [`Schedule`] matching the provided [`ScheduleLabel`].
158    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    /// Configures a collection of system sets in the provided schedule, adding any sets that do not exist.
169    #[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    /// Suppress warnings and errors that would result from systems in these sets having ambiguities
181    /// (conflicting access but indeterminate order) with systems in `set`.
182    ///
183    /// When possible, do this directly in the `.add_systems(Update, a.ambiguous_with(b))` call.
184    /// However, sometimes two independent plugins `A` and `B` are reported as ambiguous, which you
185    /// can only suppress as the consumer of both.
186    #[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/// Chain systems into dependencies
212#[derive(PartialEq)]
213pub enum Chain {
214    /// Run nodes in order. If there are deferred parameters in preceding systems a
215    /// [`apply_deferred`] will be added on the edge.
216    Yes,
217    /// Run nodes in order. This will not add [`apply_deferred`] between nodes.
218    YesIgnoreDeferred,
219    /// Nodes are allowed to run in any order.
220    No,
221}
222
223/// A collection of systems, and the metadata and executor needed to run them
224/// in a certain order under certain conditions.
225///
226/// # Example
227/// Here is an example of a `Schedule` running a "Hello world" system:
228/// ```
229/// # use bevy_ecs::prelude::*;
230/// fn hello_world() { println!("Hello world!") }
231///
232/// fn main() {
233///     let mut world = World::new();
234///     let mut schedule = Schedule::default();
235///     schedule.add_systems(hello_world);
236///
237///     schedule.run(&mut world);
238/// }
239/// ```
240///
241/// A schedule can also run several systems in an ordered way:
242/// ```
243/// # use bevy_ecs::prelude::*;
244/// fn system_one() { println!("System 1 works!") }
245/// fn system_two() { println!("System 2 works!") }
246/// fn system_three() { println!("System 3 works!") }
247///
248/// fn main() {
249///     let mut world = World::new();
250///     let mut schedule = Schedule::default();
251///     schedule.add_systems((
252///         system_two,
253///         system_one.before(system_two),
254///         system_three.after(system_two),
255///     ));
256///
257///     schedule.run(&mut world);
258/// }
259/// ```
260pub 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    /// Creates a schedule with a default label. Only use in situations where
273    /// you don't care about the [`ScheduleLabel`]. Inserting a default schedule
274    /// into the world risks overwriting another schedule. For most situations
275    /// you should use [`Schedule::new`].
276    fn default() -> Self {
277        Self::new(DefaultSchedule)
278    }
279}
280
281impl Schedule {
282    /// Constructs an empty `Schedule`.
283    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    /// Get the `InternedScheduleLabel` for this `Schedule`.
294    pub fn label(&self) -> InternedScheduleLabel {
295        self.label
296    }
297
298    /// Add a collection of systems to the schedule.
299    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    /// Suppress warnings and errors that would result from systems in these sets having ambiguities
305    /// (conflicting access but indeterminate order) with systems in `set`.
306    #[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    /// Configures a collection of system sets in this schedule, adding them if they does not exist.
336    #[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    /// Changes miscellaneous build settings.
343    pub fn set_build_settings(&mut self, settings: ScheduleBuildSettings) -> &mut Self {
344        self.graph.settings = settings;
345        self
346    }
347
348    /// Returns the schedule's current `ScheduleBuildSettings`.
349    pub fn get_build_settings(&self) -> ScheduleBuildSettings {
350        self.graph.settings.clone()
351    }
352
353    /// Returns the schedule's current execution strategy.
354    pub fn get_executor_kind(&self) -> ExecutorKind {
355        self.executor.kind()
356    }
357
358    /// Sets the schedule's execution strategy.
359    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    /// Set whether the schedule applies deferred system buffers on final time or not. This is a catch-all
368    /// in case a system uses commands but was not explicitly ordered before an instance of
369    /// [`apply_deferred`]. By default this
370    /// setting is true, but may be disabled if needed.
371    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    /// Runs all systems in this schedule on the `world`, using its current execution strategy.
377    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    /// Initializes any newly-added systems and conditions, rebuilds the executable schedule,
401    /// and re-initializes the executor.
402    ///
403    /// Moves all systems and run conditions out of the [`ScheduleGraph`].
404    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    /// Returns the [`ScheduleGraph`].
430    pub fn graph(&self) -> &ScheduleGraph {
431        &self.graph
432    }
433
434    /// Returns a mutable reference to the [`ScheduleGraph`].
435    pub fn graph_mut(&mut self) -> &mut ScheduleGraph {
436        &mut self.graph
437    }
438
439    /// Returns the [`SystemSchedule`].
440    pub(crate) fn executable(&self) -> &SystemSchedule {
441        &self.executable
442    }
443
444    /// Iterates the change ticks of all systems in the schedule and clamps any older than
445    /// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE).
446    /// This prevents overflow and thus prevents false positives.
447    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    /// Directly applies any accumulated [`Deferred`](crate::system::Deferred) system parameters (like [`Commands`](crate::prelude::Commands)) to the `world`.
468    ///
469    /// Like always, deferred system parameters are applied in the "topological sort order" of the schedule graph.
470    /// As a result, buffers from one system are only guaranteed to be applied before those of other systems
471    /// if there is an explicit system ordering between the two systems.
472    ///
473    /// This is used in rendering to extract data from the main world, storing the data in system buffers,
474    /// before applying their buffers in a different world.
475    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    /// Returns an iterator over all systems in this schedule.
482    ///
483    /// Note: this method will return [`ScheduleNotInitialized`] if the
484    /// schedule has never been initialized or run.
485    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    /// Returns the number of systems in this schedule.
503    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/// A directed acyclic graph structure.
513#[derive(Default)]
514pub struct Dag {
515    /// A directed graph.
516    graph: DiGraphMap<NodeId, ()>,
517    /// A cached topological ordering of the graph.
518    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    /// The directed graph of the stored systems, connected by their ordering dependencies.
530    pub fn graph(&self) -> &DiGraphMap<NodeId, ()> {
531        &self.graph
532    }
533
534    /// A cached topological ordering of the graph.
535    ///
536    /// The order is determined by the ordering dependencies between systems.
537    pub fn cached_topsort(&self) -> &[NodeId] {
538        &self.topsort
539    }
540}
541
542/// A [`SystemSet`] with metadata, stored in a [`ScheduleGraph`].
543struct 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
565/// A [`BoxedSystem`] with metadata, stored in a [`ScheduleGraph`].
566struct 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/// Metadata for a [`Schedule`].
587///
588/// The order isn't optimized; calling `ScheduleGraph::build_schedule` will return a
589/// `SystemSchedule` where the order is optimized for execution.
590#[derive(Default)]
591pub struct ScheduleGraph {
592    /// List of systems in the schedule
593    systems: Vec<SystemNode>,
594    /// List of conditions for each system, in the same order as `systems`
595    system_conditions: Vec<Vec<BoxedCondition>>,
596    /// List of system sets in the schedule
597    system_sets: Vec<SystemSetNode>,
598    /// List of conditions for each system set, in the same order as `system_sets`
599    system_set_conditions: Vec<Vec<BoxedCondition>>,
600    /// Map from system set to node id
601    system_set_ids: HashMap<InternedSystemSet, NodeId>,
602    /// Systems that have not been initialized yet; for system sets, we store the index of the first uninitialized condition
603    /// (all the conditions after that index still need to be initialized)
604    uninit: Vec<(NodeId, usize)>,
605    /// Directed acyclic graph of the hierarchy (which systems/sets are children of which sets)
606    hierarchy: Dag,
607    /// Directed acyclic graph of the dependency (which systems/sets have to run before which other systems/sets)
608    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    /// Dependency edges that will **not** automatically insert an instance of `apply_deferred` on the edge.
616    no_sync_edges: BTreeSet<(NodeId, NodeId)>,
617    auto_sync_node_ids: HashMap<u32, NodeId>,
618}
619
620impl ScheduleGraph {
621    /// Creates an empty [`ScheduleGraph`] with default settings.
622    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    /// Returns the system at the given [`NodeId`], if it exists.
644    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    /// Returns `true` if the given system set is part of the graph. Otherwise, returns `false`.
654    pub fn contains_set(&self, set: impl SystemSet) -> bool {
655        self.system_set_ids.contains_key(&set.intern())
656    }
657
658    /// Returns the system at the given [`NodeId`].
659    ///
660    /// Panics if it doesn't exist.
661    #[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    /// Returns the set at the given [`NodeId`], if it exists.
669    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    /// Returns the set at the given [`NodeId`].
677    ///
678    /// Panics if it doesn't exist.
679    #[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    /// Returns an iterator over all systems in this schedule, along with the conditions for each system.
687    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    /// Returns an iterator over all system sets in this schedule, along with the conditions for each
701    /// system set.
702    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    /// Returns the [`Dag`] of the hierarchy.
712    ///
713    /// The hierarchy is a directed acyclic graph of the systems and sets,
714    /// where an edge denotes that a system or set is the child of another set.
715    pub fn hierarchy(&self) -> &Dag {
716        &self.hierarchy
717    }
718
719    /// Returns the [`Dag`] of the dependencies in the schedule.
720    ///
721    /// Nodes in this graph are systems and sets, and edges denote that
722    /// a system or set has to run before another system or set.
723    pub fn dependency(&self) -> &Dag {
724        &self.dependency
725    }
726
727    /// Returns the list of systems that conflict with each other, i.e. have ambiguities in their access.
728    ///
729    /// If the `Vec<ComponentId>` is empty, the systems conflict on [`World`] access.
730    /// Must be called after [`ScheduleGraph::build_schedule`] to be non-empty.
731    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    /// Adds the config nodes to the graph.
772    ///
773    /// `collect_nodes` controls whether the `NodeId`s of the processed config nodes are stored in the returned [`ProcessConfigsResult`].
774    /// `process_config` is the function which processes each individual config node and returns a corresponding `NodeId`.
775    ///
776    /// The fields on the returned [`ProcessConfigsResult`] are:
777    /// - `nodes`: a vector of all node ids contained in the nested `NodeConfigs`
778    /// - `densely_chained`: a boolean that is true if all nested nodes are linearly chained (with successive `after` orderings) in the order they are defined
779    #[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                // Densely chained if
798                // * chained and all configs in the chain are densely chained, or
799                // * unchained with a single densely chained config
800                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                        // if the current result is densely chained, we only need to chain the first node
819                        let current_nodes = if current_result.densely_chained {
820                            &current_result.nodes[..1]
821                        } else {
822                            &current_result.nodes
823                        };
824                        // if the previous result was densely chained, we only need to chain the last node
825                        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    /// Add a [`SystemConfig`] to the graph, including its dependencies and conditions.
862    fn add_system_inner(&mut self, config: SystemConfig) -> Result<NodeId, ScheduleBuildError> {
863        let id = NodeId::System(self.systems.len());
864
865        // graph updates are immediate
866        self.update_graphs(id, config.graph_info)?;
867
868        // system init has to be deferred (need `&mut World`)
869        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    /// Add a single `SystemSetConfig` to the graph, including its dependencies and conditions.
882    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        // graph updates are immediate
895        self.update_graphs(id, graph_info)?;
896
897        // system init has to be deferred (need `&mut World`)
898        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    /// Checks that a system set isn't included in itself.
914    /// If not present, add the set to the graph.
915    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    /// Check that no set is included in itself.
941    /// Add all the sets from the [`GraphInfo`]'s hierarchy to the graph.
942    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    /// Checks that no system set is dependent on itself.
955    /// Add all the sets from the [`GraphInfo`]'s dependencies to the graph.
956    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    /// Update the internal graphs (hierarchy, dependency, ambiguity) by adding a single [`GraphInfo`]
986    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            // ensure set also appears in dependency graph
1009            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            // ensure set also appears in hierarchy graph
1031            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    /// Initializes any newly-added systems and conditions by calling [`System::initialize`]
1053    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    /// Build a [`SystemSchedule`] optimized for scheduler access from the [`ScheduleGraph`].
1072    ///
1073    /// This method also
1074    /// - checks for dependency or hierarchy cycles
1075    /// - checks for system access conflicts and reports ambiguities
1076    pub fn build_schedule(
1077        &mut self,
1078        components: &Components,
1079        schedule_label: InternedScheduleLabel,
1080        ignored_ambiguities: &BTreeSet<ComponentId>,
1081    ) -> Result<SystemSchedule, ScheduleBuildError> {
1082        // check hierarchy for cycles
1083        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        // remove redundant edges
1090        self.hierarchy.graph = hier_results.transitive_reduction;
1091
1092        // check dependencies for cycles
1093        self.dependency.topsort =
1094            self.topsort_graph(&self.dependency.graph, ReportCycles::Dependency)?;
1095
1096        // check for systems or system sets depending on sets they belong to
1097        let dep_results = check_graph(&self.dependency.graph, &self.dependency.topsort);
1098        self.check_for_cross_dependencies(&dep_results, &hier_results.connected)?;
1099
1100        // map all system sets to their systems
1101        // go in reverse topological order (bottom-up) for efficiency
1102        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        // check that there are no edges to system-type sets that have multiple instances
1107        self.check_system_type_set_ambiguity(&set_systems)?;
1108
1109        let mut dependency_flattened = self.get_dependency_flattened(&set_systems);
1110
1111        // modify graph with auto sync points
1112        if self.settings.auto_insert_apply_deferred {
1113            dependency_flattened = self.auto_insert_apply_deferred(&mut dependency_flattened)?;
1114        }
1115
1116        // topsort
1117        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        // remove redundant edges
1128        dependency_flattened_dag.graph = flat_results.transitive_reduction;
1129
1130        // flatten: combine `in_set` with `ambiguous_with` information
1131        let ambiguous_with_flattened = self.get_ambiguous_with_flattened(&set_systems);
1132
1133        // check for conflicts
1134        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        // build the schedule
1143        Ok(self.build_schedule_inner(dependency_flattened_dag, hier_results.reachable))
1144    }
1145
1146    // modify the graph to have sync nodes for any dependents after a system with deferred system params
1147    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        // calculate the number of sync points each sync point is from the beginning of the graph
1155        // use the same sync point if the distance is the same
1156        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                    // edge is now redundant
1185                    sync_point_graph.remove_edge(*node, target);
1186                }
1187            }
1188        }
1189
1190        Ok(sync_point_graph)
1191    }
1192
1193    /// add an [`apply_deferred`] system with no config
1194    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        // ignore ambiguities with auto sync points
1204        // They aren't under user control, so no one should know or care.
1205        self.ambiguous_with_all.insert(id);
1206
1207        id
1208    }
1209
1210    /// Returns the `NodeId` of the cached auto sync point. Will create
1211    /// a new one if needed.
1212    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    /// Return a map from system set `NodeId` to a list of system `NodeId`s that are included in the set.
1225    /// Also return a map from system set `NodeId` to a `FixedBitSet` of system `NodeId`s that are included in the set,
1226    /// where the bitset order is the same as `self.systems`
1227    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        // flatten: combine `in_set` with `before` and `after` information
1269        // have to do it like this to preserve transitivity
1270        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                // collapse dependencies for empty sets
1275                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                            // there is no specific component conflicting, but the systems are overall incompatible
1384                            // for example 2 systems with `Query<EntityMut>`
1385                            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                // ignore system sets that have no conditions
1425                // ignore system type sets (already covered, they don't have conditions)
1426                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        // get the number of dependencies and the immediate dependents of each system
1435        // (needed by multi_threaded executor to run systems in the correct order)
1436        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        // get the rows and columns of the hierarchy graph's reachability matrix
1455        // (needed to we can evaluate conditions in the correct order)
1456        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    /// Updates the `SystemSchedule` from the `ScheduleGraph`.
1496    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        // move systems out of old schedule
1508        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        // move systems into new schedule
1529        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
1545/// Values returned by [`ScheduleGraph::process_configs`]
1546struct ProcessConfigsResult {
1547    /// All nodes contained inside this `process_configs` call's [`NodeConfigs`] hierarchy,
1548    /// if `ancestor_chained` is true
1549    nodes: Vec<NodeId>,
1550    /// True if and only if all nodes are "densely chained", meaning that all nested nodes
1551    /// are linearly chained (as if `after` system ordering had been applied between each node)
1552    /// in the order they are defined
1553    densely_chained: bool,
1554}
1555
1556/// Trait used by [`ScheduleGraph::process_configs`] to process a single [`NodeConfig`].
1557trait ProcessNodeConfig: Sized {
1558    /// Process a single [`NodeConfig`].
1559    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
1574/// Used to select the appropriate reporting function.
1575enum ReportCycles {
1576    Hierarchy,
1577    Dependency,
1578}
1579
1580// methods for reporting errors
1581impl 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                // never get the sets of the members or this will infinite recurse when the report_sets setting is on.
1627                .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    /// If [`ScheduleBuildSettings::hierarchy_detection`] is [`LogLevel::Ignore`] this check
1641    /// is skipped.
1642    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    /// Tries to topologically sort `graph`.
1685    ///
1686    /// If the graph is acyclic, returns [`Ok`] with the list of [`NodeId`] in a valid
1687    /// topological order. If the graph contains cycles, returns [`Err`] with the list of
1688    /// strongly-connected components that contain cycles (also in a valid topological order).
1689    ///
1690    /// # Errors
1691    ///
1692    /// If the graph contain cycles, then an error is returned.
1693    fn topsort_graph(
1694        &self,
1695        graph: &DiGraphMap<NodeId, ()>,
1696        report: ReportCycles,
1697    ) -> Result<Vec<NodeId>, ScheduleBuildError> {
1698        // Tarjan's SCC algorithm returns elements in *reverse* topological order.
1699        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            // A strongly-connected component is a group of nodes who can all reach each other
1705            // through one or more paths. If an SCC contains more than one node, there must be
1706            // at least one cycle within them.
1707            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            // reverse to get topological order
1715            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    /// Logs details of cycles in the hierarchy graph.
1737    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    /// Logs details of cycles in the dependency graph.
1759    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        // check that there is no ordering between system sets that intersect
1805        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    /// if [`ScheduleBuildSettings::ambiguity_detection`] is [`LogLevel::Ignore`], this check is skipped
1847    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                // one or both systems must be exclusive
1887                let world = core::any::type_name::<World>();
1888                writeln!(message, "    conflict on: {world}").unwrap();
1889            }
1890        }
1891
1892        message
1893    }
1894
1895    /// convert conflicts to human readable format
1896    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/// Category of errors encountered during schedule construction.
1942#[derive(Error, Display, Debug)]
1943#[error(ignore)]
1944#[non_exhaustive]
1945pub enum ScheduleBuildError {
1946    /// A system set contains itself.
1947    #[display("System set `{_0}` contains itself.")]
1948    HierarchyLoop(String),
1949    /// The hierarchy of system sets contains a cycle.
1950    #[display("System set hierarchy contains cycle(s).\n{_0}")]
1951    HierarchyCycle(String),
1952    /// The hierarchy of system sets contains redundant edges.
1953    ///
1954    /// This error is disabled by default, but can be opted-in using [`ScheduleBuildSettings`].
1955    #[display("System set hierarchy contains redundant edges.\n{_0}")]
1956    HierarchyRedundancy(String),
1957    /// A system (set) has been told to run before itself.
1958    #[display("System set `{_0}` depends on itself.")]
1959    DependencyLoop(String),
1960    /// The dependency graph contains a cycle.
1961    #[display("System dependencies contain cycle(s).\n{_0}")]
1962    DependencyCycle(String),
1963    /// Tried to order a system (set) relative to a system set it belongs to.
1964    #[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    /// Tried to order system sets that share systems.
1967    #[display("`{_0}` and `{_1}` have a `before`-`after` relationship (which may be transitive) but share systems.")]
1968    SetsHaveOrderButIntersect(String, String),
1969    /// Tried to order a system (set) relative to all instances of some system function.
1970    #[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    /// Systems with conflicting access have indeterminate run order.
1973    ///
1974    /// This error is disabled by default, but can be opted-in using [`ScheduleBuildSettings`].
1975    #[display("Systems with conflicting access have indeterminate run order.\n{_0}")]
1976    Ambiguity(String),
1977    /// Tried to run a schedule before all of its systems have been initialized.
1978    #[display("Systems in schedule have not been initialized.")]
1979    Uninitialized,
1980}
1981
1982/// Specifies how schedule construction should respond to detecting a certain kind of issue.
1983#[derive(Debug, Clone, PartialEq)]
1984pub enum LogLevel {
1985    /// Occurrences are completely ignored.
1986    Ignore,
1987    /// Occurrences are logged only.
1988    Warn,
1989    /// Occurrences are logged and result in errors.
1990    Error,
1991}
1992
1993/// Specifies miscellaneous settings for schedule construction.
1994#[derive(Clone, Debug)]
1995pub struct ScheduleBuildSettings {
1996    /// Determines whether the presence of ambiguities (systems with conflicting access but indeterminate order)
1997    /// is only logged or also results in an [`Ambiguity`](ScheduleBuildError::Ambiguity) error.
1998    ///
1999    /// Defaults to [`LogLevel::Ignore`].
2000    pub ambiguity_detection: LogLevel,
2001    /// Determines whether the presence of redundant edges in the hierarchy of system sets is only
2002    /// logged or also results in a [`HierarchyRedundancy`](ScheduleBuildError::HierarchyRedundancy)
2003    /// error.
2004    ///
2005    /// Defaults to [`LogLevel::Warn`].
2006    pub hierarchy_detection: LogLevel,
2007    /// Auto insert [`apply_deferred`] systems into the schedule,
2008    /// when there are [`Deferred`](crate::prelude::Deferred)
2009    /// in one system and there are ordering dependencies on that system. [`Commands`](crate::system::Commands) is one
2010    /// such deferred buffer.
2011    ///
2012    /// You may want to disable this if you only want to sync deferred params at the end of the schedule,
2013    /// or want to manually insert all your sync points.
2014    ///
2015    /// Defaults to `true`
2016    pub auto_insert_apply_deferred: bool,
2017    /// If set to true, node names will be shortened instead of the fully qualified type path.
2018    ///
2019    /// Defaults to `true`.
2020    pub use_shortnames: bool,
2021    /// If set to true, report all system sets the conflicting systems are part of.
2022    ///
2023    /// Defaults to `true`.
2024    pub report_sets: bool,
2025}
2026
2027impl Default for ScheduleBuildSettings {
2028    fn default() -> Self {
2029        Self::new()
2030    }
2031}
2032
2033impl ScheduleBuildSettings {
2034    /// Default build settings.
2035    /// See the field-level documentation for the default value of each field.
2036    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/// Error to denote that [`Schedule::initialize`] or [`Schedule::run`] has not yet been called for
2048/// this schedule.
2049#[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    // regression test for https://github.com/bevyengine/bevy/issues/9114
2077    #[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        // inserted a sync point
2108        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        // insert two parallel command systems, it should only create one sync point
2116        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        // inserted sync points
2129        assert_eq!(schedule.executable.systems.len(), 4);
2130
2131        // merges sync points on rebuild
2132        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        // insert two consecutive command systems, it should create two sync points
2150        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}