bevy_ecs/schedule/
config.rs

1use alloc::{boxed::Box, vec, vec::Vec};
2use variadics_please::all_tuples;
3
4use crate::{
5    schedule::{
6        auto_insert_apply_deferred::IgnoreDeferred,
7        condition::{BoxedCondition, SystemCondition},
8        graph::{Ambiguity, Dependency, DependencyKind, GraphInfo},
9        set::{InternedSystemSet, IntoSystemSet, SystemSet},
10        Chain,
11    },
12    system::{BoxedSystem, IntoSystem, ScheduleSystem, System},
13};
14
15fn new_condition<M>(condition: impl SystemCondition<M>) -> BoxedCondition {
16    let condition_system = IntoSystem::into_system(condition);
17    assert!(
18        condition_system.is_send(),
19        "SystemCondition `{}` accesses `NonSend` resources. This is not currently supported.",
20        condition_system.name()
21    );
22
23    Box::new(condition_system)
24}
25
26fn ambiguous_with(graph_info: &mut GraphInfo, set: InternedSystemSet) {
27    match &mut graph_info.ambiguous_with {
28        detection @ Ambiguity::Check => {
29            *detection = Ambiguity::IgnoreWithSet(vec![set]);
30        }
31        Ambiguity::IgnoreWithSet(ambiguous_with) => {
32            ambiguous_with.push(set);
33        }
34        Ambiguity::IgnoreAll => (),
35    }
36}
37
38/// Stores data to differentiate different schedulable structs.
39pub trait Schedulable {
40    /// Additional data used to configure independent scheduling. Stored in [`ScheduleConfig`].
41    type Metadata;
42    /// Additional data used to configure a schedulable group. Stored in [`ScheduleConfigs`].
43    type GroupMetadata;
44
45    /// Initializes a configuration from this node.
46    fn into_config(self) -> ScheduleConfig<Self>
47    where
48        Self: Sized;
49}
50
51impl Schedulable for ScheduleSystem {
52    type Metadata = GraphInfo;
53    type GroupMetadata = Chain;
54
55    fn into_config(self) -> ScheduleConfig<Self> {
56        let sets = self.default_system_sets().clone();
57        ScheduleConfig {
58            node: self,
59            metadata: GraphInfo {
60                hierarchy: sets,
61                ..Default::default()
62            },
63            conditions: Vec::new(),
64        }
65    }
66}
67
68impl Schedulable for InternedSystemSet {
69    type Metadata = GraphInfo;
70    type GroupMetadata = Chain;
71
72    fn into_config(self) -> ScheduleConfig<Self> {
73        assert!(
74            self.system_type().is_none(),
75            "configuring system type sets is not allowed"
76        );
77
78        ScheduleConfig {
79            node: self,
80            metadata: GraphInfo::default(),
81            conditions: Vec::new(),
82        }
83    }
84}
85
86/// Stores configuration for a single generic node (a system or a system set)
87///
88/// The configuration includes the node itself, scheduling metadata
89/// (hierarchy: in which sets is the node contained,
90/// dependencies: before/after which other nodes should this node run)
91/// and the run conditions associated with this node.
92pub struct ScheduleConfig<T: Schedulable> {
93    pub(crate) node: T,
94    pub(crate) metadata: T::Metadata,
95    pub(crate) conditions: Vec<BoxedCondition>,
96}
97
98/// Single or nested configurations for [`Schedulable`]s.
99pub enum ScheduleConfigs<T: Schedulable> {
100    /// Configuration for a single [`Schedulable`].
101    ScheduleConfig(ScheduleConfig<T>),
102    /// Configuration for a tuple of nested `Configs` instances.
103    Configs {
104        /// Configuration for each element of the tuple.
105        configs: Vec<ScheduleConfigs<T>>,
106        /// Run conditions applied to everything in the tuple.
107        collective_conditions: Vec<BoxedCondition>,
108        /// Metadata to be applied to all elements in the tuple.
109        metadata: T::GroupMetadata,
110    },
111}
112
113impl<T: Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>> ScheduleConfigs<T> {
114    /// Adds a new boxed system set to the systems.
115    pub fn in_set_inner(&mut self, set: InternedSystemSet) {
116        match self {
117            Self::ScheduleConfig(config) => {
118                config.metadata.hierarchy.push(set);
119            }
120            Self::Configs { configs, .. } => {
121                for config in configs {
122                    config.in_set_inner(set);
123                }
124            }
125        }
126    }
127
128    fn before_inner(&mut self, set: InternedSystemSet) {
129        match self {
130            Self::ScheduleConfig(config) => {
131                config
132                    .metadata
133                    .dependencies
134                    .push(Dependency::new(DependencyKind::Before, set));
135            }
136            Self::Configs { configs, .. } => {
137                for config in configs {
138                    config.before_inner(set);
139                }
140            }
141        }
142    }
143
144    fn after_inner(&mut self, set: InternedSystemSet) {
145        match self {
146            Self::ScheduleConfig(config) => {
147                config
148                    .metadata
149                    .dependencies
150                    .push(Dependency::new(DependencyKind::After, set));
151            }
152            Self::Configs { configs, .. } => {
153                for config in configs {
154                    config.after_inner(set);
155                }
156            }
157        }
158    }
159
160    fn before_ignore_deferred_inner(&mut self, set: InternedSystemSet) {
161        match self {
162            Self::ScheduleConfig(config) => {
163                config
164                    .metadata
165                    .dependencies
166                    .push(Dependency::new(DependencyKind::Before, set).add_config(IgnoreDeferred));
167            }
168            Self::Configs { configs, .. } => {
169                for config in configs {
170                    config.before_ignore_deferred_inner(set.intern());
171                }
172            }
173        }
174    }
175
176    fn after_ignore_deferred_inner(&mut self, set: InternedSystemSet) {
177        match self {
178            Self::ScheduleConfig(config) => {
179                config
180                    .metadata
181                    .dependencies
182                    .push(Dependency::new(DependencyKind::After, set).add_config(IgnoreDeferred));
183            }
184            Self::Configs { configs, .. } => {
185                for config in configs {
186                    config.after_ignore_deferred_inner(set.intern());
187                }
188            }
189        }
190    }
191
192    fn distributive_run_if_inner<M>(&mut self, condition: impl SystemCondition<M> + Clone) {
193        match self {
194            Self::ScheduleConfig(config) => {
195                config.conditions.push(new_condition(condition));
196            }
197            Self::Configs { configs, .. } => {
198                for config in configs {
199                    config.distributive_run_if_inner(condition.clone());
200                }
201            }
202        }
203    }
204
205    fn ambiguous_with_inner(&mut self, set: InternedSystemSet) {
206        match self {
207            Self::ScheduleConfig(config) => {
208                ambiguous_with(&mut config.metadata, set);
209            }
210            Self::Configs { configs, .. } => {
211                for config in configs {
212                    config.ambiguous_with_inner(set);
213                }
214            }
215        }
216    }
217
218    fn ambiguous_with_all_inner(&mut self) {
219        match self {
220            Self::ScheduleConfig(config) => {
221                config.metadata.ambiguous_with = Ambiguity::IgnoreAll;
222            }
223            Self::Configs { configs, .. } => {
224                for config in configs {
225                    config.ambiguous_with_all_inner();
226                }
227            }
228        }
229    }
230
231    /// Adds a new boxed run condition to the systems.
232    ///
233    /// This is useful if you have a run condition whose concrete type is unknown.
234    /// Prefer `run_if` for run conditions whose type is known at compile time.
235    pub fn run_if_dyn(&mut self, condition: BoxedCondition) {
236        match self {
237            Self::ScheduleConfig(config) => {
238                config.conditions.push(condition);
239            }
240            Self::Configs {
241                collective_conditions,
242                ..
243            } => {
244                collective_conditions.push(condition);
245            }
246        }
247    }
248
249    fn chain_inner(mut self) -> Self {
250        match &mut self {
251            Self::ScheduleConfig(_) => { /* no op */ }
252            Self::Configs { metadata, .. } => {
253                metadata.set_chained();
254            }
255        };
256        self
257    }
258
259    fn chain_ignore_deferred_inner(mut self) -> Self {
260        match &mut self {
261            Self::ScheduleConfig(_) => { /* no op */ }
262            Self::Configs { metadata, .. } => {
263                metadata.set_chained_with_config(IgnoreDeferred);
264            }
265        }
266        self
267    }
268}
269
270/// Types that can convert into a [`ScheduleConfigs`].
271///
272/// This trait is implemented for "systems" (functions whose arguments all implement
273/// [`SystemParam`](crate::system::SystemParam)), or tuples thereof.
274/// It is a common entry point for system configurations.
275///
276/// # Usage notes
277///
278/// This trait should only be used as a bound for trait implementations or as an
279/// argument to a function. If system configs need to be returned from a
280/// function or stored somewhere, use [`ScheduleConfigs`] instead of this trait.
281///
282/// # Examples
283///
284/// ```
285/// # use bevy_ecs::{schedule::IntoScheduleConfigs, system::ScheduleSystem};
286/// # struct AppMock;
287/// # struct Update;
288/// # impl AppMock {
289/// #     pub fn add_systems<M>(
290/// #         &mut self,
291/// #         schedule: Update,
292/// #         systems: impl IntoScheduleConfigs<ScheduleSystem, M>,
293/// #    ) -> &mut Self { self }
294/// # }
295/// # let mut app = AppMock;
296///
297/// fn handle_input() {}
298///
299/// fn update_camera() {}
300/// fn update_character() {}
301///
302/// app.add_systems(
303///     Update,
304///     (
305///         handle_input,
306///         (update_camera, update_character).after(handle_input)
307///     )
308/// );
309/// ```
310#[diagnostic::on_unimplemented(
311    message = "`{Self}` does not describe a valid system configuration",
312    label = "invalid system configuration"
313)]
314pub trait IntoScheduleConfigs<T: Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>, Marker>:
315    Sized
316{
317    /// Convert into a [`ScheduleConfigs`].
318    fn into_configs(self) -> ScheduleConfigs<T>;
319
320    /// Add these systems to the provided `set`.
321    #[track_caller]
322    fn in_set(self, set: impl SystemSet) -> ScheduleConfigs<T> {
323        self.into_configs().in_set(set)
324    }
325
326    /// Runs before all systems in `set`. If `self` has any systems that produce [`Commands`](crate::system::Commands)
327    /// or other [`Deferred`](crate::system::Deferred) operations, all systems in `set` will see their effect.
328    ///
329    /// If automatically inserting [`ApplyDeferred`](crate::schedule::ApplyDeferred) like
330    /// this isn't desired, use [`before_ignore_deferred`](Self::before_ignore_deferred) instead.
331    ///
332    /// Calling [`.chain`](Self::chain) is often more convenient and ensures that all systems are added to the schedule.
333    /// Please check the [caveats section of `.after`](Self::after) for details.
334    fn before<M>(self, set: impl IntoSystemSet<M>) -> ScheduleConfigs<T> {
335        self.into_configs().before(set)
336    }
337
338    /// Run after all systems in `set`. If `set` has any systems that produce [`Commands`](crate::system::Commands)
339    /// or other [`Deferred`](crate::system::Deferred) operations, all systems in `self` will see their effect.
340    ///
341    /// If automatically inserting [`ApplyDeferred`](crate::schedule::ApplyDeferred) like
342    /// this isn't desired, use [`after_ignore_deferred`](Self::after_ignore_deferred) instead.
343    ///
344    /// Calling [`.chain`](Self::chain) is often more convenient and ensures that all systems are added to the schedule.
345    ///
346    /// # Caveats
347    ///
348    /// 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.
349    ///
350    /// 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`]
351    /// If that is the case, `.after`/`.before` will not provide the desired behavior
352    /// and the systems can run in parallel or in any order determined by the scheduler.
353    /// Only use `after(GameSystem::B)` and `before(GameSystem::B)` when you know that `B` has already been scheduled for you,
354    /// e.g. when it was provided by Bevy or a third-party dependency,
355    /// or you manually scheduled it somewhere else in your app.
356    ///
357    /// Another caveat is that if `GameSystem::B` is placed in a different schedule than `GameSystem::A`,
358    /// any ordering calls between them—whether using `.before`, `.after`, or `.chain`—will be silently ignored.
359    ///
360    /// [`configure_sets`]: https://docs.rs/bevy/latest/bevy/app/struct.App.html#method.configure_sets
361    fn after<M>(self, set: impl IntoSystemSet<M>) -> ScheduleConfigs<T> {
362        self.into_configs().after(set)
363    }
364
365    /// Run before all systems in `set`.
366    ///
367    /// Unlike [`before`](Self::before), this will not cause the systems in
368    /// `set` to wait for the deferred effects of `self` to be applied.
369    fn before_ignore_deferred<M>(self, set: impl IntoSystemSet<M>) -> ScheduleConfigs<T> {
370        self.into_configs().before_ignore_deferred(set)
371    }
372
373    /// Run after all systems in `set`.
374    ///
375    /// Unlike [`after`](Self::after), this will not wait for the deferred
376    /// effects of systems in `set` to be applied.
377    fn after_ignore_deferred<M>(self, set: impl IntoSystemSet<M>) -> ScheduleConfigs<T> {
378        self.into_configs().after_ignore_deferred(set)
379    }
380
381    /// Add a run condition to each contained system.
382    ///
383    /// Each system will receive its own clone of the [`SystemCondition`] and will only run
384    /// if the `SystemCondition` is true.
385    ///
386    /// Each individual condition will be evaluated at most once (per schedule run),
387    /// right before the corresponding system prepares to run.
388    ///
389    /// This is equivalent to calling [`run_if`](IntoScheduleConfigs::run_if) on each individual
390    /// system, as shown below:
391    ///
392    /// ```
393    /// # use bevy_ecs::prelude::*;
394    /// # let mut schedule = Schedule::default();
395    /// # fn a() {}
396    /// # fn b() {}
397    /// # fn condition() -> bool { true }
398    /// schedule.add_systems((a, b).distributive_run_if(condition));
399    /// schedule.add_systems((a.run_if(condition), b.run_if(condition)));
400    /// ```
401    ///
402    /// # Note
403    ///
404    /// Because the conditions are evaluated separately for each system, there is no guarantee
405    /// that all evaluations in a single schedule run will yield the same result. If another
406    /// system is run inbetween two evaluations it could cause the result of the condition to change.
407    ///
408    /// Use [`run_if`](ScheduleConfigs::run_if) on a [`SystemSet`] if you want to make sure
409    /// that either all or none of the systems are run, or you don't want to evaluate the run
410    /// condition for each contained system separately.
411    fn distributive_run_if<M>(
412        self,
413        condition: impl SystemCondition<M> + Clone,
414    ) -> ScheduleConfigs<T> {
415        self.into_configs().distributive_run_if(condition)
416    }
417
418    /// Run the systems only if the [`SystemCondition`] is `true`.
419    ///
420    /// The `SystemCondition` will be evaluated at most once (per schedule run),
421    /// the first time a system in this set prepares to run.
422    ///
423    /// If this set contains more than one system, calling `run_if` is equivalent to adding each
424    /// system to a common set and configuring the run condition on that set, as shown below:
425    ///
426    /// # Examples
427    ///
428    /// ```
429    /// # use bevy_ecs::prelude::*;
430    /// # let mut schedule = Schedule::default();
431    /// # fn a() {}
432    /// # fn b() {}
433    /// # fn condition() -> bool { true }
434    /// # #[derive(SystemSet, Debug, Eq, PartialEq, Hash, Clone, Copy)]
435    /// # struct C;
436    /// schedule.add_systems((a, b).run_if(condition));
437    /// schedule.add_systems((a, b).in_set(C)).configure_sets(C.run_if(condition));
438    /// ```
439    ///
440    /// # Note
441    ///
442    /// Because the condition will only be evaluated once, there is no guarantee that the condition
443    /// is upheld after the first system has run. You need to make sure that no other systems that
444    /// could invalidate the condition are scheduled inbetween the first and last run system.
445    ///
446    /// Use [`distributive_run_if`](IntoScheduleConfigs::distributive_run_if) if you want the
447    /// condition to be evaluated for each individual system, right before one is run.
448    fn run_if<M>(self, condition: impl SystemCondition<M>) -> ScheduleConfigs<T> {
449        self.into_configs().run_if(condition)
450    }
451
452    /// Suppress warnings and errors that would result from these systems having ambiguities
453    /// (conflicting access but indeterminate order) with systems in `set`.
454    fn ambiguous_with<M>(self, set: impl IntoSystemSet<M>) -> ScheduleConfigs<T> {
455        self.into_configs().ambiguous_with(set)
456    }
457
458    /// Suppress warnings and errors that would result from these systems having ambiguities
459    /// (conflicting access but indeterminate order) with any other system.
460    fn ambiguous_with_all(self) -> ScheduleConfigs<T> {
461        self.into_configs().ambiguous_with_all()
462    }
463
464    /// Treat this collection as a sequence of systems.
465    ///
466    /// Ordering constraints will be applied between the successive elements.
467    ///
468    /// If the preceding node on an edge has deferred parameters, an [`ApplyDeferred`](crate::schedule::ApplyDeferred)
469    /// will be inserted on the edge. If this behavior is not desired consider using
470    /// [`chain_ignore_deferred`](Self::chain_ignore_deferred) instead.
471    fn chain(self) -> ScheduleConfigs<T> {
472        self.into_configs().chain()
473    }
474
475    /// Treat this collection as a sequence of systems.
476    ///
477    /// Ordering constraints will be applied between the successive elements.
478    ///
479    /// Unlike [`chain`](Self::chain) this will **not** add [`ApplyDeferred`](crate::schedule::ApplyDeferred) on the edges.
480    fn chain_ignore_deferred(self) -> ScheduleConfigs<T> {
481        self.into_configs().chain_ignore_deferred()
482    }
483}
484
485impl<T: Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>> IntoScheduleConfigs<T, ()>
486    for ScheduleConfigs<T>
487{
488    fn into_configs(self) -> Self {
489        self
490    }
491
492    #[track_caller]
493    fn in_set(mut self, set: impl SystemSet) -> Self {
494        assert!(
495            set.system_type().is_none(),
496            "adding arbitrary systems to a system type set is not allowed"
497        );
498
499        self.in_set_inner(set.intern());
500
501        self
502    }
503
504    fn before<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
505        let set = set.into_system_set();
506        self.before_inner(set.intern());
507        self
508    }
509
510    fn after<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
511        let set = set.into_system_set();
512        self.after_inner(set.intern());
513        self
514    }
515
516    fn before_ignore_deferred<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
517        let set = set.into_system_set();
518        self.before_ignore_deferred_inner(set.intern());
519        self
520    }
521
522    fn after_ignore_deferred<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
523        let set = set.into_system_set();
524        self.after_ignore_deferred_inner(set.intern());
525        self
526    }
527
528    fn distributive_run_if<M>(
529        mut self,
530        condition: impl SystemCondition<M> + Clone,
531    ) -> ScheduleConfigs<T> {
532        self.distributive_run_if_inner(condition);
533        self
534    }
535
536    fn run_if<M>(mut self, condition: impl SystemCondition<M>) -> ScheduleConfigs<T> {
537        self.run_if_dyn(new_condition(condition));
538        self
539    }
540
541    fn ambiguous_with<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
542        let set = set.into_system_set();
543        self.ambiguous_with_inner(set.intern());
544        self
545    }
546
547    fn ambiguous_with_all(mut self) -> Self {
548        self.ambiguous_with_all_inner();
549        self
550    }
551
552    fn chain(self) -> Self {
553        self.chain_inner()
554    }
555
556    fn chain_ignore_deferred(self) -> Self {
557        self.chain_ignore_deferred_inner()
558    }
559}
560
561impl<F, Marker> IntoScheduleConfigs<ScheduleSystem, Marker> for F
562where
563    F: IntoSystem<(), (), Marker>,
564{
565    fn into_configs(self) -> ScheduleConfigs<ScheduleSystem> {
566        let boxed_system = Box::new(IntoSystem::into_system(self));
567        ScheduleConfigs::ScheduleConfig(ScheduleSystem::into_config(boxed_system))
568    }
569}
570
571impl IntoScheduleConfigs<ScheduleSystem, ()> for BoxedSystem<(), ()> {
572    fn into_configs(self) -> ScheduleConfigs<ScheduleSystem> {
573        ScheduleConfigs::ScheduleConfig(ScheduleSystem::into_config(self))
574    }
575}
576
577impl<S: SystemSet> IntoScheduleConfigs<InternedSystemSet, ()> for S {
578    fn into_configs(self) -> ScheduleConfigs<InternedSystemSet> {
579        ScheduleConfigs::ScheduleConfig(InternedSystemSet::into_config(self.intern()))
580    }
581}
582
583#[doc(hidden)]
584pub struct ScheduleConfigTupleMarker;
585
586macro_rules! impl_node_type_collection {
587    ($(#[$meta:meta])* $(($param: ident, $sys: ident)),*) => {
588        $(#[$meta])*
589        impl<$($param, $sys),*, T: Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>> IntoScheduleConfigs<T, (ScheduleConfigTupleMarker, $($param,)*)> for ($($sys,)*)
590        where
591            $($sys: IntoScheduleConfigs<T, $param>),*
592        {
593            #[expect(
594                clippy::allow_attributes,
595                reason = "We are inside a macro, and as such, `non_snake_case` is not guaranteed to apply."
596            )]
597            #[allow(
598                non_snake_case,
599                reason = "Variable names are provided by the macro caller, not by us."
600            )]
601            fn into_configs(self) -> ScheduleConfigs<T> {
602                let ($($sys,)*) = self;
603                ScheduleConfigs::Configs {
604                    metadata: Default::default(),
605                    configs: vec![$($sys.into_configs(),)*],
606                    collective_conditions: Vec::new(),
607                }
608            }
609        }
610    }
611}
612
613all_tuples!(
614    #[doc(fake_variadic)]
615    impl_node_type_collection,
616    1,
617    20,
618    P,
619    S
620);