Path: blob/main/crates/bevy_ecs/src/schedule/schedule.rs
6849 views
#![expect(1clippy::module_inception,2reason = "This instance of module inception is being discussed; see #17344."3)]4use alloc::{5boxed::Box,6collections::{BTreeMap, BTreeSet},7format,8string::{String, ToString},9vec,10vec::Vec,11};12use bevy_platform::collections::{HashMap, HashSet};13use bevy_utils::{default, prelude::DebugName, TypeIdMap};14use core::{15any::{Any, TypeId},16fmt::{Debug, Write},17};18use fixedbitset::FixedBitSet;19use log::{error, info, warn};20use pass::ScheduleBuildPassObj;21use thiserror::Error;22#[cfg(feature = "trace")]23use tracing::info_span;2425use crate::{component::CheckChangeTicks, system::System};26use crate::{27component::{ComponentId, Components},28prelude::Component,29resource::Resource,30schedule::*,31system::ScheduleSystem,32world::World,33};3435use crate::{query::AccessConflicts, storage::SparseSetIndex};36pub use stepping::Stepping;37use Direction::{Incoming, Outgoing};3839/// Resource that stores [`Schedule`]s mapped to [`ScheduleLabel`]s excluding the current running [`Schedule`].40#[derive(Default, Resource)]41pub struct Schedules {42inner: HashMap<InternedScheduleLabel, Schedule>,43/// List of [`ComponentId`]s to ignore when reporting system order ambiguity conflicts44pub ignored_scheduling_ambiguities: BTreeSet<ComponentId>,45}4647impl Schedules {48/// Constructs an empty `Schedules` with zero initial capacity.49pub fn new() -> Self {50Self::default()51}5253/// Inserts a labeled schedule into the map.54///55/// If the map already had an entry for `label`, `schedule` is inserted,56/// and the old schedule is returned. Otherwise, `None` is returned.57pub fn insert(&mut self, schedule: Schedule) -> Option<Schedule> {58self.inner.insert(schedule.label, schedule)59}6061/// Removes the schedule corresponding to the `label` from the map, returning it if it existed.62pub fn remove(&mut self, label: impl ScheduleLabel) -> Option<Schedule> {63self.inner.remove(&label.intern())64}6566/// Removes the (schedule, label) pair corresponding to the `label` from the map, returning it if it existed.67pub fn remove_entry(68&mut self,69label: impl ScheduleLabel,70) -> Option<(InternedScheduleLabel, Schedule)> {71self.inner.remove_entry(&label.intern())72}7374/// Does a schedule with the provided label already exist?75pub fn contains(&self, label: impl ScheduleLabel) -> bool {76self.inner.contains_key(&label.intern())77}7879/// Returns a reference to the schedule associated with `label`, if it exists.80pub fn get(&self, label: impl ScheduleLabel) -> Option<&Schedule> {81self.inner.get(&label.intern())82}8384/// Returns a mutable reference to the schedule associated with `label`, if it exists.85pub fn get_mut(&mut self, label: impl ScheduleLabel) -> Option<&mut Schedule> {86self.inner.get_mut(&label.intern())87}8889/// Returns a mutable reference to the schedules associated with `label`, creating one if it doesn't already exist.90pub fn entry(&mut self, label: impl ScheduleLabel) -> &mut Schedule {91self.inner92.entry(label.intern())93.or_insert_with(|| Schedule::new(label))94}9596/// Returns an iterator over all schedules. Iteration order is undefined.97pub fn iter(&self) -> impl Iterator<Item = (&dyn ScheduleLabel, &Schedule)> {98self.inner99.iter()100.map(|(label, schedule)| (&**label, schedule))101}102/// Returns an iterator over mutable references to all schedules. Iteration order is undefined.103pub fn iter_mut(&mut self) -> impl Iterator<Item = (&dyn ScheduleLabel, &mut Schedule)> {104self.inner105.iter_mut()106.map(|(label, schedule)| (&**label, schedule))107}108109/// Iterates the change ticks of all systems in all stored schedules and clamps any older than110/// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE).111/// This prevents overflow and thus prevents false positives.112pub(crate) fn check_change_ticks(&mut self, check: CheckChangeTicks) {113#[cfg(feature = "trace")]114let _all_span = info_span!("check stored schedule ticks").entered();115#[cfg_attr(116not(feature = "trace"),117expect(118unused_variables,119reason = "The `label` variable goes unused if the `trace` feature isn't active"120)121)]122for (label, schedule) in &mut self.inner {123#[cfg(feature = "trace")]124let name = format!("{label:?}");125#[cfg(feature = "trace")]126let _one_span = info_span!("check schedule ticks", name = &name).entered();127schedule.check_change_ticks(check);128}129}130131/// Applies the provided [`ScheduleBuildSettings`] to all schedules.132pub fn configure_schedules(&mut self, schedule_build_settings: ScheduleBuildSettings) {133for (_, schedule) in &mut self.inner {134schedule.set_build_settings(schedule_build_settings.clone());135}136}137138/// Ignore system order ambiguities caused by conflicts on [`Component`]s of type `T`.139pub fn allow_ambiguous_component<T: Component>(&mut self, world: &mut World) {140self.ignored_scheduling_ambiguities141.insert(world.register_component::<T>());142}143144/// Ignore system order ambiguities caused by conflicts on [`Resource`]s of type `T`.145pub fn allow_ambiguous_resource<T: Resource>(&mut self, world: &mut World) {146self.ignored_scheduling_ambiguities147.insert(world.components_registrator().register_resource::<T>());148}149150/// Iterate through the [`ComponentId`]'s that will be ignored.151pub fn iter_ignored_ambiguities(&self) -> impl Iterator<Item = &ComponentId> + '_ {152self.ignored_scheduling_ambiguities.iter()153}154155/// Prints the names of the components and resources with [`info`]156///157/// May panic or retrieve incorrect names if [`Components`] is not from the same158/// world159pub fn print_ignored_ambiguities(&self, components: &Components) {160let mut message =161"System order ambiguities caused by conflicts on the following types are ignored:\n"162.to_string();163for id in self.iter_ignored_ambiguities() {164writeln!(message, "{}", components.get_name(*id).unwrap()).unwrap();165}166167info!("{message}");168}169170/// Adds one or more systems to the [`Schedule`] matching the provided [`ScheduleLabel`].171pub fn add_systems<M>(172&mut self,173schedule: impl ScheduleLabel,174systems: impl IntoScheduleConfigs<ScheduleSystem, M>,175) -> &mut Self {176self.entry(schedule).add_systems(systems);177178self179}180181/// Configures a collection of system sets in the provided schedule, adding any sets that do not exist.182#[track_caller]183pub fn configure_sets<M>(184&mut self,185schedule: impl ScheduleLabel,186sets: impl IntoScheduleConfigs<InternedSystemSet, M>,187) -> &mut Self {188self.entry(schedule).configure_sets(sets);189190self191}192193/// Suppress warnings and errors that would result from systems in these sets having ambiguities194/// (conflicting access but indeterminate order) with systems in `set`.195///196/// When possible, do this directly in the `.add_systems(Update, a.ambiguous_with(b))` call.197/// However, sometimes two independent plugins `A` and `B` are reported as ambiguous, which you198/// can only suppress as the consumer of both.199#[track_caller]200pub fn ignore_ambiguity<M1, M2, S1, S2>(201&mut self,202schedule: impl ScheduleLabel,203a: S1,204b: S2,205) -> &mut Self206where207S1: IntoSystemSet<M1>,208S2: IntoSystemSet<M2>,209{210self.entry(schedule).ignore_ambiguity(a, b);211212self213}214}215216fn make_executor(kind: ExecutorKind) -> Box<dyn SystemExecutor> {217match kind {218ExecutorKind::SingleThreaded => Box::new(SingleThreadedExecutor::new()),219#[cfg(feature = "std")]220ExecutorKind::MultiThreaded => Box::new(MultiThreadedExecutor::new()),221}222}223224/// Chain systems into dependencies225#[derive(Default)]226pub enum Chain {227/// Systems are independent. Nodes are allowed to run in any order.228#[default]229Unchained,230/// Systems are chained. `before -> after` ordering constraints231/// will be added between the successive elements.232Chained(TypeIdMap<Box<dyn Any>>),233}234235impl Chain {236/// Specify that the systems must be chained.237pub fn set_chained(&mut self) {238if matches!(self, Chain::Unchained) {239*self = Self::Chained(Default::default());240};241}242/// Specify that the systems must be chained, and add the specified configuration for243/// all dependencies created between these systems.244pub fn set_chained_with_config<T: 'static>(&mut self, config: T) {245self.set_chained();246if let Chain::Chained(config_map) = self {247config_map.insert(TypeId::of::<T>(), Box::new(config));248} else {249unreachable!()250};251}252}253254/// A collection of systems, and the metadata and executor needed to run them255/// in a certain order under certain conditions.256///257/// # Schedule labels258///259/// Each schedule has a [`ScheduleLabel`] value. This value is used to uniquely identify the260/// schedule when added to a [`World`]’s [`Schedules`], and may be used to specify which schedule261/// a system should be added to.262///263/// # Example264///265/// Here is an example of a `Schedule` running a "Hello world" system:266///267/// ```268/// # use bevy_ecs::prelude::*;269/// fn hello_world() { println!("Hello world!") }270///271/// fn main() {272/// let mut world = World::new();273/// let mut schedule = Schedule::default();274/// schedule.add_systems(hello_world);275///276/// schedule.run(&mut world);277/// }278/// ```279///280/// A schedule can also run several systems in an ordered way:281///282/// ```283/// # use bevy_ecs::prelude::*;284/// fn system_one() { println!("System 1 works!") }285/// fn system_two() { println!("System 2 works!") }286/// fn system_three() { println!("System 3 works!") }287///288/// fn main() {289/// let mut world = World::new();290/// let mut schedule = Schedule::default();291/// schedule.add_systems((292/// system_two,293/// system_one.before(system_two),294/// system_three.after(system_two),295/// ));296///297/// schedule.run(&mut world);298/// }299/// ```300///301/// Schedules are often inserted into a [`World`] and identified by their [`ScheduleLabel`] only:302///303/// ```304/// # use bevy_ecs::prelude::*;305/// use bevy_ecs::schedule::ScheduleLabel;306///307/// // Declare a new schedule label.308/// #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]309/// struct Update;310///311/// // This system shall be part of the schedule.312/// fn an_update_system() {313/// println!("Hello world!");314/// }315///316/// fn main() {317/// let mut world = World::new();318///319/// // Add a system to the schedule with that label (creating it automatically).320/// world.get_resource_or_init::<Schedules>().add_systems(Update, an_update_system);321///322/// // Run the schedule, and therefore run the system.323/// world.run_schedule(Update);324/// }325/// ```326pub struct Schedule {327label: InternedScheduleLabel,328graph: ScheduleGraph,329executable: SystemSchedule,330executor: Box<dyn SystemExecutor>,331executor_initialized: bool,332warnings: Vec<ScheduleBuildWarning>,333}334335#[derive(ScheduleLabel, Hash, PartialEq, Eq, Debug, Clone)]336struct DefaultSchedule;337338impl Default for Schedule {339/// Creates a schedule with a default label. Only use in situations where340/// you don't care about the [`ScheduleLabel`]. Inserting a default schedule341/// into the world risks overwriting another schedule. For most situations342/// you should use [`Schedule::new`].343fn default() -> Self {344Self::new(DefaultSchedule)345}346}347348impl Schedule {349/// Constructs an empty `Schedule`.350pub fn new(label: impl ScheduleLabel) -> Self {351let mut this = Self {352label: label.intern(),353graph: ScheduleGraph::new(),354executable: SystemSchedule::new(),355executor: make_executor(ExecutorKind::default()),356executor_initialized: false,357warnings: Vec::new(),358};359// Call `set_build_settings` to add any default build passes360this.set_build_settings(Default::default());361this362}363364/// Returns the [`InternedScheduleLabel`] for this `Schedule`,365/// corresponding to the [`ScheduleLabel`] this schedule was created with.366pub fn label(&self) -> InternedScheduleLabel {367self.label368}369370/// Add a collection of systems to the schedule.371pub fn add_systems<M>(372&mut self,373systems: impl IntoScheduleConfigs<ScheduleSystem, M>,374) -> &mut Self {375self.graph.process_configs(systems.into_configs(), false);376self377}378379/// Suppress warnings and errors that would result from systems in these sets having ambiguities380/// (conflicting access but indeterminate order) with systems in `set`.381#[track_caller]382pub fn ignore_ambiguity<M1, M2, S1, S2>(&mut self, a: S1, b: S2) -> &mut Self383where384S1: IntoSystemSet<M1>,385S2: IntoSystemSet<M2>,386{387let a = a.into_system_set();388let b = b.into_system_set();389390let a_id = self.graph.system_sets.get_key_or_insert(a.intern());391let b_id = self.graph.system_sets.get_key_or_insert(b.intern());392393self.graph394.ambiguous_with395.add_edge(NodeId::Set(a_id), NodeId::Set(b_id));396397self398}399400/// Configures a collection of system sets in this schedule, adding them if they does not exist.401#[track_caller]402pub fn configure_sets<M>(403&mut self,404sets: impl IntoScheduleConfigs<InternedSystemSet, M>,405) -> &mut Self {406self.graph.configure_sets(sets);407self408}409410/// Add a custom build pass to the schedule.411pub fn add_build_pass<T: ScheduleBuildPass>(&mut self, pass: T) -> &mut Self {412self.graph.passes.insert(TypeId::of::<T>(), Box::new(pass));413self414}415416/// Remove a custom build pass.417pub fn remove_build_pass<T: ScheduleBuildPass>(&mut self) {418self.graph.passes.remove(&TypeId::of::<T>());419}420421/// Changes miscellaneous build settings.422///423/// If [`settings.auto_insert_apply_deferred`][ScheduleBuildSettings::auto_insert_apply_deferred]424/// is `false`, this clears `*_ignore_deferred` edge settings configured so far.425///426/// Generally this method should be used before adding systems or set configurations to the schedule,427/// not after.428pub fn set_build_settings(&mut self, settings: ScheduleBuildSettings) -> &mut Self {429if settings.auto_insert_apply_deferred {430if !self431.graph432.passes433.contains_key(&TypeId::of::<passes::AutoInsertApplyDeferredPass>())434{435self.add_build_pass(passes::AutoInsertApplyDeferredPass::default());436}437} else {438self.remove_build_pass::<passes::AutoInsertApplyDeferredPass>();439}440self.graph.settings = settings;441self442}443444/// Returns the schedule's current `ScheduleBuildSettings`.445pub fn get_build_settings(&self) -> ScheduleBuildSettings {446self.graph.settings.clone()447}448449/// Returns the schedule's current execution strategy.450pub fn get_executor_kind(&self) -> ExecutorKind {451self.executor.kind()452}453454/// Sets the schedule's execution strategy.455pub fn set_executor_kind(&mut self, executor: ExecutorKind) -> &mut Self {456if executor != self.executor.kind() {457self.executor = make_executor(executor);458self.executor_initialized = false;459}460self461}462463/// Set whether the schedule applies deferred system buffers on final time or not. This is a catch-all464/// in case a system uses commands but was not explicitly ordered before an instance of465/// [`ApplyDeferred`]. By default this466/// setting is true, but may be disabled if needed.467pub fn set_apply_final_deferred(&mut self, apply_final_deferred: bool) -> &mut Self {468self.executor.set_apply_final_deferred(apply_final_deferred);469self470}471472/// Runs all systems in this schedule on the `world`, using its current execution strategy.473pub fn run(&mut self, world: &mut World) {474#[cfg(feature = "trace")]475let _span = info_span!("schedule", name = ?self.label).entered();476477world.check_change_ticks();478self.initialize(world).unwrap_or_else(|e| {479panic!(480"Error when initializing schedule {:?}: {}",481self.label,482e.to_string(self.graph(), world)483)484});485486let error_handler = world.default_error_handler();487488#[cfg(not(feature = "bevy_debug_stepping"))]489self.executor490.run(&mut self.executable, world, None, error_handler);491492#[cfg(feature = "bevy_debug_stepping")]493{494let skip_systems = match world.get_resource_mut::<Stepping>() {495None => None,496Some(mut stepping) => stepping.skipped_systems(self),497};498499self.executor.run(500&mut self.executable,501world,502skip_systems.as_ref(),503error_handler,504);505}506}507508/// Initializes any newly-added systems and conditions, rebuilds the executable schedule,509/// and re-initializes the executor.510///511/// Moves all systems and run conditions out of the [`ScheduleGraph`].512pub fn initialize(&mut self, world: &mut World) -> Result<(), ScheduleBuildError> {513if self.graph.changed {514self.graph.initialize(world);515let ignored_ambiguities = world516.get_resource_or_init::<Schedules>()517.ignored_scheduling_ambiguities518.clone();519self.warnings = self.graph.update_schedule(520world,521&mut self.executable,522&ignored_ambiguities,523self.label,524)?;525self.graph.changed = false;526self.executor_initialized = false;527}528529if !self.executor_initialized {530self.executor.init(&self.executable);531self.executor_initialized = true;532}533534Ok(())535}536537/// Returns the [`ScheduleGraph`].538pub fn graph(&self) -> &ScheduleGraph {539&self.graph540}541542/// Returns a mutable reference to the [`ScheduleGraph`].543pub fn graph_mut(&mut self) -> &mut ScheduleGraph {544&mut self.graph545}546547/// Returns the [`SystemSchedule`].548pub(crate) fn executable(&self) -> &SystemSchedule {549&self.executable550}551552/// Iterates the change ticks of all systems in the schedule and clamps any older than553/// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE).554/// This prevents overflow and thus prevents false positives.555pub fn check_change_ticks(&mut self, check: CheckChangeTicks) {556for system in &mut self.executable.systems {557if !is_apply_deferred(system) {558system.check_change_tick(check);559}560}561562for conditions in &mut self.executable.system_conditions {563for condition in conditions {564condition.check_change_tick(check);565}566}567568for conditions in &mut self.executable.set_conditions {569for condition in conditions {570condition.check_change_tick(check);571}572}573}574575/// Directly applies any accumulated [`Deferred`](crate::system::Deferred) system parameters (like [`Commands`](crate::prelude::Commands)) to the `world`.576///577/// Like always, deferred system parameters are applied in the "topological sort order" of the schedule graph.578/// As a result, buffers from one system are only guaranteed to be applied before those of other systems579/// if there is an explicit system ordering between the two systems.580///581/// This is used in rendering to extract data from the main world, storing the data in system buffers,582/// before applying their buffers in a different world.583pub fn apply_deferred(&mut self, world: &mut World) {584for SystemWithAccess { system, .. } in &mut self.executable.systems {585system.apply_deferred(world);586}587}588589/// Returns an iterator over all systems in this schedule.590///591/// Note: this method will return [`ScheduleNotInitialized`] if the592/// schedule has never been initialized or run.593pub fn systems(594&self,595) -> Result<impl Iterator<Item = (SystemKey, &ScheduleSystem)> + Sized, ScheduleNotInitialized>596{597if !self.executor_initialized {598return Err(ScheduleNotInitialized);599}600601let iter = self602.executable603.system_ids604.iter()605.zip(&self.executable.systems)606.map(|(&node_id, system)| (node_id, &system.system));607608Ok(iter)609}610611/// Returns the number of systems in this schedule.612pub fn systems_len(&self) -> usize {613if !self.executor_initialized {614self.graph.systems.len()615} else {616self.executable.systems.len()617}618}619620/// Returns warnings that were generated during the last call to621/// [`Schedule::initialize`].622pub fn warnings(&self) -> &[ScheduleBuildWarning] {623&self.warnings624}625}626627/// A directed acyclic graph structure.628pub struct Dag<N: GraphNodeId> {629/// A directed graph.630graph: DiGraph<N>,631/// A cached topological ordering of the graph.632topsort: Vec<N>,633}634635impl<N: GraphNodeId> Dag<N> {636fn new() -> Self {637Self {638graph: DiGraph::default(),639topsort: Vec::new(),640}641}642643/// The directed graph of the stored systems, connected by their ordering dependencies.644pub fn graph(&self) -> &DiGraph<N> {645&self.graph646}647648/// A cached topological ordering of the graph.649///650/// The order is determined by the ordering dependencies between systems.651pub fn cached_topsort(&self) -> &[N] {652&self.topsort653}654}655656impl<N: GraphNodeId> Default for Dag<N> {657fn default() -> Self {658Self {659graph: Default::default(),660topsort: Default::default(),661}662}663}664665/// Metadata for a [`Schedule`].666///667/// The order isn't optimized; calling `ScheduleGraph::build_schedule` will return a668/// `SystemSchedule` where the order is optimized for execution.669#[derive(Default)]670pub struct ScheduleGraph {671/// Container of systems in the schedule.672pub systems: Systems,673/// Container of system sets in the schedule.674pub system_sets: SystemSets,675/// Directed acyclic graph of the hierarchy (which systems/sets are children of which sets)676hierarchy: Dag<NodeId>,677/// Directed acyclic graph of the dependency (which systems/sets have to run before which other systems/sets)678dependency: Dag<NodeId>,679ambiguous_with: UnGraph<NodeId>,680/// Nodes that are allowed to have ambiguous ordering relationship with any other systems.681pub ambiguous_with_all: HashSet<NodeId>,682conflicting_systems: Vec<(SystemKey, SystemKey, Vec<ComponentId>)>,683anonymous_sets: usize,684changed: bool,685settings: ScheduleBuildSettings,686passes: BTreeMap<TypeId, Box<dyn ScheduleBuildPassObj>>,687}688689impl ScheduleGraph {690/// Creates an empty [`ScheduleGraph`] with default settings.691pub fn new() -> Self {692Self {693systems: Systems::default(),694system_sets: SystemSets::default(),695hierarchy: Dag::new(),696dependency: Dag::new(),697ambiguous_with: UnGraph::default(),698ambiguous_with_all: HashSet::default(),699conflicting_systems: Vec::new(),700anonymous_sets: 0,701changed: false,702settings: default(),703passes: default(),704}705}706707/// Returns the [`Dag`] of the hierarchy.708///709/// The hierarchy is a directed acyclic graph of the systems and sets,710/// where an edge denotes that a system or set is the child of another set.711pub fn hierarchy(&self) -> &Dag<NodeId> {712&self.hierarchy713}714715/// Returns the [`Dag`] of the dependencies in the schedule.716///717/// Nodes in this graph are systems and sets, and edges denote that718/// a system or set has to run before another system or set.719pub fn dependency(&self) -> &Dag<NodeId> {720&self.dependency721}722723/// Returns the list of systems that conflict with each other, i.e. have ambiguities in their access.724///725/// If the `Vec<ComponentId>` is empty, the systems conflict on [`World`] access.726/// Must be called after [`ScheduleGraph::build_schedule`] to be non-empty.727pub fn conflicting_systems(&self) -> &[(SystemKey, SystemKey, Vec<ComponentId>)] {728&self.conflicting_systems729}730731fn process_config<T: ProcessScheduleConfig + Schedulable>(732&mut self,733config: ScheduleConfig<T>,734collect_nodes: bool,735) -> ProcessConfigsResult {736ProcessConfigsResult {737densely_chained: true,738nodes: collect_nodes739.then_some(T::process_config(self, config))740.into_iter()741.collect(),742}743}744745fn apply_collective_conditions<746T: ProcessScheduleConfig + Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>,747>(748&mut self,749configs: &mut [ScheduleConfigs<T>],750collective_conditions: Vec<BoxedCondition>,751) {752if !collective_conditions.is_empty() {753if let [config] = configs {754for condition in collective_conditions {755config.run_if_dyn(condition);756}757} else {758let set = self.create_anonymous_set();759for config in configs.iter_mut() {760config.in_set_inner(set.intern());761}762let mut set_config = InternedSystemSet::into_config(set.intern());763set_config.conditions.extend(collective_conditions);764self.configure_set_inner(set_config);765}766}767}768769/// Adds the config nodes to the graph.770///771/// `collect_nodes` controls whether the `NodeId`s of the processed config nodes are stored in the returned [`ProcessConfigsResult`].772/// `process_config` is the function which processes each individual config node and returns a corresponding `NodeId`.773///774/// The fields on the returned [`ProcessConfigsResult`] are:775/// - `nodes`: a vector of all node ids contained in the nested `ScheduleConfigs`776/// - `densely_chained`: a boolean that is true if all nested nodes are linearly chained (with successive `after` orderings) in the order they are defined777#[track_caller]778fn process_configs<779T: ProcessScheduleConfig + Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>,780>(781&mut self,782configs: ScheduleConfigs<T>,783collect_nodes: bool,784) -> ProcessConfigsResult {785match configs {786ScheduleConfigs::ScheduleConfig(config) => self.process_config(config, collect_nodes),787ScheduleConfigs::Configs {788metadata,789mut configs,790collective_conditions,791} => {792self.apply_collective_conditions(&mut configs, collective_conditions);793794let is_chained = matches!(metadata, Chain::Chained(_));795796// Densely chained if797// * chained and all configs in the chain are densely chained, or798// * unchained with a single densely chained config799let mut densely_chained = is_chained || configs.len() == 1;800let mut configs = configs.into_iter();801let mut nodes = Vec::new();802803let Some(first) = configs.next() else {804return ProcessConfigsResult {805nodes: Vec::new(),806densely_chained,807};808};809let mut previous_result = self.process_configs(first, collect_nodes || is_chained);810densely_chained &= previous_result.densely_chained;811812for current in configs {813let current_result = self.process_configs(current, collect_nodes || is_chained);814densely_chained &= current_result.densely_chained;815816if let Chain::Chained(chain_options) = &metadata {817// if the current result is densely chained, we only need to chain the first node818let current_nodes = if current_result.densely_chained {819¤t_result.nodes[..1]820} else {821¤t_result.nodes822};823// if the previous result was densely chained, we only need to chain the last node824let previous_nodes = if previous_result.densely_chained {825&previous_result.nodes[previous_result.nodes.len() - 1..]826} else {827&previous_result.nodes828};829830for previous_node in previous_nodes {831for current_node in current_nodes {832self.dependency833.graph834.add_edge(*previous_node, *current_node);835836for pass in self.passes.values_mut() {837pass.add_dependency(838*previous_node,839*current_node,840chain_options,841);842}843}844}845}846if collect_nodes {847nodes.append(&mut previous_result.nodes);848}849850previous_result = current_result;851}852if collect_nodes {853nodes.append(&mut previous_result.nodes);854}855856ProcessConfigsResult {857nodes,858densely_chained,859}860}861}862}863864/// Add a [`ScheduleConfig`] to the graph, including its dependencies and conditions.865fn add_system_inner(&mut self, config: ScheduleConfig<ScheduleSystem>) -> SystemKey {866let key = self.systems.insert(config.node, config.conditions);867868// graph updates are immediate869self.update_graphs(NodeId::System(key), config.metadata);870871key872}873874#[track_caller]875fn configure_sets<M>(&mut self, sets: impl IntoScheduleConfigs<InternedSystemSet, M>) {876self.process_configs(sets.into_configs(), false);877}878879/// Add a single `ScheduleConfig` to the graph, including its dependencies and conditions.880fn configure_set_inner(&mut self, config: ScheduleConfig<InternedSystemSet>) -> SystemSetKey {881let key = self.system_sets.insert(config.node, config.conditions);882883// graph updates are immediate884self.update_graphs(NodeId::Set(key), config.metadata);885886key887}888889fn create_anonymous_set(&mut self) -> AnonymousSet {890let id = self.anonymous_sets;891self.anonymous_sets += 1;892AnonymousSet::new(id)893}894895/// Update the internal graphs (hierarchy, dependency, ambiguity) by adding a single [`GraphInfo`]896fn update_graphs(&mut self, id: NodeId, graph_info: GraphInfo) {897self.changed = true;898899let GraphInfo {900hierarchy: sets,901dependencies,902ambiguous_with,903..904} = graph_info;905906self.hierarchy.graph.add_node(id);907self.dependency.graph.add_node(id);908909for key in sets910.into_iter()911.map(|set| self.system_sets.get_key_or_insert(set))912{913self.hierarchy.graph.add_edge(NodeId::Set(key), id);914915// ensure set also appears in dependency graph916self.dependency.graph.add_node(NodeId::Set(key));917}918919for (kind, key, options) in920dependencies921.into_iter()922.map(|Dependency { kind, set, options }| {923(kind, self.system_sets.get_key_or_insert(set), options)924})925{926let (lhs, rhs) = match kind {927DependencyKind::Before => (id, NodeId::Set(key)),928DependencyKind::After => (NodeId::Set(key), id),929};930self.dependency.graph.add_edge(lhs, rhs);931for pass in self.passes.values_mut() {932pass.add_dependency(lhs, rhs, &options);933}934935// ensure set also appears in hierarchy graph936self.hierarchy.graph.add_node(NodeId::Set(key));937}938939match ambiguous_with {940Ambiguity::Check => (),941Ambiguity::IgnoreWithSet(ambiguous_with) => {942for key in ambiguous_with943.into_iter()944.map(|set| self.system_sets.get_key_or_insert(set))945{946self.ambiguous_with.add_edge(id, NodeId::Set(key));947}948}949Ambiguity::IgnoreAll => {950self.ambiguous_with_all.insert(id);951}952}953}954955/// Initializes any newly-added systems and conditions by calling956/// [`System::initialize`](crate::system::System).957pub fn initialize(&mut self, world: &mut World) {958self.systems.initialize(world);959self.system_sets.initialize(world);960}961962/// Builds an execution-optimized [`SystemSchedule`] from the current state963/// of the graph. Also returns any warnings that were generated during the964/// build process.965///966/// This method also967/// - checks for dependency or hierarchy cycles968/// - checks for system access conflicts and reports ambiguities969pub fn build_schedule(970&mut self,971world: &mut World,972ignored_ambiguities: &BTreeSet<ComponentId>,973) -> Result<(SystemSchedule, Vec<ScheduleBuildWarning>), ScheduleBuildError> {974let mut warnings = Vec::new();975976// check hierarchy for cycles977self.hierarchy.topsort =978self.topsort_graph(&self.hierarchy.graph, ReportCycles::Hierarchy)?;979980let hier_results = check_graph(&self.hierarchy.graph, &self.hierarchy.topsort);981if let Some(warning) =982self.optionally_check_hierarchy_conflicts(&hier_results.transitive_edges)?983{984warnings.push(warning);985}986987// remove redundant edges988self.hierarchy.graph = hier_results.transitive_reduction;989990// check dependencies for cycles991self.dependency.topsort =992self.topsort_graph(&self.dependency.graph, ReportCycles::Dependency)?;993994// check for systems or system sets depending on sets they belong to995let dep_results = check_graph(&self.dependency.graph, &self.dependency.topsort);996self.check_for_cross_dependencies(&dep_results, &hier_results.connected)?;997998// map all system sets to their systems999// go in reverse topological order (bottom-up) for efficiency1000let (set_systems, set_system_bitsets) =1001self.map_sets_to_systems(&self.hierarchy.topsort, &self.hierarchy.graph);1002self.check_order_but_intersect(&dep_results.connected, &set_system_bitsets)?;10031004// check that there are no edges to system-type sets that have multiple instances1005self.check_system_type_set_ambiguity(&set_systems)?;10061007let mut dependency_flattened = self.get_dependency_flattened(&set_systems);10081009// modify graph with build passes1010let mut passes = core::mem::take(&mut self.passes);1011for pass in passes.values_mut() {1012pass.build(world, self, &mut dependency_flattened)?;1013}1014self.passes = passes;10151016// topsort1017let mut dependency_flattened_dag = Dag {1018topsort: self.topsort_graph(&dependency_flattened, ReportCycles::Dependency)?,1019graph: dependency_flattened,1020};10211022let flat_results = check_graph(1023&dependency_flattened_dag.graph,1024&dependency_flattened_dag.topsort,1025);10261027// remove redundant edges1028dependency_flattened_dag.graph = flat_results.transitive_reduction;10291030// flatten: combine `in_set` with `ambiguous_with` information1031let ambiguous_with_flattened = self.get_ambiguous_with_flattened(&set_systems);10321033// check for conflicts1034let conflicting_systems = self.get_conflicting_systems(1035&flat_results.disconnected,1036&ambiguous_with_flattened,1037ignored_ambiguities,1038);1039if let Some(warning) = self.optionally_check_conflicts(&conflicting_systems)? {1040warnings.push(warning);1041}1042self.conflicting_systems = conflicting_systems;10431044// build the schedule1045Ok((1046self.build_schedule_inner(dependency_flattened_dag, hier_results.reachable),1047warnings,1048))1049}10501051/// Return a map from system set `NodeId` to a list of system `NodeId`s that are included in the set.1052/// Also return a map from system set `NodeId` to a `FixedBitSet` of system `NodeId`s that are included in the set,1053/// where the bitset order is the same as `self.systems`1054fn map_sets_to_systems(1055&self,1056hierarchy_topsort: &[NodeId],1057hierarchy_graph: &DiGraph<NodeId>,1058) -> (1059HashMap<SystemSetKey, Vec<SystemKey>>,1060HashMap<SystemSetKey, HashSet<SystemKey>>,1061) {1062let mut set_systems: HashMap<SystemSetKey, Vec<SystemKey>> =1063HashMap::with_capacity_and_hasher(self.system_sets.len(), Default::default());1064let mut set_system_sets: HashMap<SystemSetKey, HashSet<SystemKey>> =1065HashMap::with_capacity_and_hasher(self.system_sets.len(), Default::default());1066for &id in hierarchy_topsort.iter().rev() {1067let NodeId::Set(set_key) = id else {1068continue;1069};10701071let mut systems = Vec::new();1072let mut system_set = HashSet::with_capacity(self.systems.len());10731074for child in hierarchy_graph.neighbors_directed(id, Outgoing) {1075match child {1076NodeId::System(key) => {1077systems.push(key);1078system_set.insert(key);1079}1080NodeId::Set(key) => {1081let child_systems = set_systems.get(&key).unwrap();1082let child_system_set = set_system_sets.get(&key).unwrap();1083systems.extend_from_slice(child_systems);1084system_set.extend(child_system_set.iter());1085}1086}1087}10881089set_systems.insert(set_key, systems);1090set_system_sets.insert(set_key, system_set);1091}1092(set_systems, set_system_sets)1093}10941095fn get_dependency_flattened(1096&mut self,1097set_systems: &HashMap<SystemSetKey, Vec<SystemKey>>,1098) -> DiGraph<SystemKey> {1099// flatten: combine `in_set` with `before` and `after` information1100// have to do it like this to preserve transitivity1101let mut dependency_flattening = self.dependency.graph.clone();1102let mut temp = Vec::new();1103for (&set, systems) in set_systems {1104for pass in self.passes.values_mut() {1105pass.collapse_set(set, systems, &dependency_flattening, &mut temp);1106}1107if systems.is_empty() {1108// collapse dependencies for empty sets1109for a in dependency_flattening.neighbors_directed(NodeId::Set(set), Incoming) {1110for b in dependency_flattening.neighbors_directed(NodeId::Set(set), Outgoing) {1111temp.push((a, b));1112}1113}1114} else {1115for a in dependency_flattening.neighbors_directed(NodeId::Set(set), Incoming) {1116for &sys in systems {1117temp.push((a, NodeId::System(sys)));1118}1119}11201121for b in dependency_flattening.neighbors_directed(NodeId::Set(set), Outgoing) {1122for &sys in systems {1123temp.push((NodeId::System(sys), b));1124}1125}1126}11271128dependency_flattening.remove_node(NodeId::Set(set));1129for (a, b) in temp.drain(..) {1130dependency_flattening.add_edge(a, b);1131}1132}11331134// By this point, we should have removed all system sets from the graph,1135// so this conversion should never fail.1136dependency_flattening1137.try_into::<SystemKey>()1138.unwrap_or_else(|n| {1139unreachable!(1140"Flattened dependency graph has a leftover system set {}",1141self.get_node_name(&NodeId::Set(n))1142)1143})1144}11451146fn get_ambiguous_with_flattened(1147&self,1148set_systems: &HashMap<SystemSetKey, Vec<SystemKey>>,1149) -> UnGraph<NodeId> {1150let mut ambiguous_with_flattened = UnGraph::default();1151for (lhs, rhs) in self.ambiguous_with.all_edges() {1152match (lhs, rhs) {1153(NodeId::System(_), NodeId::System(_)) => {1154ambiguous_with_flattened.add_edge(lhs, rhs);1155}1156(NodeId::Set(lhs), NodeId::System(_)) => {1157for &lhs_ in set_systems.get(&lhs).unwrap_or(&Vec::new()) {1158ambiguous_with_flattened.add_edge(NodeId::System(lhs_), rhs);1159}1160}1161(NodeId::System(_), NodeId::Set(rhs)) => {1162for &rhs_ in set_systems.get(&rhs).unwrap_or(&Vec::new()) {1163ambiguous_with_flattened.add_edge(lhs, NodeId::System(rhs_));1164}1165}1166(NodeId::Set(lhs), NodeId::Set(rhs)) => {1167for &lhs_ in set_systems.get(&lhs).unwrap_or(&Vec::new()) {1168for &rhs_ in set_systems.get(&rhs).unwrap_or(&vec![]) {1169ambiguous_with_flattened1170.add_edge(NodeId::System(lhs_), NodeId::System(rhs_));1171}1172}1173}1174}1175}11761177ambiguous_with_flattened1178}11791180fn get_conflicting_systems(1181&self,1182flat_results_disconnected: &Vec<(SystemKey, SystemKey)>,1183ambiguous_with_flattened: &UnGraph<NodeId>,1184ignored_ambiguities: &BTreeSet<ComponentId>,1185) -> Vec<(SystemKey, SystemKey, Vec<ComponentId>)> {1186let mut conflicting_systems = Vec::new();1187for &(a, b) in flat_results_disconnected {1188if ambiguous_with_flattened.contains_edge(NodeId::System(a), NodeId::System(b))1189|| self.ambiguous_with_all.contains(&NodeId::System(a))1190|| self.ambiguous_with_all.contains(&NodeId::System(b))1191{1192continue;1193}11941195let system_a = &self.systems[a];1196let system_b = &self.systems[b];1197if system_a.is_exclusive() || system_b.is_exclusive() {1198conflicting_systems.push((a, b, Vec::new()));1199} else {1200let access_a = &system_a.access;1201let access_b = &system_b.access;1202if !access_a.is_compatible(access_b) {1203match access_a.get_conflicts(access_b) {1204AccessConflicts::Individual(conflicts) => {1205let conflicts: Vec<_> = conflicts1206.ones()1207.map(ComponentId::get_sparse_set_index)1208.filter(|id| !ignored_ambiguities.contains(id))1209.collect();1210if !conflicts.is_empty() {1211conflicting_systems.push((a, b, conflicts));1212}1213}1214AccessConflicts::All => {1215// there is no specific component conflicting, but the systems are overall incompatible1216// for example 2 systems with `Query<EntityMut>`1217conflicting_systems.push((a, b, Vec::new()));1218}1219}1220}1221}1222}12231224conflicting_systems1225}12261227fn build_schedule_inner(1228&self,1229dependency_flattened_dag: Dag<SystemKey>,1230hier_results_reachable: FixedBitSet,1231) -> SystemSchedule {1232let dg_system_ids = dependency_flattened_dag.topsort;1233let dg_system_idx_map = dg_system_ids1234.iter()1235.cloned()1236.enumerate()1237.map(|(i, id)| (id, i))1238.collect::<HashMap<_, _>>();12391240let hg_systems = self1241.hierarchy1242.topsort1243.iter()1244.cloned()1245.enumerate()1246.filter_map(|(i, id)| Some((i, id.as_system()?)))1247.collect::<Vec<_>>();12481249let (hg_set_with_conditions_idxs, hg_set_ids): (Vec<_>, Vec<_>) = self1250.hierarchy1251.topsort1252.iter()1253.cloned()1254.enumerate()1255.filter_map(|(i, id)| {1256// ignore system sets that have no conditions1257// ignore system type sets (already covered, they don't have conditions)1258let key = id.as_set()?;1259self.system_sets.has_conditions(key).then_some((i, key))1260})1261.unzip();12621263let sys_count = self.systems.len();1264let set_with_conditions_count = hg_set_ids.len();1265let hg_node_count = self.hierarchy.graph.node_count();12661267// get the number of dependencies and the immediate dependents of each system1268// (needed by multi_threaded executor to run systems in the correct order)1269let mut system_dependencies = Vec::with_capacity(sys_count);1270let mut system_dependents = Vec::with_capacity(sys_count);1271for &sys_key in &dg_system_ids {1272let num_dependencies = dependency_flattened_dag1273.graph1274.neighbors_directed(sys_key, Incoming)1275.count();12761277let dependents = dependency_flattened_dag1278.graph1279.neighbors_directed(sys_key, Outgoing)1280.map(|dep_id| dg_system_idx_map[&dep_id])1281.collect::<Vec<_>>();12821283system_dependencies.push(num_dependencies);1284system_dependents.push(dependents);1285}12861287// get the rows and columns of the hierarchy graph's reachability matrix1288// (needed to we can evaluate conditions in the correct order)1289let mut systems_in_sets_with_conditions =1290vec![FixedBitSet::with_capacity(sys_count); set_with_conditions_count];1291for (i, &row) in hg_set_with_conditions_idxs.iter().enumerate() {1292let bitset = &mut systems_in_sets_with_conditions[i];1293for &(col, sys_key) in &hg_systems {1294let idx = dg_system_idx_map[&sys_key];1295let is_descendant = hier_results_reachable[index(row, col, hg_node_count)];1296bitset.set(idx, is_descendant);1297}1298}12991300let mut sets_with_conditions_of_systems =1301vec![FixedBitSet::with_capacity(set_with_conditions_count); sys_count];1302for &(col, sys_key) in &hg_systems {1303let i = dg_system_idx_map[&sys_key];1304let bitset = &mut sets_with_conditions_of_systems[i];1305for (idx, &row) in hg_set_with_conditions_idxs1306.iter()1307.enumerate()1308.take_while(|&(_idx, &row)| row < col)1309{1310let is_ancestor = hier_results_reachable[index(row, col, hg_node_count)];1311bitset.set(idx, is_ancestor);1312}1313}13141315SystemSchedule {1316systems: Vec::with_capacity(sys_count),1317system_conditions: Vec::with_capacity(sys_count),1318set_conditions: Vec::with_capacity(set_with_conditions_count),1319system_ids: dg_system_ids,1320set_ids: hg_set_ids,1321system_dependencies,1322system_dependents,1323sets_with_conditions_of_systems,1324systems_in_sets_with_conditions,1325}1326}13271328/// Updates the `SystemSchedule` from the `ScheduleGraph`.1329fn update_schedule(1330&mut self,1331world: &mut World,1332schedule: &mut SystemSchedule,1333ignored_ambiguities: &BTreeSet<ComponentId>,1334schedule_label: InternedScheduleLabel,1335) -> Result<Vec<ScheduleBuildWarning>, ScheduleBuildError> {1336if !self.systems.is_initialized() || !self.system_sets.is_initialized() {1337return Err(ScheduleBuildError::Uninitialized);1338}13391340// move systems out of old schedule1341for ((key, system), conditions) in schedule1342.system_ids1343.drain(..)1344.zip(schedule.systems.drain(..))1345.zip(schedule.system_conditions.drain(..))1346{1347self.systems.node_mut(key).inner = Some(system);1348*self.systems.get_conditions_mut(key).unwrap() = conditions;1349}13501351for (key, conditions) in schedule1352.set_ids1353.drain(..)1354.zip(schedule.set_conditions.drain(..))1355{1356*self.system_sets.get_conditions_mut(key).unwrap() = conditions;1357}13581359let (new_schedule, warnings) = self.build_schedule(world, ignored_ambiguities)?;1360*schedule = new_schedule;13611362for warning in &warnings {1363warn!(1364"{:?} schedule built successfully, however: {}",1365schedule_label,1366warning.to_string(self, world)1367);1368}13691370// move systems into new schedule1371for &key in &schedule.system_ids {1372let system = self.systems.node_mut(key).inner.take().unwrap();1373let conditions = core::mem::take(self.systems.get_conditions_mut(key).unwrap());1374schedule.systems.push(system);1375schedule.system_conditions.push(conditions);1376}13771378for &key in &schedule.set_ids {1379let conditions = core::mem::take(self.system_sets.get_conditions_mut(key).unwrap());1380schedule.set_conditions.push(conditions);1381}13821383Ok(warnings)1384}1385}13861387/// Values returned by [`ScheduleGraph::process_configs`]1388struct ProcessConfigsResult {1389/// All nodes contained inside this `process_configs` call's [`ScheduleConfigs`] hierarchy,1390/// if `ancestor_chained` is true1391nodes: Vec<NodeId>,1392/// True if and only if all nodes are "densely chained", meaning that all nested nodes1393/// are linearly chained (as if `after` system ordering had been applied between each node)1394/// in the order they are defined1395densely_chained: bool,1396}13971398/// Trait used by [`ScheduleGraph::process_configs`] to process a single [`ScheduleConfig`].1399trait ProcessScheduleConfig: Schedulable + Sized {1400/// Process a single [`ScheduleConfig`].1401fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig<Self>) -> NodeId;1402}14031404impl ProcessScheduleConfig for ScheduleSystem {1405fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig<Self>) -> NodeId {1406NodeId::System(schedule_graph.add_system_inner(config))1407}1408}14091410impl ProcessScheduleConfig for InternedSystemSet {1411fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig<Self>) -> NodeId {1412NodeId::Set(schedule_graph.configure_set_inner(config))1413}1414}14151416/// Used to select the appropriate reporting function.1417pub enum ReportCycles {1418/// When sets contain themselves1419Hierarchy,1420/// When the graph is no longer a DAG1421Dependency,1422}14231424// methods for reporting errors1425impl ScheduleGraph {1426/// Returns the name of the node with the given [`NodeId`]. Resolves1427/// anonymous sets to a string that describes their contents.1428///1429/// Also displays the set(s) the node is contained in if1430/// [`ScheduleBuildSettings::report_sets`] is true, and shortens system names1431/// if [`ScheduleBuildSettings::use_shortnames`] is true.1432pub fn get_node_name(&self, id: &NodeId) -> String {1433self.get_node_name_inner(id, self.settings.report_sets)1434}14351436#[inline]1437fn get_node_name_inner(&self, id: &NodeId, report_sets: bool) -> String {1438match *id {1439NodeId::System(key) => {1440let name = self.systems[key].name();1441let name = if self.settings.use_shortnames {1442name.shortname().to_string()1443} else {1444name.to_string()1445};1446if report_sets {1447let sets = self.names_of_sets_containing_node(id);1448if sets.is_empty() {1449name1450} else if sets.len() == 1 {1451format!("{name} (in set {})", sets[0])1452} else {1453format!("{name} (in sets {})", sets.join(", "))1454}1455} else {1456name1457}1458}1459NodeId::Set(key) => {1460let set = &self.system_sets[key];1461if set.is_anonymous() {1462self.anonymous_set_name(id)1463} else {1464format!("{set:?}")1465}1466}1467}1468}14691470fn anonymous_set_name(&self, id: &NodeId) -> String {1471format!(1472"({})",1473self.hierarchy1474.graph1475.edges_directed(*id, Outgoing)1476// never get the sets of the members or this will infinite recurse when the report_sets setting is on.1477.map(|(_, member_id)| self.get_node_name_inner(&member_id, false))1478.reduce(|a, b| format!("{a}, {b}"))1479.unwrap_or_default()1480)1481}14821483/// If [`ScheduleBuildSettings::hierarchy_detection`] is [`LogLevel::Ignore`] this check1484/// is skipped.1485fn optionally_check_hierarchy_conflicts(1486&self,1487transitive_edges: &[(NodeId, NodeId)],1488) -> Result<Option<ScheduleBuildWarning>, ScheduleBuildError> {1489match (1490self.settings.hierarchy_detection,1491!transitive_edges.is_empty(),1492) {1493(LogLevel::Warn, true) => Ok(Some(ScheduleBuildWarning::HierarchyRedundancy(1494transitive_edges.to_vec(),1495))),1496(LogLevel::Error, true) => {1497Err(ScheduleBuildWarning::HierarchyRedundancy(transitive_edges.to_vec()).into())1498}1499_ => Ok(None),1500}1501}15021503/// Tries to topologically sort `graph`.1504///1505/// If the graph is acyclic, returns [`Ok`] with the list of [`NodeId`] in a valid1506/// topological order. If the graph contains cycles, returns [`Err`] with the list of1507/// strongly-connected components that contain cycles (also in a valid topological order).1508///1509/// # Errors1510///1511/// If the graph contain cycles, then an error is returned.1512pub fn topsort_graph<N: GraphNodeId + Into<NodeId>>(1513&self,1514graph: &DiGraph<N>,1515report: ReportCycles,1516) -> Result<Vec<N>, ScheduleBuildError> {1517// Check explicitly for self-edges.1518// `iter_sccs` won't report them as cycles because they still form components of one node.1519if let Some((node, _)) = graph.all_edges().find(|(left, right)| left == right) {1520let error = match report {1521ReportCycles::Hierarchy => ScheduleBuildError::HierarchyLoop(node.into()),1522ReportCycles::Dependency => ScheduleBuildError::DependencyLoop(node.into()),1523};1524return Err(error);1525}15261527// Tarjan's SCC algorithm returns elements in *reverse* topological order.1528let mut top_sorted_nodes = Vec::with_capacity(graph.node_count());1529let mut sccs_with_cycles = Vec::new();15301531for scc in graph.iter_sccs() {1532// A strongly-connected component is a group of nodes who can all reach each other1533// through one or more paths. If an SCC contains more than one node, there must be1534// at least one cycle within them.1535top_sorted_nodes.extend_from_slice(&scc);1536if scc.len() > 1 {1537sccs_with_cycles.push(scc);1538}1539}15401541if sccs_with_cycles.is_empty() {1542// reverse to get topological order1543top_sorted_nodes.reverse();1544Ok(top_sorted_nodes)1545} else {1546let mut cycles = Vec::new();1547for scc in &sccs_with_cycles {1548cycles.append(&mut simple_cycles_in_component(graph, scc));1549}15501551let error = match report {1552ReportCycles::Hierarchy => ScheduleBuildError::HierarchyCycle(1553cycles1554.into_iter()1555.map(|c| c.into_iter().map(Into::into).collect())1556.collect(),1557),1558ReportCycles::Dependency => ScheduleBuildError::DependencyCycle(1559cycles1560.into_iter()1561.map(|c| c.into_iter().map(Into::into).collect())1562.collect(),1563),1564};15651566Err(error)1567}1568}15691570fn check_for_cross_dependencies(1571&self,1572dep_results: &CheckGraphResults<NodeId>,1573hier_results_connected: &HashSet<(NodeId, NodeId)>,1574) -> Result<(), ScheduleBuildError> {1575for &(a, b) in &dep_results.connected {1576if hier_results_connected.contains(&(a, b)) || hier_results_connected.contains(&(b, a))1577{1578return Err(ScheduleBuildError::CrossDependency(a, b));1579}1580}15811582Ok(())1583}15841585fn check_order_but_intersect(1586&self,1587dep_results_connected: &HashSet<(NodeId, NodeId)>,1588set_system_sets: &HashMap<SystemSetKey, HashSet<SystemKey>>,1589) -> Result<(), ScheduleBuildError> {1590// check that there is no ordering between system sets that intersect1591for &(a, b) in dep_results_connected {1592let (NodeId::Set(a_key), NodeId::Set(b_key)) = (a, b) else {1593continue;1594};15951596let a_systems = set_system_sets.get(&a_key).unwrap();1597let b_systems = set_system_sets.get(&b_key).unwrap();15981599if !a_systems.is_disjoint(b_systems) {1600return Err(ScheduleBuildError::SetsHaveOrderButIntersect(a_key, b_key));1601}1602}16031604Ok(())1605}16061607fn check_system_type_set_ambiguity(1608&self,1609set_systems: &HashMap<SystemSetKey, Vec<SystemKey>>,1610) -> Result<(), ScheduleBuildError> {1611for (&key, systems) in set_systems {1612let set = &self.system_sets[key];1613if set.system_type().is_some() {1614let instances = systems.len();1615let ambiguous_with = self.ambiguous_with.edges(NodeId::Set(key));1616let before = self1617.dependency1618.graph1619.edges_directed(NodeId::Set(key), Incoming);1620let after = self1621.dependency1622.graph1623.edges_directed(NodeId::Set(key), Outgoing);1624let relations = before.count() + after.count() + ambiguous_with.count();1625if instances > 1 && relations > 0 {1626return Err(ScheduleBuildError::SystemTypeSetAmbiguity(key));1627}1628}1629}1630Ok(())1631}16321633/// if [`ScheduleBuildSettings::ambiguity_detection`] is [`LogLevel::Ignore`], this check is skipped1634fn optionally_check_conflicts(1635&self,1636conflicts: &[(SystemKey, SystemKey, Vec<ComponentId>)],1637) -> Result<Option<ScheduleBuildWarning>, ScheduleBuildError> {1638match (self.settings.ambiguity_detection, !conflicts.is_empty()) {1639(LogLevel::Warn, true) => Ok(Some(ScheduleBuildWarning::Ambiguity(conflicts.to_vec()))),1640(LogLevel::Error, true) => {1641Err(ScheduleBuildWarning::Ambiguity(conflicts.to_vec()).into())1642}1643_ => Ok(None),1644}1645}16461647/// convert conflicts to human readable format1648pub fn conflicts_to_string<'a>(1649&'a self,1650ambiguities: &'a [(SystemKey, SystemKey, Vec<ComponentId>)],1651components: &'a Components,1652) -> impl Iterator<Item = (String, String, Vec<DebugName>)> + 'a {1653ambiguities1654.iter()1655.map(move |(system_a, system_b, conflicts)| {1656let name_a = self.get_node_name(&NodeId::System(*system_a));1657let name_b = self.get_node_name(&NodeId::System(*system_b));16581659let conflict_names: Vec<_> = conflicts1660.iter()1661.map(|id| components.get_name(*id).unwrap())1662.collect();16631664(name_a, name_b, conflict_names)1665})1666}16671668fn traverse_sets_containing_node(&self, id: NodeId, f: &mut impl FnMut(SystemSetKey) -> bool) {1669for (set_id, _) in self.hierarchy.graph.edges_directed(id, Incoming) {1670let NodeId::Set(set_key) = set_id else {1671continue;1672};1673if f(set_key) {1674self.traverse_sets_containing_node(NodeId::Set(set_key), f);1675}1676}1677}16781679fn names_of_sets_containing_node(&self, id: &NodeId) -> Vec<String> {1680let mut sets = <HashSet<_>>::default();1681self.traverse_sets_containing_node(*id, &mut |key| {1682self.system_sets[key].system_type().is_none() && sets.insert(key)1683});1684let mut sets: Vec<_> = sets1685.into_iter()1686.map(|key| self.get_node_name(&NodeId::Set(key)))1687.collect();1688sets.sort();1689sets1690}1691}16921693/// Specifies how schedule construction should respond to detecting a certain kind of issue.1694#[derive(Debug, Clone, Copy, PartialEq)]1695pub enum LogLevel {1696/// Occurrences are completely ignored.1697Ignore,1698/// Occurrences are logged only.1699Warn,1700/// Occurrences are logged and result in errors.1701Error,1702}17031704/// Specifies miscellaneous settings for schedule construction.1705#[derive(Clone, Debug)]1706pub struct ScheduleBuildSettings {1707/// Determines whether the presence of ambiguities (systems with conflicting access but indeterminate order)1708/// is only logged or also results in an [`Ambiguity`](ScheduleBuildWarning::Ambiguity)1709/// warning or error.1710///1711/// Defaults to [`LogLevel::Ignore`].1712pub ambiguity_detection: LogLevel,1713/// Determines whether the presence of redundant edges in the hierarchy of system sets is only1714/// logged or also results in a [`HierarchyRedundancy`](ScheduleBuildWarning::HierarchyRedundancy)1715/// warning or error.1716///1717/// Defaults to [`LogLevel::Warn`].1718pub hierarchy_detection: LogLevel,1719/// Auto insert [`ApplyDeferred`] systems into the schedule,1720/// when there are [`Deferred`](crate::prelude::Deferred)1721/// in one system and there are ordering dependencies on that system. [`Commands`](crate::system::Commands) is one1722/// such deferred buffer.1723///1724/// You may want to disable this if you only want to sync deferred params at the end of the schedule,1725/// or want to manually insert all your sync points.1726///1727/// Defaults to `true`1728pub auto_insert_apply_deferred: bool,1729/// If set to true, node names will be shortened instead of the fully qualified type path.1730///1731/// Defaults to `true`.1732pub use_shortnames: bool,1733/// If set to true, report all system sets the conflicting systems are part of.1734///1735/// Defaults to `true`.1736pub report_sets: bool,1737}17381739impl Default for ScheduleBuildSettings {1740fn default() -> Self {1741Self::new()1742}1743}17441745impl ScheduleBuildSettings {1746/// Default build settings.1747/// See the field-level documentation for the default value of each field.1748pub const fn new() -> Self {1749Self {1750ambiguity_detection: LogLevel::Ignore,1751hierarchy_detection: LogLevel::Warn,1752auto_insert_apply_deferred: true,1753use_shortnames: true,1754report_sets: true,1755}1756}1757}17581759/// Error to denote that [`Schedule::initialize`] or [`Schedule::run`] has not yet been called for1760/// this schedule.1761#[derive(Error, Debug)]1762#[error("executable schedule has not been built")]1763pub struct ScheduleNotInitialized;17641765#[cfg(test)]1766mod tests {1767use bevy_ecs_macros::ScheduleLabel;17681769use crate::{1770error::{ignore, panic, DefaultErrorHandler, Result},1771prelude::{ApplyDeferred, Res, Resource},1772schedule::{1773tests::ResMut, IntoScheduleConfigs, Schedule, ScheduleBuildSettings, SystemSet,1774},1775system::Commands,1776world::World,1777};17781779use super::Schedules;17801781#[derive(Resource)]1782struct Resource1;17831784#[derive(Resource)]1785struct Resource2;17861787#[test]1788fn unchanged_auto_insert_apply_deferred_has_no_effect() {1789use alloc::{vec, vec::Vec};17901791#[derive(PartialEq, Debug)]1792enum Entry {1793System(usize),1794SyncPoint(usize),1795}17961797#[derive(Resource, Default)]1798struct Log(Vec<Entry>);17991800fn system<const N: usize>(mut res: ResMut<Log>, mut commands: Commands) {1801res.0.push(Entry::System(N));1802commands1803.queue(|world: &mut World| world.resource_mut::<Log>().0.push(Entry::SyncPoint(N)));1804}18051806let mut world = World::default();1807world.init_resource::<Log>();1808let mut schedule = Schedule::default();1809schedule.add_systems((system::<1>, system::<2>).chain_ignore_deferred());1810schedule.set_build_settings(ScheduleBuildSettings {1811auto_insert_apply_deferred: true,1812..Default::default()1813});1814schedule.run(&mut world);1815let actual = world.remove_resource::<Log>().unwrap().0;18161817let expected = vec![1818Entry::System(1),1819Entry::System(2),1820Entry::SyncPoint(1),1821Entry::SyncPoint(2),1822];18231824assert_eq!(actual, expected);1825}18261827// regression test for https://github.com/bevyengine/bevy/issues/91141828#[test]1829fn ambiguous_with_not_breaking_run_conditions() {1830#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]1831struct Set;18321833let mut world = World::new();1834let mut schedule = Schedule::default();18351836let system: fn() = || {1837panic!("This system must not run");1838};18391840schedule.configure_sets(Set.run_if(|| false));1841schedule.add_systems(system.ambiguous_with(|| ()).in_set(Set));1842schedule.run(&mut world);1843}18441845#[test]1846fn inserts_a_sync_point() {1847let mut schedule = Schedule::default();1848let mut world = World::default();1849schedule.add_systems(1850(1851|mut commands: Commands| commands.insert_resource(Resource1),1852|_: Res<Resource1>| {},1853)1854.chain(),1855);1856schedule.run(&mut world);18571858// inserted a sync point1859assert_eq!(schedule.executable.systems.len(), 3);1860}18611862#[test]1863fn explicit_sync_point_used_as_auto_sync_point() {1864let mut schedule = Schedule::default();1865let mut world = World::default();1866schedule.add_systems(1867(1868|mut commands: Commands| commands.insert_resource(Resource1),1869|_: Res<Resource1>| {},1870)1871.chain(),1872);1873schedule.add_systems((|| {}, ApplyDeferred, || {}).chain());1874schedule.run(&mut world);18751876// No sync point was inserted, since we can reuse the explicit sync point.1877assert_eq!(schedule.executable.systems.len(), 5);1878}18791880#[test]1881fn conditional_explicit_sync_point_not_used_as_auto_sync_point() {1882let mut schedule = Schedule::default();1883let mut world = World::default();1884schedule.add_systems(1885(1886|mut commands: Commands| commands.insert_resource(Resource1),1887|_: Res<Resource1>| {},1888)1889.chain(),1890);1891schedule.add_systems((|| {}, ApplyDeferred.run_if(|| false), || {}).chain());1892schedule.run(&mut world);18931894// A sync point was inserted, since the explicit sync point is not always run.1895assert_eq!(schedule.executable.systems.len(), 6);1896}18971898#[test]1899fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_chain() {1900let mut schedule = Schedule::default();1901let mut world = World::default();1902schedule.add_systems(1903(1904|mut commands: Commands| commands.insert_resource(Resource1),1905|_: Res<Resource1>| {},1906)1907.chain(),1908);1909schedule.add_systems((|| {}, ApplyDeferred, || {}).chain().run_if(|| false));1910schedule.run(&mut world);19111912// A sync point was inserted, since the explicit sync point is not always run.1913assert_eq!(schedule.executable.systems.len(), 6);1914}19151916#[test]1917fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_system_set() {1918#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]1919struct Set;19201921let mut schedule = Schedule::default();1922let mut world = World::default();1923schedule.configure_sets(Set.run_if(|| false));1924schedule.add_systems(1925(1926|mut commands: Commands| commands.insert_resource(Resource1),1927|_: Res<Resource1>| {},1928)1929.chain(),1930);1931schedule.add_systems((|| {}, ApplyDeferred.in_set(Set), || {}).chain());1932schedule.run(&mut world);19331934// A sync point was inserted, since the explicit sync point is not always run.1935assert_eq!(schedule.executable.systems.len(), 6);1936}19371938#[test]1939fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_nested_system_set()1940{1941#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]1942struct Set1;1943#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]1944struct Set2;19451946let mut schedule = Schedule::default();1947let mut world = World::default();1948schedule.configure_sets(Set2.run_if(|| false));1949schedule.configure_sets(Set1.in_set(Set2));1950schedule.add_systems(1951(1952|mut commands: Commands| commands.insert_resource(Resource1),1953|_: Res<Resource1>| {},1954)1955.chain(),1956);1957schedule.add_systems((|| {}, ApplyDeferred, || {}).chain().in_set(Set1));1958schedule.run(&mut world);19591960// A sync point was inserted, since the explicit sync point is not always run.1961assert_eq!(schedule.executable.systems.len(), 6);1962}19631964#[test]1965fn merges_sync_points_into_one() {1966let mut schedule = Schedule::default();1967let mut world = World::default();1968// insert two parallel command systems, it should only create one sync point1969schedule.add_systems(1970(1971(1972|mut commands: Commands| commands.insert_resource(Resource1),1973|mut commands: Commands| commands.insert_resource(Resource2),1974),1975|_: Res<Resource1>, _: Res<Resource2>| {},1976)1977.chain(),1978);1979schedule.run(&mut world);19801981// inserted sync points1982assert_eq!(schedule.executable.systems.len(), 4);19831984// merges sync points on rebuild1985schedule.add_systems(((1986(1987|mut commands: Commands| commands.insert_resource(Resource1),1988|mut commands: Commands| commands.insert_resource(Resource2),1989),1990|_: Res<Resource1>, _: Res<Resource2>| {},1991)1992.chain(),));1993schedule.run(&mut world);19941995assert_eq!(schedule.executable.systems.len(), 7);1996}19971998#[test]1999fn adds_multiple_consecutive_syncs() {2000let mut schedule = Schedule::default();2001let mut world = World::default();2002// insert two consecutive command systems, it should create two sync points2003schedule.add_systems(2004(2005|mut commands: Commands| commands.insert_resource(Resource1),2006|mut commands: Commands| commands.insert_resource(Resource2),2007|_: Res<Resource1>, _: Res<Resource2>| {},2008)2009.chain(),2010);2011schedule.run(&mut world);20122013assert_eq!(schedule.executable.systems.len(), 5);2014}20152016#[test]2017fn do_not_consider_ignore_deferred_before_exclusive_system() {2018let mut schedule = Schedule::default();2019let mut world = World::default();2020// chain_ignore_deferred adds no sync points usually but an exception is made for exclusive systems2021schedule.add_systems(2022(2023|_: Commands| {},2024// <- no sync point is added here because the following system is not exclusive2025|mut commands: Commands| commands.insert_resource(Resource1),2026// <- sync point is added here because the following system is exclusive which expects to see all commands to that point2027|world: &mut World| assert!(world.contains_resource::<Resource1>()),2028// <- no sync point is added here because the previous system has no deferred parameters2029|_: &mut World| {},2030// <- no sync point is added here because the following system is not exclusive2031|_: Commands| {},2032)2033.chain_ignore_deferred(),2034);2035schedule.run(&mut world);20362037assert_eq!(schedule.executable.systems.len(), 6); // 5 systems + 1 sync point2038}20392040#[test]2041fn bubble_sync_point_through_ignore_deferred_node() {2042let mut schedule = Schedule::default();2043let mut world = World::default();20442045let insert_resource_config = (2046// the first system has deferred commands2047|mut commands: Commands| commands.insert_resource(Resource1),2048// the second system has no deferred commands2049|| {},2050)2051// the first two systems are chained without a sync point in between2052.chain_ignore_deferred();20532054schedule.add_systems(2055(2056insert_resource_config,2057// the third system would panic if the command of the first system was not applied2058|_: Res<Resource1>| {},2059)2060// the third system is chained after the first two, possibly with a sync point in between2061.chain(),2062);20632064// To add a sync point between the second and third system despite the second having no commands,2065// the first system has to signal the second system that there are unapplied commands.2066// With that the second system will add a sync point after it so the third system will find the resource.20672068schedule.run(&mut world);20692070assert_eq!(schedule.executable.systems.len(), 4); // 3 systems + 1 sync point2071}20722073#[test]2074fn disable_auto_sync_points() {2075let mut schedule = Schedule::default();2076schedule.set_build_settings(ScheduleBuildSettings {2077auto_insert_apply_deferred: false,2078..Default::default()2079});2080let mut world = World::default();2081schedule.add_systems(2082(2083|mut commands: Commands| commands.insert_resource(Resource1),2084|res: Option<Res<Resource1>>| assert!(res.is_none()),2085)2086.chain(),2087);2088schedule.run(&mut world);20892090assert_eq!(schedule.executable.systems.len(), 2);2091}20922093mod no_sync_edges {2094use super::*;20952096fn insert_resource(mut commands: Commands) {2097commands.insert_resource(Resource1);2098}20992100fn resource_does_not_exist(res: Option<Res<Resource1>>) {2101assert!(res.is_none());2102}21032104#[derive(SystemSet, Hash, PartialEq, Eq, Debug, Clone)]2105enum Sets {2106A,2107B,2108}21092110fn check_no_sync_edges(add_systems: impl FnOnce(&mut Schedule)) {2111let mut schedule = Schedule::default();2112let mut world = World::default();2113add_systems(&mut schedule);21142115schedule.run(&mut world);21162117assert_eq!(schedule.executable.systems.len(), 2);2118}21192120#[test]2121fn system_to_system_after() {2122check_no_sync_edges(|schedule| {2123schedule.add_systems((2124insert_resource,2125resource_does_not_exist.after_ignore_deferred(insert_resource),2126));2127});2128}21292130#[test]2131fn system_to_system_before() {2132check_no_sync_edges(|schedule| {2133schedule.add_systems((2134insert_resource.before_ignore_deferred(resource_does_not_exist),2135resource_does_not_exist,2136));2137});2138}21392140#[test]2141fn set_to_system_after() {2142check_no_sync_edges(|schedule| {2143schedule2144.add_systems((insert_resource, resource_does_not_exist.in_set(Sets::A)))2145.configure_sets(Sets::A.after_ignore_deferred(insert_resource));2146});2147}21482149#[test]2150fn set_to_system_before() {2151check_no_sync_edges(|schedule| {2152schedule2153.add_systems((insert_resource.in_set(Sets::A), resource_does_not_exist))2154.configure_sets(Sets::A.before_ignore_deferred(resource_does_not_exist));2155});2156}21572158#[test]2159fn set_to_set_after() {2160check_no_sync_edges(|schedule| {2161schedule2162.add_systems((2163insert_resource.in_set(Sets::A),2164resource_does_not_exist.in_set(Sets::B),2165))2166.configure_sets(Sets::B.after_ignore_deferred(Sets::A));2167});2168}21692170#[test]2171fn set_to_set_before() {2172check_no_sync_edges(|schedule| {2173schedule2174.add_systems((2175insert_resource.in_set(Sets::A),2176resource_does_not_exist.in_set(Sets::B),2177))2178.configure_sets(Sets::A.before_ignore_deferred(Sets::B));2179});2180}2181}21822183mod no_sync_chain {2184use super::*;21852186#[derive(Resource)]2187struct Ra;21882189#[derive(Resource)]2190struct Rb;21912192#[derive(Resource)]2193struct Rc;21942195fn run_schedule(expected_num_systems: usize, add_systems: impl FnOnce(&mut Schedule)) {2196let mut schedule = Schedule::default();2197let mut world = World::default();2198add_systems(&mut schedule);21992200schedule.run(&mut world);22012202assert_eq!(schedule.executable.systems.len(), expected_num_systems);2203}22042205#[test]2206fn only_chain_outside() {2207run_schedule(5, |schedule: &mut Schedule| {2208schedule.add_systems(2209(2210(2211|mut commands: Commands| commands.insert_resource(Ra),2212|mut commands: Commands| commands.insert_resource(Rb),2213),2214(2215|res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {2216assert!(res_a.is_some());2217assert!(res_b.is_some());2218},2219|res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {2220assert!(res_a.is_some());2221assert!(res_b.is_some());2222},2223),2224)2225.chain(),2226);2227});22282229run_schedule(4, |schedule: &mut Schedule| {2230schedule.add_systems(2231(2232(2233|mut commands: Commands| commands.insert_resource(Ra),2234|mut commands: Commands| commands.insert_resource(Rb),2235),2236(2237|res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {2238assert!(res_a.is_none());2239assert!(res_b.is_none());2240},2241|res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {2242assert!(res_a.is_none());2243assert!(res_b.is_none());2244},2245),2246)2247.chain_ignore_deferred(),2248);2249});2250}22512252#[test]2253fn chain_first() {2254run_schedule(6, |schedule: &mut Schedule| {2255schedule.add_systems(2256(2257(2258|mut commands: Commands| commands.insert_resource(Ra),2259|mut commands: Commands, res_a: Option<Res<Ra>>| {2260commands.insert_resource(Rb);2261assert!(res_a.is_some());2262},2263)2264.chain(),2265(2266|res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {2267assert!(res_a.is_some());2268assert!(res_b.is_some());2269},2270|res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {2271assert!(res_a.is_some());2272assert!(res_b.is_some());2273},2274),2275)2276.chain(),2277);2278});22792280run_schedule(5, |schedule: &mut Schedule| {2281schedule.add_systems(2282(2283(2284|mut commands: Commands| commands.insert_resource(Ra),2285|mut commands: Commands, res_a: Option<Res<Ra>>| {2286commands.insert_resource(Rb);2287assert!(res_a.is_some());2288},2289)2290.chain(),2291(2292|res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {2293assert!(res_a.is_some());2294assert!(res_b.is_none());2295},2296|res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {2297assert!(res_a.is_some());2298assert!(res_b.is_none());2299},2300),2301)2302.chain_ignore_deferred(),2303);2304});2305}23062307#[test]2308fn chain_second() {2309run_schedule(6, |schedule: &mut Schedule| {2310schedule.add_systems(2311(2312(2313|mut commands: Commands| commands.insert_resource(Ra),2314|mut commands: Commands| commands.insert_resource(Rb),2315),2316(2317|mut commands: Commands,2318res_a: Option<Res<Ra>>,2319res_b: Option<Res<Rb>>| {2320commands.insert_resource(Rc);2321assert!(res_a.is_some());2322assert!(res_b.is_some());2323},2324|res_a: Option<Res<Ra>>,2325res_b: Option<Res<Rb>>,2326res_c: Option<Res<Rc>>| {2327assert!(res_a.is_some());2328assert!(res_b.is_some());2329assert!(res_c.is_some());2330},2331)2332.chain(),2333)2334.chain(),2335);2336});23372338run_schedule(5, |schedule: &mut Schedule| {2339schedule.add_systems(2340(2341(2342|mut commands: Commands| commands.insert_resource(Ra),2343|mut commands: Commands| commands.insert_resource(Rb),2344),2345(2346|mut commands: Commands,2347res_a: Option<Res<Ra>>,2348res_b: Option<Res<Rb>>| {2349commands.insert_resource(Rc);2350assert!(res_a.is_none());2351assert!(res_b.is_none());2352},2353|res_a: Option<Res<Ra>>,2354res_b: Option<Res<Rb>>,2355res_c: Option<Res<Rc>>| {2356assert!(res_a.is_some());2357assert!(res_b.is_some());2358assert!(res_c.is_some());2359},2360)2361.chain(),2362)2363.chain_ignore_deferred(),2364);2365});2366}23672368#[test]2369fn chain_all() {2370run_schedule(7, |schedule: &mut Schedule| {2371schedule.add_systems(2372(2373(2374|mut commands: Commands| commands.insert_resource(Ra),2375|mut commands: Commands, res_a: Option<Res<Ra>>| {2376commands.insert_resource(Rb);2377assert!(res_a.is_some());2378},2379)2380.chain(),2381(2382|mut commands: Commands,2383res_a: Option<Res<Ra>>,2384res_b: Option<Res<Rb>>| {2385commands.insert_resource(Rc);2386assert!(res_a.is_some());2387assert!(res_b.is_some());2388},2389|res_a: Option<Res<Ra>>,2390res_b: Option<Res<Rb>>,2391res_c: Option<Res<Rc>>| {2392assert!(res_a.is_some());2393assert!(res_b.is_some());2394assert!(res_c.is_some());2395},2396)2397.chain(),2398)2399.chain(),2400);2401});24022403run_schedule(6, |schedule: &mut Schedule| {2404schedule.add_systems(2405(2406(2407|mut commands: Commands| commands.insert_resource(Ra),2408|mut commands: Commands, res_a: Option<Res<Ra>>| {2409commands.insert_resource(Rb);2410assert!(res_a.is_some());2411},2412)2413.chain(),2414(2415|mut commands: Commands,2416res_a: Option<Res<Ra>>,2417res_b: Option<Res<Rb>>| {2418commands.insert_resource(Rc);2419assert!(res_a.is_some());2420assert!(res_b.is_none());2421},2422|res_a: Option<Res<Ra>>,2423res_b: Option<Res<Rb>>,2424res_c: Option<Res<Rc>>| {2425assert!(res_a.is_some());2426assert!(res_b.is_some());2427assert!(res_c.is_some());2428},2429)2430.chain(),2431)2432.chain_ignore_deferred(),2433);2434});2435}2436}24372438#[derive(ScheduleLabel, Hash, Debug, Clone, PartialEq, Eq)]2439struct TestSchedule;24402441#[derive(Resource)]2442struct CheckSystemRan(usize);24432444#[test]2445fn add_systems_to_existing_schedule() {2446let mut schedules = Schedules::default();2447let schedule = Schedule::new(TestSchedule);24482449schedules.insert(schedule);2450schedules.add_systems(TestSchedule, |mut ran: ResMut<CheckSystemRan>| ran.0 += 1);24512452let mut world = World::new();24532454world.insert_resource(CheckSystemRan(0));2455world.insert_resource(schedules);2456world.run_schedule(TestSchedule);24572458let value = world2459.get_resource::<CheckSystemRan>()2460.expect("CheckSystemRan Resource Should Exist");2461assert_eq!(value.0, 1);2462}24632464#[test]2465fn add_systems_to_non_existing_schedule() {2466let mut schedules = Schedules::default();24672468schedules.add_systems(TestSchedule, |mut ran: ResMut<CheckSystemRan>| ran.0 += 1);24692470let mut world = World::new();24712472world.insert_resource(CheckSystemRan(0));2473world.insert_resource(schedules);2474world.run_schedule(TestSchedule);24752476let value = world2477.get_resource::<CheckSystemRan>()2478.expect("CheckSystemRan Resource Should Exist");2479assert_eq!(value.0, 1);2480}24812482#[derive(SystemSet, Debug, Hash, Clone, PartialEq, Eq)]2483enum TestSet {2484First,2485Second,2486}24872488#[test]2489fn configure_set_on_existing_schedule() {2490let mut schedules = Schedules::default();2491let schedule = Schedule::new(TestSchedule);24922493schedules.insert(schedule);24942495schedules.configure_sets(TestSchedule, (TestSet::First, TestSet::Second).chain());2496schedules.add_systems(2497TestSchedule,2498(|mut ran: ResMut<CheckSystemRan>| {2499assert_eq!(ran.0, 0);2500ran.0 += 1;2501})2502.in_set(TestSet::First),2503);25042505schedules.add_systems(2506TestSchedule,2507(|mut ran: ResMut<CheckSystemRan>| {2508assert_eq!(ran.0, 1);2509ran.0 += 1;2510})2511.in_set(TestSet::Second),2512);25132514let mut world = World::new();25152516world.insert_resource(CheckSystemRan(0));2517world.insert_resource(schedules);2518world.run_schedule(TestSchedule);25192520let value = world2521.get_resource::<CheckSystemRan>()2522.expect("CheckSystemRan Resource Should Exist");2523assert_eq!(value.0, 2);2524}25252526#[test]2527fn configure_set_on_new_schedule() {2528let mut schedules = Schedules::default();25292530schedules.configure_sets(TestSchedule, (TestSet::First, TestSet::Second).chain());2531schedules.add_systems(2532TestSchedule,2533(|mut ran: ResMut<CheckSystemRan>| {2534assert_eq!(ran.0, 0);2535ran.0 += 1;2536})2537.in_set(TestSet::First),2538);25392540schedules.add_systems(2541TestSchedule,2542(|mut ran: ResMut<CheckSystemRan>| {2543assert_eq!(ran.0, 1);2544ran.0 += 1;2545})2546.in_set(TestSet::Second),2547);25482549let mut world = World::new();25502551world.insert_resource(CheckSystemRan(0));2552world.insert_resource(schedules);2553world.run_schedule(TestSchedule);25542555let value = world2556.get_resource::<CheckSystemRan>()2557.expect("CheckSystemRan Resource Should Exist");2558assert_eq!(value.0, 2);2559}25602561#[test]2562fn test_default_error_handler() {2563#[derive(Resource, Default)]2564struct Ran(bool);25652566fn system(mut ran: ResMut<Ran>) -> Result {2567ran.0 = true;2568Err("I failed!".into())2569}25702571// Test that the default error handler is used2572let mut world = World::default();2573world.init_resource::<Ran>();2574world.insert_resource(DefaultErrorHandler(ignore));2575let mut schedule = Schedule::default();2576schedule.add_systems(system).run(&mut world);2577assert!(world.resource::<Ran>().0);25782579// Test that the handler doesn't change within the schedule2580schedule.add_systems(2581(|world: &mut World| {2582world.insert_resource(DefaultErrorHandler(panic));2583})2584.before(system),2585);2586schedule.run(&mut world);2587}2588}258925902591