Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ecs/src/schedule/mod.rs
6849 views
1
//! Contains APIs for ordering systems and executing them on a [`World`](crate::world::World)
2
3
mod auto_insert_apply_deferred;
4
mod condition;
5
mod config;
6
mod error;
7
mod executor;
8
mod node;
9
mod pass;
10
mod schedule;
11
mod set;
12
mod stepping;
13
14
pub use self::graph::GraphInfo;
15
pub use self::{condition::*, config::*, error::*, executor::*, node::*, schedule::*, set::*};
16
pub use pass::ScheduleBuildPass;
17
18
/// An implementation of a graph data structure.
19
pub mod graph;
20
21
/// Included optional schedule build passes.
22
pub mod passes {
23
pub use crate::schedule::auto_insert_apply_deferred::*;
24
}
25
26
use self::graph::*;
27
28
#[cfg(test)]
29
mod tests {
30
use super::*;
31
#[cfg(feature = "trace")]
32
use alloc::string::ToString;
33
use alloc::{vec, vec::Vec};
34
use core::sync::atomic::{AtomicU32, Ordering};
35
36
pub use crate::{
37
prelude::World,
38
resource::Resource,
39
schedule::{Schedule, SystemSet},
40
system::{Res, ResMut},
41
};
42
43
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
44
enum TestSystems {
45
A,
46
B,
47
C,
48
D,
49
X,
50
}
51
52
#[derive(Resource, Default)]
53
struct SystemOrder(Vec<u32>);
54
55
#[derive(Resource, Default)]
56
struct RunConditionBool(bool);
57
58
#[derive(Resource, Default)]
59
struct Counter(AtomicU32);
60
61
fn make_exclusive_system(tag: u32) -> impl FnMut(&mut World) {
62
move |world| world.resource_mut::<SystemOrder>().0.push(tag)
63
}
64
65
fn make_function_system(tag: u32) -> impl FnMut(ResMut<SystemOrder>) {
66
move |mut resource: ResMut<SystemOrder>| resource.0.push(tag)
67
}
68
69
fn named_system(mut resource: ResMut<SystemOrder>) {
70
resource.0.push(u32::MAX);
71
}
72
73
fn named_exclusive_system(world: &mut World) {
74
world.resource_mut::<SystemOrder>().0.push(u32::MAX);
75
}
76
77
fn counting_system(counter: Res<Counter>) {
78
counter.0.fetch_add(1, Ordering::Relaxed);
79
}
80
81
mod system_execution {
82
use super::*;
83
84
#[test]
85
fn run_system() {
86
let mut world = World::default();
87
let mut schedule = Schedule::default();
88
89
world.init_resource::<SystemOrder>();
90
91
schedule.add_systems(make_function_system(0));
92
schedule.run(&mut world);
93
94
assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
95
}
96
97
#[test]
98
fn run_exclusive_system() {
99
let mut world = World::default();
100
let mut schedule = Schedule::default();
101
102
world.init_resource::<SystemOrder>();
103
104
schedule.add_systems(make_exclusive_system(0));
105
schedule.run(&mut world);
106
107
assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
108
}
109
110
#[test]
111
#[cfg(not(miri))]
112
fn parallel_execution() {
113
use alloc::sync::Arc;
114
use bevy_tasks::{ComputeTaskPool, TaskPool};
115
use std::sync::Barrier;
116
117
let mut world = World::default();
118
let mut schedule = Schedule::default();
119
let thread_count = ComputeTaskPool::get_or_init(TaskPool::default).thread_num();
120
121
let barrier = Arc::new(Barrier::new(thread_count));
122
123
for _ in 0..thread_count {
124
let inner = barrier.clone();
125
schedule.add_systems(move || {
126
inner.wait();
127
});
128
}
129
130
schedule.run(&mut world);
131
}
132
}
133
134
mod system_ordering {
135
use super::*;
136
137
#[test]
138
fn order_systems() {
139
let mut world = World::default();
140
let mut schedule = Schedule::default();
141
142
world.init_resource::<SystemOrder>();
143
144
schedule.add_systems((
145
named_system,
146
make_function_system(1).before(named_system),
147
make_function_system(0)
148
.after(named_system)
149
.in_set(TestSystems::A),
150
));
151
schedule.run(&mut world);
152
153
assert_eq!(world.resource::<SystemOrder>().0, vec![1, u32::MAX, 0]);
154
155
world.insert_resource(SystemOrder::default());
156
157
assert_eq!(world.resource::<SystemOrder>().0, vec![]);
158
159
// modify the schedule after it's been initialized and test ordering with sets
160
schedule.configure_sets(TestSystems::A.after(named_system));
161
schedule.add_systems((
162
make_function_system(3)
163
.before(TestSystems::A)
164
.after(named_system),
165
make_function_system(4).after(TestSystems::A),
166
));
167
schedule.run(&mut world);
168
169
assert_eq!(
170
world.resource::<SystemOrder>().0,
171
vec![1, u32::MAX, 3, 0, 4]
172
);
173
}
174
175
#[test]
176
fn order_exclusive_systems() {
177
let mut world = World::default();
178
let mut schedule = Schedule::default();
179
180
world.init_resource::<SystemOrder>();
181
182
schedule.add_systems((
183
named_exclusive_system,
184
make_exclusive_system(1).before(named_exclusive_system),
185
make_exclusive_system(0).after(named_exclusive_system),
186
));
187
schedule.run(&mut world);
188
189
assert_eq!(world.resource::<SystemOrder>().0, vec![1, u32::MAX, 0]);
190
}
191
192
#[test]
193
fn add_systems_correct_order() {
194
let mut world = World::new();
195
let mut schedule = Schedule::default();
196
197
world.init_resource::<SystemOrder>();
198
199
schedule.add_systems(
200
(
201
make_function_system(0),
202
make_function_system(1),
203
make_exclusive_system(2),
204
make_function_system(3),
205
)
206
.chain(),
207
);
208
209
schedule.run(&mut world);
210
assert_eq!(world.resource::<SystemOrder>().0, vec![0, 1, 2, 3]);
211
}
212
213
#[test]
214
fn add_systems_correct_order_nested() {
215
let mut world = World::new();
216
let mut schedule = Schedule::default();
217
218
world.init_resource::<SystemOrder>();
219
220
schedule.add_systems(
221
(
222
(make_function_system(0), make_function_system(1)).chain(),
223
make_function_system(2),
224
(make_function_system(3), make_function_system(4)).chain(),
225
(
226
make_function_system(5),
227
(make_function_system(6), make_function_system(7)),
228
),
229
(
230
(make_function_system(8), make_function_system(9)).chain(),
231
make_function_system(10),
232
),
233
)
234
.chain(),
235
);
236
237
schedule.run(&mut world);
238
let order = &world.resource::<SystemOrder>().0;
239
assert_eq!(
240
&order[0..5],
241
&[0, 1, 2, 3, 4],
242
"first five items should be exactly ordered"
243
);
244
let unordered = &order[5..8];
245
assert!(
246
unordered.contains(&5) && unordered.contains(&6) && unordered.contains(&7),
247
"unordered must be 5, 6, and 7 in any order"
248
);
249
let partially_ordered = &order[8..11];
250
assert!(
251
partially_ordered == [8, 9, 10] || partially_ordered == [10, 8, 9],
252
"partially_ordered must be [8, 9, 10] or [10, 8, 9]"
253
);
254
assert_eq!(order.len(), 11, "must have exactly 11 order entries");
255
}
256
}
257
258
mod conditions {
259
260
use crate::{
261
change_detection::DetectChanges,
262
error::{ignore, DefaultErrorHandler, Result},
263
};
264
265
use super::*;
266
267
#[test]
268
fn system_with_condition_bool() {
269
let mut world = World::default();
270
let mut schedule = Schedule::default();
271
272
world.init_resource::<RunConditionBool>();
273
world.init_resource::<SystemOrder>();
274
275
schedule.add_systems(
276
make_function_system(0).run_if(|condition: Res<RunConditionBool>| condition.0),
277
);
278
279
schedule.run(&mut world);
280
assert_eq!(world.resource::<SystemOrder>().0, vec![]);
281
282
world.resource_mut::<RunConditionBool>().0 = true;
283
schedule.run(&mut world);
284
assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
285
}
286
287
#[test]
288
fn system_with_condition_result_bool() {
289
let mut world = World::default();
290
world.insert_resource(DefaultErrorHandler(ignore));
291
let mut schedule = Schedule::default();
292
293
world.init_resource::<SystemOrder>();
294
295
schedule.add_systems((
296
make_function_system(0).run_if(|| -> Result<bool> { Err(core::fmt::Error.into()) }),
297
make_function_system(1).run_if(|| -> Result<bool> { Ok(false) }),
298
));
299
300
schedule.run(&mut world);
301
assert_eq!(world.resource::<SystemOrder>().0, vec![]);
302
303
schedule.add_systems(make_function_system(2).run_if(|| -> Result<bool> { Ok(true) }));
304
305
schedule.run(&mut world);
306
assert_eq!(world.resource::<SystemOrder>().0, vec![2]);
307
}
308
309
#[test]
310
fn systems_with_distributive_condition() {
311
let mut world = World::default();
312
let mut schedule = Schedule::default();
313
314
world.insert_resource(RunConditionBool(true));
315
world.init_resource::<SystemOrder>();
316
317
fn change_condition(mut condition: ResMut<RunConditionBool>) {
318
condition.0 = false;
319
}
320
321
schedule.add_systems(
322
(
323
make_function_system(0),
324
change_condition,
325
make_function_system(1),
326
)
327
.chain()
328
.distributive_run_if(|condition: Res<RunConditionBool>| condition.0),
329
);
330
331
schedule.run(&mut world);
332
assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
333
}
334
335
#[test]
336
fn run_exclusive_system_with_condition() {
337
let mut world = World::default();
338
let mut schedule = Schedule::default();
339
340
world.init_resource::<RunConditionBool>();
341
world.init_resource::<SystemOrder>();
342
343
schedule.add_systems(
344
make_exclusive_system(0).run_if(|condition: Res<RunConditionBool>| condition.0),
345
);
346
347
schedule.run(&mut world);
348
assert_eq!(world.resource::<SystemOrder>().0, vec![]);
349
350
world.resource_mut::<RunConditionBool>().0 = true;
351
schedule.run(&mut world);
352
assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
353
}
354
355
#[test]
356
fn multiple_conditions_on_system() {
357
let mut world = World::default();
358
let mut schedule = Schedule::default();
359
360
world.init_resource::<Counter>();
361
362
schedule.add_systems((
363
counting_system.run_if(|| false).run_if(|| false),
364
counting_system.run_if(|| true).run_if(|| false),
365
counting_system.run_if(|| false).run_if(|| true),
366
counting_system.run_if(|| true).run_if(|| true),
367
));
368
369
schedule.run(&mut world);
370
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
371
}
372
373
#[test]
374
fn multiple_conditions_on_system_sets() {
375
let mut world = World::default();
376
let mut schedule = Schedule::default();
377
378
world.init_resource::<Counter>();
379
380
schedule.configure_sets(TestSystems::A.run_if(|| false).run_if(|| false));
381
schedule.add_systems(counting_system.in_set(TestSystems::A));
382
schedule.configure_sets(TestSystems::B.run_if(|| true).run_if(|| false));
383
schedule.add_systems(counting_system.in_set(TestSystems::B));
384
schedule.configure_sets(TestSystems::C.run_if(|| false).run_if(|| true));
385
schedule.add_systems(counting_system.in_set(TestSystems::C));
386
schedule.configure_sets(TestSystems::D.run_if(|| true).run_if(|| true));
387
schedule.add_systems(counting_system.in_set(TestSystems::D));
388
389
schedule.run(&mut world);
390
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
391
}
392
393
#[test]
394
fn systems_nested_in_system_sets() {
395
let mut world = World::default();
396
let mut schedule = Schedule::default();
397
398
world.init_resource::<Counter>();
399
400
schedule.configure_sets(TestSystems::A.run_if(|| false));
401
schedule.add_systems(counting_system.in_set(TestSystems::A).run_if(|| false));
402
schedule.configure_sets(TestSystems::B.run_if(|| true));
403
schedule.add_systems(counting_system.in_set(TestSystems::B).run_if(|| false));
404
schedule.configure_sets(TestSystems::C.run_if(|| false));
405
schedule.add_systems(counting_system.in_set(TestSystems::C).run_if(|| true));
406
schedule.configure_sets(TestSystems::D.run_if(|| true));
407
schedule.add_systems(counting_system.in_set(TestSystems::D).run_if(|| true));
408
409
schedule.run(&mut world);
410
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
411
}
412
413
#[test]
414
fn system_conditions_and_change_detection() {
415
#[derive(Resource, Default)]
416
struct Bool2(pub bool);
417
418
let mut world = World::default();
419
world.init_resource::<Counter>();
420
world.init_resource::<RunConditionBool>();
421
world.init_resource::<Bool2>();
422
let mut schedule = Schedule::default();
423
424
schedule.add_systems(
425
counting_system
426
.run_if(|res1: Res<RunConditionBool>| res1.is_changed())
427
.run_if(|res2: Res<Bool2>| res2.is_changed()),
428
);
429
430
// both resource were just added.
431
schedule.run(&mut world);
432
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
433
434
// nothing has changed
435
schedule.run(&mut world);
436
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
437
438
// RunConditionBool has changed, but counting_system did not run
439
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
440
schedule.run(&mut world);
441
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
442
443
// internal state for the bool2 run criteria was updated in the
444
// previous run, so system still does not run
445
world.get_resource_mut::<Bool2>().unwrap().0 = false;
446
schedule.run(&mut world);
447
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
448
449
// internal state for bool2 was updated, so system still does not run
450
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
451
schedule.run(&mut world);
452
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
453
454
// now check that it works correctly changing Bool2 first and then RunConditionBool
455
world.get_resource_mut::<Bool2>().unwrap().0 = false;
456
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
457
schedule.run(&mut world);
458
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 2);
459
}
460
461
#[test]
462
fn system_set_conditions_and_change_detection() {
463
#[derive(Resource, Default)]
464
struct Bool2(pub bool);
465
466
let mut world = World::default();
467
world.init_resource::<Counter>();
468
world.init_resource::<RunConditionBool>();
469
world.init_resource::<Bool2>();
470
let mut schedule = Schedule::default();
471
472
schedule.configure_sets(
473
TestSystems::A
474
.run_if(|res1: Res<RunConditionBool>| res1.is_changed())
475
.run_if(|res2: Res<Bool2>| res2.is_changed()),
476
);
477
478
schedule.add_systems(counting_system.in_set(TestSystems::A));
479
480
// both resource were just added.
481
schedule.run(&mut world);
482
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
483
484
// nothing has changed
485
schedule.run(&mut world);
486
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
487
488
// RunConditionBool has changed, but counting_system did not run
489
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
490
schedule.run(&mut world);
491
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
492
493
// internal state for the bool2 run criteria was updated in the
494
// previous run, so system still does not run
495
world.get_resource_mut::<Bool2>().unwrap().0 = false;
496
schedule.run(&mut world);
497
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
498
499
// internal state for bool2 was updated, so system still does not run
500
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
501
schedule.run(&mut world);
502
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
503
504
// the system only runs when both are changed on the same run
505
world.get_resource_mut::<Bool2>().unwrap().0 = false;
506
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
507
schedule.run(&mut world);
508
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 2);
509
}
510
511
#[test]
512
fn mixed_conditions_and_change_detection() {
513
#[derive(Resource, Default)]
514
struct Bool2(pub bool);
515
516
let mut world = World::default();
517
world.init_resource::<Counter>();
518
world.init_resource::<RunConditionBool>();
519
world.init_resource::<Bool2>();
520
let mut schedule = Schedule::default();
521
522
schedule.configure_sets(
523
TestSystems::A.run_if(|res1: Res<RunConditionBool>| res1.is_changed()),
524
);
525
526
schedule.add_systems(
527
counting_system
528
.run_if(|res2: Res<Bool2>| res2.is_changed())
529
.in_set(TestSystems::A),
530
);
531
532
// both resource were just added.
533
schedule.run(&mut world);
534
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
535
536
// nothing has changed
537
schedule.run(&mut world);
538
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
539
540
// RunConditionBool has changed, but counting_system did not run
541
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
542
schedule.run(&mut world);
543
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
544
545
// we now only change bool2 and the system also should not run
546
world.get_resource_mut::<Bool2>().unwrap().0 = false;
547
schedule.run(&mut world);
548
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
549
550
// internal state for the bool2 run criteria was updated in the
551
// previous run, so system still does not run
552
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
553
schedule.run(&mut world);
554
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
555
556
// the system only runs when both are changed on the same run
557
world.get_resource_mut::<Bool2>().unwrap().0 = false;
558
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
559
schedule.run(&mut world);
560
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 2);
561
}
562
}
563
564
mod schedule_build_errors {
565
use super::*;
566
567
#[test]
568
fn dependency_loop() {
569
let mut schedule = Schedule::default();
570
schedule.configure_sets(TestSystems::X.after(TestSystems::X));
571
let mut world = World::new();
572
let result = schedule.initialize(&mut world);
573
assert!(matches!(result, Err(ScheduleBuildError::DependencyLoop(_))));
574
}
575
576
#[test]
577
fn dependency_loop_from_chain() {
578
let mut schedule = Schedule::default();
579
schedule.configure_sets((TestSystems::X, TestSystems::X).chain());
580
let mut world = World::new();
581
let result = schedule.initialize(&mut world);
582
assert!(matches!(result, Err(ScheduleBuildError::DependencyLoop(_))));
583
}
584
585
#[test]
586
fn dependency_cycle() {
587
let mut world = World::new();
588
let mut schedule = Schedule::default();
589
590
schedule.configure_sets(TestSystems::A.after(TestSystems::B));
591
schedule.configure_sets(TestSystems::B.after(TestSystems::A));
592
593
let result = schedule.initialize(&mut world);
594
assert!(matches!(
595
result,
596
Err(ScheduleBuildError::DependencyCycle(_))
597
));
598
599
fn foo() {}
600
fn bar() {}
601
602
let mut world = World::new();
603
let mut schedule = Schedule::default();
604
605
schedule.add_systems((foo.after(bar), bar.after(foo)));
606
let result = schedule.initialize(&mut world);
607
assert!(matches!(
608
result,
609
Err(ScheduleBuildError::DependencyCycle(_))
610
));
611
}
612
613
#[test]
614
fn hierarchy_loop() {
615
let mut schedule = Schedule::default();
616
schedule.configure_sets(TestSystems::X.in_set(TestSystems::X));
617
let mut world = World::new();
618
let result = schedule.initialize(&mut world);
619
assert!(matches!(result, Err(ScheduleBuildError::HierarchyLoop(_))));
620
}
621
622
#[test]
623
fn hierarchy_cycle() {
624
let mut world = World::new();
625
let mut schedule = Schedule::default();
626
627
schedule.configure_sets(TestSystems::A.in_set(TestSystems::B));
628
schedule.configure_sets(TestSystems::B.in_set(TestSystems::A));
629
630
let result = schedule.initialize(&mut world);
631
assert!(matches!(result, Err(ScheduleBuildError::HierarchyCycle(_))));
632
}
633
634
#[test]
635
fn system_type_set_ambiguity() {
636
// Define some systems.
637
fn foo() {}
638
fn bar() {}
639
640
let mut world = World::new();
641
let mut schedule = Schedule::default();
642
643
// Schedule `bar` to run after `foo`.
644
schedule.add_systems((foo, bar.after(foo)));
645
646
// There's only one `foo`, so it's fine.
647
let result = schedule.initialize(&mut world);
648
assert!(result.is_ok());
649
650
// Schedule another `foo`.
651
schedule.add_systems(foo);
652
653
// When there are multiple instances of `foo`, dependencies on
654
// `foo` are no longer allowed. Too much ambiguity.
655
let result = schedule.initialize(&mut world);
656
assert!(matches!(
657
result,
658
Err(ScheduleBuildError::SystemTypeSetAmbiguity(_))
659
));
660
661
// same goes for `ambiguous_with`
662
let mut schedule = Schedule::default();
663
schedule.add_systems(foo);
664
schedule.add_systems(bar.ambiguous_with(foo));
665
let result = schedule.initialize(&mut world);
666
assert!(result.is_ok());
667
schedule.add_systems(foo);
668
let result = schedule.initialize(&mut world);
669
assert!(matches!(
670
result,
671
Err(ScheduleBuildError::SystemTypeSetAmbiguity(_))
672
));
673
}
674
675
#[test]
676
#[should_panic]
677
fn configure_system_type_set() {
678
fn foo() {}
679
let mut schedule = Schedule::default();
680
schedule.configure_sets(foo.into_system_set());
681
}
682
683
#[test]
684
fn hierarchy_redundancy() {
685
let mut world = World::new();
686
let mut schedule = Schedule::default();
687
688
schedule.set_build_settings(ScheduleBuildSettings {
689
hierarchy_detection: LogLevel::Error,
690
..Default::default()
691
});
692
693
// Add `A`.
694
schedule.configure_sets(TestSystems::A);
695
696
// Add `B` as child of `A`.
697
schedule.configure_sets(TestSystems::B.in_set(TestSystems::A));
698
699
// Add `X` as child of both `A` and `B`.
700
schedule.configure_sets(TestSystems::X.in_set(TestSystems::A).in_set(TestSystems::B));
701
702
// `X` cannot be the `A`'s child and grandchild at the same time.
703
let result = schedule.initialize(&mut world);
704
assert!(matches!(
705
result,
706
Err(ScheduleBuildError::Elevated(
707
ScheduleBuildWarning::HierarchyRedundancy(_)
708
))
709
));
710
}
711
712
#[test]
713
fn cross_dependency() {
714
let mut world = World::new();
715
let mut schedule = Schedule::default();
716
717
// Add `B` and give it both kinds of relationships with `A`.
718
schedule.configure_sets(TestSystems::B.in_set(TestSystems::A));
719
schedule.configure_sets(TestSystems::B.after(TestSystems::A));
720
let result = schedule.initialize(&mut world);
721
assert!(matches!(
722
result,
723
Err(ScheduleBuildError::CrossDependency(_, _))
724
));
725
}
726
727
#[test]
728
fn sets_have_order_but_intersect() {
729
let mut world = World::new();
730
let mut schedule = Schedule::default();
731
732
fn foo() {}
733
734
// Add `foo` to both `A` and `C`.
735
schedule.add_systems(foo.in_set(TestSystems::A).in_set(TestSystems::C));
736
737
// Order `A -> B -> C`.
738
schedule.configure_sets((
739
TestSystems::A,
740
TestSystems::B.after(TestSystems::A),
741
TestSystems::C.after(TestSystems::B),
742
));
743
744
let result = schedule.initialize(&mut world);
745
// `foo` can't be in both `A` and `C` because they can't run at the same time.
746
assert!(matches!(
747
result,
748
Err(ScheduleBuildError::SetsHaveOrderButIntersect(_, _))
749
));
750
}
751
752
#[test]
753
fn ambiguity() {
754
#[derive(Resource)]
755
struct X;
756
757
fn res_ref(_x: Res<X>) {}
758
fn res_mut(_x: ResMut<X>) {}
759
760
let mut world = World::new();
761
let mut schedule = Schedule::default();
762
763
schedule.set_build_settings(ScheduleBuildSettings {
764
ambiguity_detection: LogLevel::Error,
765
..Default::default()
766
});
767
768
schedule.add_systems((res_ref, res_mut));
769
let result = schedule.initialize(&mut world);
770
assert!(matches!(
771
result,
772
Err(ScheduleBuildError::Elevated(
773
ScheduleBuildWarning::Ambiguity(_)
774
))
775
));
776
}
777
}
778
779
mod system_ambiguity {
780
#[cfg(feature = "trace")]
781
use alloc::collections::BTreeSet;
782
783
use super::*;
784
use crate::prelude::*;
785
786
#[derive(Resource)]
787
struct R;
788
789
#[derive(Component)]
790
struct A;
791
792
#[derive(Component)]
793
struct B;
794
795
#[derive(Message)]
796
struct E;
797
798
#[derive(Resource, Component)]
799
struct RC;
800
801
fn empty_system() {}
802
fn res_system(_res: Res<R>) {}
803
fn resmut_system(_res: ResMut<R>) {}
804
fn nonsend_system(_ns: NonSend<R>) {}
805
fn nonsendmut_system(_ns: NonSendMut<R>) {}
806
fn read_component_system(_query: Query<&A>) {}
807
fn write_component_system(_query: Query<&mut A>) {}
808
fn with_filtered_component_system(_query: Query<&mut A, With<B>>) {}
809
fn without_filtered_component_system(_query: Query<&mut A, Without<B>>) {}
810
fn entity_ref_system(_query: Query<EntityRef>) {}
811
fn entity_mut_system(_query: Query<EntityMut>) {}
812
fn message_reader_system(_reader: MessageReader<E>) {}
813
fn message_writer_system(_writer: MessageWriter<E>) {}
814
fn message_resource_system(_events: ResMut<Messages<E>>) {}
815
fn read_world_system(_world: &World) {}
816
fn write_world_system(_world: &mut World) {}
817
818
// Tests for conflict detection
819
820
#[test]
821
fn one_of_everything() {
822
let mut world = World::new();
823
world.insert_resource(R);
824
world.spawn(A);
825
world.init_resource::<Messages<E>>();
826
827
let mut schedule = Schedule::default();
828
schedule
829
// nonsendmut system deliberately conflicts with resmut system
830
.add_systems((resmut_system, write_component_system, message_writer_system));
831
832
let _ = schedule.initialize(&mut world);
833
834
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
835
}
836
837
#[test]
838
fn read_only() {
839
let mut world = World::new();
840
world.insert_resource(R);
841
world.spawn(A);
842
world.init_resource::<Messages<E>>();
843
844
let mut schedule = Schedule::default();
845
schedule.add_systems((
846
empty_system,
847
empty_system,
848
res_system,
849
res_system,
850
nonsend_system,
851
nonsend_system,
852
read_component_system,
853
read_component_system,
854
entity_ref_system,
855
entity_ref_system,
856
message_reader_system,
857
message_reader_system,
858
read_world_system,
859
read_world_system,
860
));
861
862
let _ = schedule.initialize(&mut world);
863
864
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
865
}
866
867
#[test]
868
fn read_world() {
869
let mut world = World::new();
870
world.insert_resource(R);
871
world.spawn(A);
872
world.init_resource::<Messages<E>>();
873
874
let mut schedule = Schedule::default();
875
schedule.add_systems((
876
resmut_system,
877
write_component_system,
878
message_writer_system,
879
read_world_system,
880
));
881
882
let _ = schedule.initialize(&mut world);
883
884
assert_eq!(schedule.graph().conflicting_systems().len(), 3);
885
}
886
887
#[test]
888
fn resources() {
889
let mut world = World::new();
890
world.insert_resource(R);
891
892
let mut schedule = Schedule::default();
893
schedule.add_systems((resmut_system, res_system));
894
895
let _ = schedule.initialize(&mut world);
896
897
assert_eq!(schedule.graph().conflicting_systems().len(), 1);
898
}
899
900
#[test]
901
fn nonsend() {
902
let mut world = World::new();
903
world.insert_resource(R);
904
905
let mut schedule = Schedule::default();
906
schedule.add_systems((nonsendmut_system, nonsend_system));
907
908
let _ = schedule.initialize(&mut world);
909
910
assert_eq!(schedule.graph().conflicting_systems().len(), 1);
911
}
912
913
#[test]
914
fn components() {
915
let mut world = World::new();
916
world.spawn(A);
917
918
let mut schedule = Schedule::default();
919
schedule.add_systems((read_component_system, write_component_system));
920
921
let _ = schedule.initialize(&mut world);
922
923
assert_eq!(schedule.graph().conflicting_systems().len(), 1);
924
}
925
926
#[test]
927
fn filtered_components() {
928
let mut world = World::new();
929
world.spawn(A);
930
931
let mut schedule = Schedule::default();
932
schedule.add_systems((
933
with_filtered_component_system,
934
without_filtered_component_system,
935
));
936
937
let _ = schedule.initialize(&mut world);
938
939
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
940
}
941
942
#[test]
943
fn events() {
944
let mut world = World::new();
945
world.init_resource::<Messages<E>>();
946
947
let mut schedule = Schedule::default();
948
schedule.add_systems((
949
// All of these systems clash
950
message_reader_system,
951
message_writer_system,
952
message_resource_system,
953
));
954
955
let _ = schedule.initialize(&mut world);
956
957
assert_eq!(schedule.graph().conflicting_systems().len(), 3);
958
}
959
960
/// Test that when a struct is both a Resource and a Component, they do not
961
/// conflict with each other.
962
#[test]
963
fn shared_resource_mut_component() {
964
let mut world = World::new();
965
world.insert_resource(RC);
966
967
let mut schedule = Schedule::default();
968
schedule.add_systems((|_: ResMut<RC>| {}, |_: Query<&mut RC>| {}));
969
970
let _ = schedule.initialize(&mut world);
971
972
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
973
}
974
975
#[test]
976
fn resource_mut_and_entity_ref() {
977
let mut world = World::new();
978
world.insert_resource(R);
979
980
let mut schedule = Schedule::default();
981
schedule.add_systems((resmut_system, entity_ref_system));
982
983
let _ = schedule.initialize(&mut world);
984
985
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
986
}
987
988
#[test]
989
fn resource_and_entity_mut() {
990
let mut world = World::new();
991
world.insert_resource(R);
992
993
let mut schedule = Schedule::default();
994
schedule.add_systems((res_system, nonsend_system, entity_mut_system));
995
996
let _ = schedule.initialize(&mut world);
997
998
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
999
}
1000
1001
#[test]
1002
fn write_component_and_entity_ref() {
1003
let mut world = World::new();
1004
world.insert_resource(R);
1005
1006
let mut schedule = Schedule::default();
1007
schedule.add_systems((write_component_system, entity_ref_system));
1008
1009
let _ = schedule.initialize(&mut world);
1010
1011
assert_eq!(schedule.graph().conflicting_systems().len(), 1);
1012
}
1013
1014
#[test]
1015
fn read_component_and_entity_mut() {
1016
let mut world = World::new();
1017
world.insert_resource(R);
1018
1019
let mut schedule = Schedule::default();
1020
schedule.add_systems((read_component_system, entity_mut_system));
1021
1022
let _ = schedule.initialize(&mut world);
1023
1024
assert_eq!(schedule.graph().conflicting_systems().len(), 1);
1025
}
1026
1027
#[test]
1028
fn exclusive() {
1029
let mut world = World::new();
1030
world.insert_resource(R);
1031
world.spawn(A);
1032
world.init_resource::<Messages<E>>();
1033
1034
let mut schedule = Schedule::default();
1035
schedule.add_systems((
1036
// All 3 of these conflict with each other
1037
write_world_system,
1038
write_world_system,
1039
res_system,
1040
));
1041
1042
let _ = schedule.initialize(&mut world);
1043
1044
assert_eq!(schedule.graph().conflicting_systems().len(), 3);
1045
}
1046
1047
// Tests for silencing and resolving ambiguities
1048
#[test]
1049
fn before_and_after() {
1050
let mut world = World::new();
1051
world.init_resource::<Messages<E>>();
1052
1053
let mut schedule = Schedule::default();
1054
schedule.add_systems((
1055
message_reader_system.before(message_writer_system),
1056
message_writer_system,
1057
message_resource_system.after(message_writer_system),
1058
));
1059
1060
let _ = schedule.initialize(&mut world);
1061
1062
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
1063
}
1064
1065
#[test]
1066
fn ignore_all_ambiguities() {
1067
let mut world = World::new();
1068
world.insert_resource(R);
1069
1070
let mut schedule = Schedule::default();
1071
schedule.add_systems((
1072
resmut_system.ambiguous_with_all(),
1073
res_system,
1074
nonsend_system,
1075
));
1076
1077
let _ = schedule.initialize(&mut world);
1078
1079
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
1080
}
1081
1082
#[test]
1083
fn ambiguous_with_label() {
1084
let mut world = World::new();
1085
world.insert_resource(R);
1086
1087
#[derive(SystemSet, Hash, PartialEq, Eq, Debug, Clone)]
1088
struct IgnoreMe;
1089
1090
let mut schedule = Schedule::default();
1091
schedule.add_systems((
1092
resmut_system.ambiguous_with(IgnoreMe),
1093
res_system.in_set(IgnoreMe),
1094
nonsend_system.in_set(IgnoreMe),
1095
));
1096
1097
let _ = schedule.initialize(&mut world);
1098
1099
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
1100
}
1101
1102
#[test]
1103
fn ambiguous_with_system() {
1104
let mut world = World::new();
1105
1106
let mut schedule = Schedule::default();
1107
schedule.add_systems((
1108
write_component_system.ambiguous_with(read_component_system),
1109
read_component_system,
1110
));
1111
let _ = schedule.initialize(&mut world);
1112
1113
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
1114
}
1115
1116
#[derive(ScheduleLabel, Hash, PartialEq, Eq, Debug, Clone)]
1117
struct TestSchedule;
1118
1119
// Tests that the correct ambiguities were reported in the correct order.
1120
#[test]
1121
#[cfg(feature = "trace")]
1122
fn correct_ambiguities() {
1123
fn system_a(_res: ResMut<R>) {}
1124
fn system_b(_res: ResMut<R>) {}
1125
fn system_c(_res: ResMut<R>) {}
1126
fn system_d(_res: ResMut<R>) {}
1127
fn system_e(_res: ResMut<R>) {}
1128
1129
let mut world = World::new();
1130
world.insert_resource(R);
1131
1132
let mut schedule = Schedule::new(TestSchedule);
1133
schedule.add_systems((
1134
system_a,
1135
system_b,
1136
system_c.ambiguous_with_all(),
1137
system_d.ambiguous_with(system_b),
1138
system_e.after(system_a),
1139
));
1140
1141
schedule.graph_mut().initialize(&mut world);
1142
let _ = schedule
1143
.graph_mut()
1144
.build_schedule(&mut world, &BTreeSet::new());
1145
1146
let ambiguities: Vec<_> = schedule
1147
.graph()
1148
.conflicts_to_string(schedule.graph().conflicting_systems(), world.components())
1149
.map(|item| {
1150
(
1151
item.0,
1152
item.1,
1153
item.2
1154
.into_iter()
1155
.map(|name| name.to_string())
1156
.collect::<Vec<_>>(),
1157
)
1158
})
1159
.collect();
1160
1161
let expected = &[
1162
(
1163
"system_d".to_string(),
1164
"system_a".to_string(),
1165
vec!["bevy_ecs::schedule::tests::system_ambiguity::R".into()],
1166
),
1167
(
1168
"system_d".to_string(),
1169
"system_e".to_string(),
1170
vec!["bevy_ecs::schedule::tests::system_ambiguity::R".into()],
1171
),
1172
(
1173
"system_b".to_string(),
1174
"system_a".to_string(),
1175
vec!["bevy_ecs::schedule::tests::system_ambiguity::R".into()],
1176
),
1177
(
1178
"system_b".to_string(),
1179
"system_e".to_string(),
1180
vec!["bevy_ecs::schedule::tests::system_ambiguity::R".into()],
1181
),
1182
];
1183
1184
// ordering isn't stable so do this
1185
for entry in expected {
1186
assert!(ambiguities.contains(entry));
1187
}
1188
}
1189
1190
// Test that anonymous set names work properly
1191
// Related issue https://github.com/bevyengine/bevy/issues/9641
1192
#[test]
1193
#[cfg(feature = "trace")]
1194
fn anonymous_set_name() {
1195
let mut schedule = Schedule::new(TestSchedule);
1196
schedule.add_systems((resmut_system, resmut_system).run_if(|| true));
1197
1198
let mut world = World::new();
1199
schedule.graph_mut().initialize(&mut world);
1200
let _ = schedule
1201
.graph_mut()
1202
.build_schedule(&mut world, &BTreeSet::new());
1203
1204
let ambiguities: Vec<_> = schedule
1205
.graph()
1206
.conflicts_to_string(schedule.graph().conflicting_systems(), world.components())
1207
.map(|item| {
1208
(
1209
item.0,
1210
item.1,
1211
item.2
1212
.into_iter()
1213
.map(|name| name.to_string())
1214
.collect::<Vec<_>>(),
1215
)
1216
})
1217
.collect();
1218
1219
assert_eq!(
1220
ambiguities[0],
1221
(
1222
"resmut_system (in set (resmut_system, resmut_system))".to_string(),
1223
"resmut_system (in set (resmut_system, resmut_system))".to_string(),
1224
vec!["bevy_ecs::schedule::tests::system_ambiguity::R".into()],
1225
)
1226
);
1227
}
1228
1229
#[test]
1230
fn ignore_component_resource_ambiguities() {
1231
let mut world = World::new();
1232
world.insert_resource(R);
1233
world.allow_ambiguous_resource::<R>();
1234
let mut schedule = Schedule::new(TestSchedule);
1235
1236
// check resource
1237
schedule.add_systems((resmut_system, res_system));
1238
schedule.initialize(&mut world).unwrap();
1239
assert!(schedule.graph().conflicting_systems().is_empty());
1240
1241
// check components
1242
world.allow_ambiguous_component::<A>();
1243
schedule.add_systems((write_component_system, read_component_system));
1244
schedule.initialize(&mut world).unwrap();
1245
assert!(schedule.graph().conflicting_systems().is_empty());
1246
}
1247
}
1248
1249
#[cfg(feature = "bevy_debug_stepping")]
1250
mod stepping {
1251
use super::*;
1252
use bevy_ecs::system::SystemState;
1253
1254
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
1255
pub struct TestSchedule;
1256
1257
macro_rules! assert_executor_supports_stepping {
1258
($executor:expr) => {
1259
// create a test schedule
1260
let mut schedule = Schedule::new(TestSchedule);
1261
schedule
1262
.set_executor_kind($executor)
1263
.add_systems(|| -> () { panic!("Executor ignored Stepping") });
1264
1265
// Add our schedule to stepping & and enable stepping; this should
1266
// prevent any systems in the schedule from running
1267
let mut stepping = Stepping::default();
1268
stepping.add_schedule(TestSchedule).enable();
1269
1270
// create a world, and add the stepping resource
1271
let mut world = World::default();
1272
world.insert_resource(stepping);
1273
1274
// start a new frame by running ihe begin_frame() system
1275
let mut system_state: SystemState<Option<ResMut<Stepping>>> =
1276
SystemState::new(&mut world);
1277
let res = system_state.get_mut(&mut world);
1278
Stepping::begin_frame(res);
1279
1280
// now run the schedule; this will panic if the executor doesn't
1281
// handle stepping
1282
schedule.run(&mut world);
1283
};
1284
}
1285
1286
/// verify the [`SingleThreadedExecutor`] supports stepping
1287
#[test]
1288
fn single_threaded_executor() {
1289
assert_executor_supports_stepping!(ExecutorKind::SingleThreaded);
1290
}
1291
1292
/// verify the [`MultiThreadedExecutor`] supports stepping
1293
#[test]
1294
fn multi_threaded_executor() {
1295
assert_executor_supports_stepping!(ExecutorKind::MultiThreaded);
1296
}
1297
}
1298
}
1299
1300