Skip to main content

bevy_ecs/schedule/executor/
mod.rs

1#[cfg(feature = "std")]
2mod multi_threaded;
3mod single_threaded;
4
5use alloc::{boxed::Box, vec, vec::Vec};
6use bevy_utils::prelude::DebugName;
7use core::any::TypeId;
8
9pub use self::single_threaded::SingleThreadedExecutor;
10
11#[cfg(feature = "std")]
12pub use self::multi_threaded::{MainThreadExecutor, MultiThreadedExecutor};
13
14pub use fixedbitset::FixedBitSet;
15
16use crate::{
17    change_detection::{CheckChangeTicks, Tick},
18    error::{BevyError, ErrorContext, Result},
19    prelude::{IntoSystemSet, SystemSet},
20    query::FilteredAccessSet,
21    schedule::{
22        ConditionWithAccess, InternedSystemSet, SystemKey, SystemSetKey, SystemTypeSet,
23        SystemWithAccess,
24    },
25    system::{RunSystemError, System, SystemIn, SystemStateFlags},
26    world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
27};
28
29/// Types that can run a [`SystemSchedule`] on a [`World`].
30pub trait SystemExecutor: Send + Sync {
31    /// Called once after the schedule is built or rebuilt.
32    fn init(&mut self, schedule: &SystemSchedule);
33    /// Runs the systems in the schedule.
34    fn run(
35        &mut self,
36        schedule: &mut SystemSchedule,
37        world: &mut World,
38        skip_systems: Option<&FixedBitSet>,
39        error_handler: fn(BevyError, ErrorContext),
40    );
41    /// Sets whether deferred system buffers should be applied after all systems have run.
42    fn set_apply_final_deferred(&mut self, value: bool);
43}
44
45/// Returns the default executor for the current platform.
46///
47/// On Wasm or when the `multi_threaded` feature is disabled, this returns a
48/// [`SingleThreadedExecutor`]. Otherwise it returns a [`MultiThreadedExecutor`].
49pub fn default_executor() -> Box<dyn SystemExecutor> {
50    #[cfg(all(
51        not(target_arch = "wasm32"),
52        feature = "std",
53        feature = "multi_threaded"
54    ))]
55    {
56        Box::new(MultiThreadedExecutor::new())
57    }
58    #[cfg(any(
59        target_arch = "wasm32",
60        not(feature = "std"),
61        not(feature = "multi_threaded")
62    ))]
63    {
64        Box::new(SingleThreadedExecutor::new())
65    }
66}
67
68/// Holds systems and conditions of a [`Schedule`](super::Schedule) sorted in topological order
69/// (along with dependency information for `multi_threaded` execution).
70///
71/// Since the arrays are sorted in the same order, elements are referenced by their index.
72/// [`FixedBitSet`] is used as a smaller, more efficient substitute of `HashSet<usize>`.
73#[derive(Default)]
74pub struct SystemSchedule {
75    /// List of system node ids.
76    pub(super) system_ids: Vec<SystemKey>,
77    /// Indexed by system node id.
78    pub(super) systems: Vec<SystemWithAccess>,
79    /// Indexed by system node id.
80    pub(super) system_conditions: Vec<Vec<ConditionWithAccess>>,
81    /// Indexed by system node id.
82    /// Number of systems that the system immediately depends on.
83    #[cfg_attr(
84        not(feature = "std"),
85        expect(dead_code, reason = "currently only used with the std feature")
86    )]
87    pub(super) system_dependencies: Vec<usize>,
88    /// Indexed by system node id.
89    /// List of systems that immediately depend on the system.
90    #[cfg_attr(
91        not(feature = "std"),
92        expect(dead_code, reason = "currently only used with the std feature")
93    )]
94    pub(super) system_dependents: Vec<Vec<usize>>,
95    /// Indexed by system node id.
96    /// List of sets containing the system that have conditions
97    pub(super) sets_with_conditions_of_systems: Vec<FixedBitSet>,
98    /// List of system set node ids.
99    pub(super) set_ids: Vec<SystemSetKey>,
100    /// Indexed by system set node id.
101    pub(super) set_conditions: Vec<Vec<ConditionWithAccess>>,
102    /// Indexed by system set node id.
103    /// List of systems that are in sets that have conditions.
104    ///
105    /// If a set doesn't run because of its conditions, this is used to skip all systems in it.
106    pub(super) systems_in_sets_with_conditions: Vec<FixedBitSet>,
107}
108
109impl SystemSchedule {
110    /// Creates an empty [`SystemSchedule`].
111    pub const fn new() -> Self {
112        Self {
113            systems: Vec::new(),
114            system_conditions: Vec::new(),
115            set_conditions: Vec::new(),
116            system_ids: Vec::new(),
117            set_ids: Vec::new(),
118            system_dependencies: Vec::new(),
119            system_dependents: Vec::new(),
120            sets_with_conditions_of_systems: Vec::new(),
121            systems_in_sets_with_conditions: Vec::new(),
122        }
123    }
124
125    /// Accessor to allow running systems from a custom executor
126    ///
127    /// # Safety
128    /// - The only allowed mutations are from calling methods on the [`System`] trait. Replacing
129    ///   systems in the returned [`Vec`] should be considered undefined behavior.
130    pub unsafe fn systems_mut(&mut self) -> &mut Vec<SystemWithAccess> {
131        &mut self.systems
132    }
133}
134
135/// A special [`System`] that instructs the executor to call
136/// [`System::apply_deferred`] on the systems that have run but not applied
137/// their [`Deferred`] system parameters (like [`Commands`]) or other system buffers.
138///
139/// ## Scheduling
140///
141/// `ApplyDeferred` systems are scheduled *by default*
142/// - later in the same schedule run (for example, if a system with `Commands` param
143///   is scheduled in `Update`, all the changes will be visible in `PostUpdate`)
144/// - between systems with dependencies if the dependency [has deferred buffers]
145///   (if system `bar` directly or indirectly depends on `foo`, and `foo` uses
146///   `Commands` param, changes to the world in `foo` will be visible in `bar`)
147///
148/// ## Notes
149/// - This system (currently) does nothing if it's called manually or wrapped
150///   inside a [`PipeSystem`].
151/// - Modifying a [`Schedule`] may change the order buffers are applied.
152///
153/// [`System::apply_deferred`]: crate::system::System::apply_deferred
154/// [`Deferred`]: crate::system::Deferred
155/// [`Commands`]: crate::prelude::Commands
156/// [has deferred buffers]: crate::system::System::has_deferred
157/// [`PipeSystem`]: crate::system::PipeSystem
158/// [`Schedule`]: super::Schedule
159#[doc(alias = "apply_system_buffers")]
160pub struct ApplyDeferred;
161
162/// Returns `true` if the [`System`] is an instance of [`ApplyDeferred`].
163pub(super) fn is_apply_deferred(system: &dyn System<In = (), Out = ()>) -> bool {
164    system.system_type() == TypeId::of::<ApplyDeferred>()
165}
166
167impl System for ApplyDeferred {
168    type In = ();
169    type Out = ();
170
171    fn name(&self) -> DebugName {
172        DebugName::borrowed("bevy_ecs::apply_deferred")
173    }
174
175    fn flags(&self) -> SystemStateFlags {
176        // non-send , exclusive , no deferred
177        SystemStateFlags::NON_SEND | SystemStateFlags::EXCLUSIVE
178    }
179
180    unsafe fn run_unsafe(
181        &mut self,
182        _input: SystemIn<'_, Self>,
183        _world: UnsafeWorldCell,
184    ) -> Result<Self::Out, RunSystemError> {
185        // This system does nothing on its own. The executor will apply deferred
186        // commands from other systems instead of running this system.
187        Ok(())
188    }
189
190    #[cfg(feature = "hotpatching")]
191    #[inline]
192    fn refresh_hotpatch(&mut self) {}
193
194    fn run(
195        &mut self,
196        _input: SystemIn<'_, Self>,
197        _world: &mut World,
198    ) -> Result<Self::Out, RunSystemError> {
199        // This system does nothing on its own. The executor will apply deferred
200        // commands from other systems instead of running this system.
201        Ok(())
202    }
203
204    fn apply_deferred(&mut self, _world: &mut World) {}
205
206    fn queue_deferred(&mut self, _world: DeferredWorld) {}
207
208    fn initialize(&mut self, _world: &mut World) -> FilteredAccessSet {
209        FilteredAccessSet::new()
210    }
211
212    fn check_change_tick(&mut self, _check: CheckChangeTicks) {}
213
214    fn default_system_sets(&self) -> Vec<InternedSystemSet> {
215        vec![SystemTypeSet::<Self>::new().intern()]
216    }
217
218    fn get_last_run(&self) -> Tick {
219        // This system is never run, so it has no last run tick.
220        Tick::MAX
221    }
222
223    fn set_last_run(&mut self, _last_run: Tick) {}
224}
225
226impl IntoSystemSet<()> for ApplyDeferred {
227    type Set = SystemTypeSet<Self>;
228
229    fn into_system_set(self) -> Self::Set {
230        SystemTypeSet::<Self>::new()
231    }
232}
233
234/// These functions hide the bottom of the callstack from `RUST_BACKTRACE=1` (assuming the default panic handler is used).
235///
236/// The full callstack will still be visible with `RUST_BACKTRACE=full`.
237/// They are specialized for `System::run` & co instead of being generic over closures because this avoids an
238/// extra frame in the backtrace.
239///
240/// This is reliant on undocumented behavior in Rust's default panic handler, which checks the call stack for symbols
241/// containing the string `__rust_begin_short_backtrace` in their mangled name.
242mod __rust_begin_short_backtrace {
243    use core::hint::black_box;
244
245    #[cfg(feature = "std")]
246    use crate::world::unsafe_world_cell::UnsafeWorldCell;
247    use crate::{
248        error::Result,
249        system::{ReadOnlySystem, RunSystemError, ScheduleSystem},
250        world::World,
251    };
252
253    /// # Safety
254    /// See `System::run_unsafe`.
255    // This is only used by `MultiThreadedExecutor`, and would be dead code without `std`.
256    #[cfg(feature = "std")]
257    #[inline(never)]
258    pub(super) unsafe fn run_unsafe(
259        system: &mut ScheduleSystem,
260        world: UnsafeWorldCell,
261    ) -> Result<(), RunSystemError> {
262        // SAFETY: Upheld by caller
263        let result = unsafe { system.run_unsafe((), world) };
264        // Call `black_box` to prevent this frame from being tail-call optimized away
265        black_box(());
266        result
267    }
268
269    /// # Safety
270    /// See `ReadOnlySystem::run_unsafe`.
271    // This is only used by `MultiThreadedExecutor`, and would be dead code without `std`.
272    #[cfg(feature = "std")]
273    #[inline(never)]
274    pub(super) unsafe fn readonly_run_unsafe<O: 'static>(
275        system: &mut dyn ReadOnlySystem<In = (), Out = O>,
276        world: UnsafeWorldCell,
277    ) -> Result<O, RunSystemError> {
278        // Call `black_box` to prevent this frame from being tail-call optimized away
279        // SAFETY: Upheld by caller
280        black_box(unsafe { system.run_unsafe((), world) })
281    }
282
283    #[cfg(feature = "std")]
284    #[inline(never)]
285    pub(super) fn run(
286        system: &mut ScheduleSystem,
287        world: &mut World,
288    ) -> Result<(), RunSystemError> {
289        let result = system.run((), world);
290        // Call `black_box` to prevent this frame from being tail-call optimized away
291        black_box(());
292        result
293    }
294
295    #[inline(never)]
296    pub(super) fn run_without_applying_deferred(
297        system: &mut ScheduleSystem,
298        world: &mut World,
299    ) -> Result<(), RunSystemError> {
300        let result = system.run_without_applying_deferred((), world);
301        // Call `black_box` to prevent this frame from being tail-call optimized away
302        black_box(());
303        result
304    }
305
306    #[inline(never)]
307    pub(super) fn readonly_run<O: 'static>(
308        system: &mut dyn ReadOnlySystem<In = (), Out = O>,
309        world: &mut World,
310    ) -> Result<O, RunSystemError> {
311        // Call `black_box` to prevent this frame from being tail-call optimized away
312        black_box(system.run((), world))
313    }
314}
315
316#[cfg(test)]
317mod tests {
318    use crate::{
319        prelude::{Component, In, IntoSystem, Resource, Schedule},
320        schedule::{MultiThreadedExecutor, SingleThreadedExecutor},
321        system::{Populated, Res, ResMut, Single},
322        world::World,
323    };
324
325    #[derive(Component)]
326    struct TestComponent;
327
328    #[derive(Resource, Default)]
329    struct TestState {
330        populated_ran: bool,
331        single_ran: bool,
332    }
333
334    #[derive(Resource, Default)]
335    struct Counter(u8);
336
337    fn set_single_state(mut _single: Single<&TestComponent>, mut state: ResMut<TestState>) {
338        state.single_ran = true;
339    }
340
341    fn set_populated_state(
342        mut _populated: Populated<&TestComponent>,
343        mut state: ResMut<TestState>,
344    ) {
345        state.populated_ran = true;
346    }
347
348    #[test]
349    fn single_and_populated_skipped_and_run_singlethreaded() {
350        let mut schedule = Schedule::default();
351        schedule.set_executor(SingleThreadedExecutor::new());
352        single_and_populated_skipped_and_run("SingleThreaded", schedule);
353    }
354
355    #[test]
356    fn single_and_populated_skipped_and_run_multithreaded() {
357        let mut schedule = Schedule::default();
358        schedule.set_executor(MultiThreadedExecutor::new());
359        single_and_populated_skipped_and_run("MultiThreaded", schedule);
360    }
361
362    #[expect(clippy::print_stdout, reason = "std and println are allowed in tests")]
363    fn single_and_populated_skipped_and_run(name: &str, mut schedule: Schedule) {
364        std::println!("Testing executor: {name}");
365
366        let mut world = World::new();
367        world.init_resource::<TestState>();
368
369        schedule.add_systems((set_single_state, set_populated_state));
370        schedule.run(&mut world);
371
372        let state = world.get_resource::<TestState>().unwrap();
373        assert!(!state.single_ran);
374        assert!(!state.populated_ran);
375
376        world.spawn(TestComponent);
377
378        schedule.run(&mut world);
379        let state = world.get_resource::<TestState>().unwrap();
380        assert!(state.single_ran);
381        assert!(state.populated_ran);
382    }
383
384    fn look_for_missing_resource(_res: Res<TestState>) {}
385
386    #[test]
387    #[should_panic]
388    fn missing_resource_panics_single_threaded() {
389        let mut world = World::new();
390        let mut schedule = Schedule::default();
391
392        schedule.set_executor(SingleThreadedExecutor::new());
393        schedule.add_systems(look_for_missing_resource);
394        schedule.run(&mut world);
395    }
396
397    #[test]
398    #[should_panic]
399    fn missing_resource_panics_multi_threaded() {
400        let mut world = World::new();
401        let mut schedule = Schedule::default();
402
403        schedule.set_executor(MultiThreadedExecutor::new());
404        schedule.add_systems(look_for_missing_resource);
405        schedule.run(&mut world);
406    }
407
408    #[test]
409    fn piped_systems_first_system_skipped() {
410        // This system should be skipped when run due to no matching entity
411        fn pipe_out(_single: Single<&TestComponent>) -> u8 {
412            42
413        }
414
415        fn pipe_in(_input: In<u8>, mut counter: ResMut<Counter>) {
416            counter.0 += 1;
417        }
418
419        let mut world = World::new();
420        world.init_resource::<Counter>();
421        let mut schedule = Schedule::default();
422
423        schedule.add_systems(pipe_out.pipe(pipe_in));
424        schedule.run(&mut world);
425
426        let counter = world.resource::<Counter>();
427        assert_eq!(counter.0, 0);
428    }
429
430    #[test]
431    fn piped_system_second_system_skipped() {
432        // This system will be run before the second system is validated
433        fn pipe_out(mut counter: ResMut<Counter>) -> u8 {
434            counter.0 += 1;
435            42
436        }
437
438        // This system should be skipped when run due to no matching entity
439        fn pipe_in(_input: In<u8>, _single: Single<&TestComponent>, mut counter: ResMut<Counter>) {
440            counter.0 += 1;
441        }
442
443        let mut world = World::new();
444        world.init_resource::<Counter>();
445        let mut schedule = Schedule::default();
446
447        schedule.add_systems(pipe_out.pipe(pipe_in));
448        schedule.run(&mut world);
449        let counter = world.resource::<Counter>();
450        assert_eq!(counter.0, 1);
451    }
452
453    #[test]
454    #[should_panic]
455    fn piped_system_first_system_panics() {
456        // This system should panic when run because the resource is missing
457        fn pipe_out(_res: Res<TestState>) -> u8 {
458            42
459        }
460
461        fn pipe_in(_input: In<u8>) {}
462
463        let mut world = World::new();
464        let mut schedule = Schedule::default();
465
466        schedule.add_systems(pipe_out.pipe(pipe_in));
467        schedule.run(&mut world);
468    }
469
470    #[test]
471    #[should_panic]
472    fn piped_system_second_system_panics() {
473        fn pipe_out() -> u8 {
474            42
475        }
476
477        // This system should panic when run because the resource is missing
478        fn pipe_in(_input: In<u8>, _res: Res<TestState>) {}
479
480        let mut world = World::new();
481        let mut schedule = Schedule::default();
482
483        schedule.add_systems(pipe_out.pipe(pipe_in));
484        schedule.run(&mut world);
485    }
486
487    // This test runs without panicking because we've
488    // decided to use early-out behavior for piped systems
489    #[test]
490    fn piped_system_skip_and_panic() {
491        // This system should be skipped when run due to no matching entity
492        fn pipe_out(_single: Single<&TestComponent>) -> u8 {
493            42
494        }
495
496        // This system should panic when run because the resource is missing
497        fn pipe_in(_input: In<u8>, _res: Res<TestState>) {}
498
499        let mut world = World::new();
500        let mut schedule = Schedule::default();
501
502        schedule.add_systems(pipe_out.pipe(pipe_in));
503        schedule.run(&mut world);
504    }
505
506    #[test]
507    #[should_panic]
508    fn piped_system_panic_and_skip() {
509        // This system should panic when run because the resource is missing
510
511        fn pipe_out(_res: Res<TestState>) -> u8 {
512            42
513        }
514
515        // This system should be skipped when run due to no matching entity
516        fn pipe_in(_input: In<u8>, _single: Single<&TestComponent>) {}
517
518        let mut world = World::new();
519        let mut schedule = Schedule::default();
520
521        schedule.add_systems(pipe_out.pipe(pipe_in));
522        schedule.run(&mut world);
523    }
524
525    #[test]
526    #[should_panic]
527    fn piped_system_panic_and_panic() {
528        // This system should panic when run because the resource is missing
529
530        fn pipe_out(_res: Res<TestState>) -> u8 {
531            42
532        }
533
534        // This system should panic when run because the resource is missing
535        fn pipe_in(_input: In<u8>, _res: Res<TestState>) {}
536
537        let mut world = World::new();
538        let mut schedule = Schedule::default();
539
540        schedule.add_systems(pipe_out.pipe(pipe_in));
541        schedule.run(&mut world);
542    }
543
544    #[test]
545    fn piped_system_skip_and_skip() {
546        // This system should be skipped when run due to no matching entity
547
548        fn pipe_out(_single: Single<&TestComponent>, mut counter: ResMut<Counter>) -> u8 {
549            counter.0 += 1;
550            42
551        }
552
553        // This system should be skipped when run due to no matching entity
554        fn pipe_in(_input: In<u8>, _single: Single<&TestComponent>, mut counter: ResMut<Counter>) {
555            counter.0 += 1;
556        }
557
558        let mut world = World::new();
559        world.init_resource::<Counter>();
560        let mut schedule = Schedule::default();
561
562        schedule.add_systems(pipe_out.pipe(pipe_in));
563        schedule.run(&mut world);
564
565        let counter = world.resource::<Counter>();
566        assert_eq!(counter.0, 0);
567    }
568}
569
570#[cfg(test)]
571mod validation_tests {
572    use crate::{
573        prelude::{Component, In, IntoSystem, Resource, Schedule},
574        schedule::{MultiThreadedExecutor, SingleThreadedExecutor},
575        system::{
576            DynParamBuilder, DynSystemParam, ExclusiveSystemParam, Local, ParamBuilder, ParamSet,
577            Query, Res, ResMut, RunSystemError, RunSystemOnce, Single, SystemMeta,
578            SystemParamBuilder, SystemParamValidationError,
579        },
580        world::World,
581    };
582
583    #[derive(Component)]
584    struct TestComponent;
585
586    #[derive(Resource, Default)]
587    struct Counter(u8);
588
589    // A resource that won't be inserted, causing validation to fail.
590    #[derive(Resource)]
591    struct MissingResource;
592
593    /// An [`ExclusiveSystemParam`] that always fails validation.
594    struct AlwaysInvalid;
595
596    impl ExclusiveSystemParam for AlwaysInvalid {
597        type State = ();
598        type Item<'s> = AlwaysInvalid;
599
600        fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {}
601
602        fn get_param<'s>(
603            _state: &'s mut Self::State,
604            _system_meta: &SystemMeta,
605        ) -> Result<Self::Item<'s>, SystemParamValidationError> {
606            Err(SystemParamValidationError::invalid::<Self>(
607                "always invalid",
608            ))
609        }
610    }
611
612    #[test]
613    fn function_system_validation_failure_is_error() {
614        fn system(_res: Res<MissingResource>) {}
615
616        let mut world = World::new();
617        let result = world.run_system_once(system);
618        assert!(
619            matches!(result, Err(RunSystemError::Failed(_))),
620            "Expected Failed, got {result:?}"
621        );
622    }
623
624    #[test]
625    fn function_system_validation_skip() {
626        fn system(_single: Single<&TestComponent>) {}
627
628        let mut world = World::new();
629        let result = world.run_system_once(system);
630        assert!(
631            matches!(result, Err(RunSystemError::Skipped(_))),
632            "Expected Skipped, got {result:?}"
633        );
634    }
635
636    #[test]
637    fn function_system_validation_success() {
638        fn system(_res: Res<Counter>) {}
639
640        let mut world = World::new();
641        world.init_resource::<Counter>();
642        let result = world.run_system_once(system);
643        assert!(result.is_ok(), "Expected Ok, got {result:?}");
644    }
645
646    #[test]
647    fn adapter_system_validation_failure() {
648        fn system(_res: Res<MissingResource>) -> u32 {
649            42
650        }
651
652        let mut world = World::new();
653        let result = world.run_system_once(system.map(|_x| {}));
654        assert!(
655            matches!(result, Err(RunSystemError::Failed(_))),
656            "Expected Failed from adapter system, got {result:?}"
657        );
658    }
659
660    #[test]
661    fn adapter_system_validation_skip() {
662        fn system(_single: Single<&TestComponent>) -> u32 {
663            42
664        }
665
666        let mut world = World::new();
667        let result = world.run_system_once(system.map(|_x| {}));
668        assert!(
669            matches!(result, Err(RunSystemError::Skipped(_))),
670            "Expected Skipped from adapter system, got {result:?}"
671        );
672    }
673
674    #[test]
675    fn pipe_system_validation_failure_in_first() {
676        fn first(_res: Res<MissingResource>) -> u32 {
677            42
678        }
679        fn second(_input: In<u32>) {}
680
681        let mut world = World::new();
682        let result = world.run_system_once(first.pipe(second));
683        assert!(
684            matches!(result, Err(RunSystemError::Failed(_))),
685            "Expected Failed from pipe first system, got {result:?}"
686        );
687    }
688
689    #[test]
690    fn pipe_system_validation_failure_in_second() {
691        fn first() -> u32 {
692            42
693        }
694        fn second(_input: In<u32>, _res: Res<MissingResource>) {}
695
696        let mut world = World::new();
697        let result = world.run_system_once(first.pipe(second));
698        assert!(
699            matches!(result, Err(RunSystemError::Failed(_))),
700            "Expected Failed from pipe second system, got {result:?}"
701        );
702    }
703
704    #[test]
705    fn pipe_system_validation_skip_in_first() {
706        fn first(_single: Single<&TestComponent>) -> u32 {
707            42
708        }
709        fn second(_input: In<u32>) {}
710
711        let mut world = World::new();
712        let result = world.run_system_once(first.pipe(second));
713        assert!(
714            matches!(result, Err(RunSystemError::Skipped(_))),
715            "Expected Skipped from pipe first system, got {result:?}"
716        );
717    }
718
719    #[test]
720    fn pipe_system_validation_skip_in_second() {
721        fn first() -> u32 {
722            42
723        }
724
725        fn second(_input: In<u32>, _single: Single<&TestComponent>) {}
726
727        let mut world = World::new();
728        let result = world.run_system_once(first.pipe(second));
729        assert!(
730            matches!(result, Err(RunSystemError::Skipped(_))),
731            "Expected Skipped from pipe second system, got {result:?}"
732        );
733    }
734
735    #[test]
736    fn builder_system_validation_failure() {
737        fn system(_res: Res<MissingResource>) {}
738
739        let mut world = World::new();
740        let result = world.run_system_once(ParamBuilder.build_system(system));
741        assert!(
742            matches!(result, Err(RunSystemError::Failed(_))),
743            "Expected Failed from builder system, got {result:?}"
744        );
745    }
746
747    #[test]
748    fn builder_system_validation_skip() {
749        fn system(_single: Single<&TestComponent>) {}
750
751        let mut world = World::new();
752        let result = world.run_system_once(ParamBuilder.build_system(system));
753        assert!(
754            matches!(result, Err(RunSystemError::Skipped(_))),
755            "Expected Skipped from builder system, got {result:?}"
756        );
757    }
758
759    #[test]
760    fn dyn_system_param_validation_failure() {
761        let mut world = World::new();
762        let system = (DynParamBuilder::new::<Res<MissingResource>>(ParamBuilder),)
763            .build_state(&mut world)
764            .build_system(|_param: DynSystemParam| {});
765        let result = world.run_system_once(system);
766        assert!(
767            matches!(result, Err(RunSystemError::Failed(_))),
768            "Expected Failed from DynSystemParam system, got {result:?}"
769        );
770    }
771
772    #[test]
773    fn dyn_system_param_validation_skip() {
774        let mut world = World::new();
775        let system = (DynParamBuilder::new::<Single<&TestComponent>>(ParamBuilder),)
776            .build_state(&mut world)
777            .build_system(|_param: DynSystemParam| {});
778        let result = world.run_system_once(system);
779        assert!(
780            matches!(result, Err(RunSystemError::Skipped(_))),
781            "Expected Skipped from DynSystemParam system, got {result:?}"
782        );
783    }
784
785    #[test]
786    fn dyn_system_param_validation_success() {
787        let mut world = World::new();
788        world.init_resource::<Counter>();
789        let system = (DynParamBuilder::new::<Res<Counter>>(ParamBuilder),)
790            .build_state(&mut world)
791            .build_system(|_param: DynSystemParam| {});
792        let result = world.run_system_once(system);
793        assert!(
794            result.is_ok(),
795            "Expected Ok from DynSystemParam system, got {result:?}"
796        );
797    }
798
799    #[test]
800    fn exclusive_system_validation_failure() {
801        fn system(_world: &mut World, _param: AlwaysInvalid) {}
802
803        let mut world = World::new();
804        let result = world.run_system_once(system);
805        assert!(
806            matches!(result, Err(RunSystemError::Failed(_))),
807            "Expected Failed from exclusive system, got {result:?}"
808        );
809    }
810
811    #[test]
812    fn exclusive_system_validation_success() {
813        fn system(_world: &mut World, mut _local: Local<u32>) {}
814
815        let mut world = World::new();
816        let result = world.run_system_once(system);
817        assert!(
818            result.is_ok(),
819            "Expected Ok from exclusive system, got {result:?}"
820        );
821    }
822
823    #[test]
824    fn validation_skips_system_in_schedule_singlethreaded() {
825        let mut schedule = Schedule::default();
826        schedule.set_executor(SingleThreadedExecutor::new());
827        validation_skips_system_in_schedule("SingleThreaded", schedule);
828    }
829
830    #[test]
831    fn validation_skips_system_in_schedule_multithreaded() {
832        let mut schedule = Schedule::default();
833        schedule.set_executor(MultiThreadedExecutor::new());
834        validation_skips_system_in_schedule("MultiThreaded", schedule);
835    }
836
837    fn validation_skips_system_in_schedule(name: &str, mut schedule: Schedule) {
838        // Ensure the executor properly handles validation failures by skipping
839        // and not running the system body.
840        fn skippable_system(_single: Single<&TestComponent>, mut counter: ResMut<Counter>) {
841            counter.0 += 1;
842        }
843
844        let mut world = World::new();
845        world.init_resource::<Counter>();
846
847        schedule.add_systems(skippable_system);
848
849        // No TestComponent entity exists, so the system should be skipped.
850        schedule.run(&mut world);
851        assert_eq!(
852            world.resource::<Counter>().0,
853            0,
854            "System should have been skipped with {name}"
855        );
856    }
857
858    #[test]
859    fn param_set_validation_skip() {
860        // A system using ParamSet with a Single sub-param should be skipped
861        // when the Single has no matching entities, rather than panicking.
862        fn system(mut _set: ParamSet<(Single<&TestComponent>,)>) {}
863
864        let mut world = World::new();
865        let result = world.run_system_once(system);
866        assert!(
867            matches!(result, Err(RunSystemError::Skipped(_))),
868            "Expected Skipped from ParamSet with invalid Single, got {result:?}"
869        );
870    }
871
872    #[test]
873    fn param_set_validation_failure() {
874        // A system using ParamSet with a Res sub-param should fail validation
875        // when the resource does not exist.
876        fn system(mut _set: ParamSet<(Query<&TestComponent>, Res<MissingResource>)>) {}
877
878        let mut world = World::new();
879        let result = world.run_system_once(system);
880        assert!(
881            matches!(result, Err(RunSystemError::Failed(_))),
882            "Expected Failed from ParamSet with missing resource, got {result:?}"
883        );
884    }
885
886    #[test]
887    fn param_set_validation_success() {
888        // A system using ParamSet with valid sub-params should succeed.
889        fn system(mut set: ParamSet<(Query<&TestComponent>, Res<Counter>)>) {
890            let _q = set.p0();
891        }
892
893        let mut world = World::new();
894        world.init_resource::<Counter>();
895        let result = world.run_system_once(system);
896        assert!(
897            result.is_ok(),
898            "Expected Ok from ParamSet with valid params, got {result:?}"
899        );
900    }
901}