Skip to main content

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_ecs_macros::Event;
14use bevy_platform::{
15    collections::{HashMap, HashSet},
16    hash::FixedHasher,
17};
18use bevy_utils::{default, TypeIdHashMap};
19use core::{
20    any::{Any, TypeId},
21    fmt::{Debug, Write},
22};
23use fixedbitset::FixedBitSet;
24use indexmap::{IndexMap, IndexSet};
25use log::{info, warn};
26use pass::ScheduleBuildPassObj;
27#[cfg(feature = "debug")]
28use rand::{seq::SliceRandom, SeedableRng};
29use thiserror::Error;
30#[cfg(feature = "trace")]
31use tracing::info_span;
32
33use crate::{
34    change_detection::CheckChangeTicks,
35    system::{System, SystemAccess},
36};
37use crate::{
38    component::{ComponentId, Components},
39    prelude::Component,
40    resource::Resource,
41    schedule::*,
42    system::ScheduleSystem,
43    world::World,
44};
45
46pub use stepping::Stepping;
47use Direction::{Incoming, Outgoing};
48
49/// Resource that stores [`Schedule`]s mapped to [`ScheduleLabel`]s excluding the current running [`Schedule`].
50#[derive(Default, Resource)]
51pub struct Schedules {
52    inner: HashMap<InternedScheduleLabel, Schedule>,
53    /// List of [`ComponentId`]s to ignore when reporting system order ambiguity conflicts
54    pub ignored_scheduling_ambiguities: BTreeSet<ComponentId>,
55    /// Set of schedule labels that have been removed to execute in [`World::try_schedule_scope`].
56    temporarily_removed: HashSet<InternedScheduleLabel>,
57    /// Set of schedule labels that have attempted to be read in [`World::try_schedule_scope`],
58    /// but have no associated [`Schedule`] in `inner`
59    empty_labels: HashSet<InternedScheduleLabel>,
60}
61
62impl Schedules {
63    /// Constructs an empty `Schedules` with zero initial capacity.
64    pub fn new() -> Self {
65        Self::default()
66    }
67
68    /// Inserts a labeled schedule into the map.
69    ///
70    /// If the map already had an entry for `label`, `schedule` is inserted,
71    /// and the old schedule is returned. Otherwise, `None` is returned.
72    pub fn insert(&mut self, schedule: Schedule) -> Option<Schedule> {
73        self.temporarily_removed.remove(&schedule.label);
74        // error if above is true
75        self.inner.insert(schedule.label, schedule)
76    }
77
78    /// Inserts a labeled schedule into the map.
79    ///
80    /// If the map already had an entry for `label`, `schedule` is inserted,
81    /// and the old schedule is returned. Otherwise, `None` is returned.
82    pub fn reinsert(&mut self, schedule: Schedule) -> Option<Schedule> {
83        self.temporarily_removed.remove(&schedule.label);
84        // error if above false
85        self.inner.insert(schedule.label, schedule)
86    }
87
88    /// Removes the schedule corresponding to the `label` from the map, returning it if it existed.
89    pub fn remove(&mut self, label: impl ScheduleLabel) -> Option<Schedule> {
90        self.inner.remove(&label.intern())
91    }
92
93    /// Removes the schedule corresponding to the `label` from the map, returning it if it existed, tracks.
94    pub fn remove_temporarily(&mut self, label: impl ScheduleLabel) -> Option<Schedule> {
95        let label = label.intern();
96        let k = self.inner.remove(&label);
97        if k.is_some() {
98            self.temporarily_removed.insert(label);
99            // error if above false
100            self.empty_labels.remove(&label);
101        } else {
102            self.empty_labels.insert(label);
103        }
104        k
105    }
106
107    /// Removes the (schedule, label) pair corresponding to the `label` from the map, returning it if it existed.
108    pub fn remove_entry(
109        &mut self,
110        label: impl ScheduleLabel,
111    ) -> Option<(InternedScheduleLabel, Schedule)> {
112        self.inner.remove_entry(&label.intern())
113    }
114
115    /// Gets a set of temporarily removed schedules
116    pub fn get_temporarily_removed(&self) -> HashSet<InternedScheduleLabel> {
117        self.temporarily_removed.clone()
118    }
119
120    /// Gets a set of empty schedule labels
121    pub fn get_empty_labels(&self) -> HashSet<InternedScheduleLabel> {
122        self.empty_labels.clone()
123    }
124
125    /// Does a schedule with the provided label already exist?
126    pub fn contains(&self, label: impl ScheduleLabel) -> bool {
127        self.inner.contains_key(&label.intern())
128    }
129
130    /// Returns a reference to the schedule associated with `label`, if it exists.
131    pub fn get(&self, label: impl ScheduleLabel) -> Option<&Schedule> {
132        self.inner.get(&label.intern())
133    }
134
135    /// Returns a mutable reference to the schedule associated with `label`, if it exists.
136    pub fn get_mut(&mut self, label: impl ScheduleLabel) -> Option<&mut Schedule> {
137        self.inner.get_mut(&label.intern())
138    }
139
140    /// Returns a mutable reference to the schedules associated with `label`, creating one if it doesn't already exist.
141    pub fn entry(&mut self, label: impl ScheduleLabel) -> &mut Schedule {
142        self.inner
143            .entry(label.intern())
144            .or_insert_with(|| Schedule::new(label))
145    }
146
147    /// Returns an iterator over all schedules. Iteration order is undefined.
148    pub fn iter(&self) -> impl Iterator<Item = (&dyn ScheduleLabel, &Schedule)> {
149        self.inner
150            .iter()
151            .map(|(label, schedule)| (&**label, schedule))
152    }
153    /// Returns an iterator over mutable references to all schedules. Iteration order is undefined.
154    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&dyn ScheduleLabel, &mut Schedule)> {
155        self.inner
156            .iter_mut()
157            .map(|(label, schedule)| (&**label, schedule))
158    }
159
160    /// Iterates the change ticks of all systems in all stored schedules and clamps any older than
161    /// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE).
162    /// This prevents overflow and thus prevents false positives.
163    pub(crate) fn check_change_ticks(&mut self, check: CheckChangeTicks) {
164        #[cfg(feature = "trace")]
165        let _all_span = info_span!("check stored schedule ticks").entered();
166        #[cfg_attr(
167            not(feature = "trace"),
168            expect(
169                unused_variables,
170                reason = "The `label` variable goes unused if the `trace` feature isn't active"
171            )
172        )]
173        for (label, schedule) in &mut self.inner {
174            #[cfg(feature = "trace")]
175            let name = format!("{label:?}");
176            #[cfg(feature = "trace")]
177            let _one_span = info_span!("check schedule ticks", name = &name).entered();
178            schedule.check_change_ticks(check);
179        }
180    }
181
182    /// Applies the provided [`ScheduleBuildSettings`] to all schedules.
183    ///
184    /// This mutates all currently present schedules, but does not apply to schedules added
185    /// in the future.
186    pub fn configure_schedules(&mut self, schedule_build_settings: ScheduleBuildSettings) {
187        for (_, schedule) in &mut self.inner {
188            schedule.set_build_settings(schedule_build_settings.clone());
189        }
190    }
191
192    /// Ignore system order ambiguities caused by conflicts on [`Component`]s of type `T`.
193    pub fn allow_ambiguous_component<T: Component>(&mut self, world: &mut World) {
194        self.ignored_scheduling_ambiguities
195            .insert(world.register_component::<T>());
196    }
197
198    /// Ignore system order ambiguities caused by conflicts on [`Resource`]s of type `T`.
199    pub fn allow_ambiguous_resource<T: Resource>(&mut self, world: &mut World) {
200        self.ignored_scheduling_ambiguities
201            .insert(world.components_registrator().register_component::<T>());
202    }
203
204    /// Iterate through the [`ComponentId`]'s that will be ignored.
205    pub fn iter_ignored_ambiguities(&self) -> impl Iterator<Item = &ComponentId> + '_ {
206        self.ignored_scheduling_ambiguities.iter()
207    }
208
209    /// Prints the names of the components and resources with [`info`]
210    ///
211    /// May panic or retrieve incorrect names if [`Components`] is not from the same
212    /// world
213    pub fn print_ignored_ambiguities(&self, components: &Components) {
214        let mut message =
215            "System order ambiguities caused by conflicts on the following types are ignored:\n"
216                .to_string();
217        for id in self.iter_ignored_ambiguities() {
218            writeln!(message, "{}", components.get_name(*id).unwrap()).unwrap();
219        }
220
221        info!("{message}");
222    }
223
224    /// Adds one or more systems to the [`Schedule`] matching the provided [`ScheduleLabel`].
225    pub fn add_systems<M>(
226        &mut self,
227        schedule: impl ScheduleLabel,
228        systems: impl IntoScheduleConfigs<ScheduleSystem, M>,
229    ) -> &mut Self {
230        self.entry(schedule).add_systems(systems);
231
232        self
233    }
234
235    /// Removes all systems in a [`SystemSet`]. This will cause the schedule to be rebuilt when
236    /// the schedule is run again. A [`ScheduleError`] is returned if the schedule needs to be
237    /// [`Schedule::initialize`]'d or the `set` is not found.
238    pub fn remove_systems_in_set<M>(
239        &mut self,
240        schedule: impl ScheduleLabel,
241        set: impl IntoSystemSet<M>,
242        world: &mut World,
243        policy: ScheduleCleanupPolicy,
244    ) -> Result<usize, ScheduleError> {
245        self.get_mut(schedule)
246            .ok_or(ScheduleError::ScheduleNotFound)?
247            .remove_systems_in_set(set, world, policy)
248    }
249
250    /// Configures a collection of system sets in the provided schedule, adding any sets that do not exist.
251    #[track_caller]
252    pub fn configure_sets<M>(
253        &mut self,
254        schedule: impl ScheduleLabel,
255        sets: impl IntoScheduleConfigs<InternedSystemSet, M>,
256    ) -> &mut Self {
257        self.entry(schedule).configure_sets(sets);
258
259        self
260    }
261
262    /// Suppress warnings and errors that would result from systems in these sets having ambiguities
263    /// (conflicting access but indeterminate order) with systems in `set`.
264    ///
265    /// When possible, do this directly in the `.add_systems(Update, a.ambiguous_with(b))` call.
266    /// However, sometimes two independent plugins `A` and `B` are reported as ambiguous, which you
267    /// can only suppress as the consumer of both.
268    #[track_caller]
269    pub fn ignore_ambiguity<M1, M2, S1, S2>(
270        &mut self,
271        schedule: impl ScheduleLabel,
272        a: S1,
273        b: S2,
274    ) -> &mut Self
275    where
276        S1: IntoSystemSet<M1>,
277        S2: IntoSystemSet<M2>,
278    {
279        self.entry(schedule).ignore_ambiguity(a, b);
280
281        self
282    }
283}
284
285/// Marker stored in a [`Chain`]'s options by
286/// [`chain_weak`](crate::schedule::IntoScheduleConfigs::chain_weak) to tag its edges as weak,
287/// meaning the ordering is only kept between systems that actually conflict (access the same data in a way that is incompatible with the borrow checker). See `chain_weak`
288/// for the semantics.
289pub(crate) struct Weak;
290
291/// Chain systems into dependencies
292#[derive(Default)]
293pub enum Chain {
294    /// Systems are independent. Nodes are allowed to run in any order.
295    #[default]
296    Unchained,
297    /// Systems are chained. `before -> after` ordering constraints
298    /// will be added between the successive elements.
299    Chained(TypeIdHashMap<Box<dyn Any>>),
300}
301
302impl Chain {
303    /// Specify that the systems must be chained.
304    pub fn set_chained(&mut self) {
305        if matches!(self, Chain::Unchained) {
306            *self = Self::Chained(Default::default());
307        };
308    }
309    /// Specify that the systems must be chained, and add the specified configuration for
310    /// all dependencies created between these systems.
311    pub fn set_chained_with_config<T: 'static>(&mut self, config: T) {
312        self.set_chained();
313        if let Chain::Chained(config_map) = self {
314            config_map.insert(TypeId::of::<T>(), Box::new(config));
315        } else {
316            unreachable!()
317        };
318    }
319}
320
321/// A collection of systems, and the metadata and executor needed to run them
322/// in a certain order under certain conditions.
323///
324/// # Schedule labels
325///
326/// Each schedule has a [`ScheduleLabel`] value. This value is used to uniquely identify the
327/// schedule when added to a [`World`]’s [`Schedules`], and may be used to specify which schedule
328/// a system should be added to.
329///
330/// # Example
331///
332/// Here is an example of a `Schedule` running a "Hello world" system:
333///
334/// ```
335/// # use bevy_ecs::prelude::*;
336/// fn hello_world() { println!("Hello world!") }
337///
338/// fn main() {
339///     let mut world = World::new();
340///     let mut schedule = Schedule::default();
341///     schedule.add_systems(hello_world);
342///
343///     schedule.run(&mut world);
344/// }
345/// ```
346///
347/// A schedule can also run several systems in an ordered way:
348///
349/// ```
350/// # use bevy_ecs::prelude::*;
351/// fn system_one() { println!("System 1 works!") }
352/// fn system_two() { println!("System 2 works!") }
353/// fn system_three() { println!("System 3 works!") }
354///
355/// fn main() {
356///     let mut world = World::new();
357///     let mut schedule = Schedule::default();
358///     schedule.add_systems((
359///         system_two,
360///         system_one.before(system_two),
361///         system_three.after(system_two),
362///     ));
363///
364///     schedule.run(&mut world);
365/// }
366/// ```
367///
368/// Schedules are often inserted into a [`World`] and identified by their [`ScheduleLabel`] only:
369///
370/// ```
371/// # use bevy_ecs::prelude::*;
372/// use bevy_ecs::schedule::ScheduleLabel;
373///
374/// // Declare a new schedule label.
375/// #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
376/// struct Update;
377///
378/// // This system shall be part of the schedule.
379/// fn an_update_system() {
380///     println!("Hello world!");
381/// }
382///
383/// fn main() {
384///     let mut world = World::new();
385///
386///     // Add a system to the schedule with that label (creating it automatically).
387///     world.get_resource_or_init::<Schedules>().add_systems(Update, an_update_system);
388///
389///     // Run the schedule, and therefore run the system.
390///     world.run_schedule(Update);
391/// }
392/// ```
393pub struct Schedule {
394    label: InternedScheduleLabel,
395    graph: ScheduleGraph,
396    executable: SystemSchedule,
397    executor: Box<dyn SystemExecutor>,
398    executor_initialized: bool,
399}
400
401#[derive(ScheduleLabel, Hash, PartialEq, Eq, Debug, Clone)]
402struct DefaultSchedule;
403
404impl Default for Schedule {
405    /// Creates a schedule with a default label. Only use in situations where
406    /// you don't care about the [`ScheduleLabel`]. Inserting a default schedule
407    /// into the world risks overwriting another schedule. For most situations
408    /// you should use [`Schedule::new`].
409    fn default() -> Self {
410        Self::new(DefaultSchedule)
411    }
412}
413
414impl Schedule {
415    /// Constructs an empty `Schedule`.
416    pub fn new(label: impl ScheduleLabel) -> Self {
417        let mut this = Self {
418            label: label.intern(),
419            graph: ScheduleGraph::new(),
420            executable: SystemSchedule::new(),
421            executor: default_executor(),
422            executor_initialized: false,
423        };
424        // Call `set_build_settings` to add any default build passes
425        this.set_build_settings(Default::default());
426        this
427    }
428
429    /// Returns whether this schedule has been changed since the last time it was built.
430    pub fn is_changed(&self) -> bool {
431        self.graph.changed
432    }
433
434    /// Returns the [`InternedScheduleLabel`] for this `Schedule`,
435    /// corresponding to the [`ScheduleLabel`] this schedule was created with.
436    pub fn label(&self) -> InternedScheduleLabel {
437        self.label
438    }
439
440    /// Add a collection of systems to the schedule.
441    pub fn add_systems<M>(
442        &mut self,
443        systems: impl IntoScheduleConfigs<ScheduleSystem, M>,
444    ) -> &mut Self {
445        self.graph.process_configs(systems.into_configs(), false);
446        self
447    }
448
449    /// Removes all systems in a [`SystemSet`]. This will cause the schedule to be rebuilt when
450    /// the schedule is run again. A [`ScheduleError`] is returned if the schedule needs to be
451    /// [`Schedule::initialize`]'d or the `set` is not found.
452    ///
453    /// Note that this can remove all systems of a type if you pass
454    /// the system to this function as systems implicitly create a set based
455    /// on the system type.
456    ///
457    /// ## Example
458    /// ```
459    /// # use bevy_ecs::prelude::*;
460    /// # use bevy_ecs::schedule::ScheduleCleanupPolicy;
461    /// #
462    /// # fn my_system() {}
463    /// #
464    /// let mut schedule = Schedule::default();
465    /// // add the system to the schedule
466    /// schedule.add_systems(my_system);
467    /// let mut world = World::default();
468    ///
469    /// // remove the system
470    /// schedule.remove_systems_in_set(my_system, &mut world, ScheduleCleanupPolicy::RemoveSystemsOnly);
471    /// ```
472    pub fn remove_systems_in_set<M>(
473        &mut self,
474        set: impl IntoSystemSet<M>,
475        world: &mut World,
476        policy: ScheduleCleanupPolicy,
477    ) -> Result<usize, ScheduleError> {
478        if self.graph.changed {
479            self.initialize(world)?;
480        }
481        self.graph.remove_systems_in_set(set, policy)
482    }
483
484    /// Suppress warnings and errors that would result from systems in these sets having ambiguities
485    /// (conflicting access but indeterminate order) with systems in `set`.
486    #[track_caller]
487    pub fn ignore_ambiguity<M1, M2, S1, S2>(&mut self, a: S1, b: S2) -> &mut Self
488    where
489        S1: IntoSystemSet<M1>,
490        S2: IntoSystemSet<M2>,
491    {
492        let a = a.into_system_set();
493        let b = b.into_system_set();
494
495        let a_id = self.graph.system_sets.get_key_or_insert(a.intern());
496        let b_id = self.graph.system_sets.get_key_or_insert(b.intern());
497
498        self.graph
499            .ambiguous_with
500            .add_edge(NodeId::Set(a_id), NodeId::Set(b_id));
501
502        self
503    }
504
505    /// Configures a collection of system sets in this schedule, adding them if they does not exist.
506    #[track_caller]
507    pub fn configure_sets<M>(
508        &mut self,
509        sets: impl IntoScheduleConfigs<InternedSystemSet, M>,
510    ) -> &mut Self {
511        self.graph.configure_sets(sets);
512        self
513    }
514
515    /// Add a custom build pass to the schedule.
516    pub fn add_build_pass<T: ScheduleBuildPass>(&mut self, pass: T) -> &mut Self {
517        self.graph.passes.insert(TypeId::of::<T>(), Box::new(pass));
518        self
519    }
520
521    /// Remove a custom build pass.
522    pub fn remove_build_pass<T: ScheduleBuildPass>(&mut self) {
523        self.graph.passes.shift_remove(&TypeId::of::<T>());
524    }
525
526    /// Changes miscellaneous build settings.
527    ///
528    /// If [`settings.auto_insert_apply_deferred`][ScheduleBuildSettings::auto_insert_apply_deferred]
529    /// is `false`, this clears `*_ignore_deferred` edge settings configured so far.
530    ///
531    /// Generally this method should be used before adding systems or set configurations to the schedule,
532    /// not after.
533    pub fn set_build_settings(&mut self, settings: ScheduleBuildSettings) -> &mut Self {
534        if settings.auto_insert_apply_deferred {
535            if !self
536                .graph
537                .passes
538                .contains_key(&TypeId::of::<passes::AutoInsertApplyDeferredPass>())
539            {
540                self.add_build_pass(passes::AutoInsertApplyDeferredPass::default());
541            }
542        } else {
543            self.remove_build_pass::<passes::AutoInsertApplyDeferredPass>();
544        }
545        self.graph.settings = settings;
546        self
547    }
548
549    /// Returns the schedule's current `ScheduleBuildSettings`.
550    pub fn get_build_settings(&self) -> ScheduleBuildSettings {
551        self.graph.settings.clone()
552    }
553
554    /// Replaces the schedule's executor.
555    pub fn set_executor(&mut self, executor: impl SystemExecutor + 'static) -> &mut Self {
556        self.executor = Box::new(executor);
557        self.executor_initialized = false;
558        self
559    }
560
561    /// Set whether the schedule applies deferred system buffers on final time or not. This is a catch-all
562    /// in case a system uses commands but was not explicitly ordered before an instance of
563    /// [`ApplyDeferred`]. By default this
564    /// setting is true, but may be disabled if needed.
565    pub fn set_apply_final_deferred(&mut self, apply_final_deferred: bool) -> &mut Self {
566        self.executor.set_apply_final_deferred(apply_final_deferred);
567        self
568    }
569
570    /// Runs all systems in this schedule on the `world`, using its current execution strategy.
571    pub fn run(&mut self, world: &mut World) {
572        #[cfg(feature = "trace")]
573        let _span = info_span!("schedule", name = ?self.label).entered();
574
575        world.check_change_ticks();
576        self.initialize(world).unwrap_or_else(|e| {
577            panic!(
578                "Error when initializing schedule {:?}: {}",
579                self.label,
580                e.to_string(self.graph(), world)
581            )
582        });
583
584        let error_handler = world.fallback_error_handler();
585
586        #[cfg(not(feature = "bevy_debug_stepping"))]
587        self.executor
588            .run(&mut self.executable, world, None, error_handler);
589
590        #[cfg(feature = "bevy_debug_stepping")]
591        {
592            let skip_systems = match world.get_resource_mut::<Stepping>() {
593                None => None,
594                Some(mut stepping) => stepping.skipped_systems(self),
595            };
596
597            self.executor.run(
598                &mut self.executable,
599                world,
600                skip_systems.as_ref(),
601                error_handler,
602            );
603        }
604    }
605
606    /// Initializes any newly-added systems and conditions, rebuilds the executable schedule,
607    /// and re-initializes the executor.
608    ///
609    /// Moves all systems and run conditions out of the [`ScheduleGraph`]. If the schedule is built
610    /// successfully, returns [`Some`] with the metadata. If the schedule has previously been built
611    /// successfully, returns [`None`].
612    pub fn initialize(
613        &mut self,
614        world: &mut World,
615    ) -> Result<Option<ScheduleBuildMetadata>, ScheduleBuildError> {
616        let mut build_metadata = None;
617        if self.graph.changed {
618            self.graph.initialize(world);
619            let ignored_ambiguities = world
620                .get_resource_or_init::<Schedules>()
621                .ignored_scheduling_ambiguities
622                .clone();
623
624            let mut event = ScheduleBuilt {
625                label: self.label,
626                build_metadata: self.graph.update_schedule(
627                    world,
628                    &mut self.executable,
629                    &ignored_ambiguities,
630                    self.label,
631                )?,
632            };
633            self.graph.changed = false;
634            self.executor_initialized = false;
635
636            world.trigger_ref(&mut event);
637            build_metadata = Some(event.build_metadata);
638        }
639
640        if !self.executor_initialized {
641            self.executor.init(&self.executable);
642            self.executor_initialized = true;
643        }
644
645        Ok(build_metadata)
646    }
647
648    /// Returns the [`ScheduleGraph`].
649    pub fn graph(&self) -> &ScheduleGraph {
650        &self.graph
651    }
652
653    /// Returns a mutable reference to the [`ScheduleGraph`].
654    pub fn graph_mut(&mut self) -> &mut ScheduleGraph {
655        &mut self.graph
656    }
657
658    /// Returns the [`SystemSchedule`].
659    pub(crate) fn executable(&self) -> &SystemSchedule {
660        &self.executable
661    }
662
663    /// Iterates the change ticks of all systems in the schedule and clamps any older than
664    /// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE).
665    /// This prevents overflow and thus prevents false positives.
666    pub fn check_change_ticks(&mut self, check: CheckChangeTicks) {
667        for system in &mut self.executable.systems {
668            if !is_apply_deferred(system) {
669                system.check_change_tick(check);
670            }
671        }
672
673        for conditions in &mut self.executable.system_conditions {
674            for condition in conditions {
675                condition.check_change_tick(check);
676            }
677        }
678
679        for conditions in &mut self.executable.set_conditions {
680            for condition in conditions {
681                condition.check_change_tick(check);
682            }
683        }
684    }
685
686    /// Directly applies any accumulated [`Deferred`](crate::system::Deferred) system parameters (like [`Commands`](crate::prelude::Commands)) to the `world`.
687    ///
688    /// Like always, deferred system parameters are applied in the "topological sort order" of the schedule graph.
689    /// As a result, buffers from one system are only guaranteed to be applied before those of other systems
690    /// if there is an explicit system ordering between the two systems.
691    ///
692    /// This is used in rendering to extract data from the main world, storing the data in system buffers,
693    /// before applying their buffers in a different world.
694    pub fn apply_deferred(&mut self, world: &mut World) {
695        for SystemWithAccess { system, .. } in &mut self.executable.systems {
696            system.apply_deferred(world);
697        }
698    }
699
700    /// Returns an iterator over all systems in this schedule.
701    ///
702    /// Note: this method will return [`ScheduleNotInitialized`] if the
703    /// schedule has never been initialized or run.
704    pub fn systems(
705        &self,
706    ) -> Result<impl Iterator<Item = (SystemKey, &ScheduleSystem)> + Sized, ScheduleNotInitialized>
707    {
708        if !self.executor_initialized {
709            return Err(ScheduleNotInitialized);
710        }
711
712        let iter = self
713            .executable
714            .system_ids
715            .iter()
716            .zip(&self.executable.systems)
717            .map(|(&node_id, system)| (node_id, &system.system));
718
719        Ok(iter)
720    }
721
722    /// Returns an iterator over all systems with access in this schedule.
723    ///
724    /// Note: this method will return [`ScheduleNotInitialized`] if the
725    /// schedule has never been initialized or run.
726    pub fn systems_with_access(
727        &self,
728    ) -> Result<impl Iterator<Item = (SystemKey, &SystemWithAccess)> + Sized, ScheduleNotInitialized>
729    {
730        if !self.executor_initialized {
731            return Err(ScheduleNotInitialized);
732        }
733
734        let iter = self
735            .executable
736            .system_ids
737            .iter()
738            .zip(&self.executable.systems)
739            .map(|(&node_id, system)| (node_id, system));
740
741        Ok(iter)
742    }
743
744    /// Returns the number of systems in this schedule.
745    pub fn systems_len(&self) -> usize {
746        if !self.executor_initialized {
747            self.graph.systems.len()
748        } else {
749            self.executable.systems.len()
750        }
751    }
752}
753
754/// Metadata for a [`Schedule`].
755///
756/// The order isn't optimized; calling `ScheduleGraph::build_schedule` will return a
757/// `SystemSchedule` where the order is optimized for execution.
758#[derive(Default)]
759pub struct ScheduleGraph {
760    /// Container of systems in the schedule.
761    pub systems: Systems,
762    /// Container of system sets in the schedule.
763    pub system_sets: SystemSets,
764    /// Directed acyclic graph of the hierarchy (which systems/sets are children of which sets)
765    hierarchy: Dag<NodeId>,
766    /// Directed acyclic graph of the dependency (which systems/sets have to run before which other systems/sets)
767    dependency: Dag<NodeId>,
768    /// Map of systems in each set
769    set_systems: DagGroups<SystemSetKey, SystemKey>,
770    ambiguous_with: UnGraph<NodeId>,
771    /// Nodes that are allowed to have ambiguous ordering relationship with any other systems.
772    pub ambiguous_with_all: HashSet<NodeId>,
773    conflicting_systems: ConflictingSystems,
774    /// Dependency edges marked weak (from `chain_weak`/`before_weak`/`after_weak`), before flattening.
775    ///
776    /// During the build, edges between nodes that don't conflict are ignored.
777    weak_node_edges: HashSet<(NodeId, NodeId)>,
778    /// Dependency edges from a strict ordering (`chain`/`before`/`after`), before flattening.
779    ///
780    /// During the build, these edges are never ignored, even if the systems don't conflict (unlike [`Self::weak_node_edges`]).
781    strict_node_edges: HashSet<(NodeId, NodeId)>,
782    anonymous_sets: usize,
783    changed: bool,
784    settings: ScheduleBuildSettings,
785    passes: IndexMap<TypeId, Box<dyn ScheduleBuildPassObj>, FixedHasher>,
786}
787
788impl ScheduleGraph {
789    /// Creates an empty [`ScheduleGraph`] with default settings.
790    pub fn new() -> Self {
791        Self {
792            systems: Systems::default(),
793            system_sets: SystemSets::default(),
794            hierarchy: Dag::new(),
795            dependency: Dag::new(),
796            set_systems: DagGroups::default(),
797            ambiguous_with: UnGraph::default(),
798            ambiguous_with_all: HashSet::default(),
799            conflicting_systems: ConflictingSystems::default(),
800            weak_node_edges: HashSet::default(),
801            strict_node_edges: HashSet::default(),
802            anonymous_sets: 0,
803            changed: false,
804            settings: default(),
805            passes: default(),
806        }
807    }
808
809    /// Returns the [`Dag`] of the hierarchy.
810    ///
811    /// The hierarchy is a directed acyclic graph of the systems and sets,
812    /// where an edge denotes that a system or set is the child of another set.
813    pub fn hierarchy(&self) -> &Dag<NodeId> {
814        &self.hierarchy
815    }
816
817    /// Returns the [`Dag`] of the dependencies in the schedule.
818    ///
819    /// Nodes in this graph are systems and sets, and edges denote that
820    /// a system or set has to run before another system or set.
821    pub fn dependency(&self) -> &Dag<NodeId> {
822        &self.dependency
823    }
824
825    /// Returns the list of systems that conflict with each other, i.e. have ambiguities in their access.
826    ///
827    /// If the `Vec<ComponentId>` is empty, the systems conflict on [`World`] access.
828    /// Must be called after [`ScheduleGraph::build_schedule`] to be non-empty.
829    pub fn conflicting_systems(&self) -> &ConflictingSystems {
830        &self.conflicting_systems
831    }
832
833    fn process_config<T: ProcessScheduleConfig + Schedulable>(
834        &mut self,
835        config: ScheduleConfig<T>,
836        collect_nodes: bool,
837    ) -> ProcessConfigsResult {
838        ProcessConfigsResult {
839            densely_chained: true,
840            nodes: collect_nodes
841                .then_some(T::process_config(self, config))
842                .into_iter()
843                .collect(),
844        }
845    }
846
847    fn apply_collective_conditions<
848        T: ProcessScheduleConfig + Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>,
849    >(
850        &mut self,
851        configs: &mut [ScheduleConfigs<T>],
852        collective_conditions: Vec<BoxedCondition>,
853    ) {
854        if !collective_conditions.is_empty() {
855            if let [config] = configs {
856                for condition in collective_conditions {
857                    config.run_if_dyn(condition);
858                }
859            } else {
860                let set = self.create_anonymous_set();
861                for config in configs.iter_mut() {
862                    config.in_set_inner(set.intern());
863                }
864                let mut set_config = InternedSystemSet::into_config(set.intern());
865                set_config.conditions.extend(collective_conditions);
866                self.configure_set_inner(set_config);
867            }
868        }
869    }
870
871    /// Adds the config nodes to the graph.
872    ///
873    /// `collect_nodes` controls whether the `NodeId`s of the processed config nodes are stored in the returned [`ProcessConfigsResult`].
874    /// `process_config` is the function which processes each individual config node and returns a corresponding `NodeId`.
875    ///
876    /// The fields on the returned [`ProcessConfigsResult`] are:
877    /// - `nodes`: a vector of all node ids contained in the nested `ScheduleConfigs`
878    /// - `densely_chained`: a boolean that is true if all nested nodes are linearly chained (with successive `after` orderings) in the order they are defined
879    #[track_caller]
880    fn process_configs<
881        T: ProcessScheduleConfig + Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>,
882    >(
883        &mut self,
884        configs: ScheduleConfigs<T>,
885        collect_nodes: bool,
886    ) -> ProcessConfigsResult {
887        match configs {
888            ScheduleConfigs::ScheduleConfig(config) => self.process_config(config, collect_nodes),
889            ScheduleConfigs::Configs {
890                metadata,
891                mut configs,
892                collective_conditions,
893            } => {
894                self.apply_collective_conditions(&mut configs, collective_conditions);
895
896                let is_chained = matches!(metadata, Chain::Chained(_));
897                let is_weak = matches!(
898                    &metadata,
899                    Chain::Chained(options) if options.contains_key(&TypeId::of::<Weak>())
900                );
901
902                // Densely chained if
903                // * a non-weak chain whose configs are all densely chained, or
904                // * a single densely chained config
905                let mut densely_chained = (is_chained && !is_weak) || configs.len() == 1;
906                let mut configs = configs.into_iter();
907                let mut nodes = Vec::new();
908
909                let Some(first) = configs.next() else {
910                    return ProcessConfigsResult {
911                        nodes: Vec::new(),
912                        densely_chained,
913                    };
914                };
915                let mut previous_result = self.process_configs(first, collect_nodes || is_chained);
916                densely_chained &= previous_result.densely_chained;
917
918                for current in configs {
919                    let current_result = self.process_configs(current, collect_nodes || is_chained);
920                    densely_chained &= current_result.densely_chained;
921
922                    if let Chain::Chained(chain_options) = &metadata {
923                        // if the current result is densely chained, we only need to chain the first node
924                        let current_nodes = if current_result.densely_chained {
925                            &current_result.nodes[..1]
926                        } else {
927                            &current_result.nodes
928                        };
929                        // if the previous result was densely chained, we only need to chain the last node
930                        let previous_nodes = if previous_result.densely_chained {
931                            &previous_result.nodes[previous_result.nodes.len() - 1..]
932                        } else {
933                            &previous_result.nodes
934                        };
935
936                        self.dependency
937                            .reserve_edges(previous_nodes.len() * current_nodes.len());
938                        for previous_node in previous_nodes {
939                            for current_node in current_nodes {
940                                self.dependency.add_edge(*previous_node, *current_node);
941
942                                if is_weak {
943                                    self.weak_node_edges.insert((*previous_node, *current_node));
944                                } else {
945                                    self.strict_node_edges
946                                        .insert((*previous_node, *current_node));
947                                }
948
949                                for pass in self.passes.values_mut() {
950                                    pass.add_dependency(
951                                        *previous_node,
952                                        *current_node,
953                                        chain_options,
954                                    );
955                                }
956                            }
957                        }
958                    }
959                    if collect_nodes {
960                        nodes.append(&mut previous_result.nodes);
961                    }
962
963                    previous_result = current_result;
964                }
965                if collect_nodes {
966                    nodes.append(&mut previous_result.nodes);
967                }
968
969                ProcessConfigsResult {
970                    nodes,
971                    densely_chained,
972                }
973            }
974        }
975    }
976
977    /// Add a [`ScheduleConfig`] to the graph, including its dependencies and conditions.
978    fn add_system_inner(&mut self, config: ScheduleConfig<ScheduleSystem>) -> SystemKey {
979        let key = self.systems.insert(config.node, config.conditions);
980
981        // graph updates are immediate
982        self.update_graphs(NodeId::System(key), config.metadata);
983
984        key
985    }
986
987    #[track_caller]
988    fn configure_sets<M>(&mut self, sets: impl IntoScheduleConfigs<InternedSystemSet, M>) {
989        self.process_configs(sets.into_configs(), false);
990    }
991
992    /// Add a single `ScheduleConfig` to the graph, including its dependencies and conditions.
993    fn configure_set_inner(&mut self, config: ScheduleConfig<InternedSystemSet>) -> SystemSetKey {
994        let key = self.system_sets.insert(config.node, config.conditions);
995
996        // graph updates are immediate
997        self.update_graphs(NodeId::Set(key), config.metadata);
998
999        key
1000    }
1001
1002    fn create_anonymous_set(&mut self) -> AnonymousSet {
1003        let id = self.anonymous_sets;
1004        self.anonymous_sets += 1;
1005        AnonymousSet::new(id)
1006    }
1007
1008    /// Returns a `Vec` containing all [`SystemKey`]s in a [`SystemSet`].
1009    ///
1010    /// # Errors
1011    ///
1012    /// This method may return an error. It'll be:
1013    ///
1014    /// - `ScheduleError::Uninitialized` if the schedule has been changed,
1015    ///   and `Self::initialize` has not been called.
1016    /// - `ScheduleError::NotFound` if `system_set` isn't present in the
1017    ///   schedule.
1018    pub fn systems_in_set(
1019        &self,
1020        system_set: InternedSystemSet,
1021    ) -> Result<&IndexSet<SystemKey, FixedHasher>, ScheduleError> {
1022        if self.changed {
1023            return Err(ScheduleError::Uninitialized);
1024        }
1025        let system_set_id = self
1026            .system_sets
1027            .get_key(system_set)
1028            .ok_or(ScheduleError::SetNotFound)?;
1029        self.set_systems
1030            .get(&system_set_id)
1031            .ok_or(ScheduleError::SetNotFound)
1032    }
1033
1034    fn add_edges_for_transitive_dependencies(&mut self, node: NodeId) {
1035        let in_nodes: Vec<_> = self.hierarchy.neighbors_directed(node, Incoming).collect();
1036        let out_nodes: Vec<_> = self.hierarchy.neighbors_directed(node, Outgoing).collect();
1037
1038        self.hierarchy
1039            .reserve_edges(in_nodes.len() * out_nodes.len());
1040        for &in_node in &in_nodes {
1041            for &out_node in &out_nodes {
1042                self.hierarchy.add_edge(in_node, out_node);
1043            }
1044        }
1045
1046        let in_nodes: Vec<_> = self.dependency.neighbors_directed(node, Incoming).collect();
1047        let out_nodes: Vec<_> = self.dependency.neighbors_directed(node, Outgoing).collect();
1048
1049        self.dependency
1050            .reserve_edges(in_nodes.len() * out_nodes.len());
1051        for &in_node in &in_nodes {
1052            for &out_node in &out_nodes {
1053                self.dependency.add_edge(in_node, out_node);
1054            }
1055        }
1056    }
1057
1058    /// Remove all systems in a set and any dependencies on those systems and set.
1059    pub fn remove_systems_in_set<M>(
1060        &mut self,
1061        system_set: impl IntoSystemSet<M>,
1062        policy: ScheduleCleanupPolicy,
1063    ) -> Result<usize, ScheduleError> {
1064        let set = system_set.into_system_set();
1065        let interned = set.intern();
1066        // clone the keys out of the schedule as the systems are getting removed from self
1067        let keys = self.systems_in_set(interned)?.clone();
1068
1069        self.changed = true;
1070
1071        match policy {
1072            ScheduleCleanupPolicy::RemoveSetAndSystemsAllowBreakages => {
1073                let Some(set_key) = self.system_sets.get_key(interned) else {
1074                    return Err(ScheduleError::SetNotFound);
1075                };
1076
1077                self.remove_systems_by_keys(&keys);
1078                self.remove_set_by_key(set_key);
1079
1080                Ok(keys.len())
1081            }
1082            ScheduleCleanupPolicy::RemoveSystemsOnlyAllowBreakages => {
1083                self.remove_systems_by_keys(&keys);
1084
1085                Ok(keys.len())
1086            }
1087            ScheduleCleanupPolicy::RemoveSetAndSystems => {
1088                let Some(set_key) = self.system_sets.get_key(interned) else {
1089                    return Err(ScheduleError::SetNotFound);
1090                };
1091
1092                for &key in &keys {
1093                    self.add_edges_for_transitive_dependencies(key.into());
1094                }
1095
1096                self.add_edges_for_transitive_dependencies(set_key.into());
1097
1098                self.remove_systems_by_keys(&keys);
1099                self.remove_set_by_key(set_key);
1100
1101                Ok(keys.len())
1102            }
1103            ScheduleCleanupPolicy::RemoveSystemsOnly => {
1104                for &key in &keys {
1105                    self.add_edges_for_transitive_dependencies(key.into());
1106                }
1107
1108                self.remove_systems_by_keys(&keys);
1109
1110                Ok(keys.len())
1111            }
1112        }
1113    }
1114
1115    fn remove_systems_by_keys(&mut self, keys: &IndexSet<SystemKey, FixedHasher>) {
1116        for &key in keys {
1117            self.systems.remove(key);
1118
1119            let node = NodeId::from(key);
1120            self.hierarchy.remove_node(node);
1121            self.dependency.remove_node(node);
1122            self.ambiguous_with.remove_node(node);
1123            self.ambiguous_with_all.remove(&node);
1124            self.weak_node_edges
1125                .retain(|&(from, to)| from != node && to != node);
1126            self.strict_node_edges
1127                .retain(|&(from, to)| from != node && to != node);
1128        }
1129    }
1130
1131    fn remove_set_by_key(&mut self, key: SystemSetKey) {
1132        self.system_sets.remove(key);
1133        self.set_systems.remove(&key);
1134        let node = NodeId::from(key);
1135        self.hierarchy.remove_node(node);
1136        self.dependency.remove_node(node);
1137        self.ambiguous_with.remove_node(node);
1138        self.ambiguous_with_all.remove(&node);
1139        self.weak_node_edges
1140            .retain(|&(from, to)| from != node && to != node);
1141        self.strict_node_edges
1142            .retain(|&(from, to)| from != node && to != node);
1143    }
1144
1145    /// Update the internal graphs (hierarchy, dependency, ambiguity) by adding a single [`GraphInfo`]
1146    fn update_graphs(&mut self, id: NodeId, graph_info: GraphInfo) {
1147        self.changed = true;
1148
1149        let GraphInfo {
1150            hierarchy: sets,
1151            dependencies,
1152            ambiguous_with,
1153            ..
1154        } = graph_info;
1155
1156        self.hierarchy.add_node(id);
1157        self.dependency.add_node(id);
1158
1159        for key in sets
1160            .into_iter()
1161            .map(|set| self.system_sets.get_key_or_insert(set))
1162        {
1163            self.hierarchy.add_edge(NodeId::Set(key), id);
1164
1165            // ensure set also appears in dependency graph
1166            self.dependency.add_node(NodeId::Set(key));
1167        }
1168
1169        for (kind, key, options) in
1170            dependencies
1171                .into_iter()
1172                .map(|Dependency { kind, set, options }| {
1173                    (kind, self.system_sets.get_key_or_insert(set), options)
1174                })
1175        {
1176            let (lhs, rhs) = match kind {
1177                DependencyKind::Before => (id, NodeId::Set(key)),
1178                DependencyKind::After => (NodeId::Set(key), id),
1179            };
1180            self.dependency.add_edge(lhs, rhs);
1181            if options.contains_key(&TypeId::of::<Weak>()) {
1182                self.weak_node_edges.insert((lhs, rhs));
1183            } else {
1184                self.strict_node_edges.insert((lhs, rhs));
1185            }
1186            for pass in self.passes.values_mut() {
1187                pass.add_dependency(lhs, rhs, &options);
1188            }
1189
1190            // ensure set also appears in hierarchy graph
1191            self.hierarchy.add_node(NodeId::Set(key));
1192        }
1193
1194        match ambiguous_with {
1195            Ambiguity::Check => (),
1196            Ambiguity::IgnoreWithSet(ambiguous_with) => {
1197                for key in ambiguous_with
1198                    .into_iter()
1199                    .map(|set| self.system_sets.get_key_or_insert(set))
1200                {
1201                    self.ambiguous_with.add_edge(id, NodeId::Set(key));
1202                }
1203            }
1204            Ambiguity::IgnoreAll => {
1205                self.ambiguous_with_all.insert(id);
1206            }
1207        }
1208    }
1209
1210    /// Initializes any newly-added systems and conditions by calling
1211    /// [`System::initialize`](crate::system::System).
1212    pub fn initialize(&mut self, world: &mut World) {
1213        self.systems.initialize(world);
1214        self.system_sets.initialize(world);
1215    }
1216
1217    /// Builds an execution-optimized [`SystemSchedule`] from the current state
1218    /// of the graph. Also returns any warnings that were generated during the
1219    /// build process.
1220    ///
1221    /// This method also
1222    /// - checks for dependency or hierarchy cycles
1223    /// - checks for system access conflicts and reports ambiguities
1224    pub fn build_schedule(
1225        &mut self,
1226        world: &mut World,
1227        ignored_ambiguities: &BTreeSet<ComponentId>,
1228    ) -> Result<(SystemSchedule, ScheduleBuildMetadata), ScheduleBuildError> {
1229        let mut warnings = Vec::new();
1230
1231        // Check system set memberships for cycles.
1232        let hierarchy_analysis = self
1233            .hierarchy
1234            .analyze()
1235            .map_err(ScheduleBuildError::HierarchySort)?;
1236
1237        // Check for redundant system set memberships, logging warnings or
1238        // returning errors as configured.
1239        if self.settings.hierarchy_detection != LogLevel::Ignore
1240            && let Err(e) = hierarchy_analysis.check_for_redundant_edges()
1241        {
1242            match self.settings.hierarchy_detection {
1243                LogLevel::Error => return Err(ScheduleBuildWarning::HierarchyRedundancy(e).into()),
1244                LogLevel::Warn => warnings.push(ScheduleBuildWarning::HierarchyRedundancy(e)),
1245                LogLevel::Ignore => unreachable!(),
1246            }
1247        }
1248        // Remove redundant system set memberships.
1249        self.hierarchy.remove_redundant_edges(&hierarchy_analysis);
1250
1251        // Check system and system set ordering dependencies for cycles.
1252        let dependency_analysis = self
1253            .dependency
1254            .analyze()
1255            .map_err(ScheduleBuildError::DependencySort)?;
1256
1257        // System sets that share systems and have an ordering dependency cannot be ordered.
1258        dependency_analysis.check_for_cross_dependencies(&hierarchy_analysis)?;
1259
1260        // Group all systems by the system sets they belong to.
1261        self.set_systems = self
1262            .hierarchy
1263            .group_by_key(self.system_sets.len())
1264            .map_err(ScheduleBuildError::HierarchySort)?;
1265        // Check for system sets that share systems but have an ordering dependency.
1266        dependency_analysis.check_for_overlapping_groups(&self.set_systems)?;
1267
1268        // There can be no edges to system-type sets that have multiple instances.
1269        self.system_sets.check_type_set_ambiguity(
1270            &self.set_systems,
1271            &self.ambiguous_with,
1272            &self.dependency,
1273        )?;
1274
1275        // Flatten system ordering dependencies by collapsing system sets. This
1276        // means that if a system set has ordering dependencies, those
1277        // dependencies are applied to all systems in the set.
1278        let mut flat_dependency =
1279            self.set_systems
1280                .flatten(self.dependency.clone(), |set, systems, flattening, temp| {
1281                    for pass in self.passes.values_mut() {
1282                        pass.collapse_set(set, systems, flattening, temp);
1283                    }
1284                });
1285
1286        // Allow modification of the schedule graph by build passes.
1287        let mut passes = core::mem::take(&mut self.passes);
1288        let mut added_edges = Default::default();
1289        for pass in passes.values_mut() {
1290            pass.build(
1291                world,
1292                self,
1293                FlattenedDependencies {
1294                    dag: &mut flat_dependency,
1295                    added_edges: &mut added_edges,
1296                },
1297            )?;
1298        }
1299        self.passes = passes;
1300
1301        // Initialize any systems that were added by build passes. This ensures
1302        // that ApplyDeferred systems are recognized as exclusive.
1303        self.initialize(world);
1304
1305        #[cfg(feature = "debug")]
1306        if let Some(shuffle_seed) = self.settings.shuffle_seed {
1307            // There's nothing special about this Rng implementation, other than the fact that it is
1308            // not feature-gated.
1309            let mut rng = rand::rngs::Xoshiro128PlusPlus::seed_from_u64(shuffle_seed);
1310
1311            let mut nodes = flat_dependency.graph().nodes().collect::<Vec<_>>();
1312            nodes.shuffle(&mut rng);
1313            let mut new_flat_dependency = Dag::new();
1314            for &node in &nodes {
1315                new_flat_dependency.add_node(node);
1316            }
1317            for node in nodes {
1318                for neighbor in flat_dependency.neighbors(node) {
1319                    new_flat_dependency.add_edge(node, neighbor);
1320                }
1321            }
1322            flat_dependency = new_flat_dependency;
1323        }
1324
1325        // Check system ordering dependencies for cycles after collapsing sets and applying
1326        // build passes. This analysis still includes the weak (`chain_weak`) edges, so its
1327        // reachability captures the full ordering intent of the weak chains before they are
1328        // resolved below.
1329        let flat_dependency_analysis = flat_dependency
1330            .analyze()
1331            .map_err(ScheduleBuildError::FlatDependencySort)?;
1332
1333        // Resolve the weak edges into ordinary edges, keeping an ordering only between systems
1334        // that actually conflict and dropping it everywhere else.
1335        let resolved_weak_edges =
1336            self.resolve_weak_edges(&mut flat_dependency, &flat_dependency_analysis);
1337
1338        // Resolving weak edges mutates the graph, so recompute the analysis when it did. This
1339        // analysis is also the basis for ambiguity detection below.
1340        let flat_dependency_analysis = if resolved_weak_edges {
1341            flat_dependency
1342                .analyze()
1343                .map_err(ScheduleBuildError::FlatDependencySort)?
1344        } else {
1345            flat_dependency_analysis
1346        };
1347        flat_dependency.remove_redundant_edges(&flat_dependency_analysis);
1348
1349        // Flatten accepted system ordering ambiguities by collapsing system sets.
1350        // This means that if a system set is allowed to have ambiguous ordering
1351        // with another set, all systems in the first set are allowed to have
1352        // ambiguous ordering with all systems in the second set.
1353        let flat_ambiguous_with = self.set_systems.flatten_undirected(&self.ambiguous_with);
1354
1355        // Find all system ordering ambiguities, ignoring those that are accepted.
1356        self.conflicting_systems = self.systems.get_conflicting_systems(
1357            &flat_dependency_analysis,
1358            &flat_ambiguous_with,
1359            &self.ambiguous_with_all,
1360            ignored_ambiguities,
1361        );
1362        // If there are any ambiguities, log warnings or return errors as configured.
1363        if self.settings.ambiguity_detection != LogLevel::Ignore
1364            && let Err(e) = self.conflicting_systems.check_if_not_empty()
1365        {
1366            match self.settings.ambiguity_detection {
1367                LogLevel::Error => return Err(ScheduleBuildWarning::Ambiguity(e).into()),
1368                LogLevel::Warn => warnings.push(ScheduleBuildWarning::Ambiguity(e)),
1369                LogLevel::Ignore => unreachable!(),
1370            }
1371        }
1372
1373        // build the schedule
1374        Ok((
1375            self.build_schedule_inner(flat_dependency, hierarchy_analysis),
1376            ScheduleBuildMetadata {
1377                warnings,
1378                edges_added_by_build_passes: added_edges,
1379            },
1380        ))
1381    }
1382
1383    /// Resolves the weak (`chain_weak`) edges in `flat_dependency` into ordinary dependency
1384    /// edges, returning whether the graph was changed.
1385    ///
1386    /// `chain_weak` only asks for an ordering where two systems actually conflict. For every pair
1387    /// the weak chains order, this keeps a real edge when the systems conflict and drops it
1388    /// otherwise, so non-conflicting systems are free to run in any order (including in parallel).
1389    ///
1390    /// A weak chain can order two conflicting systems only transitively, through a non-conflicting
1391    /// system in the middle, so conflicting pairs are re-added from the reachability in `analysis`,
1392    /// which must have been generated from `flat_dependency` while it still held the weak edges.
1393    ///
1394    /// A pair that is also ordered by a strict `chain`/`before`/`after` keeps its edge even when
1395    /// its systems don't conflict, so this never drops a strict ordering.
1396    fn resolve_weak_edges(
1397        &self,
1398        flat_dependency: &mut Dag<SystemKey>,
1399        analysis: &DagAnalysis<SystemKey>,
1400    ) -> bool {
1401        let weak_edges = self.flat_node_edges(&self.weak_node_edges, flat_dependency);
1402        if weak_edges.is_empty() {
1403            return false;
1404        }
1405        let strict_edges = self.flat_node_edges(&self.strict_node_edges, flat_dependency);
1406        let condition_accesses = self.condition_accesses();
1407
1408        // Add an edge for every conflicting pair the weak edges order, including endpoints
1409        // connected only through a non-conflicting middle system. Edges made redundant by this
1410        // are removed by the transitive reduction that follows in `build_schedule`.
1411        for (from, to) in analysis.transitive_closure().all_edges() {
1412            if self.systems_conflict(from, to, &condition_accesses) {
1413                flat_dependency.add_edge(from, to);
1414            }
1415        }
1416
1417        // Drop the weak edges between non-conflicting systems, unless the same pair is also
1418        // ordered strictly. Any ordering that mattered was materialized above, and the rest is
1419        // intentionally left unordered.
1420        for &(from, to) in &weak_edges {
1421            if !self.systems_conflict(from, to, &condition_accesses)
1422                && !strict_edges.contains(&(from, to))
1423            {
1424                flat_dependency.remove_edge(from, to);
1425            }
1426        }
1427
1428        true
1429    }
1430
1431    /// Collects, for each system, the accesses of its run conditions and of the run conditions
1432    /// of every set it belongs to.
1433    ///
1434    /// A condition is evaluated just before its system (or the first ready system of its set)
1435    /// runs, so a weak ordering must respect what the conditions access as well.
1436    fn condition_accesses(&self) -> HashMap<SystemKey, Vec<&SystemAccess>> {
1437        let mut accesses: HashMap<SystemKey, Vec<&SystemAccess>> = HashMap::default();
1438        for (key, _, conditions) in self.systems.iter() {
1439            for condition in conditions {
1440                accesses.entry(key).or_default().push(&condition.access);
1441            }
1442        }
1443        for (key, _, conditions) in self.system_sets.iter() {
1444            if conditions.is_empty() {
1445                continue;
1446            }
1447            let Some(systems) = self.set_systems.get(&key) else {
1448                continue;
1449            };
1450            for &system in systems {
1451                accesses
1452                    .entry(system)
1453                    .or_default()
1454                    .extend(conditions.iter().map(|condition| &condition.access));
1455            }
1456        }
1457        accesses
1458    }
1459
1460    /// Expands a set of node-level dependency edges to the system pairs they connect, keeping only
1461    /// the pairs that exist as a direct edge in `flat_dependency`.
1462    ///
1463    /// Set endpoints fan out to their member systems. An edge routed through an empty set collapses
1464    /// to a plain edge with no direct counterpart here, and a sync point inserted by a build pass
1465    /// splits an edge in two, so neither is returned.
1466    fn flat_node_edges(
1467        &self,
1468        node_edges: &HashSet<(NodeId, NodeId)>,
1469        flat_dependency: &Dag<SystemKey>,
1470    ) -> HashSet<(SystemKey, SystemKey)> {
1471        let systems_of = |node: NodeId| -> Vec<SystemKey> {
1472            match node {
1473                NodeId::System(key) => vec![key],
1474                NodeId::Set(key) => self
1475                    .set_systems
1476                    .get(&key)
1477                    .map(|systems| systems.iter().copied().collect())
1478                    .unwrap_or_default(),
1479            }
1480        };
1481
1482        let mut edges = HashSet::default();
1483        for &(from, to) in node_edges {
1484            let (from_s, to_s) = (systems_of(from), systems_of(to));
1485            for &from in &from_s {
1486                for &to in &to_s {
1487                    if flat_dependency.contains_edge(from, to) {
1488                        edges.insert((from, to));
1489                    }
1490                }
1491            }
1492        }
1493        edges
1494    }
1495
1496    /// Returns whether an ordered pair of systems conflict, i.e. whether a weak ordering between
1497    /// them should keep an edge.
1498    ///
1499    /// Two systems conflict when their accesses are incompatible, where a system's run conditions
1500    /// (and those of its sets, see [`Self::condition_accesses`]) count toward its access. A system
1501    /// that produces deferred effects such as `Commands` (as the earlier system) and exclusive
1502    /// systems are treated as always conflicting, so their ordering and any `ApplyDeferred` sync
1503    /// point are preserved.
1504    fn systems_conflict(
1505        &self,
1506        from: SystemKey,
1507        to: SystemKey,
1508        condition_accesses: &HashMap<SystemKey, Vec<&SystemAccess>>,
1509    ) -> bool {
1510        let (from_system, to_system) = (&self.systems[from], &self.systems[to]);
1511        // Conditions are read-only, so they can conflict with the other system's access, but
1512        // never with the other system's conditions.
1513        let conditions_conflict = |system_access: &SystemAccess, other: SystemKey| {
1514            condition_accesses.get(&other).is_some_and(|accesses| {
1515                accesses
1516                    .iter()
1517                    .any(|access| !system_access.is_compatible(access))
1518            })
1519        };
1520
1521        from_system.has_deferred()
1522            || from_system.access.is_exclusive()
1523            || to_system.access.is_exclusive()
1524            || !from_system.access.is_compatible(&to_system.access)
1525            || conditions_conflict(&from_system.access, to)
1526            || conditions_conflict(&to_system.access, from)
1527    }
1528
1529    fn build_schedule_inner(
1530        &self,
1531        flat_dependency: Dag<SystemKey>,
1532        hierarchy_analysis: DagAnalysis<NodeId>,
1533    ) -> SystemSchedule {
1534        let dg_system_ids = flat_dependency.get_toposort().unwrap().to_vec();
1535        let dg_system_idx_map = dg_system_ids
1536            .iter()
1537            .cloned()
1538            .enumerate()
1539            .map(|(i, id)| (id, i))
1540            .collect::<HashMap<_, _>>();
1541
1542        let hierarchy_toposort = self.hierarchy.get_toposort().unwrap();
1543        let hg_systems = hierarchy_toposort
1544            .iter()
1545            .cloned()
1546            .enumerate()
1547            .filter_map(|(i, id)| Some((i, id.as_system()?)))
1548            .collect::<Vec<_>>();
1549        let (hg_set_with_conditions_idxs, hg_set_ids): (Vec<_>, Vec<_>) = hierarchy_toposort
1550            .iter()
1551            .cloned()
1552            .enumerate()
1553            .filter_map(|(i, id)| {
1554                // ignore system sets that have no conditions
1555                // ignore system type sets (already covered, they don't have conditions)
1556                let key = id.as_set()?;
1557                self.system_sets.has_conditions(key).then_some((i, key))
1558            })
1559            .unzip();
1560
1561        let sys_count = self.systems.len();
1562        let set_with_conditions_count = hg_set_ids.len();
1563        let hg_node_count = self.hierarchy.node_count();
1564
1565        // Get the dependencies and immediate dependents of each system, needed by the
1566        // multi_threaded executor to run systems in the correct order.
1567        let mut system_dependencies = Vec::with_capacity(sys_count);
1568        let mut system_dependents = Vec::with_capacity(sys_count);
1569        for &sys_key in &dg_system_ids {
1570            let num_dependencies = flat_dependency
1571                .neighbors_directed(sys_key, Incoming)
1572                .count();
1573
1574            let dependents = flat_dependency
1575                .neighbors_directed(sys_key, Outgoing)
1576                .map(|dep_id| dg_system_idx_map[&dep_id])
1577                .collect::<Vec<_>>();
1578
1579            system_dependencies.push(num_dependencies);
1580            system_dependents.push(dependents);
1581        }
1582
1583        // get the rows and columns of the hierarchy graph's reachability matrix
1584        // (needed to we can evaluate conditions in the correct order)
1585        let mut systems_in_sets_with_conditions =
1586            vec![FixedBitSet::with_capacity(sys_count); set_with_conditions_count];
1587        for (i, &row) in hg_set_with_conditions_idxs.iter().enumerate() {
1588            let bitset = &mut systems_in_sets_with_conditions[i];
1589            for &(col, sys_key) in &hg_systems {
1590                let idx = dg_system_idx_map[&sys_key];
1591                let is_descendant = hierarchy_analysis.reachable()[index(row, col, hg_node_count)];
1592                bitset.set(idx, is_descendant);
1593            }
1594        }
1595
1596        let mut sets_with_conditions_of_systems =
1597            vec![FixedBitSet::with_capacity(set_with_conditions_count); sys_count];
1598        for &(col, sys_key) in &hg_systems {
1599            let i = dg_system_idx_map[&sys_key];
1600            let bitset = &mut sets_with_conditions_of_systems[i];
1601            for (idx, &row) in hg_set_with_conditions_idxs
1602                .iter()
1603                .enumerate()
1604                .take_while(|&(_idx, &row)| row < col)
1605            {
1606                let is_ancestor = hierarchy_analysis.reachable()[index(row, col, hg_node_count)];
1607                bitset.set(idx, is_ancestor);
1608            }
1609        }
1610
1611        SystemSchedule {
1612            systems: Vec::with_capacity(sys_count),
1613            system_conditions: Vec::with_capacity(sys_count),
1614            set_conditions: Vec::with_capacity(set_with_conditions_count),
1615            system_ids: dg_system_ids,
1616            set_ids: hg_set_ids,
1617            system_dependencies,
1618            system_dependents,
1619            sets_with_conditions_of_systems,
1620            systems_in_sets_with_conditions,
1621        }
1622    }
1623
1624    /// Updates the `SystemSchedule` from the `ScheduleGraph`.
1625    fn update_schedule(
1626        &mut self,
1627        world: &mut World,
1628        schedule: &mut SystemSchedule,
1629        ignored_ambiguities: &BTreeSet<ComponentId>,
1630        schedule_label: InternedScheduleLabel,
1631    ) -> Result<ScheduleBuildMetadata, ScheduleBuildError> {
1632        if !self.systems.is_initialized() || !self.system_sets.is_initialized() {
1633            return Err(ScheduleBuildError::Uninitialized);
1634        }
1635
1636        // move systems out of old schedule
1637        for ((key, system), conditions) in schedule
1638            .system_ids
1639            .drain(..)
1640            .zip(schedule.systems.drain(..))
1641            .zip(schedule.system_conditions.drain(..))
1642        {
1643            if let Some(node) = self.systems.node_mut(key) {
1644                node.inner = Some(system);
1645            }
1646
1647            if let Some(node_conditions) = self.systems.get_conditions_mut(key) {
1648                *node_conditions = conditions;
1649            }
1650        }
1651
1652        for (key, conditions) in schedule
1653            .set_ids
1654            .drain(..)
1655            .zip(schedule.set_conditions.drain(..))
1656        {
1657            if let Some(node_conditions) = self.system_sets.get_conditions_mut(key) {
1658                *node_conditions = conditions;
1659            }
1660        }
1661
1662        let (new_schedule, build_metadata) = self.build_schedule(world, ignored_ambiguities)?;
1663        *schedule = new_schedule;
1664
1665        for warning in &build_metadata.warnings {
1666            warn!(
1667                "{:?} schedule built successfully, however: {}",
1668                schedule_label,
1669                warning.to_string(self, world)
1670            );
1671        }
1672
1673        // move systems into new schedule
1674        for &key in &schedule.system_ids {
1675            let system = self.systems.node_mut(key).unwrap().inner.take().unwrap();
1676            let conditions = core::mem::take(self.systems.get_conditions_mut(key).unwrap());
1677            schedule.systems.push(system);
1678            schedule.system_conditions.push(conditions);
1679        }
1680
1681        for &key in &schedule.set_ids {
1682            let conditions = core::mem::take(self.system_sets.get_conditions_mut(key).unwrap());
1683            schedule.set_conditions.push(conditions);
1684        }
1685
1686        Ok(build_metadata)
1687    }
1688}
1689
1690/// Values returned by [`ScheduleGraph::process_configs`]
1691struct ProcessConfigsResult {
1692    /// All nodes contained inside this `process_configs` call's [`ScheduleConfigs`] hierarchy,
1693    /// if `ancestor_chained` is true
1694    nodes: Vec<NodeId>,
1695    /// True if and only if all nodes are "densely chained", meaning that all nested nodes
1696    /// are linearly chained (as if `after` system ordering had been applied between each node)
1697    /// in the order they are defined
1698    densely_chained: bool,
1699}
1700
1701/// Trait used by [`ScheduleGraph::process_configs`] to process a single [`ScheduleConfig`].
1702trait ProcessScheduleConfig: Schedulable + Sized {
1703    /// Process a single [`ScheduleConfig`].
1704    fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig<Self>) -> NodeId;
1705}
1706
1707impl ProcessScheduleConfig for ScheduleSystem {
1708    fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig<Self>) -> NodeId {
1709        NodeId::System(schedule_graph.add_system_inner(config))
1710    }
1711}
1712
1713impl ProcessScheduleConfig for InternedSystemSet {
1714    fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig<Self>) -> NodeId {
1715        NodeId::Set(schedule_graph.configure_set_inner(config))
1716    }
1717}
1718
1719/// Policy to use when removing systems.
1720#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
1721pub enum ScheduleCleanupPolicy {
1722    /// Remove the referenced set and any systems in the set.
1723    /// Attempts to maintain the order between the transitive dependencies by adding new edges
1724    /// between the existing before and after dependencies on the set and the systems.
1725    /// This does not remove sets that might sub sets of the set.
1726    #[default]
1727    RemoveSetAndSystems,
1728    /// Remove only the systems in the set. The set
1729    /// Attempts to maintain the order between the transitive dependencies by adding new edges
1730    /// between the existing before and after dependencies on the systems.
1731    RemoveSystemsOnly,
1732    /// Remove the set and any systems in the set.
1733    /// Note that this will not add new edges and
1734    /// so will break any transitive dependencies on that set or systems.
1735    /// This does not remove sets that might sub sets of the set.
1736    RemoveSetAndSystemsAllowBreakages,
1737    /// Remove only the systems in the set.
1738    /// Note that this will not add new edges and
1739    /// so will break any transitive dependencies on that set or systems.
1740    RemoveSystemsOnlyAllowBreakages,
1741}
1742
1743// methods for reporting errors
1744impl ScheduleGraph {
1745    /// Returns the name of the node with the given [`NodeId`]. Resolves
1746    /// anonymous sets to a string that describes their contents.
1747    ///
1748    /// Also displays the set(s) the node is contained in if
1749    /// [`ScheduleBuildSettings::report_sets`] is true, and shortens system names
1750    /// if [`ScheduleBuildSettings::use_shortnames`] is true.
1751    pub fn get_node_name(&self, id: &NodeId) -> String {
1752        self.get_node_name_inner(id, self.settings.report_sets)
1753    }
1754
1755    #[inline]
1756    fn get_node_name_inner(&self, id: &NodeId, report_sets: bool) -> String {
1757        match *id {
1758            NodeId::System(key) => {
1759                let name = self.systems[key].name();
1760                let name = if self.settings.use_shortnames {
1761                    name.shortname().to_string()
1762                } else {
1763                    name.to_string()
1764                };
1765                if report_sets {
1766                    let sets = self.names_of_sets_containing_node(id);
1767                    if sets.is_empty() {
1768                        name
1769                    } else if sets.len() == 1 {
1770                        format!("{name} (in set {})", sets[0])
1771                    } else {
1772                        format!("{name} (in sets {})", sets.join(", "))
1773                    }
1774                } else {
1775                    name
1776                }
1777            }
1778            NodeId::Set(key) => {
1779                let set = &self.system_sets[key];
1780                if set.is_anonymous() {
1781                    self.anonymous_set_name(id)
1782                } else {
1783                    format!("{set:?}")
1784                }
1785            }
1786        }
1787    }
1788
1789    fn anonymous_set_name(&self, id: &NodeId) -> String {
1790        format!(
1791            "({})",
1792            self.hierarchy
1793                .edges_directed(*id, Outgoing)
1794                // never get the sets of the members or this will infinite recurse when the report_sets setting is on.
1795                .map(|(_, member_id)| self.get_node_name_inner(&member_id, false))
1796                .reduce(|a, b| format!("{a}, {b}"))
1797                .unwrap_or_default()
1798        )
1799    }
1800
1801    fn traverse_sets_containing_node(&self, id: NodeId, f: &mut impl FnMut(SystemSetKey) -> bool) {
1802        for (set_id, _) in self.hierarchy.edges_directed(id, Incoming) {
1803            let NodeId::Set(set_key) = set_id else {
1804                continue;
1805            };
1806            if f(set_key) {
1807                self.traverse_sets_containing_node(NodeId::Set(set_key), f);
1808            }
1809        }
1810    }
1811
1812    fn names_of_sets_containing_node(&self, id: &NodeId) -> Vec<String> {
1813        let mut sets = <HashSet<_>>::default();
1814        self.traverse_sets_containing_node(*id, &mut |key| {
1815            self.system_sets[key].system_type().is_none() && sets.insert(key)
1816        });
1817        let mut sets: Vec<_> = sets
1818            .into_iter()
1819            .map(|key| self.get_node_name(&NodeId::Set(key)))
1820            .collect();
1821        sets.sort();
1822        sets
1823    }
1824}
1825
1826/// Specifies how schedule construction should respond to detecting a certain kind of issue.
1827#[derive(Debug, Clone, Copy, PartialEq)]
1828pub enum LogLevel {
1829    /// Occurrences are completely ignored.
1830    Ignore,
1831    /// Occurrences are logged only.
1832    Warn,
1833    /// Occurrences are logged and result in errors.
1834    Error,
1835}
1836
1837/// Specifies miscellaneous settings for schedule construction.
1838#[derive(Clone, Debug)]
1839pub struct ScheduleBuildSettings {
1840    /// Determines whether the presence of ambiguities (systems with conflicting access but indeterminate order)
1841    /// is only logged or also results in an [`Ambiguity`](ScheduleBuildWarning::Ambiguity)
1842    /// warning or error.
1843    ///
1844    /// Defaults to [`LogLevel::Ignore`].
1845    pub ambiguity_detection: LogLevel,
1846    /// Determines whether the presence of redundant edges in the hierarchy of system sets is only
1847    /// logged or also results in a [`HierarchyRedundancy`](ScheduleBuildWarning::HierarchyRedundancy)
1848    /// warning or error.
1849    ///
1850    /// Defaults to [`LogLevel::Warn`].
1851    pub hierarchy_detection: LogLevel,
1852    /// Auto insert [`ApplyDeferred`] systems into the schedule,
1853    /// when there are [`Deferred`](crate::prelude::Deferred)
1854    /// in one system and there are ordering dependencies on that system. [`Commands`](crate::system::Commands) is one
1855    /// such deferred buffer.
1856    ///
1857    /// You may want to disable this if you only want to sync deferred params at the end of the schedule,
1858    /// or want to manually insert all your sync points.
1859    ///
1860    /// Defaults to `true`
1861    pub auto_insert_apply_deferred: bool,
1862    /// If set to true, node names will be shortened instead of the fully qualified type path.
1863    ///
1864    /// Defaults to `true`.
1865    pub use_shortnames: bool,
1866    /// If set to true, report all system sets the conflicting systems are part of.
1867    ///
1868    /// Defaults to `true`.
1869    pub report_sets: bool,
1870    /// If [`Some`], systems will be shuffled according to the given seed.
1871    ///
1872    /// This allows randomizing the order of systems (while still satisfying ordering constraints),
1873    /// which is useful for ensuring that ordering constraints are more likely to be correct (i.e.,
1874    /// if you spot erroneous behavior when shuffling, that is an indication that the "default"
1875    /// ordering is correct by chance, meaning your ordering constraints are not sufficient).
1876    ///
1877    /// Consider using the [`SingleThreadedExecutor`] for schedules using this. The
1878    /// [`MultiThreadedExecutor`] allows systems to run out-of-order if the "next" system has a
1879    /// conflict with a currently-running system. However, the multi-threaded executor can also
1880    /// produce orderings that are **not possible** in single-threaded execution, given a provided topographic system graph sort.
1881    ///
1882    /// Defaults to [`None`].
1883    // TODO: Currently, `auto_insert_apply_deferred` will prevent stages from being truly shuffled.
1884    // `auto_insert_apply_deferred` always prefers to put systems at the lowest "sync point depth"
1885    // that it can, but this means we can't shuffle deeper systems with shallower systems, despite
1886    // the fact their constraints allow that.
1887    #[cfg(feature = "debug")]
1888    pub shuffle_seed: Option<u64>,
1889}
1890
1891impl Default for ScheduleBuildSettings {
1892    fn default() -> Self {
1893        Self::new()
1894    }
1895}
1896
1897impl ScheduleBuildSettings {
1898    /// Default build settings.
1899    /// See the field-level documentation for the default value of each field.
1900    pub const fn new() -> Self {
1901        Self {
1902            ambiguity_detection: LogLevel::Ignore,
1903            hierarchy_detection: LogLevel::Warn,
1904            auto_insert_apply_deferred: true,
1905            use_shortnames: true,
1906            report_sets: true,
1907            #[cfg(feature = "debug")]
1908            shuffle_seed: None,
1909        }
1910    }
1911}
1912
1913/// Metadata about the schedule build process.
1914pub struct ScheduleBuildMetadata {
1915    /// Warnings about the schedule graph detected by the build process.
1916    pub warnings: Vec<ScheduleBuildWarning>,
1917    /// Edges added by [`ScheduleBuildPass`]es.
1918    ///
1919    /// These edges are not stored in the [`ScheduleGraph`], and so are only available during the
1920    /// build process.
1921    pub edges_added_by_build_passes: HashSet<(SystemKey, SystemKey)>,
1922}
1923
1924/// An event triggered when a schedule is successfully built.
1925///
1926/// Note: When this event is triggered, the corresponding [`Schedule`] is not present in the world.
1927/// So, observers will need to cache whatever data they need from this and access it later once the
1928/// schedule is not running.
1929#[derive(Event)]
1930pub struct ScheduleBuilt {
1931    /// The schedule that was built.
1932    pub label: InternedScheduleLabel,
1933    /// The metadata for the build process of this schedule.
1934    pub build_metadata: ScheduleBuildMetadata,
1935}
1936
1937/// Error to denote that [`Schedule::initialize`] or [`Schedule::run`] has not yet been called for
1938/// this schedule.
1939#[derive(Error, Debug)]
1940#[error("executable schedule has not been built")]
1941pub struct ScheduleNotInitialized;
1942
1943#[cfg(test)]
1944mod tests {
1945    use alloc::{vec, vec::Vec};
1946    use core::any::TypeId;
1947
1948    use bevy_ecs_macros::ScheduleLabel;
1949
1950    use crate::{
1951        error::{ignore, panic, FallbackErrorHandler, Result},
1952        prelude::{ApplyDeferred, IntoSystemSet, Res, Resource},
1953        schedule::{
1954            passes::AutoInsertApplyDeferredPass, tests::ResMut, FlattenedDependencies,
1955            IntoScheduleConfigs, MultiThreadedExecutor, Schedule, ScheduleBuildPass,
1956            ScheduleBuildSettings, ScheduleCleanupPolicy, SystemSet,
1957        },
1958        system::Commands,
1959        world::World,
1960    };
1961
1962    use super::Schedules;
1963
1964    #[derive(Resource)]
1965    struct Resource1;
1966
1967    #[derive(Resource)]
1968    struct Resource2;
1969
1970    #[test]
1971    fn unchanged_auto_insert_apply_deferred_has_no_effect() {
1972        use alloc::{vec, vec::Vec};
1973
1974        #[derive(PartialEq, Debug)]
1975        enum Entry {
1976            System(usize),
1977            SyncPoint(usize),
1978        }
1979
1980        #[derive(Resource, Default)]
1981        struct Log(Vec<Entry>);
1982
1983        fn system<const N: usize>(mut res: ResMut<Log>, mut commands: Commands) {
1984            res.0.push(Entry::System(N));
1985            commands
1986                .queue(|world: &mut World| world.resource_mut::<Log>().0.push(Entry::SyncPoint(N)));
1987        }
1988
1989        let mut world = World::default();
1990        world.init_resource::<Log>();
1991        let mut schedule = Schedule::default();
1992        schedule.add_systems((system::<1>, system::<2>).chain_ignore_deferred());
1993        schedule.set_build_settings(ScheduleBuildSettings {
1994            auto_insert_apply_deferred: true,
1995            ..Default::default()
1996        });
1997        schedule.run(&mut world);
1998        let actual = world.remove_resource::<Log>().unwrap().0;
1999
2000        let expected = vec![
2001            Entry::System(1),
2002            Entry::System(2),
2003            Entry::SyncPoint(1),
2004            Entry::SyncPoint(2),
2005        ];
2006
2007        assert_eq!(actual, expected);
2008    }
2009
2010    // regression test for https://github.com/bevyengine/bevy/issues/9114
2011    #[test]
2012    fn ambiguous_with_not_breaking_run_conditions() {
2013        #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
2014        struct Set;
2015
2016        let mut world = World::new();
2017        let mut schedule = Schedule::default();
2018
2019        let system: fn() = || {
2020            panic!("This system must not run");
2021        };
2022
2023        schedule.configure_sets(Set.run_if(|| false));
2024        schedule.add_systems(system.ambiguous_with(|| ()).in_set(Set));
2025        schedule.run(&mut world);
2026    }
2027
2028    #[test]
2029    fn inserts_a_sync_point() {
2030        let mut schedule = Schedule::default();
2031        let mut world = World::default();
2032        schedule.add_systems(
2033            (
2034                |mut commands: Commands| commands.insert_resource(Resource1),
2035                |_: Res<Resource1>| {},
2036            )
2037                .chain(),
2038        );
2039        schedule.run(&mut world);
2040
2041        // inserted a sync point
2042        assert_eq!(schedule.executable.systems.len(), 3);
2043    }
2044
2045    #[test]
2046    fn explicit_sync_point_used_as_auto_sync_point() {
2047        let mut schedule = Schedule::default();
2048        let mut world = World::default();
2049        schedule.add_systems(
2050            (
2051                |mut commands: Commands| commands.insert_resource(Resource1),
2052                |_: Res<Resource1>| {},
2053            )
2054                .chain(),
2055        );
2056        schedule.add_systems((|| {}, ApplyDeferred, || {}).chain());
2057        schedule.run(&mut world);
2058
2059        // No sync point was inserted, since we can reuse the explicit sync point.
2060        assert_eq!(schedule.executable.systems.len(), 5);
2061    }
2062
2063    #[test]
2064    fn conditional_explicit_sync_point_not_used_as_auto_sync_point() {
2065        let mut schedule = Schedule::default();
2066        let mut world = World::default();
2067        schedule.add_systems(
2068            (
2069                |mut commands: Commands| commands.insert_resource(Resource1),
2070                |_: Res<Resource1>| {},
2071            )
2072                .chain(),
2073        );
2074        schedule.add_systems((|| {}, ApplyDeferred.run_if(|| false), || {}).chain());
2075        schedule.run(&mut world);
2076
2077        // A sync point was inserted, since the explicit sync point is not always run.
2078        assert_eq!(schedule.executable.systems.len(), 6);
2079    }
2080
2081    #[test]
2082    fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_chain() {
2083        let mut schedule = Schedule::default();
2084        let mut world = World::default();
2085        schedule.add_systems(
2086            (
2087                |mut commands: Commands| commands.insert_resource(Resource1),
2088                |_: Res<Resource1>| {},
2089            )
2090                .chain(),
2091        );
2092        schedule.add_systems((|| {}, ApplyDeferred, || {}).chain().run_if(|| false));
2093        schedule.run(&mut world);
2094
2095        // A sync point was inserted, since the explicit sync point is not always run.
2096        assert_eq!(schedule.executable.systems.len(), 6);
2097    }
2098
2099    #[test]
2100    fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_system_set() {
2101        #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
2102        struct Set;
2103
2104        let mut schedule = Schedule::default();
2105        let mut world = World::default();
2106        schedule.configure_sets(Set.run_if(|| false));
2107        schedule.add_systems(
2108            (
2109                |mut commands: Commands| commands.insert_resource(Resource1),
2110                |_: Res<Resource1>| {},
2111            )
2112                .chain(),
2113        );
2114        schedule.add_systems((|| {}, ApplyDeferred.in_set(Set), || {}).chain());
2115        schedule.run(&mut world);
2116
2117        // A sync point was inserted, since the explicit sync point is not always run.
2118        assert_eq!(schedule.executable.systems.len(), 6);
2119    }
2120
2121    #[test]
2122    fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_nested_system_set()
2123    {
2124        #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
2125        struct Set1;
2126        #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
2127        struct Set2;
2128
2129        let mut schedule = Schedule::default();
2130        let mut world = World::default();
2131        schedule.configure_sets(Set2.run_if(|| false));
2132        schedule.configure_sets(Set1.in_set(Set2));
2133        schedule.add_systems(
2134            (
2135                |mut commands: Commands| commands.insert_resource(Resource1),
2136                |_: Res<Resource1>| {},
2137            )
2138                .chain(),
2139        );
2140        schedule.add_systems((|| {}, ApplyDeferred, || {}).chain().in_set(Set1));
2141        schedule.run(&mut world);
2142
2143        // A sync point was inserted, since the explicit sync point is not always run.
2144        assert_eq!(schedule.executable.systems.len(), 6);
2145    }
2146
2147    #[test]
2148    fn merges_sync_points_into_one() {
2149        let mut schedule = Schedule::default();
2150        let mut world = World::default();
2151        // insert two parallel command systems, it should only create one sync point
2152        schedule.add_systems(
2153            (
2154                (
2155                    |mut commands: Commands| commands.insert_resource(Resource1),
2156                    |mut commands: Commands| commands.insert_resource(Resource2),
2157                ),
2158                |_: Res<Resource1>, _: Res<Resource2>| {},
2159            )
2160                .chain(),
2161        );
2162        schedule.run(&mut world);
2163
2164        // inserted sync points
2165        assert_eq!(schedule.executable.systems.len(), 4);
2166
2167        // merges sync points on rebuild
2168        schedule.add_systems(((
2169            (
2170                |mut commands: Commands| commands.insert_resource(Resource1),
2171                |mut commands: Commands| commands.insert_resource(Resource2),
2172            ),
2173            |_: Res<Resource1>, _: Res<Resource2>| {},
2174        )
2175            .chain(),));
2176        schedule.run(&mut world);
2177
2178        assert_eq!(schedule.executable.systems.len(), 7);
2179    }
2180
2181    #[test]
2182    fn adds_multiple_consecutive_syncs() {
2183        let mut schedule = Schedule::default();
2184        let mut world = World::default();
2185        // insert two consecutive command systems, it should create two sync points
2186        schedule.add_systems(
2187            (
2188                |mut commands: Commands| commands.insert_resource(Resource1),
2189                |mut commands: Commands| commands.insert_resource(Resource2),
2190                |_: Res<Resource1>, _: Res<Resource2>| {},
2191            )
2192                .chain(),
2193        );
2194        schedule.run(&mut world);
2195
2196        assert_eq!(schedule.executable.systems.len(), 5);
2197    }
2198
2199    #[test]
2200    fn do_not_consider_ignore_deferred_before_exclusive_system() {
2201        let mut schedule = Schedule::default();
2202        let mut world = World::default();
2203        // chain_ignore_deferred adds no sync points usually but an exception is made for exclusive systems
2204        schedule.add_systems(
2205            (
2206                |_: Commands| {},
2207                // <- no sync point is added here because the following system is not exclusive
2208                |mut commands: Commands| commands.insert_resource(Resource1),
2209                // <- sync point is added here because the following system is exclusive which expects to see all commands to that point
2210                |world: &mut World| assert!(world.contains_resource::<Resource1>()),
2211                // <- no sync point is added here because the previous system has no deferred parameters
2212                |_: &mut World| {},
2213                // <- no sync point is added here because the following system is not exclusive
2214                |_: Commands| {},
2215            )
2216                .chain_ignore_deferred(),
2217        );
2218        schedule.run(&mut world);
2219
2220        assert_eq!(schedule.executable.systems.len(), 6); // 5 systems + 1 sync point
2221    }
2222
2223    #[test]
2224    fn bubble_sync_point_through_ignore_deferred_node() {
2225        let mut schedule = Schedule::default();
2226        let mut world = World::default();
2227
2228        let insert_resource_config = (
2229            // the first system has deferred commands
2230            |mut commands: Commands| commands.insert_resource(Resource1),
2231            // the second system has no deferred commands
2232            || {},
2233        )
2234            // the first two systems are chained without a sync point in between
2235            .chain_ignore_deferred();
2236
2237        schedule.add_systems(
2238            (
2239                insert_resource_config,
2240                // the third system would panic if the command of the first system was not applied
2241                |_: Res<Resource1>| {},
2242            )
2243                // the third system is chained after the first two, possibly with a sync point in between
2244                .chain(),
2245        );
2246
2247        // To add a sync point between the second and third system despite the second having no commands,
2248        // the first system has to signal the second system that there are unapplied commands.
2249        // With that the second system will add a sync point after it so the third system will find the resource.
2250
2251        schedule.run(&mut world);
2252
2253        assert_eq!(schedule.executable.systems.len(), 4); // 3 systems + 1 sync point
2254    }
2255
2256    #[test]
2257    fn disable_auto_sync_points() {
2258        let mut schedule = Schedule::default();
2259        schedule.set_build_settings(ScheduleBuildSettings {
2260            auto_insert_apply_deferred: false,
2261            ..Default::default()
2262        });
2263        let mut world = World::default();
2264        schedule.add_systems(
2265            (
2266                |mut commands: Commands| commands.insert_resource(Resource1),
2267                |res: Option<Res<Resource1>>| assert!(res.is_none()),
2268            )
2269                .chain(),
2270        );
2271        schedule.run(&mut world);
2272
2273        assert_eq!(schedule.executable.systems.len(), 2);
2274    }
2275
2276    mod no_sync_edges {
2277        use super::*;
2278
2279        fn insert_resource(mut commands: Commands) {
2280            commands.insert_resource(Resource1);
2281        }
2282
2283        fn resource_does_not_exist(res: Option<Res<Resource1>>) {
2284            assert!(res.is_none());
2285        }
2286
2287        #[derive(SystemSet, Hash, PartialEq, Eq, Debug, Clone)]
2288        enum Sets {
2289            A,
2290            B,
2291        }
2292
2293        fn check_no_sync_edges(add_systems: impl FnOnce(&mut Schedule)) {
2294            let mut schedule = Schedule::default();
2295            let mut world = World::default();
2296            add_systems(&mut schedule);
2297
2298            schedule.run(&mut world);
2299
2300            assert_eq!(schedule.executable.systems.len(), 2);
2301        }
2302
2303        #[test]
2304        fn system_to_system_after() {
2305            check_no_sync_edges(|schedule| {
2306                schedule.add_systems((
2307                    insert_resource,
2308                    resource_does_not_exist.after_ignore_deferred(insert_resource),
2309                ));
2310            });
2311        }
2312
2313        #[test]
2314        fn system_to_system_before() {
2315            check_no_sync_edges(|schedule| {
2316                schedule.add_systems((
2317                    insert_resource.before_ignore_deferred(resource_does_not_exist),
2318                    resource_does_not_exist,
2319                ));
2320            });
2321        }
2322
2323        #[test]
2324        fn set_to_system_after() {
2325            check_no_sync_edges(|schedule| {
2326                schedule
2327                    .add_systems((insert_resource, resource_does_not_exist.in_set(Sets::A)))
2328                    .configure_sets(Sets::A.after_ignore_deferred(insert_resource));
2329            });
2330        }
2331
2332        #[test]
2333        fn set_to_system_before() {
2334            check_no_sync_edges(|schedule| {
2335                schedule
2336                    .add_systems((insert_resource.in_set(Sets::A), resource_does_not_exist))
2337                    .configure_sets(Sets::A.before_ignore_deferred(resource_does_not_exist));
2338            });
2339        }
2340
2341        #[test]
2342        fn set_to_set_after() {
2343            check_no_sync_edges(|schedule| {
2344                schedule
2345                    .add_systems((
2346                        insert_resource.in_set(Sets::A),
2347                        resource_does_not_exist.in_set(Sets::B),
2348                    ))
2349                    .configure_sets(Sets::B.after_ignore_deferred(Sets::A));
2350            });
2351        }
2352
2353        #[test]
2354        fn set_to_set_before() {
2355            check_no_sync_edges(|schedule| {
2356                schedule
2357                    .add_systems((
2358                        insert_resource.in_set(Sets::A),
2359                        resource_does_not_exist.in_set(Sets::B),
2360                    ))
2361                    .configure_sets(Sets::A.before_ignore_deferred(Sets::B));
2362            });
2363        }
2364    }
2365
2366    mod no_sync_chain {
2367        use super::*;
2368
2369        #[derive(Resource)]
2370        struct Ra;
2371
2372        #[derive(Resource)]
2373        struct Rb;
2374
2375        #[derive(Resource)]
2376        struct Rc;
2377
2378        fn run_schedule(expected_num_systems: usize, add_systems: impl FnOnce(&mut Schedule)) {
2379            let mut schedule = Schedule::default();
2380            let mut world = World::default();
2381            add_systems(&mut schedule);
2382
2383            schedule.run(&mut world);
2384
2385            assert_eq!(schedule.executable.systems.len(), expected_num_systems);
2386        }
2387
2388        #[test]
2389        fn only_chain_outside() {
2390            run_schedule(5, |schedule: &mut Schedule| {
2391                schedule.add_systems(
2392                    (
2393                        (
2394                            |mut commands: Commands| commands.insert_resource(Ra),
2395                            |mut commands: Commands| commands.insert_resource(Rb),
2396                        ),
2397                        (
2398                            |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2399                                assert!(res_a.is_some());
2400                                assert!(res_b.is_some());
2401                            },
2402                            |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2403                                assert!(res_a.is_some());
2404                                assert!(res_b.is_some());
2405                            },
2406                        ),
2407                    )
2408                        .chain(),
2409                );
2410            });
2411
2412            run_schedule(4, |schedule: &mut Schedule| {
2413                schedule.add_systems(
2414                    (
2415                        (
2416                            |mut commands: Commands| commands.insert_resource(Ra),
2417                            |mut commands: Commands| commands.insert_resource(Rb),
2418                        ),
2419                        (
2420                            |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2421                                assert!(res_a.is_none());
2422                                assert!(res_b.is_none());
2423                            },
2424                            |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2425                                assert!(res_a.is_none());
2426                                assert!(res_b.is_none());
2427                            },
2428                        ),
2429                    )
2430                        .chain_ignore_deferred(),
2431                );
2432            });
2433        }
2434
2435        #[test]
2436        fn chain_first() {
2437            run_schedule(6, |schedule: &mut Schedule| {
2438                schedule.add_systems(
2439                    (
2440                        (
2441                            |mut commands: Commands| commands.insert_resource(Ra),
2442                            |mut commands: Commands, res_a: Option<Res<Ra>>| {
2443                                commands.insert_resource(Rb);
2444                                assert!(res_a.is_some());
2445                            },
2446                        )
2447                            .chain(),
2448                        (
2449                            |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2450                                assert!(res_a.is_some());
2451                                assert!(res_b.is_some());
2452                            },
2453                            |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2454                                assert!(res_a.is_some());
2455                                assert!(res_b.is_some());
2456                            },
2457                        ),
2458                    )
2459                        .chain(),
2460                );
2461            });
2462
2463            run_schedule(5, |schedule: &mut Schedule| {
2464                schedule.add_systems(
2465                    (
2466                        (
2467                            |mut commands: Commands| commands.insert_resource(Ra),
2468                            |mut commands: Commands, res_a: Option<Res<Ra>>| {
2469                                commands.insert_resource(Rb);
2470                                assert!(res_a.is_some());
2471                            },
2472                        )
2473                            .chain(),
2474                        (
2475                            |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2476                                assert!(res_a.is_some());
2477                                assert!(res_b.is_none());
2478                            },
2479                            |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2480                                assert!(res_a.is_some());
2481                                assert!(res_b.is_none());
2482                            },
2483                        ),
2484                    )
2485                        .chain_ignore_deferred(),
2486                );
2487            });
2488        }
2489
2490        #[test]
2491        fn chain_second() {
2492            run_schedule(6, |schedule: &mut Schedule| {
2493                schedule.add_systems(
2494                    (
2495                        (
2496                            |mut commands: Commands| commands.insert_resource(Ra),
2497                            |mut commands: Commands| commands.insert_resource(Rb),
2498                        ),
2499                        (
2500                            |mut commands: Commands,
2501                             res_a: Option<Res<Ra>>,
2502                             res_b: Option<Res<Rb>>| {
2503                                commands.insert_resource(Rc);
2504                                assert!(res_a.is_some());
2505                                assert!(res_b.is_some());
2506                            },
2507                            |res_a: Option<Res<Ra>>,
2508                             res_b: Option<Res<Rb>>,
2509                             res_c: Option<Res<Rc>>| {
2510                                assert!(res_a.is_some());
2511                                assert!(res_b.is_some());
2512                                assert!(res_c.is_some());
2513                            },
2514                        )
2515                            .chain(),
2516                    )
2517                        .chain(),
2518                );
2519            });
2520
2521            run_schedule(5, |schedule: &mut Schedule| {
2522                schedule.add_systems(
2523                    (
2524                        (
2525                            |mut commands: Commands| commands.insert_resource(Ra),
2526                            |mut commands: Commands| commands.insert_resource(Rb),
2527                        ),
2528                        (
2529                            |mut commands: Commands,
2530                             res_a: Option<Res<Ra>>,
2531                             res_b: Option<Res<Rb>>| {
2532                                commands.insert_resource(Rc);
2533                                assert!(res_a.is_none());
2534                                assert!(res_b.is_none());
2535                            },
2536                            |res_a: Option<Res<Ra>>,
2537                             res_b: Option<Res<Rb>>,
2538                             res_c: Option<Res<Rc>>| {
2539                                assert!(res_a.is_some());
2540                                assert!(res_b.is_some());
2541                                assert!(res_c.is_some());
2542                            },
2543                        )
2544                            .chain(),
2545                    )
2546                        .chain_ignore_deferred(),
2547                );
2548            });
2549        }
2550
2551        #[test]
2552        fn chain_all() {
2553            run_schedule(7, |schedule: &mut Schedule| {
2554                schedule.add_systems(
2555                    (
2556                        (
2557                            |mut commands: Commands| commands.insert_resource(Ra),
2558                            |mut commands: Commands, res_a: Option<Res<Ra>>| {
2559                                commands.insert_resource(Rb);
2560                                assert!(res_a.is_some());
2561                            },
2562                        )
2563                            .chain(),
2564                        (
2565                            |mut commands: Commands,
2566                             res_a: Option<Res<Ra>>,
2567                             res_b: Option<Res<Rb>>| {
2568                                commands.insert_resource(Rc);
2569                                assert!(res_a.is_some());
2570                                assert!(res_b.is_some());
2571                            },
2572                            |res_a: Option<Res<Ra>>,
2573                             res_b: Option<Res<Rb>>,
2574                             res_c: Option<Res<Rc>>| {
2575                                assert!(res_a.is_some());
2576                                assert!(res_b.is_some());
2577                                assert!(res_c.is_some());
2578                            },
2579                        )
2580                            .chain(),
2581                    )
2582                        .chain(),
2583                );
2584            });
2585
2586            run_schedule(6, |schedule: &mut Schedule| {
2587                schedule.add_systems(
2588                    (
2589                        (
2590                            |mut commands: Commands| commands.insert_resource(Ra),
2591                            |mut commands: Commands, res_a: Option<Res<Ra>>| {
2592                                commands.insert_resource(Rb);
2593                                assert!(res_a.is_some());
2594                            },
2595                        )
2596                            .chain(),
2597                        (
2598                            |mut commands: Commands,
2599                             res_a: Option<Res<Ra>>,
2600                             res_b: Option<Res<Rb>>| {
2601                                commands.insert_resource(Rc);
2602                                assert!(res_a.is_some());
2603                                assert!(res_b.is_none());
2604                            },
2605                            |res_a: Option<Res<Ra>>,
2606                             res_b: Option<Res<Rb>>,
2607                             res_c: Option<Res<Rc>>| {
2608                                assert!(res_a.is_some());
2609                                assert!(res_b.is_some());
2610                                assert!(res_c.is_some());
2611                            },
2612                        )
2613                            .chain(),
2614                    )
2615                        .chain_ignore_deferred(),
2616                );
2617            });
2618        }
2619    }
2620
2621    #[derive(ScheduleLabel, Hash, Debug, Clone, PartialEq, Eq)]
2622    struct TestSchedule;
2623
2624    #[derive(Resource)]
2625    struct CheckSystemRan(usize);
2626
2627    #[test]
2628    fn add_systems_to_existing_schedule() {
2629        let mut schedules = Schedules::default();
2630        let schedule = Schedule::new(TestSchedule);
2631
2632        schedules.insert(schedule);
2633        schedules.add_systems(TestSchedule, |mut ran: ResMut<CheckSystemRan>| ran.0 += 1);
2634
2635        let mut world = World::new();
2636
2637        world.insert_resource(CheckSystemRan(0));
2638        world.insert_resource(schedules);
2639        world.run_schedule(TestSchedule);
2640
2641        let value = world
2642            .get_resource::<CheckSystemRan>()
2643            .expect("CheckSystemRan Resource Should Exist");
2644        assert_eq!(value.0, 1);
2645    }
2646
2647    #[test]
2648    fn add_systems_to_non_existing_schedule() {
2649        let mut schedules = Schedules::default();
2650
2651        schedules.add_systems(TestSchedule, |mut ran: ResMut<CheckSystemRan>| ran.0 += 1);
2652
2653        let mut world = World::new();
2654
2655        world.insert_resource(CheckSystemRan(0));
2656        world.insert_resource(schedules);
2657        world.run_schedule(TestSchedule);
2658
2659        let value = world
2660            .get_resource::<CheckSystemRan>()
2661            .expect("CheckSystemRan Resource Should Exist");
2662        assert_eq!(value.0, 1);
2663    }
2664
2665    #[derive(SystemSet, Debug, Hash, Clone, PartialEq, Eq)]
2666    enum TestSet {
2667        First,
2668        Second,
2669    }
2670
2671    #[test]
2672    fn configure_set_on_existing_schedule() {
2673        let mut schedules = Schedules::default();
2674        let schedule = Schedule::new(TestSchedule);
2675
2676        schedules.insert(schedule);
2677
2678        schedules.configure_sets(TestSchedule, (TestSet::First, TestSet::Second).chain());
2679        schedules.add_systems(
2680            TestSchedule,
2681            (|mut ran: ResMut<CheckSystemRan>| {
2682                assert_eq!(ran.0, 0);
2683                ran.0 += 1;
2684            })
2685            .in_set(TestSet::First),
2686        );
2687
2688        schedules.add_systems(
2689            TestSchedule,
2690            (|mut ran: ResMut<CheckSystemRan>| {
2691                assert_eq!(ran.0, 1);
2692                ran.0 += 1;
2693            })
2694            .in_set(TestSet::Second),
2695        );
2696
2697        let mut world = World::new();
2698
2699        world.insert_resource(CheckSystemRan(0));
2700        world.insert_resource(schedules);
2701        world.run_schedule(TestSchedule);
2702
2703        let value = world
2704            .get_resource::<CheckSystemRan>()
2705            .expect("CheckSystemRan Resource Should Exist");
2706        assert_eq!(value.0, 2);
2707    }
2708
2709    #[test]
2710    fn configure_set_on_new_schedule() {
2711        let mut schedules = Schedules::default();
2712
2713        schedules.configure_sets(TestSchedule, (TestSet::First, TestSet::Second).chain());
2714        schedules.add_systems(
2715            TestSchedule,
2716            (|mut ran: ResMut<CheckSystemRan>| {
2717                assert_eq!(ran.0, 0);
2718                ran.0 += 1;
2719            })
2720            .in_set(TestSet::First),
2721        );
2722
2723        schedules.add_systems(
2724            TestSchedule,
2725            (|mut ran: ResMut<CheckSystemRan>| {
2726                assert_eq!(ran.0, 1);
2727                ran.0 += 1;
2728            })
2729            .in_set(TestSet::Second),
2730        );
2731
2732        let mut world = World::new();
2733
2734        world.insert_resource(CheckSystemRan(0));
2735        world.insert_resource(schedules);
2736        world.run_schedule(TestSchedule);
2737
2738        let value = world
2739            .get_resource::<CheckSystemRan>()
2740            .expect("CheckSystemRan Resource Should Exist");
2741        assert_eq!(value.0, 2);
2742    }
2743
2744    #[test]
2745    fn test_default_error_handler() {
2746        #[derive(Resource, Default)]
2747        struct Ran(bool);
2748
2749        fn system(mut ran: ResMut<Ran>) -> Result {
2750            ran.0 = true;
2751            Err("I failed!".into())
2752        }
2753
2754        // Test that the fallback error handler is used
2755        let mut world = World::default();
2756        world.init_resource::<Ran>();
2757        world.insert_resource(FallbackErrorHandler(ignore));
2758        let mut schedule = Schedule::default();
2759        schedule.add_systems(system).run(&mut world);
2760        assert!(world.resource::<Ran>().0);
2761
2762        // Test that the handler doesn't change within the schedule
2763        schedule.add_systems(
2764            (|world: &mut World| {
2765                world.insert_resource(FallbackErrorHandler(panic));
2766            })
2767            .before(system),
2768        );
2769        schedule.run(&mut world);
2770    }
2771
2772    #[test]
2773    fn get_a_system_key() {
2774        fn test_system() {}
2775
2776        let mut schedule = Schedule::default();
2777        schedule.add_systems(test_system);
2778        let mut world = World::default();
2779        let _ = schedule.initialize(&mut world);
2780
2781        let keys = schedule
2782            .graph()
2783            .systems_in_set(test_system.into_system_set().intern())
2784            .unwrap();
2785        assert_eq!(keys.len(), 1);
2786    }
2787
2788    #[test]
2789    fn get_system_keys_in_set() {
2790        fn system_1() {}
2791        fn system_2() {}
2792
2793        let mut schedule = Schedule::default();
2794        schedule.add_systems((system_1, system_2).in_set(TestSet::First));
2795        let mut world = World::default();
2796        let _ = schedule.initialize(&mut world);
2797
2798        let keys = schedule
2799            .graph()
2800            .systems_in_set(TestSet::First.into_system_set().intern())
2801            .unwrap();
2802        assert_eq!(keys.len(), 2);
2803    }
2804
2805    #[test]
2806    fn get_system_keys_with_same_name() {
2807        fn test_system() {}
2808
2809        let mut schedule = Schedule::default();
2810        schedule.add_systems((test_system, test_system));
2811        let mut world = World::default();
2812        let _ = schedule.initialize(&mut world);
2813
2814        let keys = schedule
2815            .graph()
2816            .systems_in_set(test_system.into_system_set().intern())
2817            .unwrap();
2818        assert_eq!(keys.len(), 2);
2819    }
2820
2821    #[test]
2822    fn remove_a_system() {
2823        fn system() {}
2824
2825        let mut schedule = Schedule::default();
2826        schedule.add_systems(system);
2827        let mut world = World::default();
2828
2829        let remove_count = schedule.remove_systems_in_set(
2830            system,
2831            &mut world,
2832            ScheduleCleanupPolicy::RemoveSetAndSystemsAllowBreakages,
2833        );
2834        assert_eq!(remove_count.unwrap(), 1);
2835
2836        // schedule has changed, so we check initializing again
2837        schedule.initialize(&mut world).unwrap();
2838        assert_eq!(schedule.graph().systems.len(), 0);
2839    }
2840
2841    #[test]
2842    fn remove_multiple_systems() {
2843        fn system() {}
2844
2845        let mut schedule = Schedule::default();
2846        schedule.add_systems((system, system));
2847        let mut world = World::default();
2848
2849        let remove_count = schedule.remove_systems_in_set(
2850            system,
2851            &mut world,
2852            ScheduleCleanupPolicy::RemoveSetAndSystemsAllowBreakages,
2853        );
2854        assert_eq!(remove_count.unwrap(), 2);
2855
2856        // schedule has changed, so we check initializing again
2857        schedule.initialize(&mut world).unwrap();
2858        assert_eq!(schedule.graph().systems.len(), 0);
2859    }
2860
2861    #[test]
2862    fn remove_a_system_with_dependencies() {
2863        fn system_1() {}
2864        fn system_2() {}
2865
2866        let mut schedule = Schedule::default();
2867        schedule.add_systems((system_1, system_2).chain());
2868        let mut world = World::default();
2869
2870        let remove_count = schedule.remove_systems_in_set(
2871            system_1,
2872            &mut world,
2873            ScheduleCleanupPolicy::RemoveSetAndSystemsAllowBreakages,
2874        );
2875        assert_eq!(remove_count.unwrap(), 1);
2876
2877        // schedule has changed, so we check initializing again
2878        schedule.initialize(&mut world).unwrap();
2879        assert_eq!(schedule.graph().systems.len(), 1);
2880    }
2881
2882    #[test]
2883    fn remove_a_system_and_still_ordered() {
2884        #[derive(Resource)]
2885        struct A;
2886
2887        fn system_1(_: ResMut<A>) {}
2888        fn system_2() {}
2889        fn system_3(_: ResMut<A>) {}
2890
2891        let mut schedule = Schedule::default();
2892        schedule.add_systems((system_1, system_2, system_3).chain());
2893        let mut world = World::new();
2894
2895        let _ = schedule.remove_systems_in_set(
2896            system_2,
2897            &mut world,
2898            ScheduleCleanupPolicy::RemoveSetAndSystems,
2899        );
2900
2901        let result = schedule.initialize(&mut world);
2902        assert!(result.is_ok());
2903        let conflicts = schedule.graph().conflicting_systems();
2904        assert!(conflicts.is_empty());
2905    }
2906
2907    #[test]
2908    fn remove_a_set_and_still_ordered() {
2909        #[derive(Resource)]
2910        struct A;
2911
2912        #[derive(SystemSet, Hash, PartialEq, Eq, Clone, Debug)]
2913        struct B;
2914
2915        fn system_1(_: ResMut<A>) {}
2916        fn system_2() {}
2917        fn system_3(_: ResMut<A>) {}
2918
2919        let mut schedule = Schedule::default();
2920        schedule.add_systems((system_1.before(B), system_2, system_3.after(B)));
2921        let mut world = World::new();
2922
2923        let _ = schedule.remove_systems_in_set(
2924            B,
2925            &mut world,
2926            ScheduleCleanupPolicy::RemoveSetAndSystems,
2927        );
2928
2929        let result = schedule.initialize(&mut world);
2930        assert!(result.is_ok());
2931        let conflicts = schedule.graph().conflicting_systems();
2932        assert!(conflicts.is_empty());
2933    }
2934
2935    #[test]
2936    fn build_pass_iteration_order() {
2937        #[derive(Debug)]
2938        struct Pass<const N: usize>;
2939
2940        impl<const N: usize> ScheduleBuildPass for Pass<N> {
2941            type EdgeOptions = ();
2942            fn add_dependency(
2943                &mut self,
2944                _from: crate::schedule::NodeId,
2945                _to: crate::schedule::NodeId,
2946                _options: Option<&Self::EdgeOptions>,
2947            ) {
2948            }
2949            fn build(
2950                &mut self,
2951                _world: &mut World,
2952                _graph: &mut super::ScheduleGraph,
2953                _dependency_flattened: FlattenedDependencies<'_>,
2954            ) -> core::result::Result<(), crate::schedule::ScheduleBuildError> {
2955                Ok(())
2956            }
2957            fn collapse_set(
2958                &mut self,
2959                _set: crate::schedule::SystemSetKey,
2960                _systems: &indexmap::IndexSet<
2961                    crate::schedule::SystemKey,
2962                    bevy_platform::hash::FixedHasher,
2963                >,
2964                _dependency_flattening: &crate::schedule::graph::DiGraph<crate::schedule::NodeId>,
2965            ) -> impl Iterator<Item = (crate::schedule::NodeId, crate::schedule::NodeId)>
2966            {
2967                core::iter::empty()
2968            }
2969        }
2970
2971        let mut schedule = Schedule::default();
2972        schedule.add_build_pass(Pass::<0>);
2973        schedule.add_build_pass(Pass::<1>);
2974        schedule.add_build_pass(Pass::<2>);
2975
2976        let pass_order: Vec<TypeId> = schedule.graph().passes.keys().cloned().collect();
2977
2978        assert_eq!(
2979            pass_order,
2980            vec![
2981                TypeId::of::<AutoInsertApplyDeferredPass>(),
2982                TypeId::of::<Pass<0>>(),
2983                TypeId::of::<Pass<1>>(),
2984                TypeId::of::<Pass<2>>()
2985            ]
2986        );
2987    }
2988
2989    #[cfg(feature = "debug")]
2990    #[test]
2991    fn schedule_builds_randomly_with_shuffler() {
2992        fn run_schedule_with_shuffler(shuffle_seed: Option<u64>) -> Vec<u32> {
2993            #[derive(Resource, Default)]
2994            struct Counters(Vec<u32>);
2995
2996            let mut schedule = Schedule::default();
2997
2998            // Note: we use a mutable resource to ensure that all the systems are conflicting and
2999            // therefore must be resolved by the system toposort.
3000            fn system<const N: u32>(mut counters: ResMut<Counters>) {
3001                counters.0.push(N);
3002            }
3003
3004            // Create a simple graph like so:
3005            // 0
3006            // ->10
3007            //   ->20
3008            //   ->21
3009            // ->11
3010            schedule.add_systems(system::<0>);
3011            schedule.add_systems((system::<10>, system::<11>).after(system::<0>));
3012            schedule.add_systems((system::<20>, system::<21>).after(system::<10>));
3013
3014            schedule.set_build_settings(ScheduleBuildSettings {
3015                shuffle_seed,
3016                ..Default::default()
3017            });
3018
3019            let mut world = World::new();
3020            world.init_resource::<Counters>();
3021            schedule.initialize(&mut world).unwrap();
3022            schedule.run(&mut world);
3023
3024            world.remove_resource::<Counters>().unwrap().0
3025        }
3026
3027        for _ in 0..10 {
3028            assert_eq!(
3029                run_schedule_with_shuffler(None),
3030                // Without a shuffler, schedule building is totally deterministic (but arbitrary).
3031                [0, 11, 10, 20, 21]
3032            );
3033        }
3034
3035        // With the right seed, we can find every ordering that satisfies the ordering constraints.
3036        // This is every valid permutation of these ordering constraints.
3037        assert_eq!(
3038            run_schedule_with_shuffler(Some(100000001)),
3039            [0, 10, 20, 21, 11]
3040        );
3041        assert_eq!(
3042            run_schedule_with_shuffler(Some(100000030)),
3043            [0, 10, 21, 20, 11]
3044        );
3045        assert_eq!(
3046            run_schedule_with_shuffler(Some(100000020)),
3047            [0, 10, 20, 11, 21]
3048        );
3049        assert_eq!(
3050            run_schedule_with_shuffler(Some(100000063)),
3051            [0, 10, 21, 11, 20]
3052        );
3053        assert_eq!(
3054            run_schedule_with_shuffler(Some(100000003)),
3055            [0, 10, 11, 20, 21]
3056        );
3057        assert_eq!(
3058            run_schedule_with_shuffler(Some(100000080)),
3059            [0, 10, 11, 21, 20]
3060        );
3061        assert_eq!(
3062            run_schedule_with_shuffler(Some(100000000)),
3063            [0, 11, 10, 20, 21]
3064        );
3065        assert_eq!(
3066            run_schedule_with_shuffler(Some(100000004)),
3067            [0, 11, 10, 21, 20]
3068        );
3069
3070        // For future: if somehow these seeds become invalid, you can find new ones using:
3071        //
3072        // let mut unique = bevy_platform::collections::HashMap::new();
3073        // for i in 100_000_000..100_001_000 {
3074        //     let order = run_schedule_with_shuffler(Some(make_shuffler(i)));
3075        //     if !unique.contains_key(&order) {
3076        //         unique.insert(order, i);
3077        //     }
3078        // }
3079        // panic!("unique={unique:?}");
3080    }
3081
3082    /// Total number of dependency edges in the built schedule.
3083    fn total_dependencies(schedule: &Schedule) -> usize {
3084        schedule.executable.system_dependencies.iter().sum()
3085    }
3086
3087    #[test]
3088    fn chain_weak_adds_no_edges_for_non_conflicting_systems() {
3089        fn read<const N: usize>(_: Res<Resource1>) {}
3090
3091        let mut world = World::default();
3092
3093        // A strict chain adds a dependency edge between every successive pair.
3094        let mut strict = Schedule::default();
3095        strict.add_systems((read::<1>, read::<2>, read::<3>).chain());
3096        strict.initialize(&mut world).unwrap();
3097        assert_eq!(total_dependencies(&strict), 2);
3098
3099        // A weak chain of non-conflicting systems adds no ordering edges at all: with
3100        // nothing to serialize, the systems are free to run in any order, including in
3101        // parallel.
3102        let mut weak = Schedule::default();
3103        weak.add_systems((read::<1>, read::<2>, read::<3>).chain_weak());
3104        weak.initialize(&mut world).unwrap();
3105        assert_eq!(total_dependencies(&weak), 0);
3106    }
3107
3108    #[test]
3109    fn chain_weak_orders_conflicting_systems_with_finish_edge() {
3110        fn write<const N: usize>(_: ResMut<Resource1>) {}
3111
3112        let mut world = World::default();
3113        let mut schedule = Schedule::default();
3114        // Both systems write `Resource1`, so they conflict and must be ordered. A weak
3115        // chain materializes a normal dependency edge for conflicting pairs.
3116        schedule.add_systems((write::<1>, write::<2>).chain_weak());
3117        schedule.initialize(&mut world).unwrap();
3118
3119        assert_eq!(total_dependencies(&schedule), 1);
3120    }
3121
3122    #[test]
3123    fn chain_weak_keeps_finish_dependency_for_deferred() {
3124        let mut world = World::default();
3125        let mut schedule = Schedule::default();
3126        schedule.set_executor(MultiThreadedExecutor::new());
3127        schedule.add_systems(
3128            (
3129                |mut commands: Commands| commands.insert_resource(Resource1),
3130                |_: Res<Resource1>| {},
3131            )
3132                .chain_weak(),
3133        );
3134        // A sync point is inserted between the producer and reader before the weak edges are
3135        // resolved, so the direct edge is already gone and the ordering is kept. The reader
3136        // requires `Resource1`, so a run that doesn't panic proves the insert was applied first.
3137        schedule.run(&mut world);
3138
3139        // A sync point was inserted between the two systems, so there are two edges.
3140        assert_eq!(schedule.executable.systems.len(), 3);
3141        assert_eq!(total_dependencies(&schedule), 2);
3142    }
3143
3144    #[test]
3145    fn chain_weak_keeps_finish_dependency_for_exclusive() {
3146        fn read<const N: usize>(_: Res<Resource1>) {}
3147        fn exclusive(_: &mut World) {}
3148
3149        let mut world = World::default();
3150        let mut schedule = Schedule::default();
3151        schedule.add_systems((read::<1>, exclusive, read::<2>).chain_weak());
3152        schedule.initialize(&mut world).unwrap();
3153
3154        // Exclusive systems conflict with everything, so edges touching them are kept:
3155        // `read1 -> exclusive` and `exclusive -> read2`. The two readers don't conflict with
3156        // each other, so no direct edge is added between them.
3157        assert_eq!(total_dependencies(&schedule), 2);
3158    }
3159
3160    #[test]
3161    fn chain_weak_between_non_conflicting_sets_adds_no_edges() {
3162        #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
3163        enum Sets {
3164            A,
3165            B,
3166        }
3167
3168        fn read<const N: usize>(_: Res<Resource1>) {}
3169
3170        let mut world = World::default();
3171        let mut schedule = Schedule::default();
3172        schedule.configure_sets((Sets::A, Sets::B).chain_weak());
3173        schedule.add_systems((read::<1>.in_set(Sets::A), read::<2>.in_set(Sets::B)));
3174        schedule.initialize(&mut world).unwrap();
3175
3176        // The systems in the two sets don't conflict, so the weak set ordering adds no edge.
3177        assert_eq!(total_dependencies(&schedule), 0);
3178    }
3179
3180    #[test]
3181    fn chain_weak_between_sets_fans_out_conflict_edges() {
3182        #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
3183        enum Sets {
3184            A,
3185            B,
3186        }
3187
3188        fn write<const N: usize>(_: ResMut<Resource1>) {}
3189
3190        let mut world = World::default();
3191        let mut schedule = Schedule::default();
3192        schedule.configure_sets((Sets::A, Sets::B).chain_weak());
3193        schedule.add_systems((
3194            (write::<1>, write::<2>).in_set(Sets::A),
3195            (write::<3>, write::<4>).in_set(Sets::B),
3196        ));
3197        schedule.initialize(&mut world).unwrap();
3198
3199        // Every system in A conflicts with every system in B, so the weak set ordering
3200        // materializes a 2x2 fan-out of edges. (Members within a set are
3201        // unordered, so their mutual conflict is a separate ambiguity this test ignores.)
3202        assert_eq!(total_dependencies(&schedule), 4);
3203    }
3204
3205    #[test]
3206    fn before_weak_between_non_conflicting_sets_adds_no_edges() {
3207        #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
3208        enum Sets {
3209            A,
3210            B,
3211        }
3212
3213        fn read<const N: usize>(_: Res<Resource1>) {}
3214
3215        let mut world = World::default();
3216        let mut schedule = Schedule::default();
3217        schedule.configure_sets(Sets::A.before_weak(Sets::B));
3218        schedule.add_systems((read::<1>.in_set(Sets::A), read::<2>.in_set(Sets::B)));
3219        schedule.initialize(&mut world).unwrap();
3220
3221        // The two systems don't conflict, so the weak `before` ordering adds no edge.
3222        assert_eq!(total_dependencies(&schedule), 0);
3223    }
3224
3225    #[test]
3226    fn after_weak_orders_conflicting_sets_with_finish_edge() {
3227        #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
3228        enum Sets {
3229            A,
3230            B,
3231        }
3232
3233        fn write<const N: usize>(_: ResMut<Resource1>) {}
3234
3235        let mut world = World::default();
3236        let mut schedule = Schedule::default();
3237        schedule.configure_sets(Sets::B.after_weak(Sets::A));
3238        schedule.add_systems((write::<1>.in_set(Sets::A), write::<2>.in_set(Sets::B)));
3239        schedule.initialize(&mut world).unwrap();
3240
3241        // The systems conflict, so the weak `after` ordering materializes a single
3242        // edge in the `A -> B` direction.
3243        assert_eq!(total_dependencies(&schedule), 1);
3244    }
3245
3246    #[test]
3247    fn before_weak_keeps_finish_dependency_for_deferred() {
3248        #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
3249        enum Sets {
3250            A,
3251            B,
3252        }
3253
3254        fn with_commands(_: Commands) {}
3255        fn read<const N: usize>(_: Res<Resource1>) {}
3256
3257        let mut world = World::default();
3258        let mut schedule = Schedule::default();
3259        schedule.configure_sets(Sets::A.before_weak(Sets::B));
3260        // `Sets::A` produces deferred effects, which count as a conflict, so the ordering is
3261        // kept and a sync point is inserted between the two systems.
3262        schedule.add_systems((with_commands.in_set(Sets::A), read::<2>.in_set(Sets::B)));
3263        schedule.initialize(&mut world).unwrap();
3264
3265        // `with_commands -> ApplyDeferred -> read`, so two edges.
3266        assert_eq!(total_dependencies(&schedule), 2);
3267    }
3268
3269    #[test]
3270    fn before_weak_orders_conflicting_systems() {
3271        #[derive(Resource, Default)]
3272        struct Order(Vec<u32>);
3273        #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
3274        enum Sets {
3275            A,
3276            B,
3277        }
3278
3279        fn record<const N: u32>(mut order: ResMut<Order>) {
3280            order.0.push(N);
3281        }
3282
3283        let mut world = World::default();
3284        world.init_resource::<Order>();
3285        let mut schedule = Schedule::default();
3286        // Use the multi-threaded executor so ordering isn't just an artifact of topological
3287        // order (the single-threaded executor always runs in topological order).
3288        schedule.set_executor(MultiThreadedExecutor::new());
3289        schedule.configure_sets(Sets::A.before_weak(Sets::B));
3290        // Both systems write `Order`, so they conflict and the weak ordering pins their order.
3291        schedule.add_systems((record::<1>.in_set(Sets::A), record::<2>.in_set(Sets::B)));
3292        schedule.run(&mut world);
3293
3294        assert_eq!(world.resource::<Order>().0, vec![1, 2]);
3295    }
3296
3297    #[test]
3298    fn chain_weak_orders_conflicting_systems() {
3299        #[derive(Resource, Default)]
3300        struct Order(Vec<u32>);
3301
3302        fn record<const N: u32>(mut order: ResMut<Order>) {
3303            order.0.push(N);
3304        }
3305
3306        let mut world = World::default();
3307        world.init_resource::<Order>();
3308        let mut schedule = Schedule::default();
3309        // Use the multi-threaded executor so ordering isn't just an artifact of topological
3310        // order (the single-threaded executor always runs in topological order).
3311        schedule.set_executor(MultiThreadedExecutor::new());
3312        // Both systems write `Order`, so they conflict and the weak ordering pins their order.
3313        schedule.add_systems((record::<1>, record::<2>).chain_weak());
3314        schedule.run(&mut world);
3315
3316        assert_eq!(world.resource::<Order>().0, vec![1, 2]);
3317    }
3318
3319    #[test]
3320    fn chain_weak_materializes_transitive_conflict_edge() {
3321        fn write<const N: usize>(_: ResMut<Resource1>) {}
3322        fn read(_: Res<Resource2>) {}
3323
3324        let mut world = World::default();
3325        let mut schedule = Schedule::default();
3326        // The first and last systems conflict on `Resource1`, and the middle one only reads
3327        // `Resource2` and conflicts with neither. `chain_weak` records only the adjacent
3328        // pairs, so the ordering between the conflicting endpoints exists only transitively.
3329        // It must still be materialized as an edge, or the two would race.
3330        schedule.add_systems((write::<1>, read, write::<3>).chain_weak());
3331        schedule.initialize(&mut world).unwrap();
3332
3333        // Exactly one edge: between the two conflicting endpoints. The middle system is free.
3334        assert_eq!(total_dependencies(&schedule), 1);
3335        // ...and because that pair is ordered, it isn't reported as an ambiguity.
3336        assert!(schedule.graph().conflicting_systems().is_empty());
3337    }
3338
3339    #[test]
3340    fn weak_chain_leaves_explicit_strict_edge_intact() {
3341        #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
3342        enum Sets {
3343            A,
3344            B,
3345            C,
3346        }
3347
3348        fn read<const N: usize>(_: Res<Resource1>) {}
3349
3350        let mut world = World::default();
3351        let mut schedule = Schedule::default();
3352        // Weak A -> B -> C over non-conflicting systems, plus an explicit strict A -> C.
3353        schedule.configure_sets((Sets::A, Sets::B, Sets::C).chain_weak());
3354        schedule.configure_sets(Sets::C.after(Sets::A));
3355        schedule.add_systems((
3356            read::<1>.in_set(Sets::A),
3357            read::<2>.in_set(Sets::B),
3358            read::<3>.in_set(Sets::C),
3359        ));
3360        schedule.initialize(&mut world).unwrap();
3361
3362        // The weak chain is non-conflicting, so it contributes no edges. The explicit strict
3363        // `A -> C` edge is the only ordering that remains.
3364        assert_eq!(total_dependencies(&schedule), 1);
3365    }
3366
3367    #[test]
3368    fn weak_ordering_keeps_edge_shared_with_strict_ordering() {
3369        #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
3370        enum Sets {
3371            A,
3372            B,
3373        }
3374
3375        fn read<const N: usize>(_: Res<Resource1>) {}
3376
3377        let mut world = World::default();
3378        let mut schedule = Schedule::default();
3379        // The same set pair is ordered both weakly and strictly. The systems don't conflict, so
3380        // the weak ordering on its own would be dropped, but the explicit strict ordering must
3381        // survive.
3382        schedule.configure_sets((Sets::A, Sets::B).chain_weak());
3383        schedule.configure_sets(Sets::B.after(Sets::A));
3384        schedule.add_systems((read::<1>.in_set(Sets::A), read::<2>.in_set(Sets::B)));
3385        schedule.initialize(&mut world).unwrap();
3386
3387        assert_eq!(total_dependencies(&schedule), 1);
3388    }
3389
3390    #[test]
3391    fn strict_chain_after_weak_group_waits_for_whole_group() {
3392        fn read<const N: usize>(_: Res<Resource1>) {}
3393
3394        let mut world = World::default();
3395        let mut schedule = Schedule::default();
3396        // A weak group strictly chained before a final system: the final system must wait
3397        // for every member of the group to finish.
3398        schedule.add_systems(((read::<1>, read::<2>, read::<3>).chain_weak(), read::<4>).chain());
3399        schedule.initialize(&mut world).unwrap();
3400
3401        // The inner weak group is non-conflicting, so it adds no internal edges. The outer
3402        // strict chain still orders every group member before the final system: 3 finish edges.
3403        assert_eq!(total_dependencies(&schedule), 3);
3404    }
3405
3406    #[test]
3407    fn chain_weak_orders_writer_before_system_with_conflicting_condition() {
3408        fn write(_: ResMut<Resource1>) {}
3409        fn noop() {}
3410
3411        let mut world = World::default();
3412        let mut schedule = Schedule::default();
3413        // The second system accesses nothing itself, but its run condition reads `Resource1`,
3414        // which the first system writes. The condition is evaluated just before the system
3415        // runs, so the weak ordering must keep the edge for the condition to observe the write.
3416        schedule.add_systems((write, noop.run_if(|_: Res<Resource1>| true)).chain_weak());
3417        schedule.initialize(&mut world).unwrap();
3418
3419        assert_eq!(total_dependencies(&schedule), 1);
3420    }
3421
3422    #[test]
3423    fn chain_weak_orders_system_with_conflicting_condition_before_writer() {
3424        fn write(_: ResMut<Resource1>) {}
3425        fn noop() {}
3426
3427        let mut world = World::default();
3428        let mut schedule = Schedule::default();
3429        // The first system's run condition reads `Resource1`, which the second system writes.
3430        // The condition is evaluated just before the first system runs, so the weak ordering
3431        // must keep the edge for the condition to observe the pre-write value.
3432        schedule.add_systems((noop.run_if(|_: Res<Resource1>| true), write).chain_weak());
3433        schedule.initialize(&mut world).unwrap();
3434
3435        assert_eq!(total_dependencies(&schedule), 1);
3436    }
3437
3438    #[test]
3439    fn chain_weak_orders_writer_before_set_with_conflicting_condition() {
3440        #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
3441        enum Sets {
3442            A,
3443            B,
3444        }
3445
3446        fn write(_: ResMut<Resource1>) {}
3447        fn noop() {}
3448
3449        let mut world = World::default();
3450        let mut schedule = Schedule::default();
3451        schedule.configure_sets((Sets::A, Sets::B).chain_weak());
3452        // `Sets::B`'s run condition reads `Resource1`, which the system in `Sets::A` writes.
3453        // The condition is evaluated just before the first system in the set runs, so the
3454        // weak ordering must keep the edge even though the member itself accesses nothing.
3455        schedule.configure_sets(Sets::B.run_if(|_: Res<Resource1>| true));
3456        schedule.add_systems((write.in_set(Sets::A), noop.in_set(Sets::B)));
3457        schedule.initialize(&mut world).unwrap();
3458
3459        assert_eq!(total_dependencies(&schedule), 1);
3460    }
3461
3462    #[test]
3463    fn weak_set_ordering_with_ignore_deferred_adds_no_sync_point() {
3464        #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
3465        enum Sets {
3466            A,
3467            B,
3468        }
3469
3470        fn insert_resource(mut commands: Commands) {
3471            commands.insert_resource(Resource1);
3472        }
3473        fn resource_does_not_exist(res: Option<Res<Resource1>>) {
3474            assert!(res.is_none());
3475        }
3476
3477        let mut world = World::default();
3478        let mut schedule = Schedule::default();
3479        // `Sets::A` produces deferred effects, which count as a conflict, so the weak ordering
3480        // is kept, but `ignore_deferred` keeps out the sync point that would come with it.
3481        schedule.configure_sets((Sets::A, Sets::B).chain_weak());
3482        schedule.configure_sets(Sets::A.before_ignore_deferred(Sets::B));
3483        schedule.add_systems((
3484            insert_resource.in_set(Sets::A),
3485            resource_does_not_exist.in_set(Sets::B),
3486        ));
3487        schedule.run(&mut world);
3488
3489        assert_eq!(schedule.executable.systems.len(), 2);
3490        assert_eq!(total_dependencies(&schedule), 1);
3491    }
3492
3493    #[test]
3494    fn weak_edge_after_ignore_deferred_edge_gets_bubbled_sync_point() {
3495        fn insert_resource(mut commands: Commands) {
3496            commands.insert_resource(Resource1);
3497        }
3498        fn read_other(_: Res<Resource2>) {}
3499        fn resource_exists(res: Option<Res<Resource1>>) {
3500            assert!(res.is_some());
3501        }
3502
3503        let mut world = World::default();
3504        world.insert_resource(Resource2);
3505        let mut schedule = Schedule::default();
3506        schedule.set_executor(MultiThreadedExecutor::new());
3507        schedule.add_systems(
3508            (
3509                (insert_resource, read_other).chain_ignore_deferred(),
3510                resource_exists,
3511            )
3512                .chain_weak(),
3513        );
3514        // The unapplied commands bubble through the `ignore_deferred` edge and land on the weak
3515        // edge that follows. That splits the weak edge before the weak edges are resolved, so
3516        // the ordering is kept even though the pair it orders doesn't conflict.
3517        schedule.run(&mut world);
3518
3519        assert_eq!(schedule.executable.systems.len(), 4); // 3 systems + 1 sync point
3520        assert_eq!(total_dependencies(&schedule), 3);
3521    }
3522
3523    #[test]
3524    fn chain_ignore_deferred_around_weak_group_fans_out_without_sync_point() {
3525        fn read<const N: usize>(_: Res<Resource1>) {}
3526        fn with_commands(_: Commands) {}
3527
3528        let mut world = World::default();
3529        let mut schedule = Schedule::default();
3530        // The deferred system comes after the weak group, so nothing bubbles back onto its edge.
3531        schedule.add_systems(
3532            (
3533                (read::<1>, read::<2>).chain_weak(),
3534                with_commands,
3535                read::<3>,
3536            )
3537                .chain_ignore_deferred(),
3538        );
3539        schedule.initialize(&mut world).unwrap();
3540
3541        // The weak group is non-conflicting, so its internal edge is dropped. A weak group is
3542        // not densely chained, so the outer chain orders both of its members before
3543        // `with_commands`, and no outer edge gets a sync point.
3544        assert_eq!(schedule.executable.systems.len(), 4);
3545        assert_eq!(total_dependencies(&schedule), 3);
3546    }
3547
3548    #[test]
3549    fn chain_weak_between_ignore_deferred_groups_drops_edge_between_them() {
3550        fn read<const N: usize>(_: Res<Resource1>) {}
3551        fn with_commands(_: Commands) {}
3552
3553        let mut world = World::default();
3554        let mut schedule = Schedule::default();
3555        // Only the earlier system of a pair matters for the deferred check, so `with_commands`
3556        // at the start of the second group doesn't make the weak edge into it conflict.
3557        schedule.add_systems(
3558            (
3559                (read::<1>, read::<2>).chain_ignore_deferred(),
3560                (with_commands, read::<3>).chain_ignore_deferred(),
3561            )
3562                .chain_weak(),
3563        );
3564        schedule.initialize(&mut world).unwrap();
3565
3566        // Each group keeps its own edge, and the weak edge between them is dropped.
3567        assert_eq!(schedule.executable.systems.len(), 4);
3568        assert_eq!(total_dependencies(&schedule), 2);
3569    }
3570
3571    #[test]
3572    fn ignore_deferred_still_syncs_before_exclusive_system_in_weak_chain() {
3573        fn insert_resource(mut commands: Commands) {
3574            commands.insert_resource(Resource1);
3575        }
3576        fn exclusive(world: &mut World) {
3577            assert!(world.contains_resource::<Resource1>());
3578        }
3579        fn read(_: Res<Resource1>) {}
3580
3581        let mut world = World::default();
3582        let mut schedule = Schedule::default();
3583        schedule
3584            .add_systems(((insert_resource, exclusive).chain_ignore_deferred(), read).chain_weak());
3585        // `ignore_deferred` makes an exception for exclusive systems, so `exclusive` still gets
3586        // its sync point.
3587        schedule.run(&mut world);
3588
3589        // `insert_resource -> ApplyDeferred -> exclusive -> read`. The weak edge into `read` is
3590        // kept because an exclusive system conflicts with everything.
3591        assert_eq!(schedule.executable.systems.len(), 4); // 3 systems + 1 sync point
3592        assert_eq!(total_dependencies(&schedule), 3);
3593    }
3594}