Thanks to visit codestin.com
Credit goes to docs.rs

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