Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ecs/src/schedule/config.rs
6849 views
1
use alloc::{boxed::Box, vec, vec::Vec};
2
use variadics_please::all_tuples;
3
4
use 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
15
fn 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
26
fn 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.
39
pub 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
51
impl 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
68
impl 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.
92
pub 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.
99
pub 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
113
impl<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
)]
314
pub 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
485
impl<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
561
impl<F, Marker> IntoScheduleConfigs<ScheduleSystem, Marker> for F
562
where
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
571
impl IntoScheduleConfigs<ScheduleSystem, ()> for BoxedSystem<(), ()> {
572
fn into_configs(self) -> ScheduleConfigs<ScheduleSystem> {
573
ScheduleConfigs::ScheduleConfig(ScheduleSystem::into_config(self))
574
}
575
}
576
577
impl<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)]
584
pub struct ScheduleConfigTupleMarker;
585
586
macro_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
613
all_tuples!(
614
#[doc(fake_variadic)]
615
impl_node_type_collection,
616
1,
617
20,
618
P,
619
S
620
);
621
622