1#![expect(
2 clippy::module_inception,
3 reason = "This instance of module inception is being discussed; see #17344."
4)]
5use alloc::{
6boxed::Box,
7collections::BTreeSet,
8format,
9 string::{String, ToString},
10vec,
11vec::Vec,
12};
13use bevy_ecs_macros::Event;
14use bevy_platform::{
15 collections::{HashMap, HashSet},
16hash::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;
3233use crate::{
34change_detection::CheckChangeTicks,
35 system::{System, SystemAccess},
36};
37use crate::{
38 component::{ComponentId, Components},
39prelude::Component,
40resource::Resource,
41schedule::*,
42system::ScheduleSystem,
43world::World,
44};
4546pub use stepping::Stepping;
47use Direction::{Incoming, Outgoing};
4849/// Resource that stores [`Schedule`]s mapped to [`ScheduleLabel`]s excluding the current running [`Schedule`].
50#[derive(#[automatically_derived]
impl ::core::default::Default for Schedules {
#[inline]
fn default() -> Self {
Self {
inner: ::core::default::Default::default(),
ignored_scheduling_ambiguities: ::core::default::Default::default(),
temporarily_removed: ::core::default::Default::default(),
empty_labels: ::core::default::Default::default(),
}
}
}Default, impl bevy_ecs::component::Component for Schedules where
Self: ::core::marker::Send + ::core::marker::Sync + 'static {
const STORAGE_TYPE: bevy_ecs::component::StorageType =
bevy_ecs::component::StorageType::SparseSet;
type Mutability = bevy_ecs::component::Mutable;
fn register_required_components(_requiree:
bevy_ecs::component::ComponentId,
required_components:
&mut bevy_ecs::component::RequiredComponentsRegistrator) {
let resource_component_id =
if let ::core::option::Option::Some(id) =
required_components.components_registrator().component_id::<Schedules>()
{
id
} else {
required_components.components_registrator().register_component::<Schedules>()
};
required_components.register_required::<bevy_ecs::resource::IsResource>(move
||
bevy_ecs::resource::IsResource::new(resource_component_id));
}
fn clone_behavior() -> bevy_ecs::component::ComponentCloneBehavior {
use bevy_ecs::component::{
DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone,
};
(&&&bevy_ecs::component::DefaultCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()
}
fn relationship_accessor()
->
::core::option::Option<bevy_ecs::relationship::ComponentRelationshipAccessor<Self>> {
::core::option::Option::None
}
}
impl bevy_ecs::resource::Resource for Schedules where
Self: ::core::marker::Send + ::core::marker::Sync + 'static {}Resource)]
51pub struct Schedules {
52 inner: HashMap<InternedScheduleLabel, Schedule>,
53/// List of [`ComponentId`]s to ignore when reporting system order ambiguity conflicts
54pub ignored_scheduling_ambiguities: BTreeSet<ComponentId>,
55/// Set of schedule labels that have been removed to execute in [`World::try_schedule_scope`].
56temporarily_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`
59empty_labels: HashSet<InternedScheduleLabel>,
60}
6162impl Schedules {
63/// Constructs an empty `Schedules` with zero initial capacity.
64pub fn new() -> Self {
65Self::default()
66 }
6768/// 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.
72pub fn insert(&mut self, schedule: Schedule) -> Option<Schedule> {
73self.temporarily_removed.remove(&schedule.label);
74// error if above is true
75self.inner.insert(schedule.label, schedule)
76 }
7778/// 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.
82pub fn reinsert(&mut self, schedule: Schedule) -> Option<Schedule> {
83self.temporarily_removed.remove(&schedule.label);
84// error if above false
85self.inner.insert(schedule.label, schedule)
86 }
8788/// Removes the schedule corresponding to the `label` from the map, returning it if it existed.
89pub fn remove(&mut self, label: impl ScheduleLabel) -> Option<Schedule> {
90self.inner.remove(&label.intern())
91 }
9293/// Removes the schedule corresponding to the `label` from the map, returning it if it existed, tracks.
94pub fn remove_temporarily(&mut self, label: impl ScheduleLabel) -> Option<Schedule> {
95let label = label.intern();
96let k = self.inner.remove(&label);
97if k.is_some() {
98self.temporarily_removed.insert(label);
99// error if above false
100self.empty_labels.remove(&label);
101 } else {
102self.empty_labels.insert(label);
103 }
104k105 }
106107/// Removes the (schedule, label) pair corresponding to the `label` from the map, returning it if it existed.
108pub fn remove_entry(
109&mut self,
110 label: impl ScheduleLabel,
111 ) -> Option<(InternedScheduleLabel, Schedule)> {
112self.inner.remove_entry(&label.intern())
113 }
114115/// Gets a set of temporarily removed schedules
116pub fn get_temporarily_removed(&self) -> HashSet<InternedScheduleLabel> {
117self.temporarily_removed.clone()
118 }
119120/// Gets a set of empty schedule labels
121pub fn get_empty_labels(&self) -> HashSet<InternedScheduleLabel> {
122self.empty_labels.clone()
123 }
124125/// Does a schedule with the provided label already exist?
126pub fn contains(&self, label: impl ScheduleLabel) -> bool {
127self.inner.contains_key(&label.intern())
128 }
129130/// Returns a reference to the schedule associated with `label`, if it exists.
131pub fn get(&self, label: impl ScheduleLabel) -> Option<&Schedule> {
132self.inner.get(&label.intern())
133 }
134135/// Returns a mutable reference to the schedule associated with `label`, if it exists.
136pub fn get_mut(&mut self, label: impl ScheduleLabel) -> Option<&mut Schedule> {
137self.inner.get_mut(&label.intern())
138 }
139140/// Returns a mutable reference to the schedules associated with `label`, creating one if it doesn't already exist.
141pub fn entry(&mut self, label: impl ScheduleLabel) -> &mut Schedule {
142self.inner
143 .entry(label.intern())
144 .or_insert_with(|| Schedule::new(label))
145 }
146147/// Returns an iterator over all schedules. Iteration order is undefined.
148pub fn iter(&self) -> impl Iterator<Item = (&dyn ScheduleLabel, &Schedule)> {
149self.inner
150 .iter()
151 .map(|(label, schedule)| (&**label, schedule))
152 }
153/// Returns an iterator over mutable references to all schedules. Iteration order is undefined.
154pub fn iter_mut(&mut self) -> impl Iterator<Item = (&dyn ScheduleLabel, &mut Schedule)> {
155self.inner
156 .iter_mut()
157 .map(|(label, schedule)| (&**label, schedule))
158 }
159160/// 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.
163pub(crate) fn check_change_ticks(&mut self, check: CheckChangeTicks) {
164#[cfg(feature = "trace")]
165let _all_span = {
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("check stored schedule ticks",
"bevy_ecs::schedule::schedule", ::tracing::Level::INFO,
::tracing_core::__macro_support::Option::Some("src/schedule/schedule.rs"),
::tracing_core::__macro_support::Option::Some(165u32),
::tracing_core::__macro_support::Option::Some("bevy_ecs::schedule::schedule"),
::tracing_core::field::FieldSet::new(&[],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::SPAN)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let mut interest = ::tracing::subscriber::Interest::never();
if ::tracing::Level::INFO <= ::tracing::level_filters::STATIC_MAX_LEVEL &&
::tracing::Level::INFO <=
::tracing::level_filters::LevelFilter::current() &&
{ interest = __CALLSITE.interest(); !interest.is_never() } &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest) {
let meta = __CALLSITE.metadata();
::tracing::Span::new(meta, &{ meta.fields().value_set_all(&[]) })
} else {
let span =
::tracing::__macro_support::__disabled_span(__CALLSITE.metadata());
{};
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 )]
173for (label, schedule) in &mut self.inner {
174#[cfg(feature = "trace")]
175let name = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0:?}", label))
})format!("{label:?}");
176#[cfg(feature = "trace")]
177let _one_span = {
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("check schedule ticks",
"bevy_ecs::schedule::schedule", ::tracing::Level::INFO,
::tracing_core::__macro_support::Option::Some("src/schedule/schedule.rs"),
::tracing_core::__macro_support::Option::Some(177u32),
::tracing_core::__macro_support::Option::Some("bevy_ecs::schedule::schedule"),
::tracing_core::field::FieldSet::new(&[{
const NAME:
::tracing::__macro_support::FieldName<{
::tracing::__macro_support::FieldName::len("name")
}> =
::tracing::__macro_support::FieldName::new("name");
NAME.as_str()
}], ::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::SPAN)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let mut interest = ::tracing::subscriber::Interest::never();
if ::tracing::Level::INFO <= ::tracing::level_filters::STATIC_MAX_LEVEL &&
::tracing::Level::INFO <=
::tracing::level_filters::LevelFilter::current() &&
{ interest = __CALLSITE.interest(); !interest.is_never() } &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest) {
let meta = __CALLSITE.metadata();
::tracing::Span::new(meta,
&{
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
meta.fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&&name
as &dyn ::tracing::field::Value))])
})
} else {
let span =
::tracing::__macro_support::__disabled_span(__CALLSITE.metadata());
{};
span
}
}info_span!("check schedule ticks", name = &name).entered();
178 schedule.check_change_ticks(check);
179 }
180 }
181182/// 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.
186pub fn configure_schedules(&mut self, schedule_build_settings: ScheduleBuildSettings) {
187for (_, schedule) in &mut self.inner {
188 schedule.set_build_settings(schedule_build_settings.clone());
189 }
190 }
191192/// Ignore system order ambiguities caused by conflicts on [`Component`]s of type `T`.
193pub fn allow_ambiguous_component<T: Component>(&mut self, world: &mut World) {
194self.ignored_scheduling_ambiguities
195 .insert(world.register_component::<T>());
196 }
197198/// Ignore system order ambiguities caused by conflicts on [`Resource`]s of type `T`.
199pub fn allow_ambiguous_resource<T: Resource>(&mut self, world: &mut World) {
200self.ignored_scheduling_ambiguities
201 .insert(world.components_registrator().register_component::<T>());
202 }
203204/// Iterate through the [`ComponentId`]'s that will be ignored.
205pub fn iter_ignored_ambiguities(&self) -> impl Iterator<Item = &ComponentId> + '_ {
206self.ignored_scheduling_ambiguities.iter()
207 }
208209/// 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
213pub fn print_ignored_ambiguities(&self, components: &Components) {
214let mut message =
215"System order ambiguities caused by conflicts on the following types are ignored:\n"
216.to_string();
217for id in self.iter_ignored_ambiguities() {
218message.write_fmt(format_args!("{0}\n", components.get_name(*id).unwrap()))writeln!(message, "{}", components.get_name(*id).unwrap()).unwrap();
219 }
220221{
{
let lvl = ::log::Level::Info;
if lvl <= ::log::STATIC_MAX_LEVEL && lvl <= ::log::max_level() {
::log::__private_api::log({ ::log::__private_api::GlobalLogger },
format_args!("{0}", message), lvl,
&("bevy_ecs::schedule::schedule",
"bevy_ecs::schedule::schedule",
::log::__private_api::loc()), ());
}
}
};info!("{message}");
222 }
223224/// Adds one or more systems to the [`Schedule`] matching the provided [`ScheduleLabel`].
225pub fn add_systems<M>(
226&mut self,
227 schedule: impl ScheduleLabel,
228 systems: impl IntoScheduleConfigs<ScheduleSystem, M>,
229 ) -> &mut Self {
230self.entry(schedule).add_systems(systems);
231232self233 }
234235/// 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.
238pub 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> {
245self.get_mut(schedule)
246 .ok_or(ScheduleError::ScheduleNotFound)?
247.remove_systems_in_set(set, world, policy)
248 }
249250/// Configures a collection of system sets in the provided schedule, adding any sets that do not exist.
251#[track_caller]
252pub fn configure_sets<M>(
253&mut self,
254 schedule: impl ScheduleLabel,
255 sets: impl IntoScheduleConfigs<InternedSystemSet, M>,
256 ) -> &mut Self {
257self.entry(schedule).configure_sets(sets);
258259self260 }
261262/// 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]
269pub fn ignore_ambiguity<M1, M2, S1, S2>(
270&mut self,
271 schedule: impl ScheduleLabel,
272 a: S1,
273 b: S2,
274 ) -> &mut Self
275where
276S1: IntoSystemSet<M1>,
277 S2: IntoSystemSet<M2>,
278 {
279self.entry(schedule).ignore_ambiguity(a, b);
280281self282 }
283}
284285/// 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;
290291/// Chain systems into dependencies
292#[derive(#[automatically_derived]
impl ::core::default::Default for Chain {
#[inline]
fn default() -> Self { Self::Unchained }
}Default)]
293pub enum Chain {
294/// Systems are independent. Nodes are allowed to run in any order.
295#[default]
296Unchained,
297/// Systems are chained. `before -> after` ordering constraints
298 /// will be added between the successive elements.
299Chained(TypeIdHashMap<Box<dyn Any>>),
300}
301302impl Chain {
303/// Specify that the systems must be chained.
304pub fn set_chained(&mut self) {
305if #[allow(non_exhaustive_omitted_patterns)] match self {
Chain::Unchained => true,
_ => false,
}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.
311pub fn set_chained_with_config<T: 'static>(&mut self, config: T) {
312self.set_chained();
313if let Chain::Chained(config_map) = self {
314config_map.insert(TypeId::of::<T>(), Box::new(config));
315 } else {
316::core::panicking::panic("internal error: entered unreachable code")unreachable!()317 };
318 }
319}
320321/// 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}
400401#[derive(const _: () =
{
extern crate alloc;
impl bevy_ecs::schedule::ScheduleLabel for DefaultSchedule where
Self: 'static + ::core::marker::Send + ::core::marker::Sync +
::core::clone::Clone + ::core::cmp::Eq + ::core::fmt::Debug +
::core::hash::Hash {
fn dyn_clone(&self)
-> alloc::boxed::Box<dyn bevy_ecs::schedule::ScheduleLabel> {
alloc::boxed::Box::new(::core::clone::Clone::clone(self))
}
}
};ScheduleLabel, #[automatically_derived]
impl ::core::hash::Hash for DefaultSchedule {
#[inline]
fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {}
}Hash, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for DefaultSchedule { }
#[automatically_derived]
impl ::core::cmp::PartialEq for DefaultSchedule {
#[inline]
fn eq(&self, other: &Self) -> bool { true }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for DefaultSchedule { }Eq, #[automatically_derived]
impl ::core::fmt::Debug for DefaultSchedule {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f, "DefaultSchedule")
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for DefaultSchedule {
#[inline]
fn clone(&self) -> Self { Self }
}Clone)]
402struct DefaultSchedule;
403404impl Defaultfor 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`].
409fn default() -> Self {
410Self::new(DefaultSchedule)
411 }
412}
413414impl Schedule {
415/// Constructs an empty `Schedule`.
416pub fn new(label: impl ScheduleLabel) -> Self {
417let 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
425this.set_build_settings(Default::default());
426this427 }
428429/// Returns whether this schedule has been changed since the last time it was built.
430pub fn is_changed(&self) -> bool {
431self.graph.changed
432 }
433434/// Returns the [`InternedScheduleLabel`] for this `Schedule`,
435 /// corresponding to the [`ScheduleLabel`] this schedule was created with.
436pub fn label(&self) -> InternedScheduleLabel {
437self.label
438 }
439440/// Add a collection of systems to the schedule.
441pub fn add_systems<M>(
442&mut self,
443 systems: impl IntoScheduleConfigs<ScheduleSystem, M>,
444 ) -> &mut Self {
445self.graph.process_configs(systems.into_configs(), false);
446self447 }
448449/// 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 /// ```
472pub 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> {
478if self.graph.changed {
479self.initialize(world)?;
480 }
481self.graph.remove_systems_in_set(set, policy)
482 }
483484/// 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]
487pub fn ignore_ambiguity<M1, M2, S1, S2>(&mut self, a: S1, b: S2) -> &mut Self
488where
489S1: IntoSystemSet<M1>,
490 S2: IntoSystemSet<M2>,
491 {
492let a = a.into_system_set();
493let b = b.into_system_set();
494495let a_id = self.graph.system_sets.get_key_or_insert(a.intern());
496let b_id = self.graph.system_sets.get_key_or_insert(b.intern());
497498self.graph
499 .ambiguous_with
500 .add_edge(NodeId::Set(a_id), NodeId::Set(b_id));
501502self503 }
504505/// Configures a collection of system sets in this schedule, adding them if they does not exist.
506#[track_caller]
507pub fn configure_sets<M>(
508&mut self,
509 sets: impl IntoScheduleConfigs<InternedSystemSet, M>,
510 ) -> &mut Self {
511self.graph.configure_sets(sets);
512self513 }
514515/// Add a custom build pass to the schedule.
516pub fn add_build_pass<T: ScheduleBuildPass>(&mut self, pass: T) -> &mut Self {
517self.graph.passes.insert(TypeId::of::<T>(), Box::new(pass));
518self519 }
520521/// Remove a custom build pass.
522pub fn remove_build_pass<T: ScheduleBuildPass>(&mut self) {
523self.graph.passes.shift_remove(&TypeId::of::<T>());
524 }
525526/// 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.
533pub fn set_build_settings(&mut self, settings: ScheduleBuildSettings) -> &mut Self {
534if settings.auto_insert_apply_deferred {
535if !self536 .graph
537 .passes
538 .contains_key(&TypeId::of::<passes::AutoInsertApplyDeferredPass>())
539 {
540self.add_build_pass(passes::AutoInsertApplyDeferredPass::default());
541 }
542 } else {
543self.remove_build_pass::<passes::AutoInsertApplyDeferredPass>();
544 }
545self.graph.settings = settings;
546self547 }
548549/// Returns the schedule's current `ScheduleBuildSettings`.
550pub fn get_build_settings(&self) -> ScheduleBuildSettings {
551self.graph.settings.clone()
552 }
553554/// Replaces the schedule's executor.
555pub fn set_executor(&mut self, executor: impl SystemExecutor + 'static) -> &mut Self {
556self.executor = Box::new(executor);
557self.executor_initialized = false;
558self559 }
560561/// 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.
565pub fn set_apply_final_deferred(&mut self, apply_final_deferred: bool) -> &mut Self {
566self.executor.set_apply_final_deferred(apply_final_deferred);
567self568 }
569570/// Runs all systems in this schedule on the `world`, using its current execution strategy.
571pub fn run(&mut self, world: &mut World) {
572#[cfg(feature = "trace")]
573let _span = {
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("schedule",
"bevy_ecs::schedule::schedule", ::tracing::Level::INFO,
::tracing_core::__macro_support::Option::Some("src/schedule/schedule.rs"),
::tracing_core::__macro_support::Option::Some(573u32),
::tracing_core::__macro_support::Option::Some("bevy_ecs::schedule::schedule"),
::tracing_core::field::FieldSet::new(&[{
const NAME:
::tracing::__macro_support::FieldName<{
::tracing::__macro_support::FieldName::len("name")
}> =
::tracing::__macro_support::FieldName::new("name");
NAME.as_str()
}], ::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::SPAN)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let mut interest = ::tracing::subscriber::Interest::never();
if ::tracing::Level::INFO <= ::tracing::level_filters::STATIC_MAX_LEVEL &&
::tracing::Level::INFO <=
::tracing::level_filters::LevelFilter::current() &&
{ interest = __CALLSITE.interest(); !interest.is_never() } &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest) {
let meta = __CALLSITE.metadata();
::tracing::Span::new(meta,
&{
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
meta.fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&::tracing::field::debug(&self.label)
as &dyn ::tracing::field::Value))])
})
} else {
let span =
::tracing::__macro_support::__disabled_span(__CALLSITE.metadata());
{};
span
}
}info_span!("schedule", name = ?self.label).entered();
574575world.check_change_ticks();
576self.initialize(world).unwrap_or_else(|e| {
577{
::core::panicking::panic_fmt(format_args!("Error when initializing schedule {0:?}: {1}",
self.label, e.to_string(self.graph(), world)));
}panic!(
578"Error when initializing schedule {:?}: {}",
579self.label,
580 e.to_string(self.graph(), world)
581 )582 });
583584let error_handler = world.fallback_error_handler();
585586#[cfg(not(feature = "bevy_debug_stepping"))]
587self.executor
588 .run(&mut self.executable, world, None, error_handler);
589590#[cfg(feature = "bevy_debug_stepping")]
591{
592let skip_systems = match world.get_resource_mut::<Stepping>() {
593None => None,
594Some(mut stepping) => stepping.skipped_systems(self),
595 };
596597self.executor.run(
598&mut self.executable,
599world,
600skip_systems.as_ref(),
601error_handler,
602 );
603 }
604 }
605606/// 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`].
612pub fn initialize(
613&mut self,
614 world: &mut World,
615 ) -> Result<Option<ScheduleBuildMetadata>, ScheduleBuildError> {
616let mut build_metadata = None;
617if self.graph.changed {
618self.graph.initialize(world);
619let ignored_ambiguities = world620 .get_resource_or_init::<Schedules>()
621 .ignored_scheduling_ambiguities
622 .clone();
623624let mut event = ScheduleBuilt {
625 label: self.label,
626 build_metadata: self.graph.update_schedule(
627 world,
628&mut self.executable,
629&ignored_ambiguities,
630self.label,
631 )?,
632 };
633self.graph.changed = false;
634self.executor_initialized = false;
635636world.trigger_ref(&mut event);
637build_metadata = Some(event.build_metadata);
638 }
639640if !self.executor_initialized {
641self.executor.init(&self.executable);
642self.executor_initialized = true;
643 }
644645Ok(build_metadata)
646 }
647648/// Returns the [`ScheduleGraph`].
649pub fn graph(&self) -> &ScheduleGraph {
650&self.graph
651 }
652653/// Returns a mutable reference to the [`ScheduleGraph`].
654pub fn graph_mut(&mut self) -> &mut ScheduleGraph {
655&mut self.graph
656 }
657658/// Returns the [`SystemSchedule`].
659pub(crate) fn executable(&self) -> &SystemSchedule {
660&self.executable
661 }
662663/// 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.
666pub fn check_change_ticks(&mut self, check: CheckChangeTicks) {
667for system in &mut self.executable.systems {
668if !is_apply_deferred(system) {
669 system.check_change_tick(check);
670 }
671 }
672673for conditions in &mut self.executable.system_conditions {
674for condition in conditions {
675 condition.check_change_tick(check);
676 }
677 }
678679for conditions in &mut self.executable.set_conditions {
680for condition in conditions {
681 condition.check_change_tick(check);
682 }
683 }
684 }
685686/// 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.
694pub fn apply_deferred(&mut self, world: &mut World) {
695for SystemWithAccess { system, .. } in &mut self.executable.systems {
696 system.apply_deferred(world);
697 }
698 }
699700/// 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.
704pub fn systems(
705&self,
706 ) -> Result<impl Iterator<Item = (SystemKey, &ScheduleSystem)> + Sized, ScheduleNotInitialized>
707 {
708if !self.executor_initialized {
709return Err(ScheduleNotInitialized);
710 }
711712let iter = self713 .executable
714 .system_ids
715 .iter()
716 .zip(&self.executable.systems)
717 .map(|(&node_id, system)| (node_id, &system.system));
718719Ok(iter)
720 }
721722/// 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.
726pub fn systems_with_access(
727&self,
728 ) -> Result<impl Iterator<Item = (SystemKey, &SystemWithAccess)> + Sized, ScheduleNotInitialized>
729 {
730if !self.executor_initialized {
731return Err(ScheduleNotInitialized);
732 }
733734let iter = self735 .executable
736 .system_ids
737 .iter()
738 .zip(&self.executable.systems)
739 .map(|(&node_id, system)| (node_id, system));
740741Ok(iter)
742 }
743744/// Returns the number of systems in this schedule.
745pub fn systems_len(&self) -> usize {
746if !self.executor_initialized {
747self.graph.systems.len()
748 } else {
749self.executable.systems.len()
750 }
751 }
752}
753754/// 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(#[automatically_derived]
impl ::core::default::Default for ScheduleGraph {
#[inline]
fn default() -> Self {
Self {
systems: ::core::default::Default::default(),
system_sets: ::core::default::Default::default(),
hierarchy: ::core::default::Default::default(),
dependency: ::core::default::Default::default(),
set_systems: ::core::default::Default::default(),
ambiguous_with: ::core::default::Default::default(),
ambiguous_with_all: ::core::default::Default::default(),
conflicting_systems: ::core::default::Default::default(),
weak_node_edges: ::core::default::Default::default(),
strict_node_edges: ::core::default::Default::default(),
anonymous_sets: ::core::default::Default::default(),
changed: ::core::default::Default::default(),
settings: ::core::default::Default::default(),
passes: ::core::default::Default::default(),
}
}
}Default)]
759pub struct ScheduleGraph {
760/// Container of systems in the schedule.
761pub systems: Systems,
762/// Container of system sets in the schedule.
763pub system_sets: SystemSets,
764/// Directed acyclic graph of the hierarchy (which systems/sets are children of which sets)
765hierarchy: Dag<NodeId>,
766/// Directed acyclic graph of the dependency (which systems/sets have to run before which other systems/sets)
767dependency: Dag<NodeId>,
768/// Map of systems in each set
769set_systems: DagGroups<SystemSetKey, SystemKey>,
770 ambiguous_with: UnGraph<NodeId>,
771/// Nodes that are allowed to have ambiguous ordering relationship with any other systems.
772pub 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.
777weak_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`]).
781strict_node_edges: HashSet<(NodeId, NodeId)>,
782 anonymous_sets: usize,
783 changed: bool,
784 settings: ScheduleBuildSettings,
785 passes: IndexMap<TypeId, Box<dyn ScheduleBuildPassObj>, FixedHasher>,
786}
787788impl ScheduleGraph {
789/// Creates an empty [`ScheduleGraph`] with default settings.
790pub fn new() -> Self {
791Self {
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 }
808809/// 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.
813pub fn hierarchy(&self) -> &Dag<NodeId> {
814&self.hierarchy
815 }
816817/// 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.
821pub fn dependency(&self) -> &Dag<NodeId> {
822&self.dependency
823 }
824825/// 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.
829pub fn conflicting_systems(&self) -> &ConflictingSystems {
830&self.conflicting_systems
831 }
832833fn process_config<T: ProcessScheduleConfig + Schedulable>(
834&mut self,
835 config: ScheduleConfig<T>,
836 collect_nodes: bool,
837 ) -> ProcessConfigsResult {
838ProcessConfigsResult {
839 densely_chained: true,
840 nodes: collect_nodes841 .then_some(T::process_config(self, config))
842 .into_iter()
843 .collect(),
844 }
845 }
846847fn 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 ) {
854if !collective_conditions.is_empty() {
855if let [config] = configs {
856for condition in collective_conditions {
857 config.run_if_dyn(condition);
858 }
859 } else {
860let set = self.create_anonymous_set();
861for config in configs.iter_mut() {
862 config.in_set_inner(set.intern());
863 }
864let mut set_config = InternedSystemSet::into_config(set.intern());
865set_config.conditions.extend(collective_conditions);
866self.configure_set_inner(set_config);
867 }
868 }
869 }
870871/// 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]
880fn process_configs<
881 T: ProcessScheduleConfig + Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>,
882 >(
883&mut self,
884 configs: ScheduleConfigs<T>,
885 collect_nodes: bool,
886 ) -> ProcessConfigsResult {
887match configs {
888 ScheduleConfigs::ScheduleConfig(config) => self.process_config(config, collect_nodes),
889 ScheduleConfigs::Configs {
890 metadata,
891mut configs,
892 collective_conditions,
893 } => {
894self.apply_collective_conditions(&mut configs, collective_conditions);
895896let is_chained = #[allow(non_exhaustive_omitted_patterns)] match metadata {
Chain::Chained(_) => true,
_ => false,
}matches!(metadata, Chain::Chained(_));
897let is_weak = #[allow(non_exhaustive_omitted_patterns)] match &metadata {
Chain::Chained(options) if options.contains_key(&TypeId::of::<Weak>()) =>
true,
_ => false,
}matches!(
898&metadata,
899 Chain::Chained(options) if options.contains_key(&TypeId::of::<Weak>())
900 );
901902// Densely chained if
903 // * a non-weak chain whose configs are all densely chained, or
904 // * a single densely chained config
905let mut densely_chained = (is_chained && !is_weak) || configs.len() == 1;
906let mut configs = configs.into_iter();
907let mut nodes = Vec::new();
908909let Some(first) = configs.next() else {
910return ProcessConfigsResult {
911 nodes: Vec::new(),
912densely_chained,
913 };
914 };
915let mut previous_result = self.process_configs(first, collect_nodes || is_chained);
916densely_chained &= previous_result.densely_chained;
917918for current in configs {
919let current_result = self.process_configs(current, collect_nodes || is_chained);
920 densely_chained &= current_result.densely_chained;
921922if let Chain::Chained(chain_options) = &metadata {
923// if the current result is densely chained, we only need to chain the first node
924let current_nodes = if current_result.densely_chained {
925¤t_result.nodes[..1]
926 } else {
927¤t_result.nodes
928 };
929// if the previous result was densely chained, we only need to chain the last node
930let previous_nodes = if previous_result.densely_chained {
931&previous_result.nodes[previous_result.nodes.len() - 1..]
932 } else {
933&previous_result.nodes
934 };
935936self.dependency
937 .reserve_edges(previous_nodes.len() * current_nodes.len());
938for previous_node in previous_nodes {
939for current_node in current_nodes {
940self.dependency.add_edge(*previous_node, *current_node);
941942if is_weak {
943self.weak_node_edges.insert((*previous_node, *current_node));
944 } else {
945self.strict_node_edges
946 .insert((*previous_node, *current_node));
947 }
948949for pass in self.passes.values_mut() {
950 pass.add_dependency(
951*previous_node,
952*current_node,
953 chain_options,
954 );
955 }
956 }
957 }
958 }
959if collect_nodes {
960 nodes.append(&mut previous_result.nodes);
961 }
962963 previous_result = current_result;
964 }
965if collect_nodes {
966nodes.append(&mut previous_result.nodes);
967 }
968969ProcessConfigsResult {
970nodes,
971densely_chained,
972 }
973 }
974 }
975 }
976977/// Add a [`ScheduleConfig`] to the graph, including its dependencies and conditions.
978fn add_system_inner(&mut self, config: ScheduleConfig<ScheduleSystem>) -> SystemKey {
979let key = self.systems.insert(config.node, config.conditions);
980981// graph updates are immediate
982self.update_graphs(NodeId::System(key), config.metadata);
983984key985 }
986987#[track_caller]
988fn configure_sets<M>(&mut self, sets: impl IntoScheduleConfigs<InternedSystemSet, M>) {
989self.process_configs(sets.into_configs(), false);
990 }
991992/// Add a single `ScheduleConfig` to the graph, including its dependencies and conditions.
993fn configure_set_inner(&mut self, config: ScheduleConfig<InternedSystemSet>) -> SystemSetKey {
994let key = self.system_sets.insert(config.node, config.conditions);
995996// graph updates are immediate
997self.update_graphs(NodeId::Set(key), config.metadata);
998999key1000 }
10011002fn create_anonymous_set(&mut self) -> AnonymousSet {
1003let id = self.anonymous_sets;
1004self.anonymous_sets += 1;
1005AnonymousSet::new(id)
1006 }
10071008/// 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.
1018pub fn systems_in_set(
1019&self,
1020 system_set: InternedSystemSet,
1021 ) -> Result<&IndexSet<SystemKey, FixedHasher>, ScheduleError> {
1022if self.changed {
1023return Err(ScheduleError::Uninitialized);
1024 }
1025let system_set_id = self
1026.system_sets
1027 .get_key(system_set)
1028 .ok_or(ScheduleError::SetNotFound)?;
1029self.set_systems
1030 .get(&system_set_id)
1031 .ok_or(ScheduleError::SetNotFound)
1032 }
10331034fn add_edges_for_transitive_dependencies(&mut self, node: NodeId) {
1035let in_nodes: Vec<_> = self.hierarchy.neighbors_directed(node, Incoming).collect();
1036let out_nodes: Vec<_> = self.hierarchy.neighbors_directed(node, Outgoing).collect();
10371038self.hierarchy
1039 .reserve_edges(in_nodes.len() * out_nodes.len());
1040for &in_node in &in_nodes {
1041for &out_node in &out_nodes {
1042self.hierarchy.add_edge(in_node, out_node);
1043 }
1044 }
10451046let in_nodes: Vec<_> = self.dependency.neighbors_directed(node, Incoming).collect();
1047let out_nodes: Vec<_> = self.dependency.neighbors_directed(node, Outgoing).collect();
10481049self.dependency
1050 .reserve_edges(in_nodes.len() * out_nodes.len());
1051for &in_node in &in_nodes {
1052for &out_node in &out_nodes {
1053self.dependency.add_edge(in_node, out_node);
1054 }
1055 }
1056 }
10571058/// Remove all systems in a set and any dependencies on those systems and set.
1059pub fn remove_systems_in_set<M>(
1060&mut self,
1061 system_set: impl IntoSystemSet<M>,
1062 policy: ScheduleCleanupPolicy,
1063 ) -> Result<usize, ScheduleError> {
1064let set = system_set.into_system_set();
1065let interned = set.intern();
1066// clone the keys out of the schedule as the systems are getting removed from self
1067let keys = self.systems_in_set(interned)?.clone();
10681069self.changed = true;
10701071match policy {
1072 ScheduleCleanupPolicy::RemoveSetAndSystemsAllowBreakages => {
1073let Some(set_key) = self.system_sets.get_key(interned) else {
1074return Err(ScheduleError::SetNotFound);
1075 };
10761077self.remove_systems_by_keys(&keys);
1078self.remove_set_by_key(set_key);
10791080Ok(keys.len())
1081 }
1082 ScheduleCleanupPolicy::RemoveSystemsOnlyAllowBreakages => {
1083self.remove_systems_by_keys(&keys);
10841085Ok(keys.len())
1086 }
1087 ScheduleCleanupPolicy::RemoveSetAndSystems => {
1088let Some(set_key) = self.system_sets.get_key(interned) else {
1089return Err(ScheduleError::SetNotFound);
1090 };
10911092for &key in &keys {
1093self.add_edges_for_transitive_dependencies(key.into());
1094 }
10951096self.add_edges_for_transitive_dependencies(set_key.into());
10971098self.remove_systems_by_keys(&keys);
1099self.remove_set_by_key(set_key);
11001101Ok(keys.len())
1102 }
1103 ScheduleCleanupPolicy::RemoveSystemsOnly => {
1104for &key in &keys {
1105self.add_edges_for_transitive_dependencies(key.into());
1106 }
11071108self.remove_systems_by_keys(&keys);
11091110Ok(keys.len())
1111 }
1112 }
1113 }
11141115fn remove_systems_by_keys(&mut self, keys: &IndexSet<SystemKey, FixedHasher>) {
1116for &key in keys {
1117self.systems.remove(key);
11181119let node = NodeId::from(key);
1120self.hierarchy.remove_node(node);
1121self.dependency.remove_node(node);
1122self.ambiguous_with.remove_node(node);
1123self.ambiguous_with_all.remove(&node);
1124self.weak_node_edges
1125 .retain(|&(from, to)| from != node && to != node);
1126self.strict_node_edges
1127 .retain(|&(from, to)| from != node && to != node);
1128 }
1129 }
11301131fn remove_set_by_key(&mut self, key: SystemSetKey) {
1132self.system_sets.remove(key);
1133self.set_systems.remove(&key);
1134let node = NodeId::from(key);
1135self.hierarchy.remove_node(node);
1136self.dependency.remove_node(node);
1137self.ambiguous_with.remove_node(node);
1138self.ambiguous_with_all.remove(&node);
1139self.weak_node_edges
1140 .retain(|&(from, to)| from != node && to != node);
1141self.strict_node_edges
1142 .retain(|&(from, to)| from != node && to != node);
1143 }
11441145/// Update the internal graphs (hierarchy, dependency, ambiguity) by adding a single [`GraphInfo`]
1146fn update_graphs(&mut self, id: NodeId, graph_info: GraphInfo) {
1147self.changed = true;
11481149let GraphInfo {
1150 hierarchy: sets,
1151 dependencies,
1152 ambiguous_with,
1153 ..
1154 } = graph_info;
11551156self.hierarchy.add_node(id);
1157self.dependency.add_node(id);
11581159for key in sets
1160 .into_iter()
1161 .map(|set| self.system_sets.get_key_or_insert(set))
1162 {
1163self.hierarchy.add_edge(NodeId::Set(key), id);
11641165// ensure set also appears in dependency graph
1166self.dependency.add_node(NodeId::Set(key));
1167 }
11681169for (kind, key, options) in
1170dependencies
1171 .into_iter()
1172 .map(|Dependency { kind, set, options }| {
1173 (kind, self.system_sets.get_key_or_insert(set), options)
1174 })
1175 {
1176let (lhs, rhs) = match kind {
1177 DependencyKind::Before => (id, NodeId::Set(key)),
1178 DependencyKind::After => (NodeId::Set(key), id),
1179 };
1180self.dependency.add_edge(lhs, rhs);
1181if options.contains_key(&TypeId::of::<Weak>()) {
1182self.weak_node_edges.insert((lhs, rhs));
1183 } else {
1184self.strict_node_edges.insert((lhs, rhs));
1185 }
1186for pass in self.passes.values_mut() {
1187 pass.add_dependency(lhs, rhs, &options);
1188 }
11891190// ensure set also appears in hierarchy graph
1191self.hierarchy.add_node(NodeId::Set(key));
1192 }
11931194match ambiguous_with {
1195 Ambiguity::Check => (),
1196 Ambiguity::IgnoreWithSet(ambiguous_with) => {
1197for key in ambiguous_with
1198 .into_iter()
1199 .map(|set| self.system_sets.get_key_or_insert(set))
1200 {
1201self.ambiguous_with.add_edge(id, NodeId::Set(key));
1202 }
1203 }
1204 Ambiguity::IgnoreAll => {
1205self.ambiguous_with_all.insert(id);
1206 }
1207 }
1208 }
12091210/// Initializes any newly-added systems and conditions by calling
1211 /// [`System::initialize`](crate::system::System).
1212pub fn initialize(&mut self, world: &mut World) {
1213self.systems.initialize(world);
1214self.system_sets.initialize(world);
1215 }
12161217/// 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
1224pub fn build_schedule(
1225&mut self,
1226 world: &mut World,
1227 ignored_ambiguities: &BTreeSet<ComponentId>,
1228 ) -> Result<(SystemSchedule, ScheduleBuildMetadata), ScheduleBuildError> {
1229let mut warnings = Vec::new();
12301231// Check system set memberships for cycles.
1232let hierarchy_analysis = self
1233.hierarchy
1234 .analyze()
1235 .map_err(ScheduleBuildError::HierarchySort)?;
12361237// Check for redundant system set memberships, logging warnings or
1238 // returning errors as configured.
1239if self.settings.hierarchy_detection != LogLevel::Ignore1240 && let Err(e) = hierarchy_analysis.check_for_redundant_edges()
1241 {
1242match self.settings.hierarchy_detection {
1243 LogLevel::Error => return Err(ScheduleBuildWarning::HierarchyRedundancy(e).into()),
1244 LogLevel::Warn => warnings.push(ScheduleBuildWarning::HierarchyRedundancy(e)),
1245 LogLevel::Ignore => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
1246 }
1247 }
1248// Remove redundant system set memberships.
1249self.hierarchy.remove_redundant_edges(&hierarchy_analysis);
12501251// Check system and system set ordering dependencies for cycles.
1252let dependency_analysis = self
1253.dependency
1254 .analyze()
1255 .map_err(ScheduleBuildError::DependencySort)?;
12561257// System sets that share systems and have an ordering dependency cannot be ordered.
1258dependency_analysis.check_for_cross_dependencies(&hierarchy_analysis)?;
12591260// Group all systems by the system sets they belong to.
1261self.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.
1266dependency_analysis.check_for_overlapping_groups(&self.set_systems)?;
12671268// There can be no edges to system-type sets that have multiple instances.
1269self.system_sets.check_type_set_ambiguity(
1270&self.set_systems,
1271&self.ambiguous_with,
1272&self.dependency,
1273 )?;
12741275// 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.
1278let mut flat_dependency =
1279self.set_systems
1280 .flatten(self.dependency.clone(), |set, systems, flattening, temp| {
1281for pass in self.passes.values_mut() {
1282 pass.collapse_set(set, systems, flattening, temp);
1283 }
1284 });
12851286// Allow modification of the schedule graph by build passes.
1287let mut passes = core::mem::take(&mut self.passes);
1288let mut added_edges = Default::default();
1289for pass in passes.values_mut() {
1290 pass.build(
1291 world,
1292self,
1293 FlattenedDependencies {
1294 dag: &mut flat_dependency,
1295 added_edges: &mut added_edges,
1296 },
1297 )?;
1298 }
1299self.passes = passes;
13001301// Initialize any systems that were added by build passes. This ensures
1302 // that ApplyDeferred systems are recognized as exclusive.
1303self.initialize(world);
13041305#[cfg(feature = "debug")]
1306if 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.
1309let mut rng = rand::rngs::Xoshiro128PlusPlus::seed_from_u64(shuffle_seed);
13101311let mut nodes = flat_dependency.graph().nodes().collect::<Vec<_>>();
1312nodes.shuffle(&mut rng);
1313let mut new_flat_dependency = Dag::new();
1314for &node in &nodes {
1315 new_flat_dependency.add_node(node);
1316 }
1317for node in nodes {
1318for neighbor in flat_dependency.neighbors(node) {
1319 new_flat_dependency.add_edge(node, neighbor);
1320 }
1321 }
1322flat_dependency = new_flat_dependency;
1323 }
13241325// 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.
1329let flat_dependency_analysis = flat_dependency
1330 .analyze()
1331 .map_err(ScheduleBuildError::FlatDependencySort)?;
13321333// Resolve the weak edges into ordinary edges, keeping an ordering only between systems
1334 // that actually conflict and dropping it everywhere else.
1335let resolved_weak_edges =
1336self.resolve_weak_edges(&mut flat_dependency, &flat_dependency_analysis);
13371338// Resolving weak edges mutates the graph, so recompute the analysis when it did. This
1339 // analysis is also the basis for ambiguity detection below.
1340let flat_dependency_analysis = if resolved_weak_edges {
1341 flat_dependency
1342 .analyze()
1343 .map_err(ScheduleBuildError::FlatDependencySort)?
1344} else {
1345flat_dependency_analysis1346 };
1347flat_dependency.remove_redundant_edges(&flat_dependency_analysis);
13481349// 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.
1353let flat_ambiguous_with = self.set_systems.flatten_undirected(&self.ambiguous_with);
13541355// Find all system ordering ambiguities, ignoring those that are accepted.
1356self.conflicting_systems = self.systems.get_conflicting_systems(
1357&flat_dependency_analysis,
1358&flat_ambiguous_with,
1359&self.ambiguous_with_all,
1360ignored_ambiguities,
1361 );
1362// If there are any ambiguities, log warnings or return errors as configured.
1363if self.settings.ambiguity_detection != LogLevel::Ignore1364 && let Err(e) = self.conflicting_systems.check_if_not_empty()
1365 {
1366match self.settings.ambiguity_detection {
1367 LogLevel::Error => return Err(ScheduleBuildWarning::Ambiguity(e).into()),
1368 LogLevel::Warn => warnings.push(ScheduleBuildWarning::Ambiguity(e)),
1369 LogLevel::Ignore => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
1370 }
1371 }
13721373// build the schedule
1374Ok((
1375self.build_schedule_inner(flat_dependency, hierarchy_analysis),
1376ScheduleBuildMetadata {
1377warnings,
1378 edges_added_by_build_passes: added_edges,
1379 },
1380 ))
1381 }
13821383/// 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.
1396fn resolve_weak_edges(
1397&self,
1398 flat_dependency: &mut Dag<SystemKey>,
1399 analysis: &DagAnalysis<SystemKey>,
1400 ) -> bool {
1401let weak_edges = self.flat_node_edges(&self.weak_node_edges, flat_dependency);
1402if weak_edges.is_empty() {
1403return false;
1404 }
1405let strict_edges = self.flat_node_edges(&self.strict_node_edges, flat_dependency);
1406let condition_accesses = self.condition_accesses();
14071408// 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`.
1411for (from, to) in analysis.transitive_closure().all_edges() {
1412if self.systems_conflict(from, to, &condition_accesses) {
1413 flat_dependency.add_edge(from, to);
1414 }
1415 }
14161417// 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.
1420for &(from, to) in &weak_edges {
1421if !self.systems_conflict(from, to, &condition_accesses)
1422 && !strict_edges.contains(&(from, to))
1423 {
1424 flat_dependency.remove_edge(from, to);
1425 }
1426 }
14271428true
1429}
14301431/// 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.
1436fn condition_accesses(&self) -> HashMap<SystemKey, Vec<&SystemAccess>> {
1437let mut accesses: HashMap<SystemKey, Vec<&SystemAccess>> = HashMap::default();
1438for (key, _, conditions) in self.systems.iter() {
1439for condition in conditions {
1440 accesses.entry(key).or_default().push(&condition.access);
1441 }
1442 }
1443for (key, _, conditions) in self.system_sets.iter() {
1444if conditions.is_empty() {
1445continue;
1446 }
1447let Some(systems) = self.set_systems.get(&key) else {
1448continue;
1449 };
1450for &system in systems {
1451 accesses
1452 .entry(system)
1453 .or_default()
1454 .extend(conditions.iter().map(|condition| &condition.access));
1455 }
1456 }
1457accesses1458 }
14591460/// 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.
1466fn flat_node_edges(
1467&self,
1468 node_edges: &HashSet<(NodeId, NodeId)>,
1469 flat_dependency: &Dag<SystemKey>,
1470 ) -> HashSet<(SystemKey, SystemKey)> {
1471let systems_of = |node: NodeId| -> Vec<SystemKey> {
1472match node {
1473 NodeId::System(key) => ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[key]))vec![key],
1474 NodeId::Set(key) => self1475 .set_systems
1476 .get(&key)
1477 .map(|systems| systems.iter().copied().collect())
1478 .unwrap_or_default(),
1479 }
1480 };
14811482let mut edges = HashSet::default();
1483for &(from, to) in node_edges {
1484let (from_s, to_s) = (systems_of(from), systems_of(to));
1485for &from in &from_s {
1486for &to in &to_s {
1487if flat_dependency.contains_edge(from, to) {
1488 edges.insert((from, to));
1489 }
1490 }
1491 }
1492 }
1493edges1494 }
14951496/// 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.
1504fn systems_conflict(
1505&self,
1506 from: SystemKey,
1507 to: SystemKey,
1508 condition_accesses: &HashMap<SystemKey, Vec<&SystemAccess>>,
1509 ) -> bool {
1510let (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.
1513let conditions_conflict = |system_access: &SystemAccess, other: SystemKey| {
1514condition_accesses.get(&other).is_some_and(|accesses| {
1515accesses1516 .iter()
1517 .any(|access| !system_access.is_compatible(access))
1518 })
1519 };
15201521from_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 }
15281529fn build_schedule_inner(
1530&self,
1531 flat_dependency: Dag<SystemKey>,
1532 hierarchy_analysis: DagAnalysis<NodeId>,
1533 ) -> SystemSchedule {
1534let dg_system_ids = flat_dependency.get_toposort().unwrap().to_vec();
1535let dg_system_idx_map = dg_system_ids1536 .iter()
1537 .cloned()
1538 .enumerate()
1539 .map(|(i, id)| (id, i))
1540 .collect::<HashMap<_, _>>();
15411542let hierarchy_toposort = self.hierarchy.get_toposort().unwrap();
1543let hg_systems = hierarchy_toposort1544 .iter()
1545 .cloned()
1546 .enumerate()
1547 .filter_map(|(i, id)| Some((i, id.as_system()?)))
1548 .collect::<Vec<_>>();
1549let (hg_set_with_conditions_idxs, hg_set_ids): (Vec<_>, Vec<_>) = hierarchy_toposort1550 .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)
1556let key = id.as_set()?;
1557self.system_sets.has_conditions(key).then_some((i, key))
1558 })
1559 .unzip();
15601561let sys_count = self.systems.len();
1562let set_with_conditions_count = hg_set_ids.len();
1563let hg_node_count = self.hierarchy.node_count();
15641565// Get the dependencies and immediate dependents of each system, needed by the
1566 // multi_threaded executor to run systems in the correct order.
1567let mut system_dependencies = Vec::with_capacity(sys_count);
1568let mut system_dependents = Vec::with_capacity(sys_count);
1569for &sys_key in &dg_system_ids {
1570let num_dependencies = flat_dependency
1571 .neighbors_directed(sys_key, Incoming)
1572 .count();
15731574let dependents = flat_dependency
1575 .neighbors_directed(sys_key, Outgoing)
1576 .map(|dep_id| dg_system_idx_map[&dep_id])
1577 .collect::<Vec<_>>();
15781579 system_dependencies.push(num_dependencies);
1580 system_dependents.push(dependents);
1581 }
15821583// get the rows and columns of the hierarchy graph's reachability matrix
1584 // (needed to we can evaluate conditions in the correct order)
1585let mut systems_in_sets_with_conditions =
1586::alloc::vec::from_elem(FixedBitSet::with_capacity(sys_count),
set_with_conditions_count)vec![FixedBitSet::with_capacity(sys_count); set_with_conditions_count];
1587for (i, &row) in hg_set_with_conditions_idxs.iter().enumerate() {
1588let bitset = &mut systems_in_sets_with_conditions[i];
1589for &(col, sys_key) in &hg_systems {
1590let idx = dg_system_idx_map[&sys_key];
1591let is_descendant = hierarchy_analysis.reachable()[index(row, col, hg_node_count)];
1592 bitset.set(idx, is_descendant);
1593 }
1594 }
15951596let mut sets_with_conditions_of_systems =
1597::alloc::vec::from_elem(FixedBitSet::with_capacity(set_with_conditions_count),
sys_count)vec![FixedBitSet::with_capacity(set_with_conditions_count); sys_count];
1598for &(col, sys_key) in &hg_systems {
1599let i = dg_system_idx_map[&sys_key];
1600let bitset = &mut sets_with_conditions_of_systems[i];
1601for (idx, &row) in hg_set_with_conditions_idxs
1602 .iter()
1603 .enumerate()
1604 .take_while(|&(_idx, &row)| row < col)
1605 {
1606let is_ancestor = hierarchy_analysis.reachable()[index(row, col, hg_node_count)];
1607 bitset.set(idx, is_ancestor);
1608 }
1609 }
16101611SystemSchedule {
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,
1617system_dependencies,
1618system_dependents,
1619sets_with_conditions_of_systems,
1620systems_in_sets_with_conditions,
1621 }
1622 }
16231624/// Updates the `SystemSchedule` from the `ScheduleGraph`.
1625fn 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> {
1632if !self.systems.is_initialized() || !self.system_sets.is_initialized() {
1633return Err(ScheduleBuildError::Uninitialized);
1634 }
16351636// move systems out of old schedule
1637for ((key, system), conditions) in schedule
1638 .system_ids
1639 .drain(..)
1640 .zip(schedule.systems.drain(..))
1641 .zip(schedule.system_conditions.drain(..))
1642 {
1643if let Some(node) = self.systems.node_mut(key) {
1644 node.inner = Some(system);
1645 }
16461647if let Some(node_conditions) = self.systems.get_conditions_mut(key) {
1648*node_conditions = conditions;
1649 }
1650 }
16511652for (key, conditions) in schedule
1653 .set_ids
1654 .drain(..)
1655 .zip(schedule.set_conditions.drain(..))
1656 {
1657if let Some(node_conditions) = self.system_sets.get_conditions_mut(key) {
1658*node_conditions = conditions;
1659 }
1660 }
16611662let (new_schedule, build_metadata) = self.build_schedule(world, ignored_ambiguities)?;
1663*schedule = new_schedule;
16641665for warning in &build_metadata.warnings {
1666{
{
let lvl = ::log::Level::Warn;
if lvl <= ::log::STATIC_MAX_LEVEL && lvl <= ::log::max_level() {
::log::__private_api::log({ ::log::__private_api::GlobalLogger },
format_args!("{0:?} schedule built successfully, however: {1}",
schedule_label, warning.to_string(self, world)), lvl,
&("bevy_ecs::schedule::schedule",
"bevy_ecs::schedule::schedule",
::log::__private_api::loc()), ());
}
}
};warn!(
1667"{:?} schedule built successfully, however: {}",
1668 schedule_label,
1669 warning.to_string(self, world)
1670 );
1671 }
16721673// move systems into new schedule
1674for &key in &schedule.system_ids {
1675let system = self.systems.node_mut(key).unwrap().inner.take().unwrap();
1676let conditions = core::mem::take(self.systems.get_conditions_mut(key).unwrap());
1677 schedule.systems.push(system);
1678 schedule.system_conditions.push(conditions);
1679 }
16801681for &key in &schedule.set_ids {
1682let conditions = core::mem::take(self.system_sets.get_conditions_mut(key).unwrap());
1683 schedule.set_conditions.push(conditions);
1684 }
16851686Ok(build_metadata)
1687 }
1688}
16891690/// 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
1694nodes: 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
1698densely_chained: bool,
1699}
17001701/// Trait used by [`ScheduleGraph::process_configs`] to process a single [`ScheduleConfig`].
1702trait ProcessScheduleConfig: Schedulable + Sized {
1703/// Process a single [`ScheduleConfig`].
1704fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig<Self>) -> NodeId;
1705}
17061707impl ProcessScheduleConfigfor ScheduleSystem {
1708fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig<Self>) -> NodeId {
1709 NodeId::System(schedule_graph.add_system_inner(config))
1710 }
1711}
17121713impl ProcessScheduleConfigfor InternedSystemSet {
1714fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig<Self>) -> NodeId {
1715 NodeId::Set(schedule_graph.configure_set_inner(config))
1716 }
1717}
17181719/// Policy to use when removing systems.
1720#[derive(#[automatically_derived]
impl ::core::default::Default for ScheduleCleanupPolicy {
#[inline]
fn default() -> Self { Self::RemoveSetAndSystems }
}Default, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for ScheduleCleanupPolicy { }
#[automatically_derived]
impl ::core::clone::Clone for ScheduleCleanupPolicy {
#[inline]
fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for ScheduleCleanupPolicy { }Copy, #[automatically_derived]
impl ::core::fmt::Debug for ScheduleCleanupPolicy {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f,
match self {
ScheduleCleanupPolicy::RemoveSetAndSystems =>
"RemoveSetAndSystems",
ScheduleCleanupPolicy::RemoveSystemsOnly =>
"RemoveSystemsOnly",
ScheduleCleanupPolicy::RemoveSetAndSystemsAllowBreakages =>
"RemoveSetAndSystemsAllowBreakages",
ScheduleCleanupPolicy::RemoveSystemsOnlyAllowBreakages =>
"RemoveSystemsOnlyAllowBreakages",
})
}
}Debug, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for ScheduleCleanupPolicy { }
#[automatically_derived]
impl ::core::cmp::PartialEq for ScheduleCleanupPolicy {
#[inline]
fn eq(&self, other: &Self) -> bool {
::core::intrinsics::discriminant_value(self) ==
::core::intrinsics::discriminant_value(other)
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for ScheduleCleanupPolicy { }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]
1727RemoveSetAndSystems,
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.
1731RemoveSystemsOnly,
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.
1736RemoveSetAndSystemsAllowBreakages,
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.
1740RemoveSystemsOnlyAllowBreakages,
1741}
17421743// 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.
1751pub fn get_node_name(&self, id: &NodeId) -> String {
1752self.get_node_name_inner(id, self.settings.report_sets)
1753 }
17541755#[inline]
1756fn get_node_name_inner(&self, id: &NodeId, report_sets: bool) -> String {
1757match *id {
1758 NodeId::System(key) => {
1759let name = self.systems[key].name();
1760let name = if self.settings.use_shortnames {
1761name.shortname().to_string()
1762 } else {
1763name.to_string()
1764 };
1765if report_sets {
1766let sets = self.names_of_sets_containing_node(id);
1767if sets.is_empty() {
1768name1769 } else if sets.len() == 1 {
1770::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{1} (in set {0})", sets[0], name))
})format!("{name} (in set {})", sets[0])1771 } else {
1772::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{1} (in sets {0})",
sets.join(", "), name))
})format!("{name} (in sets {})", sets.join(", "))1773 }
1774 } else {
1775name1776 }
1777 }
1778 NodeId::Set(key) => {
1779let set = &self.system_sets[key];
1780if set.is_anonymous() {
1781self.anonymous_set_name(id)
1782 } else {
1783::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0:?}", set))
})format!("{set:?}")1784 }
1785 }
1786 }
1787 }
17881789fn anonymous_set_name(&self, id: &NodeId) -> String {
1790::alloc::__export::must_use({
::alloc::fmt::format(format_args!("({0})",
self.hierarchy.edges_directed(*id,
Outgoing).map(|(_, member_id)|
self.get_node_name_inner(&member_id,
false)).reduce(|a, b|
::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}, {1}", a, b))
})).unwrap_or_default()))
})format!(
1791"({})",
1792self.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 }
18001801fn traverse_sets_containing_node(&self, id: NodeId, f: &mut impl FnMut(SystemSetKey) -> bool) {
1802for (set_id, _) in self.hierarchy.edges_directed(id, Incoming) {
1803let NodeId::Set(set_key) = set_id else {
1804continue;
1805 };
1806if f(set_key) {
1807self.traverse_sets_containing_node(NodeId::Set(set_key), f);
1808 }
1809 }
1810 }
18111812fn names_of_sets_containing_node(&self, id: &NodeId) -> Vec<String> {
1813let mut sets = <HashSet<_>>::default();
1814self.traverse_sets_containing_node(*id, &mut |key| {
1815self.system_sets[key].system_type().is_none() && sets.insert(key)
1816 });
1817let mut sets: Vec<_> = sets1818 .into_iter()
1819 .map(|key| self.get_node_name(&NodeId::Set(key)))
1820 .collect();
1821sets.sort();
1822sets1823 }
1824}
18251826/// Specifies how schedule construction should respond to detecting a certain kind of issue.
1827#[derive(#[automatically_derived]
impl ::core::fmt::Debug for LogLevel {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f,
match self {
LogLevel::Ignore => "Ignore",
LogLevel::Warn => "Warn",
LogLevel::Error => "Error",
})
}
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for LogLevel { }
#[automatically_derived]
impl ::core::clone::Clone for LogLevel {
#[inline]
fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for LogLevel { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for LogLevel { }
#[automatically_derived]
impl ::core::cmp::PartialEq for LogLevel {
#[inline]
fn eq(&self, other: &Self) -> bool {
::core::intrinsics::discriminant_value(self) ==
::core::intrinsics::discriminant_value(other)
}
}PartialEq)]
1828pub enum LogLevel {
1829/// Occurrences are completely ignored.
1830Ignore,
1831/// Occurrences are logged only.
1832Warn,
1833/// Occurrences are logged and result in errors.
1834Error,
1835}
18361837/// Specifies miscellaneous settings for schedule construction.
1838#[derive(#[automatically_derived]
impl ::core::clone::Clone for ScheduleBuildSettings {
#[inline]
fn clone(&self) -> Self {
Self {
ambiguity_detection: ::core::clone::Clone::clone(&self.ambiguity_detection),
hierarchy_detection: ::core::clone::Clone::clone(&self.hierarchy_detection),
auto_insert_apply_deferred: ::core::clone::Clone::clone(&self.auto_insert_apply_deferred),
use_shortnames: ::core::clone::Clone::clone(&self.use_shortnames),
report_sets: ::core::clone::Clone::clone(&self.report_sets),
shuffle_seed: ::core::clone::Clone::clone(&self.shuffle_seed),
}
}
}Clone, #[automatically_derived]
impl ::core::fmt::Debug for ScheduleBuildSettings {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
let names: &'static _ =
&["ambiguity_detection", "hierarchy_detection",
"auto_insert_apply_deferred", "use_shortnames",
"report_sets", "shuffle_seed"];
let values: &[&dyn ::core::fmt::Debug] =
&[&self.ambiguity_detection, &self.hierarchy_detection,
&self.auto_insert_apply_deferred, &self.use_shortnames,
&self.report_sets, &&self.shuffle_seed];
::core::fmt::Formatter::debug_struct_fields_finish(f,
"ScheduleBuildSettings", names, values)
}
}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`].
1845pub 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`].
1851pub 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`
1861pub 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`.
1865pub use_shortnames: bool,
1866/// If set to true, report all system sets the conflicting systems are part of.
1867 ///
1868 /// Defaults to `true`.
1869pub 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")]
1888pub shuffle_seed: Option<u64>,
1889}
18901891impl Defaultfor ScheduleBuildSettings {
1892fn default() -> Self {
1893Self::new()
1894 }
1895}
18961897impl ScheduleBuildSettings {
1898/// Default build settings.
1899 /// See the field-level documentation for the default value of each field.
1900pub const fn new() -> Self {
1901Self {
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")]
1908shuffle_seed: None,
1909 }
1910 }
1911}
19121913/// Metadata about the schedule build process.
1914pub struct ScheduleBuildMetadata {
1915/// Warnings about the schedule graph detected by the build process.
1916pub 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.
1921pub edges_added_by_build_passes: HashSet<(SystemKey, SystemKey)>,
1922}
19231924/// 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(impl bevy_ecs::event::Event for ScheduleBuilt where
Self: ::core::marker::Send + ::core::marker::Sync + 'static {
type Trigger<'a> = bevy_ecs::event::GlobalTrigger;
}Event)]
1930pub struct ScheduleBuilt {
1931/// The schedule that was built.
1932pub label: InternedScheduleLabel,
1933/// The metadata for the build process of this schedule.
1934pub build_metadata: ScheduleBuildMetadata,
1935}
19361937/// Error to denote that [`Schedule::initialize`] or [`Schedule::run`] has not yet been called for
1938/// this schedule.
1939#[derive(#[allow(unused_qualifications)]
#[automatically_derived]
impl ::thiserror::__private21::Error for ScheduleNotInitialized { }
#[allow(unused_qualifications)]
#[automatically_derived]
impl ::core::fmt::Display for ScheduleNotInitialized {
#[allow(clippy :: used_underscore_binding)]
fn fmt(&self, __formatter: &mut ::core::fmt::Formatter)
-> ::core::fmt::Result {
#[allow(unused_variables, deprecated)]
let Self {} = self;
__formatter.write_str("executable schedule has not been built")
}
}Error, #[automatically_derived]
impl ::core::fmt::Debug for ScheduleNotInitialized {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f, "ScheduleNotInitialized")
}
}Debug)]
1940#[error("executable schedule has not been built")]
1941pub struct ScheduleNotInitialized;
19421943#[cfg(test)]
1944mod tests {
1945use alloc::{vec, vec::Vec};
1946use core::any::TypeId;
19471948use bevy_ecs_macros::ScheduleLabel;
19491950use 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 };
19611962use super::Schedules;
19631964#[derive(Resource)]
1965struct Resource1;
19661967#[derive(Resource)]
1968struct Resource2;
19691970#[test]
1971fn unchanged_auto_insert_apply_deferred_has_no_effect() {
1972use alloc::{vec, vec::Vec};
19731974#[derive(PartialEq, Debug)]
1975enum Entry {
1976 System(usize),
1977 SyncPoint(usize),
1978 }
19791980#[derive(Resource, Default)]
1981struct Log(Vec<Entry>);
19821983fn 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 }
19881989let mut world = World::default();
1990 world.init_resource::<Log>();
1991let 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);
1998let actual = world.remove_resource::<Log>().unwrap().0;
19992000let expected = vec![
2001 Entry::System(1),
2002 Entry::System(2),
2003 Entry::SyncPoint(1),
2004 Entry::SyncPoint(2),
2005 ];
20062007assert_eq!(actual, expected);
2008 }
20092010// regression test for https://github.com/bevyengine/bevy/issues/9114
2011#[test]
2012fn ambiguous_with_not_breaking_run_conditions() {
2013#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
2014struct Set;
20152016let mut world = World::new();
2017let mut schedule = Schedule::default();
20182019let system: fn() = || {
2020panic!("This system must not run");
2021 };
20222023 schedule.configure_sets(Set.run_if(|| false));
2024 schedule.add_systems(system.ambiguous_with(|| ()).in_set(Set));
2025 schedule.run(&mut world);
2026 }
20272028#[test]
2029fn inserts_a_sync_point() {
2030let mut schedule = Schedule::default();
2031let 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);
20402041// inserted a sync point
2042assert_eq!(schedule.executable.systems.len(), 3);
2043 }
20442045#[test]
2046fn explicit_sync_point_used_as_auto_sync_point() {
2047let mut schedule = Schedule::default();
2048let 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);
20582059// No sync point was inserted, since we can reuse the explicit sync point.
2060assert_eq!(schedule.executable.systems.len(), 5);
2061 }
20622063#[test]
2064fn conditional_explicit_sync_point_not_used_as_auto_sync_point() {
2065let mut schedule = Schedule::default();
2066let 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);
20762077// A sync point was inserted, since the explicit sync point is not always run.
2078assert_eq!(schedule.executable.systems.len(), 6);
2079 }
20802081#[test]
2082fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_chain() {
2083let mut schedule = Schedule::default();
2084let 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);
20942095// A sync point was inserted, since the explicit sync point is not always run.
2096assert_eq!(schedule.executable.systems.len(), 6);
2097 }
20982099#[test]
2100fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_system_set() {
2101#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
2102struct Set;
21032104let mut schedule = Schedule::default();
2105let 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);
21162117// A sync point was inserted, since the explicit sync point is not always run.
2118assert_eq!(schedule.executable.systems.len(), 6);
2119 }
21202121#[test]
2122fn conditional_explicit_sync_point_not_used_as_auto_sync_point_condition_on_nested_system_set()
2123 {
2124#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
2125struct Set1;
2126#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
2127struct Set2;
21282129let mut schedule = Schedule::default();
2130let 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);
21422143// A sync point was inserted, since the explicit sync point is not always run.
2144assert_eq!(schedule.executable.systems.len(), 6);
2145 }
21462147#[test]
2148fn merges_sync_points_into_one() {
2149let mut schedule = Schedule::default();
2150let mut world = World::default();
2151// insert two parallel command systems, it should only create one sync point
2152schedule.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);
21632164// inserted sync points
2165assert_eq!(schedule.executable.systems.len(), 4);
21662167// merges sync points on rebuild
2168schedule.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);
21772178assert_eq!(schedule.executable.systems.len(), 7);
2179 }
21802181#[test]
2182fn adds_multiple_consecutive_syncs() {
2183let mut schedule = Schedule::default();
2184let mut world = World::default();
2185// insert two consecutive command systems, it should create two sync points
2186schedule.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);
21952196assert_eq!(schedule.executable.systems.len(), 5);
2197 }
21982199#[test]
2200fn do_not_consider_ignore_deferred_before_exclusive_system() {
2201let mut schedule = Schedule::default();
2202let mut world = World::default();
2203// chain_ignore_deferred adds no sync points usually but an exception is made for exclusive systems
2204schedule.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);
22192220assert_eq!(schedule.executable.systems.len(), 6); // 5 systems + 1 sync point
2221}
22222223#[test]
2224fn bubble_sync_point_through_ignore_deferred_node() {
2225let mut schedule = Schedule::default();
2226let mut world = World::default();
22272228let 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();
22362237 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 );
22462247// 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.
22502251schedule.run(&mut world);
22522253assert_eq!(schedule.executable.systems.len(), 4); // 3 systems + 1 sync point
2254}
22552256#[test]
2257fn disable_auto_sync_points() {
2258let mut schedule = Schedule::default();
2259 schedule.set_build_settings(ScheduleBuildSettings {
2260 auto_insert_apply_deferred: false,
2261 ..Default::default()
2262 });
2263let 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);
22722273assert_eq!(schedule.executable.systems.len(), 2);
2274 }
22752276mod no_sync_edges {
2277use super::*;
22782279fn insert_resource(mut commands: Commands) {
2280 commands.insert_resource(Resource1);
2281 }
22822283fn resource_does_not_exist(res: Option<Res<Resource1>>) {
2284assert!(res.is_none());
2285 }
22862287#[derive(SystemSet, Hash, PartialEq, Eq, Debug, Clone)]
2288enum Sets {
2289 A,
2290 B,
2291 }
22922293fn check_no_sync_edges(add_systems: impl FnOnce(&mut Schedule)) {
2294let mut schedule = Schedule::default();
2295let mut world = World::default();
2296 add_systems(&mut schedule);
22972298 schedule.run(&mut world);
22992300assert_eq!(schedule.executable.systems.len(), 2);
2301 }
23022303#[test]
2304fn 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 }
23122313#[test]
2314fn 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 }
23222323#[test]
2324fn 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 }
23312332#[test]
2333fn 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 }
23402341#[test]
2342fn 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 }
23522353#[test]
2354fn 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 }
23652366mod no_sync_chain {
2367use super::*;
23682369#[derive(Resource)]
2370struct Ra;
23712372#[derive(Resource)]
2373struct Rb;
23742375#[derive(Resource)]
2376struct Rc;
23772378fn run_schedule(expected_num_systems: usize, add_systems: impl FnOnce(&mut Schedule)) {
2379let mut schedule = Schedule::default();
2380let mut world = World::default();
2381 add_systems(&mut schedule);
23822383 schedule.run(&mut world);
23842385assert_eq!(schedule.executable.systems.len(), expected_num_systems);
2386 }
23872388#[test]
2389fn 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>>| {
2399assert!(res_a.is_some());
2400assert!(res_b.is_some());
2401 },
2402 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2403assert!(res_a.is_some());
2404assert!(res_b.is_some());
2405 },
2406 ),
2407 )
2408 .chain(),
2409 );
2410 });
24112412 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>>| {
2421assert!(res_a.is_none());
2422assert!(res_b.is_none());
2423 },
2424 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2425assert!(res_a.is_none());
2426assert!(res_b.is_none());
2427 },
2428 ),
2429 )
2430 .chain_ignore_deferred(),
2431 );
2432 });
2433 }
24342435#[test]
2436fn 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);
2444assert!(res_a.is_some());
2445 },
2446 )
2447 .chain(),
2448 (
2449 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2450assert!(res_a.is_some());
2451assert!(res_b.is_some());
2452 },
2453 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2454assert!(res_a.is_some());
2455assert!(res_b.is_some());
2456 },
2457 ),
2458 )
2459 .chain(),
2460 );
2461 });
24622463 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);
2470assert!(res_a.is_some());
2471 },
2472 )
2473 .chain(),
2474 (
2475 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2476assert!(res_a.is_some());
2477assert!(res_b.is_none());
2478 },
2479 |res_a: Option<Res<Ra>>, res_b: Option<Res<Rb>>| {
2480assert!(res_a.is_some());
2481assert!(res_b.is_none());
2482 },
2483 ),
2484 )
2485 .chain_ignore_deferred(),
2486 );
2487 });
2488 }
24892490#[test]
2491fn 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);
2504assert!(res_a.is_some());
2505assert!(res_b.is_some());
2506 },
2507 |res_a: Option<Res<Ra>>,
2508 res_b: Option<Res<Rb>>,
2509 res_c: Option<Res<Rc>>| {
2510assert!(res_a.is_some());
2511assert!(res_b.is_some());
2512assert!(res_c.is_some());
2513 },
2514 )
2515 .chain(),
2516 )
2517 .chain(),
2518 );
2519 });
25202521 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);
2533assert!(res_a.is_none());
2534assert!(res_b.is_none());
2535 },
2536 |res_a: Option<Res<Ra>>,
2537 res_b: Option<Res<Rb>>,
2538 res_c: Option<Res<Rc>>| {
2539assert!(res_a.is_some());
2540assert!(res_b.is_some());
2541assert!(res_c.is_some());
2542 },
2543 )
2544 .chain(),
2545 )
2546 .chain_ignore_deferred(),
2547 );
2548 });
2549 }
25502551#[test]
2552fn 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);
2560assert!(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);
2569assert!(res_a.is_some());
2570assert!(res_b.is_some());
2571 },
2572 |res_a: Option<Res<Ra>>,
2573 res_b: Option<Res<Rb>>,
2574 res_c: Option<Res<Rc>>| {
2575assert!(res_a.is_some());
2576assert!(res_b.is_some());
2577assert!(res_c.is_some());
2578 },
2579 )
2580 .chain(),
2581 )
2582 .chain(),
2583 );
2584 });
25852586 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);
2593assert!(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);
2602assert!(res_a.is_some());
2603assert!(res_b.is_none());
2604 },
2605 |res_a: Option<Res<Ra>>,
2606 res_b: Option<Res<Rb>>,
2607 res_c: Option<Res<Rc>>| {
2608assert!(res_a.is_some());
2609assert!(res_b.is_some());
2610assert!(res_c.is_some());
2611 },
2612 )
2613 .chain(),
2614 )
2615 .chain_ignore_deferred(),
2616 );
2617 });
2618 }
2619 }
26202621#[derive(ScheduleLabel, Hash, Debug, Clone, PartialEq, Eq)]
2622struct TestSchedule;
26232624#[derive(Resource)]
2625struct CheckSystemRan(usize);
26262627#[test]
2628fn add_systems_to_existing_schedule() {
2629let mut schedules = Schedules::default();
2630let schedule = Schedule::new(TestSchedule);
26312632 schedules.insert(schedule);
2633 schedules.add_systems(TestSchedule, |mut ran: ResMut<CheckSystemRan>| ran.0 += 1);
26342635let mut world = World::new();
26362637 world.insert_resource(CheckSystemRan(0));
2638 world.insert_resource(schedules);
2639 world.run_schedule(TestSchedule);
26402641let value = world
2642 .get_resource::<CheckSystemRan>()
2643 .expect("CheckSystemRan Resource Should Exist");
2644assert_eq!(value.0, 1);
2645 }
26462647#[test]
2648fn add_systems_to_non_existing_schedule() {
2649let mut schedules = Schedules::default();
26502651 schedules.add_systems(TestSchedule, |mut ran: ResMut<CheckSystemRan>| ran.0 += 1);
26522653let mut world = World::new();
26542655 world.insert_resource(CheckSystemRan(0));
2656 world.insert_resource(schedules);
2657 world.run_schedule(TestSchedule);
26582659let value = world
2660 .get_resource::<CheckSystemRan>()
2661 .expect("CheckSystemRan Resource Should Exist");
2662assert_eq!(value.0, 1);
2663 }
26642665#[derive(SystemSet, Debug, Hash, Clone, PartialEq, Eq)]
2666enum TestSet {
2667 First,
2668 Second,
2669 }
26702671#[test]
2672fn configure_set_on_existing_schedule() {
2673let mut schedules = Schedules::default();
2674let schedule = Schedule::new(TestSchedule);
26752676 schedules.insert(schedule);
26772678 schedules.configure_sets(TestSchedule, (TestSet::First, TestSet::Second).chain());
2679 schedules.add_systems(
2680 TestSchedule,
2681 (|mut ran: ResMut<CheckSystemRan>| {
2682assert_eq!(ran.0, 0);
2683 ran.0 += 1;
2684 })
2685 .in_set(TestSet::First),
2686 );
26872688 schedules.add_systems(
2689 TestSchedule,
2690 (|mut ran: ResMut<CheckSystemRan>| {
2691assert_eq!(ran.0, 1);
2692 ran.0 += 1;
2693 })
2694 .in_set(TestSet::Second),
2695 );
26962697let mut world = World::new();
26982699 world.insert_resource(CheckSystemRan(0));
2700 world.insert_resource(schedules);
2701 world.run_schedule(TestSchedule);
27022703let value = world
2704 .get_resource::<CheckSystemRan>()
2705 .expect("CheckSystemRan Resource Should Exist");
2706assert_eq!(value.0, 2);
2707 }
27082709#[test]
2710fn configure_set_on_new_schedule() {
2711let mut schedules = Schedules::default();
27122713 schedules.configure_sets(TestSchedule, (TestSet::First, TestSet::Second).chain());
2714 schedules.add_systems(
2715 TestSchedule,
2716 (|mut ran: ResMut<CheckSystemRan>| {
2717assert_eq!(ran.0, 0);
2718 ran.0 += 1;
2719 })
2720 .in_set(TestSet::First),
2721 );
27222723 schedules.add_systems(
2724 TestSchedule,
2725 (|mut ran: ResMut<CheckSystemRan>| {
2726assert_eq!(ran.0, 1);
2727 ran.0 += 1;
2728 })
2729 .in_set(TestSet::Second),
2730 );
27312732let mut world = World::new();
27332734 world.insert_resource(CheckSystemRan(0));
2735 world.insert_resource(schedules);
2736 world.run_schedule(TestSchedule);
27372738let value = world
2739 .get_resource::<CheckSystemRan>()
2740 .expect("CheckSystemRan Resource Should Exist");
2741assert_eq!(value.0, 2);
2742 }
27432744#[test]
2745fn test_default_error_handler() {
2746#[derive(Resource, Default)]
2747struct Ran(bool);
27482749fn system(mut ran: ResMut<Ran>) -> Result {
2750 ran.0 = true;
2751Err("I failed!".into())
2752 }
27532754// Test that the fallback error handler is used
2755let mut world = World::default();
2756 world.init_resource::<Ran>();
2757 world.insert_resource(FallbackErrorHandler(ignore));
2758let mut schedule = Schedule::default();
2759 schedule.add_systems(system).run(&mut world);
2760assert!(world.resource::<Ran>().0);
27612762// Test that the handler doesn't change within the schedule
2763schedule.add_systems(
2764 (|world: &mut World| {
2765 world.insert_resource(FallbackErrorHandler(panic));
2766 })
2767 .before(system),
2768 );
2769 schedule.run(&mut world);
2770 }
27712772#[test]
2773fn get_a_system_key() {
2774fn test_system() {}
27752776let mut schedule = Schedule::default();
2777 schedule.add_systems(test_system);
2778let mut world = World::default();
2779let _ = schedule.initialize(&mut world);
27802781let keys = schedule
2782 .graph()
2783 .systems_in_set(test_system.into_system_set().intern())
2784 .unwrap();
2785assert_eq!(keys.len(), 1);
2786 }
27872788#[test]
2789fn get_system_keys_in_set() {
2790fn system_1() {}
2791fn system_2() {}
27922793let mut schedule = Schedule::default();
2794 schedule.add_systems((system_1, system_2).in_set(TestSet::First));
2795let mut world = World::default();
2796let _ = schedule.initialize(&mut world);
27972798let keys = schedule
2799 .graph()
2800 .systems_in_set(TestSet::First.into_system_set().intern())
2801 .unwrap();
2802assert_eq!(keys.len(), 2);
2803 }
28042805#[test]
2806fn get_system_keys_with_same_name() {
2807fn test_system() {}
28082809let mut schedule = Schedule::default();
2810 schedule.add_systems((test_system, test_system));
2811let mut world = World::default();
2812let _ = schedule.initialize(&mut world);
28132814let keys = schedule
2815 .graph()
2816 .systems_in_set(test_system.into_system_set().intern())
2817 .unwrap();
2818assert_eq!(keys.len(), 2);
2819 }
28202821#[test]
2822fn remove_a_system() {
2823fn system() {}
28242825let mut schedule = Schedule::default();
2826 schedule.add_systems(system);
2827let mut world = World::default();
28282829let remove_count = schedule.remove_systems_in_set(
2830 system,
2831&mut world,
2832 ScheduleCleanupPolicy::RemoveSetAndSystemsAllowBreakages,
2833 );
2834assert_eq!(remove_count.unwrap(), 1);
28352836// schedule has changed, so we check initializing again
2837schedule.initialize(&mut world).unwrap();
2838assert_eq!(schedule.graph().systems.len(), 0);
2839 }
28402841#[test]
2842fn remove_multiple_systems() {
2843fn system() {}
28442845let mut schedule = Schedule::default();
2846 schedule.add_systems((system, system));
2847let mut world = World::default();
28482849let remove_count = schedule.remove_systems_in_set(
2850 system,
2851&mut world,
2852 ScheduleCleanupPolicy::RemoveSetAndSystemsAllowBreakages,
2853 );
2854assert_eq!(remove_count.unwrap(), 2);
28552856// schedule has changed, so we check initializing again
2857schedule.initialize(&mut world).unwrap();
2858assert_eq!(schedule.graph().systems.len(), 0);
2859 }
28602861#[test]
2862fn remove_a_system_with_dependencies() {
2863fn system_1() {}
2864fn system_2() {}
28652866let mut schedule = Schedule::default();
2867 schedule.add_systems((system_1, system_2).chain());
2868let mut world = World::default();
28692870let remove_count = schedule.remove_systems_in_set(
2871 system_1,
2872&mut world,
2873 ScheduleCleanupPolicy::RemoveSetAndSystemsAllowBreakages,
2874 );
2875assert_eq!(remove_count.unwrap(), 1);
28762877// schedule has changed, so we check initializing again
2878schedule.initialize(&mut world).unwrap();
2879assert_eq!(schedule.graph().systems.len(), 1);
2880 }
28812882#[test]
2883fn remove_a_system_and_still_ordered() {
2884#[derive(Resource)]
2885struct A;
28862887fn system_1(_: ResMut<A>) {}
2888fn system_2() {}
2889fn system_3(_: ResMut<A>) {}
28902891let mut schedule = Schedule::default();
2892 schedule.add_systems((system_1, system_2, system_3).chain());
2893let mut world = World::new();
28942895let _ = schedule.remove_systems_in_set(
2896 system_2,
2897&mut world,
2898 ScheduleCleanupPolicy::RemoveSetAndSystems,
2899 );
29002901let result = schedule.initialize(&mut world);
2902assert!(result.is_ok());
2903let conflicts = schedule.graph().conflicting_systems();
2904assert!(conflicts.is_empty());
2905 }
29062907#[test]
2908fn remove_a_set_and_still_ordered() {
2909#[derive(Resource)]
2910struct A;
29112912#[derive(SystemSet, Hash, PartialEq, Eq, Clone, Debug)]
2913struct B;
29142915fn system_1(_: ResMut<A>) {}
2916fn system_2() {}
2917fn system_3(_: ResMut<A>) {}
29182919let mut schedule = Schedule::default();
2920 schedule.add_systems((system_1.before(B), system_2, system_3.after(B)));
2921let mut world = World::new();
29222923let _ = schedule.remove_systems_in_set(
2924 B,
2925&mut world,
2926 ScheduleCleanupPolicy::RemoveSetAndSystems,
2927 );
29282929let result = schedule.initialize(&mut world);
2930assert!(result.is_ok());
2931let conflicts = schedule.graph().conflicting_systems();
2932assert!(conflicts.is_empty());
2933 }
29342935#[test]
2936fn build_pass_iteration_order() {
2937#[derive(Debug)]
2938struct Pass<const N: usize>;
29392940impl<const N: usize> ScheduleBuildPass for Pass<N> {
2941type EdgeOptions = ();
2942fn add_dependency(
2943&mut self,
2944 _from: crate::schedule::NodeId,
2945 _to: crate::schedule::NodeId,
2946 _options: Option<&Self::EdgeOptions>,
2947 ) {
2948 }
2949fn build(
2950&mut self,
2951 _world: &mut World,
2952 _graph: &mut super::ScheduleGraph,
2953 _dependency_flattened: FlattenedDependencies<'_>,
2954 ) -> core::result::Result<(), crate::schedule::ScheduleBuildError> {
2955Ok(())
2956 }
2957fn collapse_set(
2958&mut self,
2959 _set: crate::schedule::SystemSetKey,
2960 _systems: &indexmap::IndexSet<
2961crate::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 }
29702971let 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>);
29752976let pass_order: Vec<TypeId> = schedule.graph().passes.keys().cloned().collect();
29772978assert_eq!(
2979 pass_order,
2980vec![
2981 TypeId::of::<AutoInsertApplyDeferredPass>(),
2982 TypeId::of::<Pass<0>>(),
2983 TypeId::of::<Pass<1>>(),
2984 TypeId::of::<Pass<2>>()
2985 ]
2986 );
2987 }
29882989#[cfg(feature = "debug")]
2990 #[test]
2991fn schedule_builds_randomly_with_shuffler() {
2992fn run_schedule_with_shuffler(shuffle_seed: Option<u64>) -> Vec<u32> {
2993#[derive(Resource, Default)]
2994struct Counters(Vec<u32>);
29952996let mut schedule = Schedule::default();
29972998// Note: we use a mutable resource to ensure that all the systems are conflicting and
2999 // therefore must be resolved by the system toposort.
3000fn system<const N: u32>(mut counters: ResMut<Counters>) {
3001 counters.0.push(N);
3002 }
30033004// Create a simple graph like so:
3005 // 0
3006 // ->10
3007 // ->20
3008 // ->21
3009 // ->11
3010schedule.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>));
30133014 schedule.set_build_settings(ScheduleBuildSettings {
3015 shuffle_seed,
3016 ..Default::default()
3017 });
30183019let mut world = World::new();
3020 world.init_resource::<Counters>();
3021 schedule.initialize(&mut world).unwrap();
3022 schedule.run(&mut world);
30233024 world.remove_resource::<Counters>().unwrap().0
3025}
30263027for _ in 0..10 {
3028assert_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 }
30343035// With the right seed, we can find every ordering that satisfies the ordering constraints.
3036 // This is every valid permutation of these ordering constraints.
3037assert_eq!(
3038 run_schedule_with_shuffler(Some(100000001)),
3039 [0, 10, 20, 21, 11]
3040 );
3041assert_eq!(
3042 run_schedule_with_shuffler(Some(100000030)),
3043 [0, 10, 21, 20, 11]
3044 );
3045assert_eq!(
3046 run_schedule_with_shuffler(Some(100000020)),
3047 [0, 10, 20, 11, 21]
3048 );
3049assert_eq!(
3050 run_schedule_with_shuffler(Some(100000063)),
3051 [0, 10, 21, 11, 20]
3052 );
3053assert_eq!(
3054 run_schedule_with_shuffler(Some(100000003)),
3055 [0, 10, 11, 20, 21]
3056 );
3057assert_eq!(
3058 run_schedule_with_shuffler(Some(100000080)),
3059 [0, 10, 11, 21, 20]
3060 );
3061assert_eq!(
3062 run_schedule_with_shuffler(Some(100000000)),
3063 [0, 11, 10, 20, 21]
3064 );
3065assert_eq!(
3066 run_schedule_with_shuffler(Some(100000004)),
3067 [0, 11, 10, 21, 20]
3068 );
30693070// 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}
30813082/// Total number of dependency edges in the built schedule.
3083fn total_dependencies(schedule: &Schedule) -> usize {
3084 schedule.executable.system_dependencies.iter().sum()
3085 }
30863087#[test]
3088fn chain_weak_adds_no_edges_for_non_conflicting_systems() {
3089fn read<const N: usize>(_: Res<Resource1>) {}
30903091let mut world = World::default();
30923093// A strict chain adds a dependency edge between every successive pair.
3094let mut strict = Schedule::default();
3095 strict.add_systems((read::<1>, read::<2>, read::<3>).chain());
3096 strict.initialize(&mut world).unwrap();
3097assert_eq!(total_dependencies(&strict), 2);
30983099// 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.
3102let mut weak = Schedule::default();
3103 weak.add_systems((read::<1>, read::<2>, read::<3>).chain_weak());
3104 weak.initialize(&mut world).unwrap();
3105assert_eq!(total_dependencies(&weak), 0);
3106 }
31073108#[test]
3109fn chain_weak_orders_conflicting_systems_with_finish_edge() {
3110fn write<const N: usize>(_: ResMut<Resource1>) {}
31113112let mut world = World::default();
3113let 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.
3116schedule.add_systems((write::<1>, write::<2>).chain_weak());
3117 schedule.initialize(&mut world).unwrap();
31183119assert_eq!(total_dependencies(&schedule), 1);
3120 }
31213122#[test]
3123fn chain_weak_keeps_finish_dependency_for_deferred() {
3124let mut world = World::default();
3125let 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.
3137schedule.run(&mut world);
31383139// A sync point was inserted between the two systems, so there are two edges.
3140assert_eq!(schedule.executable.systems.len(), 3);
3141assert_eq!(total_dependencies(&schedule), 2);
3142 }
31433144#[test]
3145fn chain_weak_keeps_finish_dependency_for_exclusive() {
3146fn read<const N: usize>(_: Res<Resource1>) {}
3147fn exclusive(_: &mut World) {}
31483149let mut world = World::default();
3150let mut schedule = Schedule::default();
3151 schedule.add_systems((read::<1>, exclusive, read::<2>).chain_weak());
3152 schedule.initialize(&mut world).unwrap();
31533154// 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.
3157assert_eq!(total_dependencies(&schedule), 2);
3158 }
31593160#[test]
3161fn chain_weak_between_non_conflicting_sets_adds_no_edges() {
3162#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
3163enum Sets {
3164 A,
3165 B,
3166 }
31673168fn read<const N: usize>(_: Res<Resource1>) {}
31693170let mut world = World::default();
3171let 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();
31753176// The systems in the two sets don't conflict, so the weak set ordering adds no edge.
3177assert_eq!(total_dependencies(&schedule), 0);
3178 }
31793180#[test]
3181fn chain_weak_between_sets_fans_out_conflict_edges() {
3182#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
3183enum Sets {
3184 A,
3185 B,
3186 }
31873188fn write<const N: usize>(_: ResMut<Resource1>) {}
31893190let mut world = World::default();
3191let 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();
31983199// 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.)
3202assert_eq!(total_dependencies(&schedule), 4);
3203 }
32043205#[test]
3206fn before_weak_between_non_conflicting_sets_adds_no_edges() {
3207#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
3208enum Sets {
3209 A,
3210 B,
3211 }
32123213fn read<const N: usize>(_: Res<Resource1>) {}
32143215let mut world = World::default();
3216let 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();
32203221// The two systems don't conflict, so the weak `before` ordering adds no edge.
3222assert_eq!(total_dependencies(&schedule), 0);
3223 }
32243225#[test]
3226fn after_weak_orders_conflicting_sets_with_finish_edge() {
3227#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
3228enum Sets {
3229 A,
3230 B,
3231 }
32323233fn write<const N: usize>(_: ResMut<Resource1>) {}
32343235let mut world = World::default();
3236let 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();
32403241// The systems conflict, so the weak `after` ordering materializes a single
3242 // edge in the `A -> B` direction.
3243assert_eq!(total_dependencies(&schedule), 1);
3244 }
32453246#[test]
3247fn before_weak_keeps_finish_dependency_for_deferred() {
3248#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
3249enum Sets {
3250 A,
3251 B,
3252 }
32533254fn with_commands(_: Commands) {}
3255fn read<const N: usize>(_: Res<Resource1>) {}
32563257let mut world = World::default();
3258let 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.
3262schedule.add_systems((with_commands.in_set(Sets::A), read::<2>.in_set(Sets::B)));
3263 schedule.initialize(&mut world).unwrap();
32643265// `with_commands -> ApplyDeferred -> read`, so two edges.
3266assert_eq!(total_dependencies(&schedule), 2);
3267 }
32683269#[test]
3270fn before_weak_orders_conflicting_systems() {
3271#[derive(Resource, Default)]
3272struct Order(Vec<u32>);
3273#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
3274enum Sets {
3275 A,
3276 B,
3277 }
32783279fn record<const N: u32>(mut order: ResMut<Order>) {
3280 order.0.push(N);
3281 }
32823283let mut world = World::default();
3284 world.init_resource::<Order>();
3285let 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).
3288schedule.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.
3291schedule.add_systems((record::<1>.in_set(Sets::A), record::<2>.in_set(Sets::B)));
3292 schedule.run(&mut world);
32933294assert_eq!(world.resource::<Order>().0, vec![1, 2]);
3295 }
32963297#[test]
3298fn chain_weak_orders_conflicting_systems() {
3299#[derive(Resource, Default)]
3300struct Order(Vec<u32>);
33013302fn record<const N: u32>(mut order: ResMut<Order>) {
3303 order.0.push(N);
3304 }
33053306let mut world = World::default();
3307 world.init_resource::<Order>();
3308let 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).
3311schedule.set_executor(MultiThreadedExecutor::new());
3312// Both systems write `Order`, so they conflict and the weak ordering pins their order.
3313schedule.add_systems((record::<1>, record::<2>).chain_weak());
3314 schedule.run(&mut world);
33153316assert_eq!(world.resource::<Order>().0, vec![1, 2]);
3317 }
33183319#[test]
3320fn chain_weak_materializes_transitive_conflict_edge() {
3321fn write<const N: usize>(_: ResMut<Resource1>) {}
3322fn read(_: Res<Resource2>) {}
33233324let mut world = World::default();
3325let 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.
3330schedule.add_systems((write::<1>, read, write::<3>).chain_weak());
3331 schedule.initialize(&mut world).unwrap();
33323333// Exactly one edge: between the two conflicting endpoints. The middle system is free.
3334assert_eq!(total_dependencies(&schedule), 1);
3335// ...and because that pair is ordered, it isn't reported as an ambiguity.
3336assert!(schedule.graph().conflicting_systems().is_empty());
3337 }
33383339#[test]
3340fn weak_chain_leaves_explicit_strict_edge_intact() {
3341#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
3342enum Sets {
3343 A,
3344 B,
3345 C,
3346 }
33473348fn read<const N: usize>(_: Res<Resource1>) {}
33493350let mut world = World::default();
3351let mut schedule = Schedule::default();
3352// Weak A -> B -> C over non-conflicting systems, plus an explicit strict A -> C.
3353schedule.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();
33613362// The weak chain is non-conflicting, so it contributes no edges. The explicit strict
3363 // `A -> C` edge is the only ordering that remains.
3364assert_eq!(total_dependencies(&schedule), 1);
3365 }
33663367#[test]
3368fn weak_ordering_keeps_edge_shared_with_strict_ordering() {
3369#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
3370enum Sets {
3371 A,
3372 B,
3373 }
33743375fn read<const N: usize>(_: Res<Resource1>) {}
33763377let mut world = World::default();
3378let 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.
3382schedule.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();
33863387assert_eq!(total_dependencies(&schedule), 1);
3388 }
33893390#[test]
3391fn strict_chain_after_weak_group_waits_for_whole_group() {
3392fn read<const N: usize>(_: Res<Resource1>) {}
33933394let mut world = World::default();
3395let 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.
3398schedule.add_systems(((read::<1>, read::<2>, read::<3>).chain_weak(), read::<4>).chain());
3399 schedule.initialize(&mut world).unwrap();
34003401// 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.
3403assert_eq!(total_dependencies(&schedule), 3);
3404 }
34053406#[test]
3407fn chain_weak_orders_writer_before_system_with_conflicting_condition() {
3408fn write(_: ResMut<Resource1>) {}
3409fn noop() {}
34103411let mut world = World::default();
3412let 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.
3416schedule.add_systems((write, noop.run_if(|_: Res<Resource1>| true)).chain_weak());
3417 schedule.initialize(&mut world).unwrap();
34183419assert_eq!(total_dependencies(&schedule), 1);
3420 }
34213422#[test]
3423fn chain_weak_orders_system_with_conflicting_condition_before_writer() {
3424fn write(_: ResMut<Resource1>) {}
3425fn noop() {}
34263427let mut world = World::default();
3428let 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.
3432schedule.add_systems((noop.run_if(|_: Res<Resource1>| true), write).chain_weak());
3433 schedule.initialize(&mut world).unwrap();
34343435assert_eq!(total_dependencies(&schedule), 1);
3436 }
34373438#[test]
3439fn chain_weak_orders_writer_before_set_with_conflicting_condition() {
3440#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
3441enum Sets {
3442 A,
3443 B,
3444 }
34453446fn write(_: ResMut<Resource1>) {}
3447fn noop() {}
34483449let mut world = World::default();
3450let 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.
3455schedule.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();
34583459assert_eq!(total_dependencies(&schedule), 1);
3460 }
34613462#[test]
3463fn weak_set_ordering_with_ignore_deferred_adds_no_sync_point() {
3464#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
3465enum Sets {
3466 A,
3467 B,
3468 }
34693470fn insert_resource(mut commands: Commands) {
3471 commands.insert_resource(Resource1);
3472 }
3473fn resource_does_not_exist(res: Option<Res<Resource1>>) {
3474assert!(res.is_none());
3475 }
34763477let mut world = World::default();
3478let 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.
3481schedule.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);
34883489assert_eq!(schedule.executable.systems.len(), 2);
3490assert_eq!(total_dependencies(&schedule), 1);
3491 }
34923493#[test]
3494fn weak_edge_after_ignore_deferred_edge_gets_bubbled_sync_point() {
3495fn insert_resource(mut commands: Commands) {
3496 commands.insert_resource(Resource1);
3497 }
3498fn read_other(_: Res<Resource2>) {}
3499fn resource_exists(res: Option<Res<Resource1>>) {
3500assert!(res.is_some());
3501 }
35023503let mut world = World::default();
3504 world.insert_resource(Resource2);
3505let 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.
3517schedule.run(&mut world);
35183519assert_eq!(schedule.executable.systems.len(), 4); // 3 systems + 1 sync point
3520assert_eq!(total_dependencies(&schedule), 3);
3521 }
35223523#[test]
3524fn chain_ignore_deferred_around_weak_group_fans_out_without_sync_point() {
3525fn read<const N: usize>(_: Res<Resource1>) {}
3526fn with_commands(_: Commands) {}
35273528let mut world = World::default();
3529let mut schedule = Schedule::default();
3530// The deferred system comes after the weak group, so nothing bubbles back onto its edge.
3531schedule.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();
35403541// 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.
3544assert_eq!(schedule.executable.systems.len(), 4);
3545assert_eq!(total_dependencies(&schedule), 3);
3546 }
35473548#[test]
3549fn chain_weak_between_ignore_deferred_groups_drops_edge_between_them() {
3550fn read<const N: usize>(_: Res<Resource1>) {}
3551fn with_commands(_: Commands) {}
35523553let mut world = World::default();
3554let 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.
3557schedule.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();
35653566// Each group keeps its own edge, and the weak edge between them is dropped.
3567assert_eq!(schedule.executable.systems.len(), 4);
3568assert_eq!(total_dependencies(&schedule), 2);
3569 }
35703571#[test]
3572fn ignore_deferred_still_syncs_before_exclusive_system_in_weak_chain() {
3573fn insert_resource(mut commands: Commands) {
3574 commands.insert_resource(Resource1);
3575 }
3576fn exclusive(world: &mut World) {
3577assert!(world.contains_resource::<Resource1>());
3578 }
3579fn read(_: Res<Resource1>) {}
35803581let mut world = World::default();
3582let 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.
3587schedule.run(&mut world);
35883589// `insert_resource -> ApplyDeferred -> exclusive -> read`. The weak edge into `read` is
3590 // kept because an exclusive system conflicts with everything.
3591assert_eq!(schedule.executable.systems.len(), 4); // 3 systems + 1 sync point
3592assert_eq!(total_dependencies(&schedule), 3);
3593 }
3594}