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