Skip to main content

bevy_ecs/schedule/
mod.rs

1//! Contains APIs for ordering systems and executing them on a [`World`](crate::world::World)
2
3mod 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
18/// An implementation of a graph data structure.
19pub mod graph;
20
21/// Included optional schedule build passes.
22pub 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            // The resource was just added for the first time, so it should be considered changed.
137            schedule.run(&mut world);
138            // The resource has not been modified since the last run, so it should not be considered changed.
139            schedule.run(&mut world);
140
141            world.resource_mut::<TestResource>().0 = true;
142            // The resource has been modified, so it should be considered changed.
143            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            // modify the schedule after it's been initialized and test ordering with sets
221            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            // both resource were just added.
492            schedule.run(&mut world);
493            assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
494
495            // nothing has changed
496            schedule.run(&mut world);
497            assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
498
499            // RunConditionBool has changed, but counting_system did 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            // internal state for the bool2 run criteria was updated in the
505            // previous run, so system still does not run
506            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            // internal state for bool2 was updated, so system still does not run
511            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            // now check that it works correctly changing Bool2 first and then RunConditionBool
516            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            // both resource were just added.
542            schedule.run(&mut world);
543            assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
544
545            // nothing has changed
546            schedule.run(&mut world);
547            assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
548
549            // RunConditionBool has changed, but counting_system did not run
550            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            // internal state for the bool2 run criteria was updated in the
555            // previous run, so system still does not run
556            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            // internal state for bool2 was updated, so system still does not run
561            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            // the system only runs when both are changed on the same run
566            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            // both resource were just added.
594            schedule.run(&mut world);
595            assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
596
597            // nothing has changed
598            schedule.run(&mut world);
599            assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
600
601            // RunConditionBool has changed, but counting_system did not run
602            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            // we now only change bool2 and the system also should not run
607            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            // internal state for the bool2 run criteria was updated in the
612            // previous run, so system still does not run
613            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            // the system only runs when both are changed on the same run
618            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            // Define some systems.
722            fn foo() {}
723            fn bar() {}
724
725            let mut world = World::new();
726            let mut schedule = Schedule::default();
727
728            // Schedule `bar` to run after `foo`.
729            schedule.add_systems((foo, bar.after(foo)));
730
731            // There's only one `foo`, so it's fine.
732            let result = schedule.initialize(&mut world);
733            assert!(result.is_ok());
734
735            // Schedule another `foo`.
736            schedule.add_systems(foo);
737
738            // When there are multiple instances of `foo`, dependencies on
739            // `foo` are no longer allowed. Too much ambiguity.
740            let result = schedule.initialize(&mut world);
741            assert!(matches!(
742                result,
743                Err(ScheduleBuildError::SystemTypeSetAmbiguity(_))
744            ));
745
746            // same goes for `ambiguous_with`
747            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            // Add `A`.
779            schedule.configure_sets(TestSystems::A);
780
781            // Add `B` as child of `A`.
782            schedule.configure_sets(TestSystems::B.in_set(TestSystems::A));
783
784            // Add `X` as child of both `A` and `B`.
785            schedule.configure_sets(TestSystems::X.in_set(TestSystems::A).in_set(TestSystems::B));
786
787            // `X` cannot be the `A`'s child and grandchild at the same time.
788            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            // Add `B` and give it both kinds of relationships with `A`.
803            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            // Add `foo` to both `A` and `C`.
820            schedule.add_systems(foo.in_set(TestSystems::A).in_set(TestSystems::C));
821
822            // Order `A -> B -> C`.
823            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            // `foo` can't be in both `A` and `C` because they can't run at the same time.
831            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        // Tests for conflict detection
901
902        #[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                // nonsendmut system deliberately conflicts with resmut system
912                .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                // All of these systems clash
1032                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            // this should fail, since resources are components
1053            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            // this should not fail, since the queries are disjoint
1062            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            // this should fail, since resources are components and non_sends also do access with components
1076            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            // this should not fail, since the queries are disjoint
1086            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                // All 3 of these conflict with each other
1125                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        // Tests for silencing and resolving ambiguities
1136        #[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        // Tests that the correct ambiguities were reported in the correct order.
1208        #[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            // ordering isn't stable so do this
1274            for entry in expected {
1275                assert!(ambiguities.contains(entry));
1276            }
1277        }
1278
1279        // Test that anonymous set names work properly
1280        // Related issue https://github.com/bevyengine/bevy/issues/9641
1281        #[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            // check resource
1327            schedule.add_systems((resmut_system, res_system));
1328            schedule.initialize(&mut world).unwrap();
1329            assert!(schedule.graph().conflicting_systems().is_empty());
1330
1331            // check components
1332            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            // create a test schedule
1349            let mut schedule = Schedule::new(TestSchedule);
1350            schedule.set_executor(executor);
1351            schedule.add_systems(|| -> () { panic!("Executor ignored Stepping") });
1352
1353            // Add our schedule to stepping & and enable stepping; this should
1354            // prevent any systems in the schedule from running
1355            let mut stepping = Stepping::default();
1356            stepping.add_schedule(TestSchedule).enable();
1357
1358            // create a world, and add the stepping resource
1359            let mut world = World::default();
1360            world.insert_resource(stepping);
1361
1362            // start a new frame by running the begin_frame() system
1363            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            // now run the schedule; this will panic if the executor doesn't
1369            // handle stepping
1370            schedule.run(&mut world);
1371        }
1372
1373        /// verify the [`SingleThreadedExecutor`] supports stepping
1374        #[test]
1375        fn single_threaded_executor() {
1376            assert_executor_supports_stepping(SingleThreadedExecutor::new());
1377        }
1378
1379        /// verify the [`MultiThreadedExecutor`] supports stepping
1380        #[test]
1381        fn multi_threaded_executor() {
1382            assert_executor_supports_stepping(MultiThreadedExecutor::new());
1383        }
1384    }
1385}