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