use alloc::{boxed::Box, vec, vec::Vec};1use variadics_please::all_tuples;23use crate::{4schedule::{5auto_insert_apply_deferred::IgnoreDeferred,6condition::{BoxedCondition, SystemCondition},7graph::{Ambiguity, Dependency, DependencyKind, GraphInfo},8set::{InternedSystemSet, IntoSystemSet, SystemSet},9Chain,10},11system::{BoxedSystem, IntoSystem, ScheduleSystem, System},12};1314fn new_condition<M>(condition: impl SystemCondition<M>) -> BoxedCondition {15let condition_system = IntoSystem::into_system(condition);16assert!(17condition_system.is_send(),18"SystemCondition `{}` accesses `NonSend` resources. This is not currently supported.",19condition_system.name()20);2122Box::new(condition_system)23}2425fn ambiguous_with(graph_info: &mut GraphInfo, set: InternedSystemSet) {26match &mut graph_info.ambiguous_with {27detection @ Ambiguity::Check => {28*detection = Ambiguity::IgnoreWithSet(vec![set]);29}30Ambiguity::IgnoreWithSet(ambiguous_with) => {31ambiguous_with.push(set);32}33Ambiguity::IgnoreAll => (),34}35}3637/// Stores data to differentiate different schedulable structs.38pub trait Schedulable {39/// Additional data used to configure independent scheduling. Stored in [`ScheduleConfig`].40type Metadata;41/// Additional data used to configure a schedulable group. Stored in [`ScheduleConfigs`].42type GroupMetadata;4344/// Initializes a configuration from this node.45fn into_config(self) -> ScheduleConfig<Self>46where47Self: Sized;48}4950impl Schedulable for ScheduleSystem {51type Metadata = GraphInfo;52type GroupMetadata = Chain;5354fn into_config(self) -> ScheduleConfig<Self> {55let sets = self.default_system_sets().clone();56ScheduleConfig {57node: self,58metadata: GraphInfo {59hierarchy: sets,60..Default::default()61},62conditions: Vec::new(),63}64}65}6667impl Schedulable for InternedSystemSet {68type Metadata = GraphInfo;69type GroupMetadata = Chain;7071fn into_config(self) -> ScheduleConfig<Self> {72assert!(73self.system_type().is_none(),74"configuring system type sets is not allowed"75);7677ScheduleConfig {78node: self,79metadata: GraphInfo::default(),80conditions: Vec::new(),81}82}83}8485/// Stores configuration for a single generic node (a system or a system set)86///87/// The configuration includes the node itself, scheduling metadata88/// (hierarchy: in which sets is the node contained,89/// dependencies: before/after which other nodes should this node run)90/// and the run conditions associated with this node.91pub struct ScheduleConfig<T: Schedulable> {92pub(crate) node: T,93pub(crate) metadata: T::Metadata,94pub(crate) conditions: Vec<BoxedCondition>,95}9697/// Single or nested configurations for [`Schedulable`]s.98pub enum ScheduleConfigs<T: Schedulable> {99/// Configuration for a single [`Schedulable`].100ScheduleConfig(ScheduleConfig<T>),101/// Configuration for a tuple of nested `Configs` instances.102Configs {103/// Configuration for each element of the tuple.104configs: Vec<ScheduleConfigs<T>>,105/// Run conditions applied to everything in the tuple.106collective_conditions: Vec<BoxedCondition>,107/// Metadata to be applied to all elements in the tuple.108metadata: T::GroupMetadata,109},110}111112impl<T: Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>> ScheduleConfigs<T> {113/// Adds a new boxed system set to the systems.114pub fn in_set_inner(&mut self, set: InternedSystemSet) {115match self {116Self::ScheduleConfig(config) => {117config.metadata.hierarchy.push(set);118}119Self::Configs { configs, .. } => {120for config in configs {121config.in_set_inner(set);122}123}124}125}126127fn before_inner(&mut self, set: InternedSystemSet) {128match self {129Self::ScheduleConfig(config) => {130config131.metadata132.dependencies133.push(Dependency::new(DependencyKind::Before, set));134}135Self::Configs { configs, .. } => {136for config in configs {137config.before_inner(set);138}139}140}141}142143fn after_inner(&mut self, set: InternedSystemSet) {144match self {145Self::ScheduleConfig(config) => {146config147.metadata148.dependencies149.push(Dependency::new(DependencyKind::After, set));150}151Self::Configs { configs, .. } => {152for config in configs {153config.after_inner(set);154}155}156}157}158159fn before_ignore_deferred_inner(&mut self, set: InternedSystemSet) {160match self {161Self::ScheduleConfig(config) => {162config163.metadata164.dependencies165.push(Dependency::new(DependencyKind::Before, set).add_config(IgnoreDeferred));166}167Self::Configs { configs, .. } => {168for config in configs {169config.before_ignore_deferred_inner(set.intern());170}171}172}173}174175fn after_ignore_deferred_inner(&mut self, set: InternedSystemSet) {176match self {177Self::ScheduleConfig(config) => {178config179.metadata180.dependencies181.push(Dependency::new(DependencyKind::After, set).add_config(IgnoreDeferred));182}183Self::Configs { configs, .. } => {184for config in configs {185config.after_ignore_deferred_inner(set.intern());186}187}188}189}190191fn distributive_run_if_inner<M>(&mut self, condition: impl SystemCondition<M> + Clone) {192match self {193Self::ScheduleConfig(config) => {194config.conditions.push(new_condition(condition));195}196Self::Configs { configs, .. } => {197for config in configs {198config.distributive_run_if_inner(condition.clone());199}200}201}202}203204fn ambiguous_with_inner(&mut self, set: InternedSystemSet) {205match self {206Self::ScheduleConfig(config) => {207ambiguous_with(&mut config.metadata, set);208}209Self::Configs { configs, .. } => {210for config in configs {211config.ambiguous_with_inner(set);212}213}214}215}216217fn ambiguous_with_all_inner(&mut self) {218match self {219Self::ScheduleConfig(config) => {220config.metadata.ambiguous_with = Ambiguity::IgnoreAll;221}222Self::Configs { configs, .. } => {223for config in configs {224config.ambiguous_with_all_inner();225}226}227}228}229230/// Adds a new boxed run condition to the systems.231///232/// This is useful if you have a run condition whose concrete type is unknown.233/// Prefer `run_if` for run conditions whose type is known at compile time.234pub fn run_if_dyn(&mut self, condition: BoxedCondition) {235match self {236Self::ScheduleConfig(config) => {237config.conditions.push(condition);238}239Self::Configs {240collective_conditions,241..242} => {243collective_conditions.push(condition);244}245}246}247248fn chain_inner(mut self) -> Self {249match &mut self {250Self::ScheduleConfig(_) => { /* no op */ }251Self::Configs { metadata, .. } => {252metadata.set_chained();253}254};255self256}257258fn chain_ignore_deferred_inner(mut self) -> Self {259match &mut self {260Self::ScheduleConfig(_) => { /* no op */ }261Self::Configs { metadata, .. } => {262metadata.set_chained_with_config(IgnoreDeferred);263}264}265self266}267}268269/// Types that can convert into a [`ScheduleConfigs`].270///271/// This trait is implemented for "systems" (functions whose arguments all implement272/// [`SystemParam`](crate::system::SystemParam)), or tuples thereof.273/// It is a common entry point for system configurations.274///275/// # Usage notes276///277/// This trait should only be used as a bound for trait implementations or as an278/// argument to a function. If system configs need to be returned from a279/// function or stored somewhere, use [`ScheduleConfigs`] instead of this trait.280///281/// # Examples282///283/// ```284/// # use bevy_ecs::{schedule::IntoScheduleConfigs, system::ScheduleSystem};285/// # struct AppMock;286/// # struct Update;287/// # impl AppMock {288/// # pub fn add_systems<M>(289/// # &mut self,290/// # schedule: Update,291/// # systems: impl IntoScheduleConfigs<ScheduleSystem, M>,292/// # ) -> &mut Self { self }293/// # }294/// # let mut app = AppMock;295///296/// fn handle_input() {}297///298/// fn update_camera() {}299/// fn update_character() {}300///301/// app.add_systems(302/// Update,303/// (304/// handle_input,305/// (update_camera, update_character).after(handle_input)306/// )307/// );308/// ```309#[diagnostic::on_unimplemented(310message = "`{Self}` does not describe a valid system configuration",311label = "invalid system configuration"312)]313pub trait IntoScheduleConfigs<T: Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>, Marker>:314Sized315{316/// Convert into a [`ScheduleConfigs`].317fn into_configs(self) -> ScheduleConfigs<T>;318319/// Add these systems to the provided `set`.320#[track_caller]321fn in_set(self, set: impl SystemSet) -> ScheduleConfigs<T> {322self.into_configs().in_set(set)323}324325/// Runs before all systems in `set`. If `self` has any systems that produce [`Commands`](crate::system::Commands)326/// or other [`Deferred`](crate::system::Deferred) operations, all systems in `set` will see their effect.327///328/// If automatically inserting [`ApplyDeferred`](crate::schedule::ApplyDeferred) like329/// this isn't desired, use [`before_ignore_deferred`](Self::before_ignore_deferred) instead.330///331/// Calling [`.chain`](Self::chain) is often more convenient and ensures that all systems are added to the schedule.332/// Please check the [caveats section of `.after`](Self::after) for details.333fn before<M>(self, set: impl IntoSystemSet<M>) -> ScheduleConfigs<T> {334self.into_configs().before(set)335}336337/// Run after all systems in `set`. If `set` has any systems that produce [`Commands`](crate::system::Commands)338/// or other [`Deferred`](crate::system::Deferred) operations, all systems in `self` will see their effect.339///340/// If automatically inserting [`ApplyDeferred`](crate::schedule::ApplyDeferred) like341/// this isn't desired, use [`after_ignore_deferred`](Self::after_ignore_deferred) instead.342///343/// Calling [`.chain`](Self::chain) is often more convenient and ensures that all systems are added to the schedule.344///345/// # Caveats346///347/// If you configure two [`System`]s like `(GameSystem::A).after(GameSystem::B)` or `(GameSystem::A).before(GameSystem::B)`, the `GameSystem::B` will not be automatically scheduled.348///349/// This means that the system `GameSystem::A` and the system or systems in `GameSystem::B` will run independently of each other if `GameSystem::B` was never explicitly scheduled with [`configure_sets`]350/// If that is the case, `.after`/`.before` will not provide the desired behavior351/// and the systems can run in parallel or in any order determined by the scheduler.352/// Only use `after(GameSystem::B)` and `before(GameSystem::B)` when you know that `B` has already been scheduled for you,353/// e.g. when it was provided by Bevy or a third-party dependency,354/// or you manually scheduled it somewhere else in your app.355///356/// Another caveat is that if `GameSystem::B` is placed in a different schedule than `GameSystem::A`,357/// any ordering calls between them—whether using `.before`, `.after`, or `.chain`—will be silently ignored.358///359/// [`configure_sets`]: https://docs.rs/bevy/latest/bevy/app/struct.App.html#method.configure_sets360fn after<M>(self, set: impl IntoSystemSet<M>) -> ScheduleConfigs<T> {361self.into_configs().after(set)362}363364/// Run before all systems in `set`.365///366/// Unlike [`before`](Self::before), this will not cause the systems in367/// `set` to wait for the deferred effects of `self` to be applied.368fn before_ignore_deferred<M>(self, set: impl IntoSystemSet<M>) -> ScheduleConfigs<T> {369self.into_configs().before_ignore_deferred(set)370}371372/// Run after all systems in `set`.373///374/// Unlike [`after`](Self::after), this will not wait for the deferred375/// effects of systems in `set` to be applied.376fn after_ignore_deferred<M>(self, set: impl IntoSystemSet<M>) -> ScheduleConfigs<T> {377self.into_configs().after_ignore_deferred(set)378}379380/// Add a run condition to each contained system.381///382/// Each system will receive its own clone of the [`SystemCondition`] and will only run383/// if the `SystemCondition` is true.384///385/// Each individual condition will be evaluated at most once (per schedule run),386/// right before the corresponding system prepares to run.387///388/// This is equivalent to calling [`run_if`](IntoScheduleConfigs::run_if) on each individual389/// system, as shown below:390///391/// ```392/// # use bevy_ecs::prelude::*;393/// # let mut schedule = Schedule::default();394/// # fn a() {}395/// # fn b() {}396/// # fn condition() -> bool { true }397/// schedule.add_systems((a, b).distributive_run_if(condition));398/// schedule.add_systems((a.run_if(condition), b.run_if(condition)));399/// ```400///401/// # Note402///403/// Because the conditions are evaluated separately for each system, there is no guarantee404/// that all evaluations in a single schedule run will yield the same result. If another405/// system is run inbetween two evaluations it could cause the result of the condition to change.406///407/// Use [`run_if`](ScheduleConfigs::run_if) on a [`SystemSet`] if you want to make sure408/// that either all or none of the systems are run, or you don't want to evaluate the run409/// condition for each contained system separately.410fn distributive_run_if<M>(411self,412condition: impl SystemCondition<M> + Clone,413) -> ScheduleConfigs<T> {414self.into_configs().distributive_run_if(condition)415}416417/// Run the systems only if the [`SystemCondition`] is `true`.418///419/// The `SystemCondition` will be evaluated at most once (per schedule run),420/// the first time a system in this set prepares to run.421///422/// If this set contains more than one system, calling `run_if` is equivalent to adding each423/// system to a common set and configuring the run condition on that set, as shown below:424///425/// # Examples426///427/// ```428/// # use bevy_ecs::prelude::*;429/// # let mut schedule = Schedule::default();430/// # fn a() {}431/// # fn b() {}432/// # fn condition() -> bool { true }433/// # #[derive(SystemSet, Debug, Eq, PartialEq, Hash, Clone, Copy)]434/// # struct C;435/// schedule.add_systems((a, b).run_if(condition));436/// schedule.add_systems((a, b).in_set(C)).configure_sets(C.run_if(condition));437/// ```438///439/// # Note440///441/// Because the condition will only be evaluated once, there is no guarantee that the condition442/// is upheld after the first system has run. You need to make sure that no other systems that443/// could invalidate the condition are scheduled inbetween the first and last run system.444///445/// Use [`distributive_run_if`](IntoScheduleConfigs::distributive_run_if) if you want the446/// condition to be evaluated for each individual system, right before one is run.447fn run_if<M>(self, condition: impl SystemCondition<M>) -> ScheduleConfigs<T> {448self.into_configs().run_if(condition)449}450451/// Suppress warnings and errors that would result from these systems having ambiguities452/// (conflicting access but indeterminate order) with systems in `set`.453fn ambiguous_with<M>(self, set: impl IntoSystemSet<M>) -> ScheduleConfigs<T> {454self.into_configs().ambiguous_with(set)455}456457/// Suppress warnings and errors that would result from these systems having ambiguities458/// (conflicting access but indeterminate order) with any other system.459fn ambiguous_with_all(self) -> ScheduleConfigs<T> {460self.into_configs().ambiguous_with_all()461}462463/// Treat this collection as a sequence of systems.464///465/// Ordering constraints will be applied between the successive elements.466///467/// If the preceding node on an edge has deferred parameters, an [`ApplyDeferred`](crate::schedule::ApplyDeferred)468/// will be inserted on the edge. If this behavior is not desired consider using469/// [`chain_ignore_deferred`](Self::chain_ignore_deferred) instead.470fn chain(self) -> ScheduleConfigs<T> {471self.into_configs().chain()472}473474/// Treat this collection as a sequence of systems.475///476/// Ordering constraints will be applied between the successive elements.477///478/// Unlike [`chain`](Self::chain) this will **not** add [`ApplyDeferred`](crate::schedule::ApplyDeferred) on the edges.479fn chain_ignore_deferred(self) -> ScheduleConfigs<T> {480self.into_configs().chain_ignore_deferred()481}482}483484impl<T: Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>> IntoScheduleConfigs<T, ()>485for ScheduleConfigs<T>486{487fn into_configs(self) -> Self {488self489}490491#[track_caller]492fn in_set(mut self, set: impl SystemSet) -> Self {493assert!(494set.system_type().is_none(),495"adding arbitrary systems to a system type set is not allowed"496);497498self.in_set_inner(set.intern());499500self501}502503fn before<M>(mut self, set: impl IntoSystemSet<M>) -> Self {504let set = set.into_system_set();505self.before_inner(set.intern());506self507}508509fn after<M>(mut self, set: impl IntoSystemSet<M>) -> Self {510let set = set.into_system_set();511self.after_inner(set.intern());512self513}514515fn before_ignore_deferred<M>(mut self, set: impl IntoSystemSet<M>) -> Self {516let set = set.into_system_set();517self.before_ignore_deferred_inner(set.intern());518self519}520521fn after_ignore_deferred<M>(mut self, set: impl IntoSystemSet<M>) -> Self {522let set = set.into_system_set();523self.after_ignore_deferred_inner(set.intern());524self525}526527fn distributive_run_if<M>(528mut self,529condition: impl SystemCondition<M> + Clone,530) -> ScheduleConfigs<T> {531self.distributive_run_if_inner(condition);532self533}534535fn run_if<M>(mut self, condition: impl SystemCondition<M>) -> ScheduleConfigs<T> {536self.run_if_dyn(new_condition(condition));537self538}539540fn ambiguous_with<M>(mut self, set: impl IntoSystemSet<M>) -> Self {541let set = set.into_system_set();542self.ambiguous_with_inner(set.intern());543self544}545546fn ambiguous_with_all(mut self) -> Self {547self.ambiguous_with_all_inner();548self549}550551fn chain(self) -> Self {552self.chain_inner()553}554555fn chain_ignore_deferred(self) -> Self {556self.chain_ignore_deferred_inner()557}558}559560impl<F, Marker> IntoScheduleConfigs<ScheduleSystem, Marker> for F561where562F: IntoSystem<(), (), Marker>,563{564fn into_configs(self) -> ScheduleConfigs<ScheduleSystem> {565let boxed_system = Box::new(IntoSystem::into_system(self));566ScheduleConfigs::ScheduleConfig(ScheduleSystem::into_config(boxed_system))567}568}569570impl IntoScheduleConfigs<ScheduleSystem, ()> for BoxedSystem<(), ()> {571fn into_configs(self) -> ScheduleConfigs<ScheduleSystem> {572ScheduleConfigs::ScheduleConfig(ScheduleSystem::into_config(self))573}574}575576impl<S: SystemSet> IntoScheduleConfigs<InternedSystemSet, ()> for S {577fn into_configs(self) -> ScheduleConfigs<InternedSystemSet> {578ScheduleConfigs::ScheduleConfig(InternedSystemSet::into_config(self.intern()))579}580}581582#[doc(hidden)]583pub struct ScheduleConfigTupleMarker;584585macro_rules! impl_node_type_collection {586($(#[$meta:meta])* $(($param: ident, $sys: ident)),*) => {587$(#[$meta])*588impl<$($param, $sys),*, T: Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>> IntoScheduleConfigs<T, (ScheduleConfigTupleMarker, $($param,)*)> for ($($sys,)*)589where590$($sys: IntoScheduleConfigs<T, $param>),*591{592#[expect(593clippy::allow_attributes,594reason = "We are inside a macro, and as such, `non_snake_case` is not guaranteed to apply."595)]596#[allow(597non_snake_case,598reason = "Variable names are provided by the macro caller, not by us."599)]600fn into_configs(self) -> ScheduleConfigs<T> {601let ($($sys,)*) = self;602ScheduleConfigs::Configs {603metadata: Default::default(),604configs: vec![$($sys.into_configs(),)*],605collective_conditions: Vec::new(),606}607}608}609}610}611612all_tuples!(613#[doc(fake_variadic)]614impl_node_type_collection,6151,61620,617P,618S619);620621622