Thanks to visit codestin.com
Credit goes to docs.rs

bevy_ecs/schedule/executor/
mod.rs

1#[cfg(feature = "std")]
2mod multi_threaded;
3mod simple;
4mod single_threaded;
5
6use alloc::{vec, vec::Vec};
7use bevy_utils::prelude::DebugName;
8use core::any::TypeId;
9
10#[expect(deprecated, reason = "We still need to support this.")]
11pub use self::{simple::SimpleExecutor, single_threaded::SingleThreadedExecutor};
12
13#[cfg(feature = "std")]
14pub use self::multi_threaded::{MainThreadExecutor, MultiThreadedExecutor};
15
16use fixedbitset::FixedBitSet;
17
18use crate::{
19    component::{CheckChangeTicks, Tick},
20    error::{BevyError, ErrorContext, Result},
21    prelude::{IntoSystemSet, SystemSet},
22    query::FilteredAccessSet,
23    schedule::{
24        ConditionWithAccess, InternedSystemSet, SystemKey, SystemSetKey, SystemTypeSet,
25        SystemWithAccess,
26    },
27    system::{RunSystemError, System, SystemIn, SystemParamValidationError, SystemStateFlags},
28    world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
29};
30
31/// Types that can run a [`SystemSchedule`] on a [`World`].
32pub(super) trait SystemExecutor: Send + Sync {
33    fn kind(&self) -> ExecutorKind;
34    fn init(&mut self, schedule: &SystemSchedule);
35    fn run(
36        &mut self,
37        schedule: &mut SystemSchedule,
38        world: &mut World,
39        skip_systems: Option<&FixedBitSet>,
40        error_handler: fn(BevyError, ErrorContext),
41    );
42    fn set_apply_final_deferred(&mut self, value: bool);
43}
44
45/// Specifies how a [`Schedule`](super::Schedule) will be run.
46///
47/// The default depends on the target platform:
48///  - [`SingleThreaded`](ExecutorKind::SingleThreaded) on Wasm.
49///  - [`MultiThreaded`](ExecutorKind::MultiThreaded) everywhere else.
50#[derive(PartialEq, Eq, Default, Debug, Copy, Clone)]
51pub enum ExecutorKind {
52    /// Runs the schedule using a single thread.
53    ///
54    /// Useful if you're dealing with a single-threaded environment, saving your threads for
55    /// other things, or just trying minimize overhead.
56    #[cfg_attr(
57        any(
58            target_arch = "wasm32",
59            not(feature = "std"),
60            not(feature = "multi_threaded")
61        ),
62        default
63    )]
64    SingleThreaded,
65    /// Like [`SingleThreaded`](ExecutorKind::SingleThreaded) but calls [`apply_deferred`](crate::system::System::apply_deferred)
66    /// immediately after running each system.
67    #[deprecated(
68        since = "0.17.0",
69        note = "Use SingleThreaded instead. See https://github.com/bevyengine/bevy/issues/18453 for motivation."
70    )]
71    Simple,
72    /// Runs the schedule using a thread pool. Non-conflicting systems can run in parallel.
73    #[cfg(feature = "std")]
74    #[cfg_attr(all(not(target_arch = "wasm32"), feature = "multi_threaded"), default)]
75    MultiThreaded,
76}
77
78/// Holds systems and conditions of a [`Schedule`](super::Schedule) sorted in topological order
79/// (along with dependency information for `multi_threaded` execution).
80///
81/// Since the arrays are sorted in the same order, elements are referenced by their index.
82/// [`FixedBitSet`] is used as a smaller, more efficient substitute of `HashSet<usize>`.
83#[derive(Default)]
84pub struct SystemSchedule {
85    /// List of system node ids.
86    pub(super) system_ids: Vec<SystemKey>,
87    /// Indexed by system node id.
88    pub(super) systems: Vec<SystemWithAccess>,
89    /// Indexed by system node id.
90    pub(super) system_conditions: Vec<Vec<ConditionWithAccess>>,
91    /// Indexed by system node id.
92    /// Number of systems that the system immediately depends on.
93    #[cfg_attr(
94        not(feature = "std"),
95        expect(dead_code, reason = "currently only used with the std feature")
96    )]
97    pub(super) system_dependencies: Vec<usize>,
98    /// Indexed by system node id.
99    /// List of systems that immediately depend on the system.
100    #[cfg_attr(
101        not(feature = "std"),
102        expect(dead_code, reason = "currently only used with the std feature")
103    )]
104    pub(super) system_dependents: Vec<Vec<usize>>,
105    /// Indexed by system node id.
106    /// List of sets containing the system that have conditions
107    pub(super) sets_with_conditions_of_systems: Vec<FixedBitSet>,
108    /// List of system set node ids.
109    pub(super) set_ids: Vec<SystemSetKey>,
110    /// Indexed by system set node id.
111    pub(super) set_conditions: Vec<Vec<ConditionWithAccess>>,
112    /// Indexed by system set node id.
113    /// List of systems that are in sets that have conditions.
114    ///
115    /// If a set doesn't run because of its conditions, this is used to skip all systems in it.
116    pub(super) systems_in_sets_with_conditions: Vec<FixedBitSet>,
117}
118
119impl SystemSchedule {
120    /// Creates an empty [`SystemSchedule`].
121    pub const fn new() -> Self {
122        Self {
123            systems: Vec::new(),
124            system_conditions: Vec::new(),
125            set_conditions: Vec::new(),
126            system_ids: Vec::new(),
127            set_ids: Vec::new(),
128            system_dependencies: Vec::new(),
129            system_dependents: Vec::new(),
130            sets_with_conditions_of_systems: Vec::new(),
131            systems_in_sets_with_conditions: Vec::new(),
132        }
133    }
134}
135
136/// A special [`System`] that instructs the executor to call
137/// [`System::apply_deferred`] on the systems that have run but not applied
138/// their [`Deferred`] system parameters (like [`Commands`]) or other system buffers.
139///
140/// ## Scheduling
141///
142/// `ApplyDeferred` systems are scheduled *by default*
143/// - later in the same schedule run (for example, if a system with `Commands` param
144///   is scheduled in `Update`, all the changes will be visible in `PostUpdate`)
145/// - between systems with dependencies if the dependency [has deferred buffers]
146///   (if system `bar` directly or indirectly depends on `foo`, and `foo` uses
147///   `Commands` param, changes to the world in `foo` will be visible in `bar`)
148///
149/// ## Notes
150/// - This system (currently) does nothing if it's called manually or wrapped
151///   inside a [`PipeSystem`].
152/// - Modifying a [`Schedule`] may change the order buffers are applied.
153///
154/// [`System::apply_deferred`]: crate::system::System::apply_deferred
155/// [`Deferred`]: crate::system::Deferred
156/// [`Commands`]: crate::prelude::Commands
157/// [has deferred buffers]: crate::system::System::has_deferred
158/// [`PipeSystem`]: crate::system::PipeSystem
159/// [`Schedule`]: super::Schedule
160#[doc(alias = "apply_system_buffers")]
161pub struct ApplyDeferred;
162
163/// Returns `true` if the [`System`] is an instance of [`ApplyDeferred`].
164pub(super) fn is_apply_deferred(system: &dyn System<In = (), Out = ()>) -> bool {
165    system.type_id() == TypeId::of::<ApplyDeferred>()
166}
167
168impl System for ApplyDeferred {
169    type In = ();
170    type Out = ();
171
172    fn name(&self) -> DebugName {
173        DebugName::borrowed("bevy_ecs::apply_deferred")
174    }
175
176    fn flags(&self) -> SystemStateFlags {
177        // non-send , exclusive , no deferred
178        SystemStateFlags::NON_SEND | SystemStateFlags::EXCLUSIVE
179    }
180
181    unsafe fn run_unsafe(
182        &mut self,
183        _input: SystemIn<'_, Self>,
184        _world: UnsafeWorldCell,
185    ) -> Result<Self::Out, RunSystemError> {
186        // This system does nothing on its own. The executor will apply deferred
187        // commands from other systems instead of running this system.
188        Ok(())
189    }
190
191    #[cfg(feature = "hotpatching")]
192    #[inline]
193    fn refresh_hotpatch(&mut self) {}
194
195    fn run(
196        &mut self,
197        _input: SystemIn<'_, Self>,
198        _world: &mut World,
199    ) -> Result<Self::Out, RunSystemError> {
200        // This system does nothing on its own. The executor will apply deferred
201        // commands from other systems instead of running this system.
202        Ok(())
203    }
204
205    fn apply_deferred(&mut self, _world: &mut World) {}
206
207    fn queue_deferred(&mut self, _world: DeferredWorld) {}
208
209    unsafe fn validate_param_unsafe(
210        &mut self,
211        _world: UnsafeWorldCell,
212    ) -> Result<(), SystemParamValidationError> {
213        // This system is always valid to run because it doesn't do anything,
214        // and only used as a marker for the executor.
215        Ok(())
216    }
217
218    fn initialize(&mut self, _world: &mut World) -> FilteredAccessSet {
219        FilteredAccessSet::new()
220    }
221
222    fn check_change_tick(&mut self, _check: CheckChangeTicks) {}
223
224    fn default_system_sets(&self) -> Vec<InternedSystemSet> {
225        vec![SystemTypeSet::<Self>::new().intern()]
226    }
227
228    fn get_last_run(&self) -> Tick {
229        // This system is never run, so it has no last run tick.
230        Tick::MAX
231    }
232
233    fn set_last_run(&mut self, _last_run: Tick) {}
234}
235
236impl IntoSystemSet<()> for ApplyDeferred {
237    type Set = SystemTypeSet<Self>;
238
239    fn into_system_set(self) -> Self::Set {
240        SystemTypeSet::<Self>::new()
241    }
242}
243
244/// These functions hide the bottom of the callstack from `RUST_BACKTRACE=1` (assuming the default panic handler is used).
245///
246/// The full callstack will still be visible with `RUST_BACKTRACE=full`.
247/// They are specialized for `System::run` & co instead of being generic over closures because this avoids an
248/// extra frame in the backtrace.
249///
250/// This is reliant on undocumented behavior in Rust's default panic handler, which checks the call stack for symbols
251/// containing the string `__rust_begin_short_backtrace` in their mangled name.
252mod __rust_begin_short_backtrace {
253    use core::hint::black_box;
254
255    #[cfg(feature = "std")]
256    use crate::world::unsafe_world_cell::UnsafeWorldCell;
257    use crate::{
258        error::Result,
259        system::{ReadOnlySystem, RunSystemError, ScheduleSystem},
260        world::World,
261    };
262
263    /// # Safety
264    /// See `System::run_unsafe`.
265    // This is only used by `MultiThreadedExecutor`, and would be dead code without `std`.
266    #[cfg(feature = "std")]
267    #[inline(never)]
268    pub(super) unsafe fn run_unsafe(
269        system: &mut ScheduleSystem,
270        world: UnsafeWorldCell,
271    ) -> Result<(), RunSystemError> {
272        let result = system.run_unsafe((), world);
273        // Call `black_box` to prevent this frame from being tail-call optimized away
274        black_box(());
275        result
276    }
277
278    /// # Safety
279    /// See `ReadOnlySystem::run_unsafe`.
280    // This is only used by `MultiThreadedExecutor`, and would be dead code without `std`.
281    #[cfg(feature = "std")]
282    #[inline(never)]
283    pub(super) unsafe fn readonly_run_unsafe<O: 'static>(
284        system: &mut dyn ReadOnlySystem<In = (), Out = O>,
285        world: UnsafeWorldCell,
286    ) -> Result<O, RunSystemError> {
287        // Call `black_box` to prevent this frame from being tail-call optimized away
288        black_box(system.run_unsafe((), world))
289    }
290
291    #[inline(never)]
292    pub(super) fn run(
293        system: &mut ScheduleSystem,
294        world: &mut World,
295    ) -> Result<(), RunSystemError> {
296        let result = system.run((), world);
297        // Call `black_box` to prevent this frame from being tail-call optimized away
298        black_box(());
299        result
300    }
301
302    #[inline(never)]
303    pub(super) fn run_without_applying_deferred(
304        system: &mut ScheduleSystem,
305        world: &mut World,
306    ) -> Result<(), RunSystemError> {
307        let result = system.run_without_applying_deferred((), world);
308        // Call `black_box` to prevent this frame from being tail-call optimized away
309        black_box(());
310        result
311    }
312
313    #[inline(never)]
314    pub(super) fn readonly_run<O: 'static>(
315        system: &mut dyn ReadOnlySystem<In = (), Out = O>,
316        world: &mut World,
317    ) -> Result<O, RunSystemError> {
318        // Call `black_box` to prevent this frame from being tail-call optimized away
319        black_box(system.run((), world))
320    }
321}
322
323#[cfg(test)]
324mod tests {
325    use crate::{
326        prelude::{Component, In, IntoSystem, Resource, Schedule},
327        schedule::ExecutorKind,
328        system::{Populated, Res, ResMut, Single},
329        world::World,
330    };
331
332    #[derive(Component)]
333    struct TestComponent;
334
335    const EXECUTORS: [ExecutorKind; 3] = [
336        #[expect(deprecated, reason = "We still need to test this.")]
337        ExecutorKind::Simple,
338        ExecutorKind::SingleThreaded,
339        ExecutorKind::MultiThreaded,
340    ];
341
342    #[derive(Resource, Default)]
343    struct TestState {
344        populated_ran: bool,
345        single_ran: bool,
346    }
347
348    #[derive(Resource, Default)]
349    struct Counter(u8);
350
351    fn set_single_state(mut _single: Single<&TestComponent>, mut state: ResMut<TestState>) {
352        state.single_ran = true;
353    }
354
355    fn set_populated_state(
356        mut _populated: Populated<&TestComponent>,
357        mut state: ResMut<TestState>,
358    ) {
359        state.populated_ran = true;
360    }
361
362    #[test]
363    #[expect(clippy::print_stdout, reason = "std and println are allowed in tests")]
364    fn single_and_populated_skipped_and_run() {
365        for executor in EXECUTORS {
366            std::println!("Testing executor: {executor:?}");
367
368            let mut world = World::new();
369            world.init_resource::<TestState>();
370
371            let mut schedule = Schedule::default();
372            schedule.set_executor_kind(executor);
373            schedule.add_systems((set_single_state, set_populated_state));
374            schedule.run(&mut world);
375
376            let state = world.get_resource::<TestState>().unwrap();
377            assert!(!state.single_ran);
378            assert!(!state.populated_ran);
379
380            world.spawn(TestComponent);
381
382            schedule.run(&mut world);
383            let state = world.get_resource::<TestState>().unwrap();
384            assert!(state.single_ran);
385            assert!(state.populated_ran);
386        }
387    }
388
389    fn look_for_missing_resource(_res: Res<TestState>) {}
390
391    #[test]
392    #[should_panic]
393    fn missing_resource_panics_simple() {
394        let mut world = World::new();
395        let mut schedule = Schedule::default();
396
397        #[expect(deprecated, reason = "We still need to test this.")]
398        schedule.set_executor_kind(ExecutorKind::Simple);
399        schedule.add_systems(look_for_missing_resource);
400        schedule.run(&mut world);
401    }
402
403    #[test]
404    #[should_panic]
405    fn missing_resource_panics_single_threaded() {
406        let mut world = World::new();
407        let mut schedule = Schedule::default();
408
409        schedule.set_executor_kind(ExecutorKind::SingleThreaded);
410        schedule.add_systems(look_for_missing_resource);
411        schedule.run(&mut world);
412    }
413
414    #[test]
415    #[should_panic]
416    fn missing_resource_panics_multi_threaded() {
417        let mut world = World::new();
418        let mut schedule = Schedule::default();
419
420        schedule.set_executor_kind(ExecutorKind::MultiThreaded);
421        schedule.add_systems(look_for_missing_resource);
422        schedule.run(&mut world);
423    }
424
425    #[test]
426    fn piped_systems_first_system_skipped() {
427        // This system should be skipped when run due to no matching entity
428        fn pipe_out(_single: Single<&TestComponent>) -> u8 {
429            42
430        }
431
432        fn pipe_in(_input: In<u8>, mut counter: ResMut<Counter>) {
433            counter.0 += 1;
434        }
435
436        let mut world = World::new();
437        world.init_resource::<Counter>();
438        let mut schedule = Schedule::default();
439
440        schedule.add_systems(pipe_out.pipe(pipe_in));
441        schedule.run(&mut world);
442
443        let counter = world.resource::<Counter>();
444        assert_eq!(counter.0, 0);
445    }
446
447    #[test]
448    fn piped_system_second_system_skipped() {
449        // This system will be run before the second system is validated
450        fn pipe_out(mut counter: ResMut<Counter>) -> u8 {
451            counter.0 += 1;
452            42
453        }
454
455        // This system should be skipped when run due to no matching entity
456        fn pipe_in(_input: In<u8>, _single: Single<&TestComponent>, mut counter: ResMut<Counter>) {
457            counter.0 += 1;
458        }
459
460        let mut world = World::new();
461        world.init_resource::<Counter>();
462        let mut schedule = Schedule::default();
463
464        schedule.add_systems(pipe_out.pipe(pipe_in));
465        schedule.run(&mut world);
466        let counter = world.resource::<Counter>();
467        assert_eq!(counter.0, 1);
468    }
469
470    #[test]
471    #[should_panic]
472    fn piped_system_first_system_panics() {
473        // This system should panic when run because the resource is missing
474        fn pipe_out(_res: Res<TestState>) -> u8 {
475            42
476        }
477
478        fn pipe_in(_input: In<u8>) {}
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    #[test]
488    #[should_panic]
489    fn piped_system_second_system_panics() {
490        fn pipe_out() -> u8 {
491            42
492        }
493
494        // This system should panic when run because the resource is missing
495        fn pipe_in(_input: In<u8>, _res: Res<TestState>) {}
496
497        let mut world = World::new();
498        let mut schedule = Schedule::default();
499
500        schedule.add_systems(pipe_out.pipe(pipe_in));
501        schedule.run(&mut world);
502    }
503
504    // This test runs without panicking because we've
505    // decided to use early-out behavior for piped systems
506    #[test]
507    fn piped_system_skip_and_panic() {
508        // This system should be skipped when run due to no matching entity
509        fn pipe_out(_single: Single<&TestComponent>) -> u8 {
510            42
511        }
512
513        // This system should panic when run because the resource is missing
514        fn pipe_in(_input: In<u8>, _res: Res<TestState>) {}
515
516        let mut world = World::new();
517        let mut schedule = Schedule::default();
518
519        schedule.add_systems(pipe_out.pipe(pipe_in));
520        schedule.run(&mut world);
521    }
522
523    #[test]
524    #[should_panic]
525    fn piped_system_panic_and_skip() {
526        // This system should panic when run because the resource is missing
527
528        fn pipe_out(_res: Res<TestState>) -> u8 {
529            42
530        }
531
532        // This system should be skipped when run due to no matching entity
533        fn pipe_in(_input: In<u8>, _single: Single<&TestComponent>) {}
534
535        let mut world = World::new();
536        let mut schedule = Schedule::default();
537
538        schedule.add_systems(pipe_out.pipe(pipe_in));
539        schedule.run(&mut world);
540    }
541
542    #[test]
543    #[should_panic]
544    fn piped_system_panic_and_panic() {
545        // This system should panic when run because the resource is missing
546
547        fn pipe_out(_res: Res<TestState>) -> u8 {
548            42
549        }
550
551        // This system should panic when run because the resource is missing
552        fn pipe_in(_input: In<u8>, _res: Res<TestState>) {}
553
554        let mut world = World::new();
555        let mut schedule = Schedule::default();
556
557        schedule.add_systems(pipe_out.pipe(pipe_in));
558        schedule.run(&mut world);
559    }
560
561    #[test]
562    fn piped_system_skip_and_skip() {
563        // This system should be skipped when run due to no matching entity
564
565        fn pipe_out(_single: Single<&TestComponent>, mut counter: ResMut<Counter>) -> u8 {
566            counter.0 += 1;
567            42
568        }
569
570        // This system should be skipped when run due to no matching entity
571        fn pipe_in(_input: In<u8>, _single: Single<&TestComponent>, mut counter: ResMut<Counter>) {
572            counter.0 += 1;
573        }
574
575        let mut world = World::new();
576        world.init_resource::<Counter>();
577        let mut schedule = Schedule::default();
578
579        schedule.add_systems(pipe_out.pipe(pipe_in));
580        schedule.run(&mut world);
581
582        let counter = world.resource::<Counter>();
583        assert_eq!(counter.0, 0);
584    }
585}