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