bevy_ecs/schedule/executor/
mod.rs

1mod multi_threaded;
2mod simple;
3mod single_threaded;
4
5pub use self::{
6    multi_threaded::{MainThreadExecutor, MultiThreadedExecutor},
7    simple::SimpleExecutor,
8    single_threaded::SingleThreadedExecutor,
9};
10
11use fixedbitset::FixedBitSet;
12
13use crate::{
14    schedule::{BoxedCondition, NodeId},
15    system::BoxedSystem,
16    world::World,
17};
18
19/// Types that can run a [`SystemSchedule`] on a [`World`].
20pub(super) trait SystemExecutor: Send + Sync {
21    fn kind(&self) -> ExecutorKind;
22    fn init(&mut self, schedule: &SystemSchedule);
23    fn run(
24        &mut self,
25        schedule: &mut SystemSchedule,
26        world: &mut World,
27        skip_systems: Option<&FixedBitSet>,
28    );
29    fn set_apply_final_deferred(&mut self, value: bool);
30}
31
32/// Specifies how a [`Schedule`](super::Schedule) will be run.
33///
34/// The default depends on the target platform:
35///  - [`SingleThreaded`](ExecutorKind::SingleThreaded) on Wasm.
36///  - [`MultiThreaded`](ExecutorKind::MultiThreaded) everywhere else.
37#[derive(PartialEq, Eq, Default, Debug, Copy, Clone)]
38pub enum ExecutorKind {
39    /// Runs the schedule using a single thread.
40    ///
41    /// Useful if you're dealing with a single-threaded environment, saving your threads for
42    /// other things, or just trying minimize overhead.
43    #[cfg_attr(any(target_arch = "wasm32", not(feature = "multi_threaded")), default)]
44    SingleThreaded,
45    /// Like [`SingleThreaded`](ExecutorKind::SingleThreaded) but calls [`apply_deferred`](crate::system::System::apply_deferred)
46    /// immediately after running each system.
47    Simple,
48    /// Runs the schedule using a thread pool. Non-conflicting systems can run in parallel.
49    #[cfg_attr(all(not(target_arch = "wasm32"), feature = "multi_threaded"), default)]
50    MultiThreaded,
51}
52
53/// Holds systems and conditions of a [`Schedule`](super::Schedule) sorted in topological order
54/// (along with dependency information for `multi_threaded` execution).
55///
56/// Since the arrays are sorted in the same order, elements are referenced by their index.
57/// [`FixedBitSet`] is used as a smaller, more efficient substitute of `HashSet<usize>`.
58#[derive(Default)]
59pub struct SystemSchedule {
60    /// List of system node ids.
61    pub(super) system_ids: Vec<NodeId>,
62    /// Indexed by system node id.
63    pub(super) systems: Vec<BoxedSystem>,
64    /// Indexed by system node id.
65    pub(super) system_conditions: Vec<Vec<BoxedCondition>>,
66    /// Indexed by system node id.
67    /// Number of systems that the system immediately depends on.
68    pub(super) system_dependencies: Vec<usize>,
69    /// Indexed by system node id.
70    /// List of systems that immediately depend on the system.
71    pub(super) system_dependents: Vec<Vec<usize>>,
72    /// Indexed by system node id.
73    /// List of sets containing the system that have conditions
74    pub(super) sets_with_conditions_of_systems: Vec<FixedBitSet>,
75    /// List of system set node ids.
76    pub(super) set_ids: Vec<NodeId>,
77    /// Indexed by system set node id.
78    pub(super) set_conditions: Vec<Vec<BoxedCondition>>,
79    /// Indexed by system set node id.
80    /// List of systems that are in sets that have conditions.
81    ///
82    /// If a set doesn't run because of its conditions, this is used to skip all systems in it.
83    pub(super) systems_in_sets_with_conditions: Vec<FixedBitSet>,
84}
85
86impl SystemSchedule {
87    /// Creates an empty [`SystemSchedule`].
88    pub const fn new() -> Self {
89        Self {
90            systems: Vec::new(),
91            system_conditions: Vec::new(),
92            set_conditions: Vec::new(),
93            system_ids: Vec::new(),
94            set_ids: Vec::new(),
95            system_dependencies: Vec::new(),
96            system_dependents: Vec::new(),
97            sets_with_conditions_of_systems: Vec::new(),
98            systems_in_sets_with_conditions: Vec::new(),
99        }
100    }
101}
102
103/// Instructs the executor to call [`System::apply_deferred`](crate::system::System::apply_deferred)
104/// on the systems that have run but not applied their [`Deferred`](crate::system::Deferred) system parameters
105/// (like [`Commands`](crate::prelude::Commands)) or other system buffers.
106///
107/// ## Scheduling
108///
109/// `apply_deferred` systems are scheduled *by default*
110/// - later in the same schedule run (for example, if a system with `Commands` param
111///   is scheduled in `Update`, all the changes will be visible in `PostUpdate`)
112/// - between systems with dependencies if the dependency
113///   [has deferred buffers](crate::system::System::has_deferred)
114///   (if system `bar` directly or indirectly depends on `foo`, and `foo` uses `Commands` param,
115///   changes to the world in `foo` will be visible in `bar`)
116///
117/// ## Notes
118/// - This function (currently) does nothing if it's called manually or wrapped inside a [`PipeSystem`](crate::system::PipeSystem).
119/// - Modifying a [`Schedule`](super::Schedule) may change the order buffers are applied.
120#[doc(alias = "apply_system_buffers")]
121#[allow(unused_variables)]
122pub fn apply_deferred(world: &mut World) {}
123
124/// Returns `true` if the [`System`](crate::system::System) is an instance of [`apply_deferred`].
125pub(super) fn is_apply_deferred(system: &BoxedSystem) -> bool {
126    use crate::system::IntoSystem;
127    // deref to use `System::type_id` instead of `Any::type_id`
128    system.as_ref().type_id() == apply_deferred.system_type_id()
129}
130
131/// These functions hide the bottom of the callstack from `RUST_BACKTRACE=1` (assuming the default panic handler is used).
132///
133/// The full callstack will still be visible with `RUST_BACKTRACE=full`.
134/// They are specialized for `System::run` & co instead of being generic over closures because this avoids an
135/// extra frame in the backtrace.
136///
137/// This is reliant on undocumented behavior in Rust's default panic handler, which checks the call stack for symbols
138/// containing the string `__rust_begin_short_backtrace` in their mangled name.
139mod __rust_begin_short_backtrace {
140    use core::hint::black_box;
141
142    use crate::{
143        system::{ReadOnlySystem, System},
144        world::{unsafe_world_cell::UnsafeWorldCell, World},
145    };
146
147    /// # Safety
148    /// See `System::run_unsafe`.
149    #[inline(never)]
150    pub(super) unsafe fn run_unsafe(
151        system: &mut dyn System<In = (), Out = ()>,
152        world: UnsafeWorldCell,
153    ) {
154        system.run_unsafe((), world);
155        black_box(());
156    }
157
158    /// # Safety
159    /// See `ReadOnlySystem::run_unsafe`.
160    #[inline(never)]
161    pub(super) unsafe fn readonly_run_unsafe<O: 'static>(
162        system: &mut dyn ReadOnlySystem<In = (), Out = O>,
163        world: UnsafeWorldCell,
164    ) -> O {
165        black_box(system.run_unsafe((), world))
166    }
167
168    #[inline(never)]
169    pub(super) fn run(system: &mut dyn System<In = (), Out = ()>, world: &mut World) {
170        system.run((), world);
171        black_box(());
172    }
173
174    #[inline(never)]
175    pub(super) fn readonly_run<O: 'static>(
176        system: &mut dyn ReadOnlySystem<In = (), Out = O>,
177        world: &mut World,
178    ) -> O {
179        black_box(system.run((), world))
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use crate::{
186        self as bevy_ecs,
187        prelude::{IntoSystemConfigs, IntoSystemSetConfigs, Resource, Schedule, SystemSet},
188        schedule::ExecutorKind,
189        system::{Commands, Res, WithParamWarnPolicy},
190        world::World,
191    };
192
193    #[derive(Resource)]
194    struct R1;
195
196    #[derive(Resource)]
197    struct R2;
198
199    const EXECUTORS: [ExecutorKind; 3] = [
200        ExecutorKind::Simple,
201        ExecutorKind::SingleThreaded,
202        ExecutorKind::MultiThreaded,
203    ];
204
205    #[test]
206    fn invalid_system_param_skips() {
207        for executor in EXECUTORS {
208            invalid_system_param_skips_core(executor);
209        }
210    }
211
212    fn invalid_system_param_skips_core(executor: ExecutorKind) {
213        let mut world = World::new();
214        let mut schedule = Schedule::default();
215        schedule.set_executor_kind(executor);
216        schedule.add_systems(
217            (
218                // This system depends on a system that is always skipped.
219                (|mut commands: Commands| {
220                    commands.insert_resource(R2);
221                })
222                .param_warn_once(),
223            )
224                .chain(),
225        );
226        schedule.run(&mut world);
227        assert!(world.get_resource::<R1>().is_none());
228        assert!(world.get_resource::<R2>().is_some());
229    }
230
231    #[derive(SystemSet, Hash, Debug, PartialEq, Eq, Clone)]
232    struct S1;
233
234    #[test]
235    fn invalid_condition_param_skips_system() {
236        for executor in EXECUTORS {
237            invalid_condition_param_skips_system_core(executor);
238        }
239    }
240
241    fn invalid_condition_param_skips_system_core(executor: ExecutorKind) {
242        let mut world = World::new();
243        let mut schedule = Schedule::default();
244        schedule.set_executor_kind(executor);
245        schedule.configure_sets(S1.run_if((|_: Res<R1>| true).param_warn_once()));
246        schedule.add_systems((
247            // System gets skipped if system set run conditions fail validation.
248            (|mut commands: Commands| {
249                commands.insert_resource(R1);
250            })
251            .param_warn_once()
252            .in_set(S1),
253            // System gets skipped if run conditions fail validation.
254            (|mut commands: Commands| {
255                commands.insert_resource(R2);
256            })
257            .param_warn_once()
258            .run_if((|_: Res<R2>| true).param_warn_once()),
259        ));
260        schedule.run(&mut world);
261        assert!(world.get_resource::<R1>().is_none());
262        assert!(world.get_resource::<R2>().is_none());
263    }
264}