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