bevy_ecs/schedule/
schedule.rs

1#![expect(
2    clippy::module_inception,
3    reason = "This instance of module inception is being discussed; see #17344."
4)]
5use alloc::borrow::Cow;
6use alloc::{
7    boxed::Box,
8    collections::{BTreeMap, BTreeSet},
9    format,
10    string::{String, ToString},
11    vec,
12    vec::Vec,
13};
14use bevy_platform::collections::{HashMap, HashSet};
15use bevy_utils::{default, TypeIdMap};
16use core::{
17    any::{Any, TypeId},
18    fmt::{Debug, Write},
19};
20use disqualified::ShortName;
21use fixedbitset::FixedBitSet;
22use log::{error, info, warn};
23use pass::ScheduleBuildPassObj;
24use thiserror::Error;
25#[cfg(feature = "trace")]
26use tracing::info_span;
27
28use crate::{
29    component::{ComponentId, Components, Tick},
30    error::default_error_handler,
31    prelude::Component,
32    resource::Resource,
33    schedule::*,
34    system::ScheduleSystem,
35    world::World,
36};
37
38use crate::{query::AccessConflicts, storage::SparseSetIndex};
39pub use stepping::Stepping;
40use Direction::{Incoming, Outgoing};
41
42/// Resource that stores [`Schedule`]s mapped to [`ScheduleLabel`]s excluding the current running [`Schedule`].
43#[derive(Default, Resource)]
44pub struct Schedules {
45    inner: HashMap<InternedScheduleLabel, Schedule>,
46    /// List of [`ComponentId`]s to ignore when reporting system order ambiguity conflicts
47    pub ignored_scheduling_ambiguities: BTreeSet<ComponentId>,
48}
49
50impl Schedules {
51    /// Constructs an empty `Schedules` with zero initial capacity.
52    pub fn new() -> Self {
53        Self::default()
54    }
55
56    /// Inserts a labeled schedule into the map.
57    ///
58    /// If the map already had an entry for `label`, `schedule` is inserted,
59    /// and the old schedule is returned. Otherwise, `None` is returned.
60    pub fn insert(&mut self, schedule: Schedule) -> Option<Schedule> {
61        self.inner.insert(schedule.label, schedule)
62    }
63
64    /// Removes the schedule corresponding to the `label` from the map, returning it if it existed.
65    pub fn remove(&mut self, label: impl ScheduleLabel) -> Option<Schedule> {
66        self.inner.remove(&label.intern())
67    }
68
69    /// Removes the (schedule, label) pair corresponding to the `label` from the map, returning it if it existed.
70    pub fn remove_entry(
71        &mut self,
72        label: impl ScheduleLabel,
73    ) -> Option<(InternedScheduleLabel, Schedule)> {
74        self.inner.remove_entry(&label.intern())
75    }
76
77    /// Does a schedule with the provided label already exist?
78    pub fn contains(&self, label: impl ScheduleLabel) -> bool {
79        self.inner.contains_key(&label.intern())
80    }
81
82    /// Returns a reference to the schedule associated with `label`, if it exists.
83    pub fn get(&self, label: impl ScheduleLabel) -> Option<&Schedule> {
84        self.inner.get(&label.intern())
85    }
86
87    /// Returns a mutable reference to the schedule associated with `label`, if it exists.
88    pub fn get_mut(&mut self, label: impl ScheduleLabel) -> Option<&mut Schedule> {
89        self.inner.get_mut(&label.intern())
90    }
91
92    /// Returns a mutable reference to the schedules associated with `label`, creating one if it doesn't already exist.
93    pub fn entry(&mut self, label: impl ScheduleLabel) -> &mut Schedule {
94        self.inner
95            .entry(label.intern())
96            .or_insert_with(|| Schedule::new(label))
97    }
98
99    /// Returns an iterator over all schedules. Iteration order is undefined.
100    pub fn iter(&self) -> impl Iterator<Item = (&dyn ScheduleLabel, &Schedule)> {
101        self.inner
102            .iter()
103            .map(|(label, schedule)| (&**label, schedule))
104    }
105    /// Returns an iterator over mutable references to all schedules. Iteration order is undefined.
106    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&dyn ScheduleLabel, &mut Schedule)> {
107        self.inner
108            .iter_mut()
109            .map(|(label, schedule)| (&**label, schedule))
110    }
111
112    /// Iterates the change ticks of all systems in all stored schedules and clamps any older than
113    /// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE).
114    /// This prevents overflow and thus prevents false positives.
115    pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) {
116        #[cfg(feature = "trace")]
117        let _all_span = info_span!("check stored schedule ticks").entered();
118        #[cfg_attr(
119            not(feature = "trace"),
120            expect(
121                unused_variables,
122                reason = "The `label` variable goes unused if the `trace` feature isn't active"
123            )
124        )]
125        for (label, schedule) in &mut self.inner {
126            #[cfg(feature = "trace")]
127            let name = format!("{label:?}");
128            #[cfg(feature = "trace")]
129            let _one_span = info_span!("check schedule ticks", name = &name).entered();
130            schedule.check_change_ticks(change_tick);
131        }
132    }
133
134    /// Applies the provided [`ScheduleBuildSettings`] to all schedules.
135    pub fn configure_schedules(&mut self, schedule_build_settings: ScheduleBuildSettings) {
136        for (_, schedule) in &mut self.inner {
137            schedule.set_build_settings(schedule_build_settings.clone());
138        }
139    }
140
141    /// Ignore system order ambiguities caused by conflicts on [`Component`]s of type `T`.
142    pub fn allow_ambiguous_component<T: Component>(&mut self, world: &mut World) {
143        self.ignored_scheduling_ambiguities
144            .insert(world.register_component::<T>());
145    }
146
147    /// Ignore system order ambiguities caused by conflicts on [`Resource`]s of type `T`.
148    pub fn allow_ambiguous_resource<T: Resource>(&mut self, world: &mut World) {
149        self.ignored_scheduling_ambiguities
150            .insert(world.components_registrator().register_resource::<T>());
151    }
152
153    /// Iterate through the [`ComponentId`]'s that will be ignored.
154    pub fn iter_ignored_ambiguities(&self) -> impl Iterator<Item = &ComponentId> + '_ {
155        self.ignored_scheduling_ambiguities.iter()
156    }
157
158    /// Prints the names of the components and resources with [`info`]
159    ///
160    /// May panic or retrieve incorrect names if [`Components`] is not from the same
161    /// world
162    pub fn print_ignored_ambiguities(&self, components: &Components) {
163        let mut message =
164            "System order ambiguities caused by conflicts on the following types are ignored:\n"
165                .to_string();
166        for id in self.iter_ignored_ambiguities() {
167            writeln!(message, "{}", components.get_name(*id).unwrap()).unwrap();
168        }
169
170        info!("{}", message);
171    }
172
173    /// Adds one or more systems to the [`Schedule`] matching the provided [`ScheduleLabel`].
174    pub fn add_systems<M>(
175        &mut self,
176        schedule: impl ScheduleLabel,
177        systems: impl IntoScheduleConfigs<ScheduleSystem, M>,
178    ) -> &mut Self {
179        self.entry(schedule).add_systems(systems);
180
181        self
182    }
183
184    /// Configures a collection of system sets in the provided schedule, adding any sets that do not exist.
185    #[track_caller]
186    pub fn configure_sets<M>(
187        &mut self,
188        schedule: impl ScheduleLabel,
189        sets: impl IntoScheduleConfigs<InternedSystemSet, M>,
190    ) -> &mut Self {
191        self.entry(schedule).configure_sets(sets);
192
193        self
194    }
195
196    /// Suppress warnings and errors that would result from systems in these sets having ambiguities
197    /// (conflicting access but indeterminate order) with systems in `set`.
198    ///
199    /// When possible, do this directly in the `.add_systems(Update, a.ambiguous_with(b))` call.
200    /// However, sometimes two independent plugins `A` and `B` are reported as ambiguous, which you
201    /// can only suppress as the consumer of both.
202    #[track_caller]
203    pub fn ignore_ambiguity<M1, M2, S1, S2>(
204        &mut self,
205        schedule: impl ScheduleLabel,
206        a: S1,
207        b: S2,
208    ) -> &mut Self
209    where
210        S1: IntoSystemSet<M1>,
211        S2: IntoSystemSet<M2>,
212    {
213        self.entry(schedule).ignore_ambiguity(a, b);
214
215        self
216    }
217}
218
219fn make_executor(kind: ExecutorKind) -> Box<dyn SystemExecutor> {
220    match kind {
221        ExecutorKind::Simple => Box::new(SimpleExecutor::new()),
222        ExecutorKind::SingleThreaded => Box::new(SingleThreadedExecutor::new()),
223        #[cfg(feature = "std")]
224        ExecutorKind::MultiThreaded => Box::new(MultiThreadedExecutor::new()),
225    }
226}
227
228/// Chain systems into dependencies
229#[derive(Default)]
230pub enum Chain {
231    /// Systems are independent. Nodes are allowed to run in any order.
232    #[default]
233    Unchained,
234    /// Systems are chained. `before -> after` ordering constraints
235    /// will be added between the successive elements.
236    Chained(TypeIdMap<Box<dyn Any>>),
237}
238impl Chain {
239    /// Specify that the systems must be chained.
240    pub fn set_chained(&mut self) {
241        if matches!(self, Chain::Unchained) {
242            *self = Self::Chained(Default::default());
243        };
244    }
245    /// Specify that the systems must be chained, and add the specified configuration for
246    /// all dependencies created between these systems.
247    pub fn set_chained_with_config<T: 'static>(&mut self, config: T) {
248        self.set_chained();
249        if let Chain::Chained(config_map) = self {
250            config_map.insert(TypeId::of::<T>(), Box::new(config));
251        } else {
252            unreachable!()
253        };
254    }
255}
256
257/// A collection of systems, and the metadata and executor needed to run them
258/// in a certain order under certain conditions.
259///
260/// # Example
261/// Here is an example of a `Schedule` running a "Hello world" system:
262/// ```
263/// # use bevy_ecs::prelude::*;
264/// fn hello_world() { println!("Hello world!") }
265///
266/// fn main() {
267///     let mut world = World::new();
268///     let mut schedule = Schedule::default();
269///     schedule.add_systems(hello_world);
270///
271///     schedule.run(&mut world);
272/// }
273/// ```
274///
275/// A schedule can also run several systems in an ordered way:
276/// ```
277/// # use bevy_ecs::prelude::*;
278/// fn system_one() { println!("System 1 works!") }
279/// fn system_two() { println!("System 2 works!") }
280/// fn system_three() { println!("System 3 works!") }
281///
282/// fn main() {
283///     let mut world = World::new();
284///     let mut schedule = Schedule::default();
285///     schedule.add_systems((
286///         system_two,
287///         system_one.before(system_two),
288///         system_three.after(system_two),
289///     ));
290///
291///     schedule.run(&mut world);
292/// }
293/// ```
294pub struct Schedule {
295    label: InternedScheduleLabel,
296    graph: ScheduleGraph,
297    executable: SystemSchedule,
298    executor: Box<dyn SystemExecutor>,
299    executor_initialized: bool,
300}
301
302#[derive(ScheduleLabel, Hash, PartialEq, Eq, Debug, Clone)]
303struct DefaultSchedule;
304
305impl Default for Schedule {
306    /// Creates a schedule with a default label. Only use in situations where
307    /// you don't care about the [`ScheduleLabel`]. Inserting a default schedule
308    /// into the world risks overwriting another schedule. For most situations
309    /// you should use [`Schedule::new`].
310    fn default() -> Self {
311        Self::new(DefaultSchedule)
312    }
313}
314
315impl Schedule {
316    /// Constructs an empty `Schedule`.
317    pub fn new(label: impl ScheduleLabel) -> Self {
318        let mut this = Self {
319            label: label.intern(),
320            graph: ScheduleGraph::new(),
321            executable: SystemSchedule::new(),
322            executor: make_executor(ExecutorKind::default()),
323            executor_initialized: false,
324        };
325        // Call `set_build_settings` to add any default build passes
326        this.set_build_settings(Default::default());
327        this
328    }
329
330    /// Get the `InternedScheduleLabel` for this `Schedule`.
331    pub fn label(&self) -> InternedScheduleLabel {
332        self.label
333    }
334
335    /// Add a collection of systems to the schedule.
336    pub fn add_systems<M>(
337        &mut self,
338        systems: impl IntoScheduleConfigs<ScheduleSystem, M>,
339    ) -> &mut Self {
340        self.graph.process_configs(systems.into_configs(), false);
341        self
342    }
343
344    /// Suppress warnings and errors that would result from systems in these sets having ambiguities
345    /// (conflicting access but indeterminate order) with systems in `set`.
346    #[track_caller]
347    pub fn ignore_ambiguity<M1, M2, S1, S2>(&mut self, a: S1, b: S2) -> &mut Self
348    where
349        S1: IntoSystemSet<M1>,
350        S2: IntoSystemSet<M2>,
351    {
352        let a = a.into_system_set();
353        let b = b.into_system_set();
354
355        let Some(&a_id) = self.graph.system_set_ids.get(&a.intern()) else {
356            panic!(
357                "Could not mark system as ambiguous, `{:?}` was not found in the schedule.
358                Did you try to call `ambiguous_with` before adding the system to the world?",
359                a
360            );
361        };
362        let Some(&b_id) = self.graph.system_set_ids.get(&b.intern()) else {
363            panic!(
364                "Could not mark system as ambiguous, `{:?}` was not found in the schedule.
365                Did you try to call `ambiguous_with` before adding the system to the world?",
366                b
367            );
368        };
369
370        self.graph.ambiguous_with.add_edge(a_id, b_id);
371
372        self
373    }
374
375    /// Configures a collection of system sets in this schedule, adding them if they does not exist.
376    #[track_caller]
377    pub fn configure_sets<M>(
378        &mut self,
379        sets: impl IntoScheduleConfigs<InternedSystemSet, M>,
380    ) -> &mut Self {
381        self.graph.configure_sets(sets);
382        self
383    }
384
385    /// Add a custom build pass to the schedule.
386    pub fn add_build_pass<T: ScheduleBuildPass>(&mut self, pass: T) -> &mut Self {
387        self.graph.passes.insert(TypeId::of::<T>(), Box::new(pass));
388        self
389    }
390
391    /// Remove a custom build pass.
392    pub fn remove_build_pass<T: ScheduleBuildPass>(&mut self) {
393        self.graph.passes.remove(&TypeId::of::<T>());
394    }
395
396    /// Changes miscellaneous build settings.
397    pub fn set_build_settings(&mut self, settings: ScheduleBuildSettings) -> &mut Self {
398        if settings.auto_insert_apply_deferred {
399            self.add_build_pass(passes::AutoInsertApplyDeferredPass::default());
400        } else {
401            self.remove_build_pass::<passes::AutoInsertApplyDeferredPass>();
402        }
403        self.graph.settings = settings;
404        self
405    }
406
407    /// Returns the schedule's current `ScheduleBuildSettings`.
408    pub fn get_build_settings(&self) -> ScheduleBuildSettings {
409        self.graph.settings.clone()
410    }
411
412    /// Returns the schedule's current execution strategy.
413    pub fn get_executor_kind(&self) -> ExecutorKind {
414        self.executor.kind()
415    }
416
417    /// Sets the schedule's execution strategy.
418    pub fn set_executor_kind(&mut self, executor: ExecutorKind) -> &mut Self {
419        if executor != self.executor.kind() {
420            self.executor = make_executor(executor);
421            self.executor_initialized = false;
422        }
423        self
424    }
425
426    /// Set whether the schedule applies deferred system buffers on final time or not. This is a catch-all
427    /// in case a system uses commands but was not explicitly ordered before an instance of
428    /// [`ApplyDeferred`]. By default this
429    /// setting is true, but may be disabled if needed.
430    pub fn set_apply_final_deferred(&mut self, apply_final_deferred: bool) -> &mut Self {
431        self.executor.set_apply_final_deferred(apply_final_deferred);
432        self
433    }
434
435    /// Runs all systems in this schedule on the `world`, using its current execution strategy.
436    pub fn run(&mut self, world: &mut World) {
437        #[cfg(feature = "trace")]
438        let _span = info_span!("schedule", name = ?self.label).entered();
439
440        world.check_change_ticks();
441        self.initialize(world)
442            .unwrap_or_else(|e| panic!("Error when initializing schedule {:?}: {e}", self.label));
443
444        let error_handler = default_error_handler();
445
446        #[cfg(not(feature = "bevy_debug_stepping"))]
447        self.executor
448            .run(&mut self.executable, world, None, error_handler);
449
450        #[cfg(feature = "bevy_debug_stepping")]
451        {
452            let skip_systems = match world.get_resource_mut::<Stepping>() {
453                None => None,
454                Some(mut stepping) => stepping.skipped_systems(self),
455            };
456
457            self.executor.run(
458                &mut self.executable,
459                world,
460                skip_systems.as_ref(),
461                error_handler,
462            );
463        }
464    }
465
466    /// Initializes any newly-added systems and conditions, rebuilds the executable schedule,
467    /// and re-initializes the executor.
468    ///
469    /// Moves all systems and run conditions out of the [`ScheduleGraph`].
470    pub fn initialize(&mut self, world: &mut World) -> Result<(), ScheduleBuildError> {
471        if self.graph.changed {
472            self.graph.initialize(world);
473            let ignored_ambiguities = world
474                .get_resource_or_init::<Schedules>()
475                .ignored_scheduling_ambiguities
476                .clone();
477            self.graph.update_schedule(
478                world,
479                &mut self.executable,
480                &ignored_ambiguities,
481                self.label,
482            )?;
483            self.graph.changed = false;
484            self.executor_initialized = false;
485        }
486
487        if !self.executor_initialized {
488            self.executor.init(&self.executable);
489            self.executor_initialized = true;
490        }
491
492        Ok(())
493    }
494
495    /// Returns the [`ScheduleGraph`].
496    pub fn graph(&self) -> &ScheduleGraph {
497        &self.graph
498    }
499
500    /// Returns a mutable reference to the [`ScheduleGraph`].
501    pub fn graph_mut(&mut self) -> &mut ScheduleGraph {
502        &mut self.graph
503    }
504
505    /// Returns the [`SystemSchedule`].
506    pub(crate) fn executable(&self) -> &SystemSchedule {
507        &self.executable
508    }
509
510    /// Iterates the change ticks of all systems in the schedule and clamps any older than
511    /// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE).
512    /// This prevents overflow and thus prevents false positives.
513    pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) {
514        for system in &mut self.executable.systems {
515            if !is_apply_deferred(system) {
516                system.check_change_tick(change_tick);
517            }
518        }
519
520        for conditions in &mut self.executable.system_conditions {
521            for system in conditions {
522                system.check_change_tick(change_tick);
523            }
524        }
525
526        for conditions in &mut self.executable.set_conditions {
527            for system in conditions {
528                system.check_change_tick(change_tick);
529            }
530        }
531    }
532
533    /// Directly applies any accumulated [`Deferred`](crate::system::Deferred) system parameters (like [`Commands`](crate::prelude::Commands)) to the `world`.
534    ///
535    /// Like always, deferred system parameters are applied in the "topological sort order" of the schedule graph.
536    /// As a result, buffers from one system are only guaranteed to be applied before those of other systems
537    /// if there is an explicit system ordering between the two systems.
538    ///
539    /// This is used in rendering to extract data from the main world, storing the data in system buffers,
540    /// before applying their buffers in a different world.
541    pub fn apply_deferred(&mut self, world: &mut World) {
542        for system in &mut self.executable.systems {
543            system.apply_deferred(world);
544        }
545    }
546
547    /// Returns an iterator over all systems in this schedule.
548    ///
549    /// Note: this method will return [`ScheduleNotInitialized`] if the
550    /// schedule has never been initialized or run.
551    pub fn systems(
552        &self,
553    ) -> Result<impl Iterator<Item = (NodeId, &ScheduleSystem)> + Sized, ScheduleNotInitialized>
554    {
555        if !self.executor_initialized {
556            return Err(ScheduleNotInitialized);
557        }
558
559        let iter = self
560            .executable
561            .system_ids
562            .iter()
563            .zip(&self.executable.systems)
564            .map(|(node_id, system)| (*node_id, system));
565
566        Ok(iter)
567    }
568
569    /// Returns the number of systems in this schedule.
570    pub fn systems_len(&self) -> usize {
571        if !self.executor_initialized {
572            self.graph.systems.len()
573        } else {
574            self.executable.systems.len()
575        }
576    }
577}
578
579/// A directed acyclic graph structure.
580#[derive(Default)]
581pub struct Dag {
582    /// A directed graph.
583    graph: DiGraph,
584    /// A cached topological ordering of the graph.
585    topsort: Vec<NodeId>,
586}
587
588impl Dag {
589    fn new() -> Self {
590        Self {
591            graph: DiGraph::default(),
592            topsort: Vec::new(),
593        }
594    }
595
596    /// The directed graph of the stored systems, connected by their ordering dependencies.
597    pub fn graph(&self) -> &DiGraph {
598        &self.graph
599    }
600
601    /// A cached topological ordering of the graph.
602    ///
603    /// The order is determined by the ordering dependencies between systems.
604    pub fn cached_topsort(&self) -> &[NodeId] {
605        &self.topsort
606    }
607}
608
609/// A [`SystemSet`] with metadata, stored in a [`ScheduleGraph`].
610struct SystemSetNode {
611    inner: InternedSystemSet,
612}
613
614impl SystemSetNode {
615    pub fn new(set: InternedSystemSet) -> Self {
616        Self { inner: set }
617    }
618
619    pub fn name(&self) -> String {
620        format!("{:?}", &self.inner)
621    }
622
623    pub fn is_system_type(&self) -> bool {
624        self.inner.system_type().is_some()
625    }
626
627    pub fn is_anonymous(&self) -> bool {
628        self.inner.is_anonymous()
629    }
630}
631
632/// A [`ScheduleSystem`] stored in a [`ScheduleGraph`].
633pub struct SystemNode {
634    inner: Option<ScheduleSystem>,
635}
636
637impl SystemNode {
638    /// Create a new [`SystemNode`]
639    pub fn new(system: ScheduleSystem) -> Self {
640        Self {
641            inner: Some(system),
642        }
643    }
644
645    /// Obtain a reference to the [`ScheduleSystem`] represented by this node.
646    pub fn get(&self) -> Option<&ScheduleSystem> {
647        self.inner.as_ref()
648    }
649
650    /// Obtain a mutable reference to the [`ScheduleSystem`] represented by this node.
651    pub fn get_mut(&mut self) -> Option<&mut ScheduleSystem> {
652        self.inner.as_mut()
653    }
654}
655
656/// Metadata for a [`Schedule`].
657///
658/// The order isn't optimized; calling `ScheduleGraph::build_schedule` will return a
659/// `SystemSchedule` where the order is optimized for execution.
660#[derive(Default)]
661pub struct ScheduleGraph {
662    /// List of systems in the schedule
663    pub systems: Vec<SystemNode>,
664    /// List of conditions for each system, in the same order as `systems`
665    pub system_conditions: Vec<Vec<BoxedCondition>>,
666    /// List of system sets in the schedule
667    system_sets: Vec<SystemSetNode>,
668    /// List of conditions for each system set, in the same order as `system_sets`
669    system_set_conditions: Vec<Vec<BoxedCondition>>,
670    /// Map from system set to node id
671    system_set_ids: HashMap<InternedSystemSet, NodeId>,
672    /// Systems that have not been initialized yet; for system sets, we store the index of the first uninitialized condition
673    /// (all the conditions after that index still need to be initialized)
674    uninit: Vec<(NodeId, usize)>,
675    /// Directed acyclic graph of the hierarchy (which systems/sets are children of which sets)
676    hierarchy: Dag,
677    /// Directed acyclic graph of the dependency (which systems/sets have to run before which other systems/sets)
678    dependency: Dag,
679    ambiguous_with: UnGraph,
680    /// Nodes that are allowed to have ambiguous ordering relationship with any other systems.
681    pub ambiguous_with_all: HashSet<NodeId>,
682    conflicting_systems: Vec<(NodeId, NodeId, Vec<ComponentId>)>,
683    anonymous_sets: usize,
684    changed: bool,
685    settings: ScheduleBuildSettings,
686
687    passes: BTreeMap<TypeId, Box<dyn ScheduleBuildPassObj>>,
688}
689
690impl ScheduleGraph {
691    /// Creates an empty [`ScheduleGraph`] with default settings.
692    pub fn new() -> Self {
693        Self {
694            systems: Vec::new(),
695            system_conditions: Vec::new(),
696            system_sets: Vec::new(),
697            system_set_conditions: Vec::new(),
698            system_set_ids: HashMap::default(),
699            uninit: Vec::new(),
700            hierarchy: Dag::new(),
701            dependency: Dag::new(),
702            ambiguous_with: UnGraph::default(),
703            ambiguous_with_all: HashSet::default(),
704            conflicting_systems: Vec::new(),
705            anonymous_sets: 0,
706            changed: false,
707            settings: default(),
708            passes: default(),
709        }
710    }
711
712    /// Returns the system at the given [`NodeId`], if it exists.
713    pub fn get_system_at(&self, id: NodeId) -> Option<&ScheduleSystem> {
714        if !id.is_system() {
715            return None;
716        }
717        self.systems
718            .get(id.index())
719            .and_then(|system| system.inner.as_ref())
720    }
721
722    /// Returns `true` if the given system set is part of the graph. Otherwise, returns `false`.
723    pub fn contains_set(&self, set: impl SystemSet) -> bool {
724        self.system_set_ids.contains_key(&set.intern())
725    }
726
727    /// Returns the system at the given [`NodeId`].
728    ///
729    /// Panics if it doesn't exist.
730    #[track_caller]
731    pub fn system_at(&self, id: NodeId) -> &ScheduleSystem {
732        self.get_system_at(id)
733            .ok_or_else(|| format!("system with id {id:?} does not exist in this Schedule"))
734            .unwrap()
735    }
736
737    /// Returns the set at the given [`NodeId`], if it exists.
738    pub fn get_set_at(&self, id: NodeId) -> Option<&dyn SystemSet> {
739        if !id.is_set() {
740            return None;
741        }
742        self.system_sets.get(id.index()).map(|set| &*set.inner)
743    }
744
745    /// Returns the set at the given [`NodeId`].
746    ///
747    /// Panics if it doesn't exist.
748    #[track_caller]
749    pub fn set_at(&self, id: NodeId) -> &dyn SystemSet {
750        self.get_set_at(id)
751            .ok_or_else(|| format!("set with id {id:?} does not exist in this Schedule"))
752            .unwrap()
753    }
754
755    /// Returns the conditions for the set at the given [`NodeId`], if it exists.
756    pub fn get_set_conditions_at(&self, id: NodeId) -> Option<&[BoxedCondition]> {
757        if !id.is_set() {
758            return None;
759        }
760        self.system_set_conditions
761            .get(id.index())
762            .map(Vec::as_slice)
763    }
764
765    /// Returns the conditions for the set at the given [`NodeId`].
766    ///
767    /// Panics if it doesn't exist.
768    #[track_caller]
769    pub fn set_conditions_at(&self, id: NodeId) -> &[BoxedCondition] {
770        self.get_set_conditions_at(id)
771            .ok_or_else(|| format!("set with id {id:?} does not exist in this Schedule"))
772            .unwrap()
773    }
774
775    /// Returns an iterator over all systems in this schedule, along with the conditions for each system.
776    pub fn systems(&self) -> impl Iterator<Item = (NodeId, &ScheduleSystem, &[BoxedCondition])> {
777        self.systems
778            .iter()
779            .zip(self.system_conditions.iter())
780            .enumerate()
781            .filter_map(|(i, (system_node, condition))| {
782                let system = system_node.inner.as_ref()?;
783                Some((NodeId::System(i), system, condition.as_slice()))
784            })
785    }
786
787    /// Returns an iterator over all system sets in this schedule, along with the conditions for each
788    /// system set.
789    pub fn system_sets(&self) -> impl Iterator<Item = (NodeId, &dyn SystemSet, &[BoxedCondition])> {
790        self.system_set_ids.iter().map(|(_, &node_id)| {
791            let set_node = &self.system_sets[node_id.index()];
792            let set = &*set_node.inner;
793            let conditions = self.system_set_conditions[node_id.index()].as_slice();
794            (node_id, set, conditions)
795        })
796    }
797
798    /// Returns the [`Dag`] of the hierarchy.
799    ///
800    /// The hierarchy is a directed acyclic graph of the systems and sets,
801    /// where an edge denotes that a system or set is the child of another set.
802    pub fn hierarchy(&self) -> &Dag {
803        &self.hierarchy
804    }
805
806    /// Returns the [`Dag`] of the dependencies in the schedule.
807    ///
808    /// Nodes in this graph are systems and sets, and edges denote that
809    /// a system or set has to run before another system or set.
810    pub fn dependency(&self) -> &Dag {
811        &self.dependency
812    }
813
814    /// Returns the list of systems that conflict with each other, i.e. have ambiguities in their access.
815    ///
816    /// If the `Vec<ComponentId>` is empty, the systems conflict on [`World`] access.
817    /// Must be called after [`ScheduleGraph::build_schedule`] to be non-empty.
818    pub fn conflicting_systems(&self) -> &[(NodeId, NodeId, Vec<ComponentId>)] {
819        &self.conflicting_systems
820    }
821
822    fn process_config<T: ProcessScheduleConfig + Schedulable>(
823        &mut self,
824        config: ScheduleConfig<T>,
825        collect_nodes: bool,
826    ) -> ProcessConfigsResult {
827        ProcessConfigsResult {
828            densely_chained: true,
829            nodes: collect_nodes
830                .then_some(T::process_config(self, config))
831                .into_iter()
832                .collect(),
833        }
834    }
835
836    fn apply_collective_conditions<
837        T: ProcessScheduleConfig + Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>,
838    >(
839        &mut self,
840        configs: &mut [ScheduleConfigs<T>],
841        collective_conditions: Vec<BoxedCondition>,
842    ) {
843        if !collective_conditions.is_empty() {
844            if let [config] = configs {
845                for condition in collective_conditions {
846                    config.run_if_dyn(condition);
847                }
848            } else {
849                let set = self.create_anonymous_set();
850                for config in configs.iter_mut() {
851                    config.in_set_inner(set.intern());
852                }
853                let mut set_config = InternedSystemSet::into_config(set.intern());
854                set_config.conditions.extend(collective_conditions);
855                self.configure_set_inner(set_config).unwrap();
856            }
857        }
858    }
859
860    /// Adds the config nodes to the graph.
861    ///
862    /// `collect_nodes` controls whether the `NodeId`s of the processed config nodes are stored in the returned [`ProcessConfigsResult`].
863    /// `process_config` is the function which processes each individual config node and returns a corresponding `NodeId`.
864    ///
865    /// The fields on the returned [`ProcessConfigsResult`] are:
866    /// - `nodes`: a vector of all node ids contained in the nested `ScheduleConfigs`
867    /// - `densely_chained`: a boolean that is true if all nested nodes are linearly chained (with successive `after` orderings) in the order they are defined
868    #[track_caller]
869    fn process_configs<
870        T: ProcessScheduleConfig + Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>,
871    >(
872        &mut self,
873        configs: ScheduleConfigs<T>,
874        collect_nodes: bool,
875    ) -> ProcessConfigsResult {
876        match configs {
877            ScheduleConfigs::ScheduleConfig(config) => self.process_config(config, collect_nodes),
878            ScheduleConfigs::Configs {
879                metadata,
880                mut configs,
881                collective_conditions,
882            } => {
883                self.apply_collective_conditions(&mut configs, collective_conditions);
884
885                let is_chained = matches!(metadata, Chain::Chained(_));
886
887                // Densely chained if
888                // * chained and all configs in the chain are densely chained, or
889                // * unchained with a single densely chained config
890                let mut densely_chained = is_chained || configs.len() == 1;
891                let mut configs = configs.into_iter();
892                let mut nodes = Vec::new();
893
894                let Some(first) = configs.next() else {
895                    return ProcessConfigsResult {
896                        nodes: Vec::new(),
897                        densely_chained,
898                    };
899                };
900                let mut previous_result = self.process_configs(first, collect_nodes || is_chained);
901                densely_chained &= previous_result.densely_chained;
902
903                for current in configs {
904                    let current_result = self.process_configs(current, collect_nodes || is_chained);
905                    densely_chained &= current_result.densely_chained;
906
907                    if let Chain::Chained(chain_options) = &metadata {
908                        // if the current result is densely chained, we only need to chain the first node
909                        let current_nodes = if current_result.densely_chained {
910                            &current_result.nodes[..1]
911                        } else {
912                            &current_result.nodes
913                        };
914                        // if the previous result was densely chained, we only need to chain the last node
915                        let previous_nodes = if previous_result.densely_chained {
916                            &previous_result.nodes[previous_result.nodes.len() - 1..]
917                        } else {
918                            &previous_result.nodes
919                        };
920
921                        for previous_node in previous_nodes {
922                            for current_node in current_nodes {
923                                self.dependency
924                                    .graph
925                                    .add_edge(*previous_node, *current_node);
926
927                                for pass in self.passes.values_mut() {
928                                    pass.add_dependency(
929                                        *previous_node,
930                                        *current_node,
931                                        chain_options,
932                                    );
933                                }
934                            }
935                        }
936                    }
937                    if collect_nodes {
938                        nodes.append(&mut previous_result.nodes);
939                    }
940
941                    previous_result = current_result;
942                }
943                if collect_nodes {
944                    nodes.append(&mut previous_result.nodes);
945                }
946
947                ProcessConfigsResult {
948                    nodes,
949                    densely_chained,
950                }
951            }
952        }
953    }
954
955    /// Add a [`ScheduleConfig`] to the graph, including its dependencies and conditions.
956    fn add_system_inner(
957        &mut self,
958        config: ScheduleConfig<ScheduleSystem>,
959    ) -> Result<NodeId, ScheduleBuildError> {
960        let id = NodeId::System(self.systems.len());
961
962        // graph updates are immediate
963        self.update_graphs(id, config.metadata)?;
964
965        // system init has to be deferred (need `&mut World`)
966        self.uninit.push((id, 0));
967        self.systems.push(SystemNode::new(config.node));
968        self.system_conditions.push(config.conditions);
969
970        Ok(id)
971    }
972
973    #[track_caller]
974    fn configure_sets<M>(&mut self, sets: impl IntoScheduleConfigs<InternedSystemSet, M>) {
975        self.process_configs(sets.into_configs(), false);
976    }
977
978    /// Add a single `ScheduleConfig` to the graph, including its dependencies and conditions.
979    fn configure_set_inner(
980        &mut self,
981        set: ScheduleConfig<InternedSystemSet>,
982    ) -> Result<NodeId, ScheduleBuildError> {
983        let ScheduleConfig {
984            node: set,
985            metadata,
986            mut conditions,
987        } = set;
988
989        let id = match self.system_set_ids.get(&set) {
990            Some(&id) => id,
991            None => self.add_set(set),
992        };
993
994        // graph updates are immediate
995        self.update_graphs(id, metadata)?;
996
997        // system init has to be deferred (need `&mut World`)
998        let system_set_conditions = &mut self.system_set_conditions[id.index()];
999        self.uninit.push((id, system_set_conditions.len()));
1000        system_set_conditions.append(&mut conditions);
1001
1002        Ok(id)
1003    }
1004
1005    fn add_set(&mut self, set: InternedSystemSet) -> NodeId {
1006        let id = NodeId::Set(self.system_sets.len());
1007        self.system_sets.push(SystemSetNode::new(set));
1008        self.system_set_conditions.push(Vec::new());
1009        self.system_set_ids.insert(set, id);
1010        id
1011    }
1012
1013    /// Checks that a system set isn't included in itself.
1014    /// If not present, add the set to the graph.
1015    fn check_hierarchy_set(
1016        &mut self,
1017        id: &NodeId,
1018        set: InternedSystemSet,
1019    ) -> Result<(), ScheduleBuildError> {
1020        match self.system_set_ids.get(&set) {
1021            Some(set_id) => {
1022                if id == set_id {
1023                    return Err(ScheduleBuildError::HierarchyLoop(self.get_node_name(id)));
1024                }
1025            }
1026            None => {
1027                self.add_set(set);
1028            }
1029        }
1030
1031        Ok(())
1032    }
1033
1034    fn create_anonymous_set(&mut self) -> AnonymousSet {
1035        let id = self.anonymous_sets;
1036        self.anonymous_sets += 1;
1037        AnonymousSet::new(id)
1038    }
1039
1040    /// Check that no set is included in itself.
1041    /// Add all the sets from the [`GraphInfo`]'s hierarchy to the graph.
1042    fn check_hierarchy_sets(
1043        &mut self,
1044        id: &NodeId,
1045        graph_info: &GraphInfo,
1046    ) -> Result<(), ScheduleBuildError> {
1047        for &set in &graph_info.hierarchy {
1048            self.check_hierarchy_set(id, set)?;
1049        }
1050
1051        Ok(())
1052    }
1053
1054    /// Checks that no system set is dependent on itself.
1055    /// Add all the sets from the [`GraphInfo`]'s dependencies to the graph.
1056    fn check_edges(
1057        &mut self,
1058        id: &NodeId,
1059        graph_info: &GraphInfo,
1060    ) -> Result<(), ScheduleBuildError> {
1061        for Dependency { set, .. } in &graph_info.dependencies {
1062            match self.system_set_ids.get(set) {
1063                Some(set_id) => {
1064                    if id == set_id {
1065                        return Err(ScheduleBuildError::DependencyLoop(self.get_node_name(id)));
1066                    }
1067                }
1068                None => {
1069                    self.add_set(*set);
1070                }
1071            }
1072        }
1073
1074        if let Ambiguity::IgnoreWithSet(ambiguous_with) = &graph_info.ambiguous_with {
1075            for set in ambiguous_with {
1076                if !self.system_set_ids.contains_key(set) {
1077                    self.add_set(*set);
1078                }
1079            }
1080        }
1081
1082        Ok(())
1083    }
1084
1085    /// Update the internal graphs (hierarchy, dependency, ambiguity) by adding a single [`GraphInfo`]
1086    fn update_graphs(
1087        &mut self,
1088        id: NodeId,
1089        graph_info: GraphInfo,
1090    ) -> Result<(), ScheduleBuildError> {
1091        self.check_hierarchy_sets(&id, &graph_info)?;
1092        self.check_edges(&id, &graph_info)?;
1093        self.changed = true;
1094
1095        let GraphInfo {
1096            hierarchy: sets,
1097            dependencies,
1098            ambiguous_with,
1099            ..
1100        } = graph_info;
1101
1102        self.hierarchy.graph.add_node(id);
1103        self.dependency.graph.add_node(id);
1104
1105        for set in sets.into_iter().map(|set| self.system_set_ids[&set]) {
1106            self.hierarchy.graph.add_edge(set, id);
1107
1108            // ensure set also appears in dependency graph
1109            self.dependency.graph.add_node(set);
1110        }
1111
1112        for (kind, set, options) in dependencies
1113            .into_iter()
1114            .map(|Dependency { kind, set, options }| (kind, self.system_set_ids[&set], options))
1115        {
1116            let (lhs, rhs) = match kind {
1117                DependencyKind::Before => (id, set),
1118                DependencyKind::After => (set, id),
1119            };
1120            self.dependency.graph.add_edge(lhs, rhs);
1121            for pass in self.passes.values_mut() {
1122                pass.add_dependency(lhs, rhs, &options);
1123            }
1124
1125            // ensure set also appears in hierarchy graph
1126            self.hierarchy.graph.add_node(set);
1127        }
1128
1129        match ambiguous_with {
1130            Ambiguity::Check => (),
1131            Ambiguity::IgnoreWithSet(ambiguous_with) => {
1132                for set in ambiguous_with
1133                    .into_iter()
1134                    .map(|set| self.system_set_ids[&set])
1135                {
1136                    self.ambiguous_with.add_edge(id, set);
1137                }
1138            }
1139            Ambiguity::IgnoreAll => {
1140                self.ambiguous_with_all.insert(id);
1141            }
1142        }
1143
1144        Ok(())
1145    }
1146
1147    /// Initializes any newly-added systems and conditions by calling [`System::initialize`](crate::system::System)
1148    pub fn initialize(&mut self, world: &mut World) {
1149        for (id, i) in self.uninit.drain(..) {
1150            match id {
1151                NodeId::System(index) => {
1152                    self.systems[index].get_mut().unwrap().initialize(world);
1153                    for condition in &mut self.system_conditions[index] {
1154                        condition.initialize(world);
1155                    }
1156                }
1157                NodeId::Set(index) => {
1158                    for condition in self.system_set_conditions[index].iter_mut().skip(i) {
1159                        condition.initialize(world);
1160                    }
1161                }
1162            }
1163        }
1164    }
1165
1166    /// Build a [`SystemSchedule`] optimized for scheduler access from the [`ScheduleGraph`].
1167    ///
1168    /// This method also
1169    /// - checks for dependency or hierarchy cycles
1170    /// - checks for system access conflicts and reports ambiguities
1171    pub fn build_schedule(
1172        &mut self,
1173        world: &mut World,
1174        schedule_label: InternedScheduleLabel,
1175        ignored_ambiguities: &BTreeSet<ComponentId>,
1176    ) -> Result<SystemSchedule, ScheduleBuildError> {
1177        // check hierarchy for cycles
1178        self.hierarchy.topsort =
1179            self.topsort_graph(&self.hierarchy.graph, ReportCycles::Hierarchy)?;
1180
1181        let hier_results = check_graph(&self.hierarchy.graph, &self.hierarchy.topsort);
1182        self.optionally_check_hierarchy_conflicts(&hier_results.transitive_edges, schedule_label)?;
1183
1184        // remove redundant edges
1185        self.hierarchy.graph = hier_results.transitive_reduction;
1186
1187        // check dependencies for cycles
1188        self.dependency.topsort =
1189            self.topsort_graph(&self.dependency.graph, ReportCycles::Dependency)?;
1190
1191        // check for systems or system sets depending on sets they belong to
1192        let dep_results = check_graph(&self.dependency.graph, &self.dependency.topsort);
1193        self.check_for_cross_dependencies(&dep_results, &hier_results.connected)?;
1194
1195        // map all system sets to their systems
1196        // go in reverse topological order (bottom-up) for efficiency
1197        let (set_systems, set_system_bitsets) =
1198            self.map_sets_to_systems(&self.hierarchy.topsort, &self.hierarchy.graph);
1199        self.check_order_but_intersect(&dep_results.connected, &set_system_bitsets)?;
1200
1201        // check that there are no edges to system-type sets that have multiple instances
1202        self.check_system_type_set_ambiguity(&set_systems)?;
1203
1204        let mut dependency_flattened = self.get_dependency_flattened(&set_systems);
1205
1206        // modify graph with build passes
1207        let mut passes = core::mem::take(&mut self.passes);
1208        for pass in passes.values_mut() {
1209            pass.build(world, self, &mut dependency_flattened)?;
1210        }
1211        self.passes = passes;
1212
1213        // topsort
1214        let mut dependency_flattened_dag = Dag {
1215            topsort: self.topsort_graph(&dependency_flattened, ReportCycles::Dependency)?,
1216            graph: dependency_flattened,
1217        };
1218
1219        let flat_results = check_graph(
1220            &dependency_flattened_dag.graph,
1221            &dependency_flattened_dag.topsort,
1222        );
1223
1224        // remove redundant edges
1225        dependency_flattened_dag.graph = flat_results.transitive_reduction;
1226
1227        // flatten: combine `in_set` with `ambiguous_with` information
1228        let ambiguous_with_flattened = self.get_ambiguous_with_flattened(&set_systems);
1229
1230        // check for conflicts
1231        let conflicting_systems = self.get_conflicting_systems(
1232            &flat_results.disconnected,
1233            &ambiguous_with_flattened,
1234            ignored_ambiguities,
1235        );
1236        self.optionally_check_conflicts(&conflicting_systems, world.components(), schedule_label)?;
1237        self.conflicting_systems = conflicting_systems;
1238
1239        // build the schedule
1240        Ok(self.build_schedule_inner(dependency_flattened_dag, hier_results.reachable))
1241    }
1242
1243    /// Return a map from system set `NodeId` to a list of system `NodeId`s that are included in the set.
1244    /// Also return a map from system set `NodeId` to a `FixedBitSet` of system `NodeId`s that are included in the set,
1245    /// where the bitset order is the same as `self.systems`
1246    fn map_sets_to_systems(
1247        &self,
1248        hierarchy_topsort: &[NodeId],
1249        hierarchy_graph: &DiGraph,
1250    ) -> (HashMap<NodeId, Vec<NodeId>>, HashMap<NodeId, FixedBitSet>) {
1251        let mut set_systems: HashMap<NodeId, Vec<NodeId>> =
1252            HashMap::with_capacity_and_hasher(self.system_sets.len(), Default::default());
1253        let mut set_system_bitsets =
1254            HashMap::with_capacity_and_hasher(self.system_sets.len(), Default::default());
1255        for &id in hierarchy_topsort.iter().rev() {
1256            if id.is_system() {
1257                continue;
1258            }
1259
1260            let mut systems = Vec::new();
1261            let mut system_bitset = FixedBitSet::with_capacity(self.systems.len());
1262
1263            for child in hierarchy_graph.neighbors_directed(id, Outgoing) {
1264                match child {
1265                    NodeId::System(_) => {
1266                        systems.push(child);
1267                        system_bitset.insert(child.index());
1268                    }
1269                    NodeId::Set(_) => {
1270                        let child_systems = set_systems.get(&child).unwrap();
1271                        let child_system_bitset = set_system_bitsets.get(&child).unwrap();
1272                        systems.extend_from_slice(child_systems);
1273                        system_bitset.union_with(child_system_bitset);
1274                    }
1275                }
1276            }
1277
1278            set_systems.insert(id, systems);
1279            set_system_bitsets.insert(id, system_bitset);
1280        }
1281        (set_systems, set_system_bitsets)
1282    }
1283
1284    fn get_dependency_flattened(&mut self, set_systems: &HashMap<NodeId, Vec<NodeId>>) -> DiGraph {
1285        // flatten: combine `in_set` with `before` and `after` information
1286        // have to do it like this to preserve transitivity
1287        let mut dependency_flattened = self.dependency.graph.clone();
1288        let mut temp = Vec::new();
1289        for (&set, systems) in set_systems {
1290            for pass in self.passes.values_mut() {
1291                pass.collapse_set(set, systems, &dependency_flattened, &mut temp);
1292            }
1293            if systems.is_empty() {
1294                // collapse dependencies for empty sets
1295                for a in dependency_flattened.neighbors_directed(set, Incoming) {
1296                    for b in dependency_flattened.neighbors_directed(set, Outgoing) {
1297                        temp.push((a, b));
1298                    }
1299                }
1300            } else {
1301                for a in dependency_flattened.neighbors_directed(set, Incoming) {
1302                    for &sys in systems {
1303                        temp.push((a, sys));
1304                    }
1305                }
1306
1307                for b in dependency_flattened.neighbors_directed(set, Outgoing) {
1308                    for &sys in systems {
1309                        temp.push((sys, b));
1310                    }
1311                }
1312            }
1313
1314            dependency_flattened.remove_node(set);
1315            for (a, b) in temp.drain(..) {
1316                dependency_flattened.add_edge(a, b);
1317            }
1318        }
1319
1320        dependency_flattened
1321    }
1322
1323    fn get_ambiguous_with_flattened(&self, set_systems: &HashMap<NodeId, Vec<NodeId>>) -> UnGraph {
1324        let mut ambiguous_with_flattened = UnGraph::default();
1325        for (lhs, rhs) in self.ambiguous_with.all_edges() {
1326            match (lhs, rhs) {
1327                (NodeId::System(_), NodeId::System(_)) => {
1328                    ambiguous_with_flattened.add_edge(lhs, rhs);
1329                }
1330                (NodeId::Set(_), NodeId::System(_)) => {
1331                    for &lhs_ in set_systems.get(&lhs).unwrap_or(&Vec::new()) {
1332                        ambiguous_with_flattened.add_edge(lhs_, rhs);
1333                    }
1334                }
1335                (NodeId::System(_), NodeId::Set(_)) => {
1336                    for &rhs_ in set_systems.get(&rhs).unwrap_or(&Vec::new()) {
1337                        ambiguous_with_flattened.add_edge(lhs, rhs_);
1338                    }
1339                }
1340                (NodeId::Set(_), NodeId::Set(_)) => {
1341                    for &lhs_ in set_systems.get(&lhs).unwrap_or(&Vec::new()) {
1342                        for &rhs_ in set_systems.get(&rhs).unwrap_or(&vec![]) {
1343                            ambiguous_with_flattened.add_edge(lhs_, rhs_);
1344                        }
1345                    }
1346                }
1347            }
1348        }
1349
1350        ambiguous_with_flattened
1351    }
1352
1353    fn get_conflicting_systems(
1354        &self,
1355        flat_results_disconnected: &Vec<(NodeId, NodeId)>,
1356        ambiguous_with_flattened: &UnGraph,
1357        ignored_ambiguities: &BTreeSet<ComponentId>,
1358    ) -> Vec<(NodeId, NodeId, Vec<ComponentId>)> {
1359        let mut conflicting_systems = Vec::new();
1360        for &(a, b) in flat_results_disconnected {
1361            if ambiguous_with_flattened.contains_edge(a, b)
1362                || self.ambiguous_with_all.contains(&a)
1363                || self.ambiguous_with_all.contains(&b)
1364            {
1365                continue;
1366            }
1367
1368            let system_a = self.systems[a.index()].get().unwrap();
1369            let system_b = self.systems[b.index()].get().unwrap();
1370            if system_a.is_exclusive() || system_b.is_exclusive() {
1371                conflicting_systems.push((a, b, Vec::new()));
1372            } else {
1373                let access_a = system_a.component_access();
1374                let access_b = system_b.component_access();
1375                if !access_a.is_compatible(access_b) {
1376                    match access_a.get_conflicts(access_b) {
1377                        AccessConflicts::Individual(conflicts) => {
1378                            let conflicts: Vec<_> = conflicts
1379                                .ones()
1380                                .map(ComponentId::get_sparse_set_index)
1381                                .filter(|id| !ignored_ambiguities.contains(id))
1382                                .collect();
1383                            if !conflicts.is_empty() {
1384                                conflicting_systems.push((a, b, conflicts));
1385                            }
1386                        }
1387                        AccessConflicts::All => {
1388                            // there is no specific component conflicting, but the systems are overall incompatible
1389                            // for example 2 systems with `Query<EntityMut>`
1390                            conflicting_systems.push((a, b, Vec::new()));
1391                        }
1392                    }
1393                }
1394            }
1395        }
1396
1397        conflicting_systems
1398    }
1399
1400    fn build_schedule_inner(
1401        &self,
1402        dependency_flattened_dag: Dag,
1403        hier_results_reachable: FixedBitSet,
1404    ) -> SystemSchedule {
1405        let dg_system_ids = dependency_flattened_dag.topsort.clone();
1406        let dg_system_idx_map = dg_system_ids
1407            .iter()
1408            .cloned()
1409            .enumerate()
1410            .map(|(i, id)| (id, i))
1411            .collect::<HashMap<_, _>>();
1412
1413        let hg_systems = self
1414            .hierarchy
1415            .topsort
1416            .iter()
1417            .cloned()
1418            .enumerate()
1419            .filter(|&(_i, id)| id.is_system())
1420            .collect::<Vec<_>>();
1421
1422        let (hg_set_with_conditions_idxs, hg_set_ids): (Vec<_>, Vec<_>) = self
1423            .hierarchy
1424            .topsort
1425            .iter()
1426            .cloned()
1427            .enumerate()
1428            .filter(|&(_i, id)| {
1429                // ignore system sets that have no conditions
1430                // ignore system type sets (already covered, they don't have conditions)
1431                id.is_set() && !self.system_set_conditions[id.index()].is_empty()
1432            })
1433            .unzip();
1434
1435        let sys_count = self.systems.len();
1436        let set_with_conditions_count = hg_set_ids.len();
1437        let hg_node_count = self.hierarchy.graph.node_count();
1438
1439        // get the number of dependencies and the immediate dependents of each system
1440        // (needed by multi_threaded executor to run systems in the correct order)
1441        let mut system_dependencies = Vec::with_capacity(sys_count);
1442        let mut system_dependents = Vec::with_capacity(sys_count);
1443        for &sys_id in &dg_system_ids {
1444            let num_dependencies = dependency_flattened_dag
1445                .graph
1446                .neighbors_directed(sys_id, Incoming)
1447                .count();
1448
1449            let dependents = dependency_flattened_dag
1450                .graph
1451                .neighbors_directed(sys_id, Outgoing)
1452                .map(|dep_id| dg_system_idx_map[&dep_id])
1453                .collect::<Vec<_>>();
1454
1455            system_dependencies.push(num_dependencies);
1456            system_dependents.push(dependents);
1457        }
1458
1459        // get the rows and columns of the hierarchy graph's reachability matrix
1460        // (needed to we can evaluate conditions in the correct order)
1461        let mut systems_in_sets_with_conditions =
1462            vec![FixedBitSet::with_capacity(sys_count); set_with_conditions_count];
1463        for (i, &row) in hg_set_with_conditions_idxs.iter().enumerate() {
1464            let bitset = &mut systems_in_sets_with_conditions[i];
1465            for &(col, sys_id) in &hg_systems {
1466                let idx = dg_system_idx_map[&sys_id];
1467                let is_descendant = hier_results_reachable[index(row, col, hg_node_count)];
1468                bitset.set(idx, is_descendant);
1469            }
1470        }
1471
1472        let mut sets_with_conditions_of_systems =
1473            vec![FixedBitSet::with_capacity(set_with_conditions_count); sys_count];
1474        for &(col, sys_id) in &hg_systems {
1475            let i = dg_system_idx_map[&sys_id];
1476            let bitset = &mut sets_with_conditions_of_systems[i];
1477            for (idx, &row) in hg_set_with_conditions_idxs
1478                .iter()
1479                .enumerate()
1480                .take_while(|&(_idx, &row)| row < col)
1481            {
1482                let is_ancestor = hier_results_reachable[index(row, col, hg_node_count)];
1483                bitset.set(idx, is_ancestor);
1484            }
1485        }
1486
1487        SystemSchedule {
1488            systems: Vec::with_capacity(sys_count),
1489            system_conditions: Vec::with_capacity(sys_count),
1490            set_conditions: Vec::with_capacity(set_with_conditions_count),
1491            system_ids: dg_system_ids,
1492            set_ids: hg_set_ids,
1493            system_dependencies,
1494            system_dependents,
1495            sets_with_conditions_of_systems,
1496            systems_in_sets_with_conditions,
1497        }
1498    }
1499
1500    /// Updates the `SystemSchedule` from the `ScheduleGraph`.
1501    fn update_schedule(
1502        &mut self,
1503        world: &mut World,
1504        schedule: &mut SystemSchedule,
1505        ignored_ambiguities: &BTreeSet<ComponentId>,
1506        schedule_label: InternedScheduleLabel,
1507    ) -> Result<(), ScheduleBuildError> {
1508        if !self.uninit.is_empty() {
1509            return Err(ScheduleBuildError::Uninitialized);
1510        }
1511
1512        // move systems out of old schedule
1513        for ((id, system), conditions) in schedule
1514            .system_ids
1515            .drain(..)
1516            .zip(schedule.systems.drain(..))
1517            .zip(schedule.system_conditions.drain(..))
1518        {
1519            self.systems[id.index()].inner = Some(system);
1520            self.system_conditions[id.index()] = conditions;
1521        }
1522
1523        for (id, conditions) in schedule
1524            .set_ids
1525            .drain(..)
1526            .zip(schedule.set_conditions.drain(..))
1527        {
1528            self.system_set_conditions[id.index()] = conditions;
1529        }
1530
1531        *schedule = self.build_schedule(world, schedule_label, ignored_ambiguities)?;
1532
1533        // move systems into new schedule
1534        for &id in &schedule.system_ids {
1535            let system = self.systems[id.index()].inner.take().unwrap();
1536            let conditions = core::mem::take(&mut self.system_conditions[id.index()]);
1537            schedule.systems.push(system);
1538            schedule.system_conditions.push(conditions);
1539        }
1540
1541        for &id in &schedule.set_ids {
1542            let conditions = core::mem::take(&mut self.system_set_conditions[id.index()]);
1543            schedule.set_conditions.push(conditions);
1544        }
1545
1546        Ok(())
1547    }
1548}
1549
1550/// Values returned by [`ScheduleGraph::process_configs`]
1551struct ProcessConfigsResult {
1552    /// All nodes contained inside this `process_configs` call's [`ScheduleConfigs`] hierarchy,
1553    /// if `ancestor_chained` is true
1554    nodes: Vec<NodeId>,
1555    /// True if and only if all nodes are "densely chained", meaning that all nested nodes
1556    /// are linearly chained (as if `after` system ordering had been applied between each node)
1557    /// in the order they are defined
1558    densely_chained: bool,
1559}
1560
1561/// Trait used by [`ScheduleGraph::process_configs`] to process a single [`ScheduleConfig`].
1562trait ProcessScheduleConfig: Schedulable + Sized {
1563    /// Process a single [`ScheduleConfig`].
1564    fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig<Self>) -> NodeId;
1565}
1566
1567impl ProcessScheduleConfig for ScheduleSystem {
1568    fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig<Self>) -> NodeId {
1569        schedule_graph.add_system_inner(config).unwrap()
1570    }
1571}
1572
1573impl ProcessScheduleConfig for InternedSystemSet {
1574    fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig<Self>) -> NodeId {
1575        schedule_graph.configure_set_inner(config).unwrap()
1576    }
1577}
1578
1579/// Used to select the appropriate reporting function.
1580pub enum ReportCycles {
1581    /// When sets contain themselves
1582    Hierarchy,
1583    /// When the graph is no longer a DAG
1584    Dependency,
1585}
1586
1587// methods for reporting errors
1588impl ScheduleGraph {
1589    fn get_node_name(&self, id: &NodeId) -> String {
1590        self.get_node_name_inner(id, self.settings.report_sets)
1591    }
1592
1593    #[inline]
1594    fn get_node_name_inner(&self, id: &NodeId, report_sets: bool) -> String {
1595        let name = match id {
1596            NodeId::System(_) => {
1597                let name = self.systems[id.index()].get().unwrap().name().to_string();
1598                if report_sets {
1599                    let sets = self.names_of_sets_containing_node(id);
1600                    if sets.is_empty() {
1601                        name
1602                    } else if sets.len() == 1 {
1603                        format!("{name} (in set {})", sets[0])
1604                    } else {
1605                        format!("{name} (in sets {})", sets.join(", "))
1606                    }
1607                } else {
1608                    name
1609                }
1610            }
1611            NodeId::Set(_) => {
1612                let set = &self.system_sets[id.index()];
1613                if set.is_anonymous() {
1614                    self.anonymous_set_name(id)
1615                } else {
1616                    set.name()
1617                }
1618            }
1619        };
1620        if self.settings.use_shortnames {
1621            ShortName(&name).to_string()
1622        } else {
1623            name
1624        }
1625    }
1626
1627    fn anonymous_set_name(&self, id: &NodeId) -> String {
1628        format!(
1629            "({})",
1630            self.hierarchy
1631                .graph
1632                .edges_directed(*id, Outgoing)
1633                // never get the sets of the members or this will infinite recurse when the report_sets setting is on.
1634                .map(|(_, member_id)| self.get_node_name_inner(&member_id, false))
1635                .reduce(|a, b| format!("{a}, {b}"))
1636                .unwrap_or_default()
1637        )
1638    }
1639
1640    fn get_node_kind(&self, id: &NodeId) -> &'static str {
1641        match id {
1642            NodeId::System(_) => "system",
1643            NodeId::Set(_) => "system set",
1644        }
1645    }
1646
1647    /// If [`ScheduleBuildSettings::hierarchy_detection`] is [`LogLevel::Ignore`] this check
1648    /// is skipped.
1649    fn optionally_check_hierarchy_conflicts(
1650        &self,
1651        transitive_edges: &[(NodeId, NodeId)],
1652        schedule_label: InternedScheduleLabel,
1653    ) -> Result<(), ScheduleBuildError> {
1654        if self.settings.hierarchy_detection == LogLevel::Ignore || transitive_edges.is_empty() {
1655            return Ok(());
1656        }
1657
1658        let message = self.get_hierarchy_conflicts_error_message(transitive_edges);
1659        match self.settings.hierarchy_detection {
1660            LogLevel::Ignore => unreachable!(),
1661            LogLevel::Warn => {
1662                error!(
1663                    "Schedule {schedule_label:?} has redundant edges:\n {}",
1664                    message
1665                );
1666                Ok(())
1667            }
1668            LogLevel::Error => Err(ScheduleBuildError::HierarchyRedundancy(message)),
1669        }
1670    }
1671
1672    fn get_hierarchy_conflicts_error_message(
1673        &self,
1674        transitive_edges: &[(NodeId, NodeId)],
1675    ) -> String {
1676        let mut message = String::from("hierarchy contains redundant edge(s)");
1677        for (parent, child) in transitive_edges {
1678            writeln!(
1679                message,
1680                " -- {} `{}` cannot be child of set `{}`, longer path exists",
1681                self.get_node_kind(child),
1682                self.get_node_name(child),
1683                self.get_node_name(parent),
1684            )
1685            .unwrap();
1686        }
1687
1688        message
1689    }
1690
1691    /// Tries to topologically sort `graph`.
1692    ///
1693    /// If the graph is acyclic, returns [`Ok`] with the list of [`NodeId`] in a valid
1694    /// topological order. If the graph contains cycles, returns [`Err`] with the list of
1695    /// strongly-connected components that contain cycles (also in a valid topological order).
1696    ///
1697    /// # Errors
1698    ///
1699    /// If the graph contain cycles, then an error is returned.
1700    pub fn topsort_graph(
1701        &self,
1702        graph: &DiGraph,
1703        report: ReportCycles,
1704    ) -> Result<Vec<NodeId>, ScheduleBuildError> {
1705        // Tarjan's SCC algorithm returns elements in *reverse* topological order.
1706        let mut top_sorted_nodes = Vec::with_capacity(graph.node_count());
1707        let mut sccs_with_cycles = Vec::new();
1708
1709        for scc in graph.iter_sccs() {
1710            // A strongly-connected component is a group of nodes who can all reach each other
1711            // through one or more paths. If an SCC contains more than one node, there must be
1712            // at least one cycle within them.
1713            top_sorted_nodes.extend_from_slice(&scc);
1714            if scc.len() > 1 {
1715                sccs_with_cycles.push(scc);
1716            }
1717        }
1718
1719        if sccs_with_cycles.is_empty() {
1720            // reverse to get topological order
1721            top_sorted_nodes.reverse();
1722            Ok(top_sorted_nodes)
1723        } else {
1724            let mut cycles = Vec::new();
1725            for scc in &sccs_with_cycles {
1726                cycles.append(&mut simple_cycles_in_component(graph, scc));
1727            }
1728
1729            let error = match report {
1730                ReportCycles::Hierarchy => ScheduleBuildError::HierarchyCycle(
1731                    self.get_hierarchy_cycles_error_message(&cycles),
1732                ),
1733                ReportCycles::Dependency => ScheduleBuildError::DependencyCycle(
1734                    self.get_dependency_cycles_error_message(&cycles),
1735                ),
1736            };
1737
1738            Err(error)
1739        }
1740    }
1741
1742    /// Logs details of cycles in the hierarchy graph.
1743    fn get_hierarchy_cycles_error_message(&self, cycles: &[Vec<NodeId>]) -> String {
1744        let mut message = format!("schedule has {} in_set cycle(s):\n", cycles.len());
1745        for (i, cycle) in cycles.iter().enumerate() {
1746            let mut names = cycle.iter().map(|id| self.get_node_name(id));
1747            let first_name = names.next().unwrap();
1748            writeln!(
1749                message,
1750                "cycle {}: set `{first_name}` contains itself",
1751                i + 1,
1752            )
1753            .unwrap();
1754            writeln!(message, "set `{first_name}`").unwrap();
1755            for name in names.chain(core::iter::once(first_name)) {
1756                writeln!(message, " ... which contains set `{name}`").unwrap();
1757            }
1758            writeln!(message).unwrap();
1759        }
1760
1761        message
1762    }
1763
1764    /// Logs details of cycles in the dependency graph.
1765    fn get_dependency_cycles_error_message(&self, cycles: &[Vec<NodeId>]) -> String {
1766        let mut message = format!("schedule has {} before/after cycle(s):\n", cycles.len());
1767        for (i, cycle) in cycles.iter().enumerate() {
1768            let mut names = cycle
1769                .iter()
1770                .map(|id| (self.get_node_kind(id), self.get_node_name(id)));
1771            let (first_kind, first_name) = names.next().unwrap();
1772            writeln!(
1773                message,
1774                "cycle {}: {first_kind} `{first_name}` must run before itself",
1775                i + 1,
1776            )
1777            .unwrap();
1778            writeln!(message, "{first_kind} `{first_name}`").unwrap();
1779            for (kind, name) in names.chain(core::iter::once((first_kind, first_name))) {
1780                writeln!(message, " ... which must run before {kind} `{name}`").unwrap();
1781            }
1782            writeln!(message).unwrap();
1783        }
1784
1785        message
1786    }
1787
1788    fn check_for_cross_dependencies(
1789        &self,
1790        dep_results: &CheckGraphResults,
1791        hier_results_connected: &HashSet<(NodeId, NodeId)>,
1792    ) -> Result<(), ScheduleBuildError> {
1793        for &(a, b) in &dep_results.connected {
1794            if hier_results_connected.contains(&(a, b)) || hier_results_connected.contains(&(b, a))
1795            {
1796                let name_a = self.get_node_name(&a);
1797                let name_b = self.get_node_name(&b);
1798                return Err(ScheduleBuildError::CrossDependency(name_a, name_b));
1799            }
1800        }
1801
1802        Ok(())
1803    }
1804
1805    fn check_order_but_intersect(
1806        &self,
1807        dep_results_connected: &HashSet<(NodeId, NodeId)>,
1808        set_system_bitsets: &HashMap<NodeId, FixedBitSet>,
1809    ) -> Result<(), ScheduleBuildError> {
1810        // check that there is no ordering between system sets that intersect
1811        for (a, b) in dep_results_connected {
1812            if !(a.is_set() && b.is_set()) {
1813                continue;
1814            }
1815
1816            let a_systems = set_system_bitsets.get(a).unwrap();
1817            let b_systems = set_system_bitsets.get(b).unwrap();
1818
1819            if !a_systems.is_disjoint(b_systems) {
1820                return Err(ScheduleBuildError::SetsHaveOrderButIntersect(
1821                    self.get_node_name(a),
1822                    self.get_node_name(b),
1823                ));
1824            }
1825        }
1826
1827        Ok(())
1828    }
1829
1830    fn check_system_type_set_ambiguity(
1831        &self,
1832        set_systems: &HashMap<NodeId, Vec<NodeId>>,
1833    ) -> Result<(), ScheduleBuildError> {
1834        for (&id, systems) in set_systems {
1835            let set = &self.system_sets[id.index()];
1836            if set.is_system_type() {
1837                let instances = systems.len();
1838                let ambiguous_with = self.ambiguous_with.edges(id);
1839                let before = self.dependency.graph.edges_directed(id, Incoming);
1840                let after = self.dependency.graph.edges_directed(id, Outgoing);
1841                let relations = before.count() + after.count() + ambiguous_with.count();
1842                if instances > 1 && relations > 0 {
1843                    return Err(ScheduleBuildError::SystemTypeSetAmbiguity(
1844                        self.get_node_name(&id),
1845                    ));
1846                }
1847            }
1848        }
1849        Ok(())
1850    }
1851
1852    /// if [`ScheduleBuildSettings::ambiguity_detection`] is [`LogLevel::Ignore`], this check is skipped
1853    fn optionally_check_conflicts(
1854        &self,
1855        conflicts: &[(NodeId, NodeId, Vec<ComponentId>)],
1856        components: &Components,
1857        schedule_label: InternedScheduleLabel,
1858    ) -> Result<(), ScheduleBuildError> {
1859        if self.settings.ambiguity_detection == LogLevel::Ignore || conflicts.is_empty() {
1860            return Ok(());
1861        }
1862
1863        let message = self.get_conflicts_error_message(conflicts, components);
1864        match self.settings.ambiguity_detection {
1865            LogLevel::Ignore => Ok(()),
1866            LogLevel::Warn => {
1867                warn!("Schedule {schedule_label:?} has ambiguities.\n{}", message);
1868                Ok(())
1869            }
1870            LogLevel::Error => Err(ScheduleBuildError::Ambiguity(message)),
1871        }
1872    }
1873
1874    fn get_conflicts_error_message(
1875        &self,
1876        ambiguities: &[(NodeId, NodeId, Vec<ComponentId>)],
1877        components: &Components,
1878    ) -> String {
1879        let n_ambiguities = ambiguities.len();
1880
1881        let mut message = format!(
1882                "{n_ambiguities} pairs of systems with conflicting data access have indeterminate execution order. \
1883                Consider adding `before`, `after`, or `ambiguous_with` relationships between these:\n",
1884            );
1885
1886        for (name_a, name_b, conflicts) in self.conflicts_to_string(ambiguities, components) {
1887            writeln!(message, " -- {name_a} and {name_b}").unwrap();
1888
1889            if !conflicts.is_empty() {
1890                writeln!(message, "    conflict on: {conflicts:?}").unwrap();
1891            } else {
1892                // one or both systems must be exclusive
1893                let world = core::any::type_name::<World>();
1894                writeln!(message, "    conflict on: {world}").unwrap();
1895            }
1896        }
1897
1898        message
1899    }
1900
1901    /// convert conflicts to human readable format
1902    pub fn conflicts_to_string<'a>(
1903        &'a self,
1904        ambiguities: &'a [(NodeId, NodeId, Vec<ComponentId>)],
1905        components: &'a Components,
1906    ) -> impl Iterator<Item = (String, String, Vec<Cow<'a, str>>)> + 'a {
1907        ambiguities
1908            .iter()
1909            .map(move |(system_a, system_b, conflicts)| {
1910                let name_a = self.get_node_name(system_a);
1911                let name_b = self.get_node_name(system_b);
1912
1913                debug_assert!(system_a.is_system(), "{name_a} is not a system.");
1914                debug_assert!(system_b.is_system(), "{name_b} is not a system.");
1915
1916                let conflict_names: Vec<_> = conflicts
1917                    .iter()
1918                    .map(|id| components.get_name(*id).unwrap())
1919                    .collect();
1920
1921                (name_a, name_b, conflict_names)
1922            })
1923    }
1924
1925    fn traverse_sets_containing_node(&self, id: NodeId, f: &mut impl FnMut(NodeId) -> bool) {
1926        for (set_id, _) in self.hierarchy.graph.edges_directed(id, Incoming) {
1927            if f(set_id) {
1928                self.traverse_sets_containing_node(set_id, f);
1929            }
1930        }
1931    }
1932
1933    fn names_of_sets_containing_node(&self, id: &NodeId) -> Vec<String> {
1934        let mut sets = <HashSet<_>>::default();
1935        self.traverse_sets_containing_node(*id, &mut |set_id| {
1936            !self.system_sets[set_id.index()].is_system_type() && sets.insert(set_id)
1937        });
1938        let mut sets: Vec<_> = sets
1939            .into_iter()
1940            .map(|set_id| self.get_node_name(&set_id))
1941            .collect();
1942        sets.sort();
1943        sets
1944    }
1945}
1946
1947/// Category of errors encountered during schedule construction.
1948#[derive(Error, Debug)]
1949#[non_exhaustive]
1950pub enum ScheduleBuildError {
1951    /// A system set contains itself.
1952    #[error("System set `{0}` contains itself.")]
1953    HierarchyLoop(String),
1954    /// The hierarchy of system sets contains a cycle.
1955    #[error("System set hierarchy contains cycle(s).\n{0}")]
1956    HierarchyCycle(String),
1957    /// The hierarchy of system sets contains redundant edges.
1958    ///
1959    /// This error is disabled by default, but can be opted-in using [`ScheduleBuildSettings`].
1960    #[error("System set hierarchy contains redundant edges.\n{0}")]
1961    HierarchyRedundancy(String),
1962    /// A system (set) has been told to run before itself.
1963    #[error("System set `{0}` depends on itself.")]
1964    DependencyLoop(String),
1965    /// The dependency graph contains a cycle.
1966    #[error("System dependencies contain cycle(s).\n{0}")]
1967    DependencyCycle(String),
1968    /// Tried to order a system (set) relative to a system set it belongs to.
1969    #[error("`{0}` and `{1}` have both `in_set` and `before`-`after` relationships (these might be transitive). This combination is unsolvable as a system cannot run before or after a set it belongs to.")]
1970    CrossDependency(String, String),
1971    /// Tried to order system sets that share systems.
1972    #[error("`{0}` and `{1}` have a `before`-`after` relationship (which may be transitive) but share systems.")]
1973    SetsHaveOrderButIntersect(String, String),
1974    /// Tried to order a system (set) relative to all instances of some system function.
1975    #[error("Tried to order against `{0}` in a schedule that has more than one `{0}` instance. `{0}` is a `SystemTypeSet` and cannot be used for ordering if ambiguous. Use a different set without this restriction.")]
1976    SystemTypeSetAmbiguity(String),
1977    /// Systems with conflicting access have indeterminate run order.
1978    ///
1979    /// This error is disabled by default, but can be opted-in using [`ScheduleBuildSettings`].
1980    #[error("Systems with conflicting access have indeterminate run order.\n{0}")]
1981    Ambiguity(String),
1982    /// Tried to run a schedule before all of its systems have been initialized.
1983    #[error("Systems in schedule have not been initialized.")]
1984    Uninitialized,
1985}
1986
1987/// Specifies how schedule construction should respond to detecting a certain kind of issue.
1988#[derive(Debug, Clone, PartialEq)]
1989pub enum LogLevel {
1990    /// Occurrences are completely ignored.
1991    Ignore,
1992    /// Occurrences are logged only.
1993    Warn,
1994    /// Occurrences are logged and result in errors.
1995    Error,
1996}
1997
1998/// Specifies miscellaneous settings for schedule construction.
1999#[derive(Clone, Debug)]
2000pub struct ScheduleBuildSettings {
2001    /// Determines whether the presence of ambiguities (systems with conflicting access but indeterminate order)
2002    /// is only logged or also results in an [`Ambiguity`](ScheduleBuildError::Ambiguity) error.
2003    ///
2004    /// Defaults to [`LogLevel::Ignore`].
2005    pub ambiguity_detection: LogLevel,
2006    /// Determines whether the presence of redundant edges in the hierarchy of system sets is only
2007    /// logged or also results in a [`HierarchyRedundancy`](ScheduleBuildError::HierarchyRedundancy)
2008    /// error.
2009    ///
2010    /// Defaults to [`LogLevel::Warn`].
2011    pub hierarchy_detection: LogLevel,
2012    /// Auto insert [`ApplyDeferred`] systems into the schedule,
2013    /// when there are [`Deferred`](crate::prelude::Deferred)
2014    /// in one system and there are ordering dependencies on that system. [`Commands`](crate::system::Commands) is one
2015    /// such deferred buffer.
2016    ///
2017    /// You may want to disable this if you only want to sync deferred params at the end of the schedule,
2018    /// or want to manually insert all your sync points.
2019    ///
2020    /// Defaults to `true`
2021    pub auto_insert_apply_deferred: bool,
2022    /// If set to true, node names will be shortened instead of the fully qualified type path.
2023    ///
2024    /// Defaults to `true`.
2025    pub use_shortnames: bool,
2026    /// If set to true, report all system sets the conflicting systems are part of.
2027    ///
2028    /// Defaults to `true`.
2029    pub report_sets: bool,
2030}
2031
2032impl Default for ScheduleBuildSettings {
2033    fn default() -> Self {
2034        Self::new()
2035    }
2036}
2037
2038impl ScheduleBuildSettings {
2039    /// Default build settings.
2040    /// See the field-level documentation for the default value of each field.
2041    pub const fn new() -> Self {
2042        Self {
2043            ambiguity_detection: LogLevel::Ignore,
2044            hierarchy_detection: LogLevel::Warn,
2045            auto_insert_apply_deferred: true,
2046            use_shortnames: true,
2047            report_sets: true,
2048        }
2049    }
2050}
2051
2052/// Error to denote that [`Schedule::initialize`] or [`Schedule::run`] has not yet been called for
2053/// this schedule.
2054#[derive(Error, Debug)]
2055#[error("executable schedule has not been built")]
2056pub struct ScheduleNotInitialized;
2057
2058#[cfg(test)]
2059mod tests {
2060    use bevy_ecs_macros::ScheduleLabel;
2061
2062    use crate::{
2063        prelude::{ApplyDeferred, Res, Resource},
2064        schedule::{
2065            tests::ResMut, IntoScheduleConfigs, Schedule, ScheduleBuildSettings, SystemSet,
2066        },
2067        system::Commands,
2068        world::World,
2069    };
2070
2071    use super::Schedules;
2072
2073    #[derive(Resource)]
2074    struct Resource1;
2075
2076    #[derive(Resource)]
2077    struct Resource2;
2078
2079    // regression test for https://github.com/bevyengine/bevy/issues/9114
2080    #[test]
2081    fn ambiguous_with_not_breaking_run_conditions() {
2082        #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
2083        struct Set;
2084
2085        let mut world = World::new();
2086        let mut schedule = Schedule::default();
2087
2088        let system: fn() = || {
2089            panic!("This system must not run");
2090        };
2091
2092        schedule.configure_sets(Set.run_if(|| false));
2093        schedule.add_systems(system.ambiguous_with(|| ()).in_set(Set));
2094        schedule.run(&mut world);
2095    }
2096
2097    #[test]
2098    fn inserts_a_sync_point() {
2099        let mut schedule = Schedule::default();
2100        let mut world = World::default();
2101        schedule.add_systems(
2102            (
2103                |mut commands: Commands| commands.insert_resource(Resource1),
2104                |_: Res<Resource1>| {},
2105            )
2106                .chain(),
2107        );
2108        schedule.run(&mut world);
2109
2110        // inserted a sync point
2111        assert_eq!(schedule.executable.systems.len(), 3);
2112    }
2113
2114    #[test]
2115    fn explicit_sync_point_used_as_auto_sync_point() {
2116        let mut schedule = Schedule::default();
2117        let mut world = World::default();
2118        schedule.add_systems(
2119            (
2120                |mut commands: Commands| commands.insert_resource(Resource1),
2121                |_: Res<Resource1>| {},
2122            )
2123                .chain(),
2124        );
2125        schedule.add_systems((|| {}, ApplyDeferred, || {}).chain());
2126        schedule.run(&mut world);
2127
2128        // No sync point was inserted, since we can reuse the explicit sync point.
2129        assert_eq!(schedule.executable.systems.len(), 5);
2130    }
2131
2132    #[test]
2133    fn conditional_explicit_sync_point_not_used_as_auto_sync_point() {
2134        let mut schedule = Schedule::default();
2135        let mut world = World::default();
2136        schedule.add_systems(
2137            (
2138                |mut commands: Commands| commands.insert_resource(Resource1),
2139                |_: Res<Resource1>| {},
2140            )
2141                .chain(),
2142        );
2143        schedule.add_systems((|| {}, ApplyDeferred.run_if(|| false), || {}).chain());
2144        schedule.run(&mut world);
2145
2146        // A sync point was inserted, since the explicit sync point is not always run.
2147        assert_eq!(schedule.executable.systems.len(), 6);
2148    }
2149
2150    #[test]
2151    fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_chain() {
2152        let mut schedule = Schedule::default();
2153        let mut world = World::default();
2154        schedule.add_systems(
2155            (
2156                |mut commands: Commands| commands.insert_resource(Resource1),
2157                |_: Res<Resource1>| {},
2158            )
2159                .chain(),
2160        );
2161        schedule.add_systems((|| {}, ApplyDeferred, || {}).chain().run_if(|| false));
2162        schedule.run(&mut world);
2163
2164        // A sync point was inserted, since the explicit sync point is not always run.
2165        assert_eq!(schedule.executable.systems.len(), 6);
2166    }
2167
2168    #[test]
2169    fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_system_set() {
2170        #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
2171        struct Set;
2172
2173        let mut schedule = Schedule::default();
2174        let mut world = World::default();
2175        schedule.configure_sets(Set.run_if(|| false));
2176        schedule.add_systems(
2177            (
2178                |mut commands: Commands| commands.insert_resource(Resource1),
2179                |_: Res<Resource1>| {},
2180            )
2181                .chain(),
2182        );
2183        schedule.add_systems((|| {}, ApplyDeferred.in_set(Set), || {}).chain());
2184        schedule.run(&mut world);
2185
2186        // A sync point was inserted, since the explicit sync point is not always run.
2187        assert_eq!(schedule.executable.systems.len(), 6);
2188    }
2189
2190    #[test]
2191    fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_nested_system_set()
2192    {
2193        #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
2194        struct Set1;
2195        #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
2196        struct Set2;
2197
2198        let mut schedule = Schedule::default();
2199        let mut world = World::default();
2200        schedule.configure_sets(Set2.run_if(|| false));
2201        schedule.configure_sets(Set1.in_set(Set2));
2202        schedule.add_systems(
2203            (
2204                |mut commands: Commands| commands.insert_resource(Resource1),
2205                |_: Res<Resource1>| {},
2206            )
2207                .chain(),
2208        );
2209        schedule.add_systems((|| {}, ApplyDeferred, || {}).chain().in_set(Set1));
2210        schedule.run(&mut world);
2211
2212        // A sync point was inserted, since the explicit sync point is not always run.
2213        assert_eq!(schedule.executable.systems.len(), 6);
2214    }
2215
2216    #[test]
2217    fn merges_sync_points_into_one() {
2218        let mut schedule = Schedule::default();
2219        let mut world = World::default();
2220        // insert two parallel command systems, it should only create one sync point
2221        schedule.add_systems(
2222            (
2223                (
2224                    |mut commands: Commands| commands.insert_resource(Resource1),
2225                    |mut commands: Commands| commands.insert_resource(Resource2),
2226                ),
2227                |_: Res<Resource1>, _: Res<Resource2>| {},
2228            )
2229                .chain(),
2230        );
2231        schedule.run(&mut world);
2232
2233        // inserted sync points
2234        assert_eq!(schedule.executable.systems.len(), 4);
2235
2236        // merges sync points on rebuild
2237        schedule.add_systems(((
2238            (
2239                |mut commands: Commands| commands.insert_resource(Resource1),
2240                |mut commands: Commands| commands.insert_resource(Resource2),
2241            ),
2242            |_: Res<Resource1>, _: Res<Resource2>| {},
2243        )
2244            .chain(),));
2245        schedule.run(&mut world);
2246
2247        assert_eq!(schedule.executable.systems.len(), 7);
2248    }
2249
2250    #[test]
2251    fn adds_multiple_consecutive_syncs() {
2252        let mut schedule = Schedule::default();
2253        let mut world = World::default();
2254        // insert two consecutive command systems, it should create two sync points
2255        schedule.add_systems(
2256            (
2257                |mut commands: Commands| commands.insert_resource(Resource1),
2258                |mut commands: Commands| commands.insert_resource(Resource2),
2259                |_: Res<Resource1>, _: Res<Resource2>| {},
2260            )
2261                .chain(),
2262        );
2263        schedule.run(&mut world);
2264
2265        assert_eq!(schedule.executable.systems.len(), 5);
2266    }
2267
2268    #[test]
2269    fn do_not_consider_ignore_deferred_before_exclusive_system() {
2270        let mut schedule = Schedule::default();
2271        let mut world = World::default();
2272        // chain_ignore_deferred adds no sync points usually but an exception is made for exclusive systems
2273        schedule.add_systems(
2274            (
2275                |_: Commands| {},
2276                // <- no sync point is added here because the following system is not exclusive
2277                |mut commands: Commands| commands.insert_resource(Resource1),
2278                // <- sync point is added here because the following system is exclusive which expects to see all commands to that point
2279                |world: &mut World| assert!(world.contains_resource::<Resource1>()),
2280                // <- no sync point is added here because the previous system has no deferred parameters
2281                |_: &mut World| {},
2282                // <- no sync point is added here because the following system is not exclusive
2283                |_: Commands| {},
2284            )
2285                .chain_ignore_deferred(),
2286        );
2287        schedule.run(&mut world);
2288
2289        assert_eq!(schedule.executable.systems.len(), 6); // 5 systems + 1 sync point
2290    }
2291
2292    #[test]
2293    fn bubble_sync_point_through_ignore_deferred_node() {
2294        let mut schedule = Schedule::default();
2295        let mut world = World::default();
2296
2297        let insert_resource_config = (
2298            // the first system has deferred commands
2299            |mut commands: Commands| commands.insert_resource(Resource1),
2300            // the second system has no deferred commands
2301            || {},
2302        )
2303            // the first two systems are chained without a sync point in between
2304            .chain_ignore_deferred();
2305
2306        schedule.add_systems(
2307            (
2308                insert_resource_config,
2309                // the third system would panic if the command of the first system was not applied
2310                |_: Res<Resource1>| {},
2311            )
2312                // the third system is chained after the first two, possibly with a sync point in between
2313                .chain(),
2314        );
2315
2316        // To add a sync point between the second and third system despite the second having no commands,
2317        // the first system has to signal the second system that there are unapplied commands.
2318        // With that the second system will add a sync point after it so the third system will find the resource.
2319
2320        schedule.run(&mut world);
2321
2322        assert_eq!(schedule.executable.systems.len(), 4); // 3 systems + 1 sync point
2323    }
2324
2325    #[test]
2326    fn disable_auto_sync_points() {
2327        let mut schedule = Schedule::default();
2328        schedule.set_build_settings(ScheduleBuildSettings {
2329            auto_insert_apply_deferred: false,
2330            ..Default::default()
2331        });
2332        let mut world = World::default();
2333        schedule.add_systems(
2334            (
2335                |mut commands: Commands| commands.insert_resource(Resource1),
2336                |res: Option<Res<Resource1>>| assert!(res.is_none()),
2337            )
2338                .chain(),
2339        );
2340        schedule.run(&mut world);
2341
2342        assert_eq!(schedule.executable.systems.len(), 2);
2343    }
2344
2345    mod no_sync_edges {
2346        use super::*;
2347
2348        fn insert_resource(mut commands: Commands) {
2349            commands.insert_resource(Resource1);
2350        }
2351
2352        fn resource_does_not_exist(res: Option<Res<Resource1>>) {
2353            assert!(res.is_none());
2354        }
2355
2356        #[derive(SystemSet, Hash, PartialEq, Eq, Debug, Clone)]
2357        enum Sets {
2358            A,
2359            B,
2360        }
2361
2362        fn check_no_sync_edges(add_systems: impl FnOnce(&mut Schedule)) {
2363            let mut schedule = Schedule::default();
2364            let mut world = World::default();
2365            add_systems(&mut schedule);
2366
2367            schedule.run(&mut world);
2368
2369            assert_eq!(schedule.executable.systems.len(), 2);
2370        }
2371
2372        #[test]
2373        fn system_to_system_after() {
2374            check_no_sync_edges(|schedule| {
2375                schedule.add_systems((
2376                    insert_resource,
2377                    resource_does_not_exist.after_ignore_deferred(insert_resource),
2378                ));
2379            });
2380        }
2381
2382        #[test]
2383        fn system_to_system_before() {
2384            check_no_sync_edges(|schedule| {
2385                schedule.add_systems((
2386                    insert_resource.before_ignore_deferred(resource_does_not_exist),
2387                    resource_does_not_exist,
2388                ));
2389            });
2390        }
2391
2392        #[test]
2393        fn set_to_system_after() {
2394            check_no_sync_edges(|schedule| {
2395                schedule
2396                    .add_systems((insert_resource, resource_does_not_exist.in_set(Sets::A)))
2397                    .configure_sets(Sets::A.after_ignore_deferred(insert_resource));
2398            });
2399        }
2400
2401        #[test]
2402        fn set_to_system_before() {
2403            check_no_sync_edges(|schedule| {
2404                schedule
2405                    .add_systems((insert_resource.in_set(Sets::A), resource_does_not_exist))
2406                    .configure_sets(Sets::A.before_ignore_deferred(resource_does_not_exist));
2407            });
2408        }
2409
2410        #[test]
2411        fn set_to_set_after() {
2412            check_no_sync_edges(|schedule| {
2413                schedule
2414                    .add_systems((
2415                        insert_resource.in_set(Sets::A),
2416                        resource_does_not_exist.in_set(Sets::B),
2417                    ))
2418                    .configure_sets(Sets::B.after_ignore_deferred(Sets::A));
2419            });
2420        }
2421
2422        #[test]
2423        fn set_to_set_before() {
2424            check_no_sync_edges(|schedule| {
2425                schedule
2426                    .add_systems((
2427                        insert_resource.in_set(Sets::A),
2428                        resource_does_not_exist.in_set(Sets::B),
2429                    ))
2430                    .configure_sets(Sets::A.before_ignore_deferred(Sets::B));
2431            });
2432        }
2433    }
2434
2435    mod no_sync_chain {
2436        use super::*;
2437
2438        #[derive(Resource)]
2439        struct Ra;
2440
2441        #[derive(Resource)]
2442        struct Rb;
2443
2444        #[derive(Resource)]
2445        struct Rc;
2446
2447        fn run_schedule(expected_num_systems: usize, add_systems: impl FnOnce(&mut Schedule)) {
2448            let mut schedule = Schedule::default();
2449            let mut world = World::default();
2450            add_systems(&mut schedule);
2451
2452            schedule.run(&mut world);
2453
2454            assert_eq!(schedule.executable.systems.len(), expected_num_systems);
2455        }
2456
2457        #[test]
2458        fn only_chain_outside() {
2459            run_schedule(5, |schedule: &mut Schedule| {
2460                schedule.add_systems(
2461                    (
2462                        (
2463                            |mut commands: Commands| commands.insert_resource(Ra),
2464                            |mut commands: Commands| commands.insert_resource(Rb),
2465                        ),
2466                        (
2467                            |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2468                                assert!(res_a.is_some());
2469                                assert!(res_b.is_some());
2470                            },
2471                            |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2472                                assert!(res_a.is_some());
2473                                assert!(res_b.is_some());
2474                            },
2475                        ),
2476                    )
2477                        .chain(),
2478                );
2479            });
2480
2481            run_schedule(4, |schedule: &mut Schedule| {
2482                schedule.add_systems(
2483                    (
2484                        (
2485                            |mut commands: Commands| commands.insert_resource(Ra),
2486                            |mut commands: Commands| commands.insert_resource(Rb),
2487                        ),
2488                        (
2489                            |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2490                                assert!(res_a.is_none());
2491                                assert!(res_b.is_none());
2492                            },
2493                            |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2494                                assert!(res_a.is_none());
2495                                assert!(res_b.is_none());
2496                            },
2497                        ),
2498                    )
2499                        .chain_ignore_deferred(),
2500                );
2501            });
2502        }
2503
2504        #[test]
2505        fn chain_first() {
2506            run_schedule(6, |schedule: &mut Schedule| {
2507                schedule.add_systems(
2508                    (
2509                        (
2510                            |mut commands: Commands| commands.insert_resource(Ra),
2511                            |mut commands: Commands, res_a: Option<Res<Ra>>| {
2512                                commands.insert_resource(Rb);
2513                                assert!(res_a.is_some());
2514                            },
2515                        )
2516                            .chain(),
2517                        (
2518                            |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2519                                assert!(res_a.is_some());
2520                                assert!(res_b.is_some());
2521                            },
2522                            |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2523                                assert!(res_a.is_some());
2524                                assert!(res_b.is_some());
2525                            },
2526                        ),
2527                    )
2528                        .chain(),
2529                );
2530            });
2531
2532            run_schedule(5, |schedule: &mut Schedule| {
2533                schedule.add_systems(
2534                    (
2535                        (
2536                            |mut commands: Commands| commands.insert_resource(Ra),
2537                            |mut commands: Commands, res_a: Option<Res<Ra>>| {
2538                                commands.insert_resource(Rb);
2539                                assert!(res_a.is_some());
2540                            },
2541                        )
2542                            .chain(),
2543                        (
2544                            |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2545                                assert!(res_a.is_some());
2546                                assert!(res_b.is_none());
2547                            },
2548                            |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2549                                assert!(res_a.is_some());
2550                                assert!(res_b.is_none());
2551                            },
2552                        ),
2553                    )
2554                        .chain_ignore_deferred(),
2555                );
2556            });
2557        }
2558
2559        #[test]
2560        fn chain_second() {
2561            run_schedule(6, |schedule: &mut Schedule| {
2562                schedule.add_systems(
2563                    (
2564                        (
2565                            |mut commands: Commands| commands.insert_resource(Ra),
2566                            |mut commands: Commands| commands.insert_resource(Rb),
2567                        ),
2568                        (
2569                            |mut commands: Commands,
2570                             res_a: Option<Res<Ra>>,
2571                             res_b: Option<Res<Rb>>| {
2572                                commands.insert_resource(Rc);
2573                                assert!(res_a.is_some());
2574                                assert!(res_b.is_some());
2575                            },
2576                            |res_a: Option<Res<Ra>>,
2577                             res_b: Option<Res<Rb>>,
2578                             res_c: Option<Res<Rc>>| {
2579                                assert!(res_a.is_some());
2580                                assert!(res_b.is_some());
2581                                assert!(res_c.is_some());
2582                            },
2583                        )
2584                            .chain(),
2585                    )
2586                        .chain(),
2587                );
2588            });
2589
2590            run_schedule(5, |schedule: &mut Schedule| {
2591                schedule.add_systems(
2592                    (
2593                        (
2594                            |mut commands: Commands| commands.insert_resource(Ra),
2595                            |mut commands: Commands| commands.insert_resource(Rb),
2596                        ),
2597                        (
2598                            |mut commands: Commands,
2599                             res_a: Option<Res<Ra>>,
2600                             res_b: Option<Res<Rb>>| {
2601                                commands.insert_resource(Rc);
2602                                assert!(res_a.is_none());
2603                                assert!(res_b.is_none());
2604                            },
2605                            |res_a: Option<Res<Ra>>,
2606                             res_b: Option<Res<Rb>>,
2607                             res_c: Option<Res<Rc>>| {
2608                                assert!(res_a.is_some());
2609                                assert!(res_b.is_some());
2610                                assert!(res_c.is_some());
2611                            },
2612                        )
2613                            .chain(),
2614                    )
2615                        .chain_ignore_deferred(),
2616                );
2617            });
2618        }
2619
2620        #[test]
2621        fn chain_all() {
2622            run_schedule(7, |schedule: &mut Schedule| {
2623                schedule.add_systems(
2624                    (
2625                        (
2626                            |mut commands: Commands| commands.insert_resource(Ra),
2627                            |mut commands: Commands, res_a: Option<Res<Ra>>| {
2628                                commands.insert_resource(Rb);
2629                                assert!(res_a.is_some());
2630                            },
2631                        )
2632                            .chain(),
2633                        (
2634                            |mut commands: Commands,
2635                             res_a: Option<Res<Ra>>,
2636                             res_b: Option<Res<Rb>>| {
2637                                commands.insert_resource(Rc);
2638                                assert!(res_a.is_some());
2639                                assert!(res_b.is_some());
2640                            },
2641                            |res_a: Option<Res<Ra>>,
2642                             res_b: Option<Res<Rb>>,
2643                             res_c: Option<Res<Rc>>| {
2644                                assert!(res_a.is_some());
2645                                assert!(res_b.is_some());
2646                                assert!(res_c.is_some());
2647                            },
2648                        )
2649                            .chain(),
2650                    )
2651                        .chain(),
2652                );
2653            });
2654
2655            run_schedule(6, |schedule: &mut Schedule| {
2656                schedule.add_systems(
2657                    (
2658                        (
2659                            |mut commands: Commands| commands.insert_resource(Ra),
2660                            |mut commands: Commands, res_a: Option<Res<Ra>>| {
2661                                commands.insert_resource(Rb);
2662                                assert!(res_a.is_some());
2663                            },
2664                        )
2665                            .chain(),
2666                        (
2667                            |mut commands: Commands,
2668                             res_a: Option<Res<Ra>>,
2669                             res_b: Option<Res<Rb>>| {
2670                                commands.insert_resource(Rc);
2671                                assert!(res_a.is_some());
2672                                assert!(res_b.is_none());
2673                            },
2674                            |res_a: Option<Res<Ra>>,
2675                             res_b: Option<Res<Rb>>,
2676                             res_c: Option<Res<Rc>>| {
2677                                assert!(res_a.is_some());
2678                                assert!(res_b.is_some());
2679                                assert!(res_c.is_some());
2680                            },
2681                        )
2682                            .chain(),
2683                    )
2684                        .chain_ignore_deferred(),
2685                );
2686            });
2687        }
2688    }
2689
2690    #[derive(ScheduleLabel, Hash, Debug, Clone, PartialEq, Eq)]
2691    struct TestSchedule;
2692
2693    #[derive(Resource)]
2694    struct CheckSystemRan(usize);
2695
2696    #[test]
2697    fn add_systems_to_existing_schedule() {
2698        let mut schedules = Schedules::default();
2699        let schedule = Schedule::new(TestSchedule);
2700
2701        schedules.insert(schedule);
2702        schedules.add_systems(TestSchedule, |mut ran: ResMut<CheckSystemRan>| ran.0 += 1);
2703
2704        let mut world = World::new();
2705
2706        world.insert_resource(CheckSystemRan(0));
2707        world.insert_resource(schedules);
2708        world.run_schedule(TestSchedule);
2709
2710        let value = world
2711            .get_resource::<CheckSystemRan>()
2712            .expect("CheckSystemRan Resource Should Exist");
2713        assert_eq!(value.0, 1);
2714    }
2715
2716    #[test]
2717    fn add_systems_to_non_existing_schedule() {
2718        let mut schedules = Schedules::default();
2719
2720        schedules.add_systems(TestSchedule, |mut ran: ResMut<CheckSystemRan>| ran.0 += 1);
2721
2722        let mut world = World::new();
2723
2724        world.insert_resource(CheckSystemRan(0));
2725        world.insert_resource(schedules);
2726        world.run_schedule(TestSchedule);
2727
2728        let value = world
2729            .get_resource::<CheckSystemRan>()
2730            .expect("CheckSystemRan Resource Should Exist");
2731        assert_eq!(value.0, 1);
2732    }
2733
2734    #[derive(SystemSet, Debug, Hash, Clone, PartialEq, Eq)]
2735    enum TestSet {
2736        First,
2737        Second,
2738    }
2739
2740    #[test]
2741    fn configure_set_on_existing_schedule() {
2742        let mut schedules = Schedules::default();
2743        let schedule = Schedule::new(TestSchedule);
2744
2745        schedules.insert(schedule);
2746
2747        schedules.configure_sets(TestSchedule, (TestSet::First, TestSet::Second).chain());
2748        schedules.add_systems(
2749            TestSchedule,
2750            (|mut ran: ResMut<CheckSystemRan>| {
2751                assert_eq!(ran.0, 0);
2752                ran.0 += 1;
2753            })
2754            .in_set(TestSet::First),
2755        );
2756
2757        schedules.add_systems(
2758            TestSchedule,
2759            (|mut ran: ResMut<CheckSystemRan>| {
2760                assert_eq!(ran.0, 1);
2761                ran.0 += 1;
2762            })
2763            .in_set(TestSet::Second),
2764        );
2765
2766        let mut world = World::new();
2767
2768        world.insert_resource(CheckSystemRan(0));
2769        world.insert_resource(schedules);
2770        world.run_schedule(TestSchedule);
2771
2772        let value = world
2773            .get_resource::<CheckSystemRan>()
2774            .expect("CheckSystemRan Resource Should Exist");
2775        assert_eq!(value.0, 2);
2776    }
2777
2778    #[test]
2779    fn configure_set_on_new_schedule() {
2780        let mut schedules = Schedules::default();
2781
2782        schedules.configure_sets(TestSchedule, (TestSet::First, TestSet::Second).chain());
2783        schedules.add_systems(
2784            TestSchedule,
2785            (|mut ran: ResMut<CheckSystemRan>| {
2786                assert_eq!(ran.0, 0);
2787                ran.0 += 1;
2788            })
2789            .in_set(TestSet::First),
2790        );
2791
2792        schedules.add_systems(
2793            TestSchedule,
2794            (|mut ran: ResMut<CheckSystemRan>| {
2795                assert_eq!(ran.0, 1);
2796                ran.0 += 1;
2797            })
2798            .in_set(TestSet::Second),
2799        );
2800
2801        let mut world = World::new();
2802
2803        world.insert_resource(CheckSystemRan(0));
2804        world.insert_resource(schedules);
2805        world.run_schedule(TestSchedule);
2806
2807        let value = world
2808            .get_resource::<CheckSystemRan>()
2809            .expect("CheckSystemRan Resource Should Exist");
2810        assert_eq!(value.0, 2);
2811    }
2812}