bevy_ecs/schedule/
config.rs

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