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);