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