bevy_ecs/schedule/executor/
simple.rs

1#![expect(deprecated, reason = "Everything here is deprecated")]
2
3use core::panic::AssertUnwindSafe;
4use fixedbitset::FixedBitSet;
5
6#[cfg(feature = "trace")]
7use tracing::info_span;
8
9#[cfg(feature = "std")]
10use std::eprintln;
11
12#[cfg(feature = "hotpatching")]
13use crate::{change_detection::DetectChanges, HotPatchChanges};
14use crate::{
15    error::{ErrorContext, ErrorHandler},
16    schedule::{
17        executor::is_apply_deferred, ConditionWithAccess, ExecutorKind, SystemExecutor,
18        SystemSchedule,
19    },
20    system::{RunSystemError, ScheduleSystem},
21    world::World,
22};
23
24use super::__rust_begin_short_backtrace;
25
26/// A variant of [`SingleThreadedExecutor`](crate::schedule::SingleThreadedExecutor) that calls
27/// [`apply_deferred`](crate::system::System::apply_deferred) immediately after running each system.
28#[derive(Default)]
29#[deprecated(
30    since = "0.17.0",
31    note = "Use SingleThreadedExecutor instead. See https://github.com/bevyengine/bevy/issues/18453 for motivation."
32)]
33pub struct SimpleExecutor {
34    /// Systems sets whose conditions have been evaluated.
35    evaluated_sets: FixedBitSet,
36    /// Systems that have run or been skipped.
37    completed_systems: FixedBitSet,
38}
39
40impl SystemExecutor for SimpleExecutor {
41    fn kind(&self) -> ExecutorKind {
42        ExecutorKind::Simple
43    }
44
45    fn init(&mut self, schedule: &SystemSchedule) {
46        let sys_count = schedule.system_ids.len();
47        let set_count = schedule.set_ids.len();
48        self.evaluated_sets = FixedBitSet::with_capacity(set_count);
49        self.completed_systems = FixedBitSet::with_capacity(sys_count);
50    }
51
52    fn run(
53        &mut self,
54        schedule: &mut SystemSchedule,
55        world: &mut World,
56        _skip_systems: Option<&FixedBitSet>,
57        error_handler: ErrorHandler,
58    ) {
59        // If stepping is enabled, make sure we skip those systems that should
60        // not be run.
61        #[cfg(feature = "bevy_debug_stepping")]
62        if let Some(skipped_systems) = _skip_systems {
63            // mark skipped systems as completed
64            self.completed_systems |= skipped_systems;
65        }
66
67        #[cfg(feature = "hotpatching")]
68        let hotpatch_tick = world
69            .get_resource_ref::<HotPatchChanges>()
70            .map(|r| r.last_changed())
71            .unwrap_or_default();
72
73        for system_index in 0..schedule.systems.len() {
74            let system = &mut schedule.systems[system_index].system;
75
76            #[cfg(feature = "trace")]
77            let name = system.name();
78            #[cfg(feature = "trace")]
79            let should_run_span = info_span!("check_conditions", name = name.as_string()).entered();
80
81            let mut should_run = !self.completed_systems.contains(system_index);
82            for set_idx in schedule.sets_with_conditions_of_systems[system_index].ones() {
83                if self.evaluated_sets.contains(set_idx) {
84                    continue;
85                }
86
87                // evaluate system set's conditions
88                let set_conditions_met = evaluate_and_fold_conditions(
89                    &mut schedule.set_conditions[set_idx],
90                    world,
91                    error_handler,
92                    system,
93                    true,
94                );
95
96                if !set_conditions_met {
97                    self.completed_systems
98                        .union_with(&schedule.systems_in_sets_with_conditions[set_idx]);
99                }
100
101                should_run &= set_conditions_met;
102                self.evaluated_sets.insert(set_idx);
103            }
104
105            // evaluate system's conditions
106            let system_conditions_met = evaluate_and_fold_conditions(
107                &mut schedule.system_conditions[system_index],
108                world,
109                error_handler,
110                system,
111                false,
112            );
113
114            should_run &= system_conditions_met;
115
116            #[cfg(feature = "trace")]
117            should_run_span.exit();
118
119            #[cfg(feature = "hotpatching")]
120            if hotpatch_tick.is_newer_than(system.get_last_run(), world.change_tick()) {
121                system.refresh_hotpatch();
122            }
123
124            // system has either been skipped or will run
125            self.completed_systems.insert(system_index);
126
127            if !should_run {
128                continue;
129            }
130
131            if is_apply_deferred(&**system) {
132                continue;
133            }
134
135            let f = AssertUnwindSafe(|| {
136                if let Err(RunSystemError::Failed(err)) =
137                    __rust_begin_short_backtrace::run(system, world)
138                {
139                    error_handler(
140                        err,
141                        ErrorContext::System {
142                            name: system.name(),
143                            last_run: system.get_last_run(),
144                        },
145                    );
146                }
147            });
148
149            #[cfg(feature = "std")]
150            #[expect(clippy::print_stderr, reason = "Allowed behind `std` feature gate.")]
151            {
152                if let Err(payload) = std::panic::catch_unwind(f) {
153                    eprintln!("Encountered a panic in system `{}`!", system.name());
154                    std::panic::resume_unwind(payload);
155                }
156            }
157
158            #[cfg(not(feature = "std"))]
159            {
160                (f)();
161            }
162        }
163
164        self.evaluated_sets.clear();
165        self.completed_systems.clear();
166    }
167
168    fn set_apply_final_deferred(&mut self, _: bool) {
169        // do nothing. simple executor does not do a final sync
170    }
171}
172
173impl SimpleExecutor {
174    /// Creates a new simple executor for use in a [`Schedule`](crate::schedule::Schedule).
175    /// This calls each system in order and immediately calls [`System::apply_deferred`](crate::system::System).
176    pub const fn new() -> Self {
177        Self {
178            evaluated_sets: FixedBitSet::new(),
179            completed_systems: FixedBitSet::new(),
180        }
181    }
182}
183#[deprecated(
184    since = "0.17.0",
185    note = "Use SingleThreadedExecutor instead. See https://github.com/bevyengine/bevy/issues/18453 for motivation."
186)]
187fn evaluate_and_fold_conditions(
188    conditions: &mut [ConditionWithAccess],
189    world: &mut World,
190    error_handler: ErrorHandler,
191    for_system: &ScheduleSystem,
192    on_set: bool,
193) -> bool {
194    #[cfg(feature = "hotpatching")]
195    let hotpatch_tick = world
196        .get_resource_ref::<HotPatchChanges>()
197        .map(|r| r.last_changed())
198        .unwrap_or_default();
199
200    #[expect(
201        clippy::unnecessary_fold,
202        reason = "Short-circuiting here would prevent conditions from mutating their own state as needed."
203    )]
204    conditions
205        .iter_mut()
206        .map(|ConditionWithAccess { condition, .. }| {
207            #[cfg(feature = "hotpatching")]
208            if hotpatch_tick.is_newer_than(condition.get_last_run(), world.change_tick()) {
209                condition.refresh_hotpatch();
210            }
211            __rust_begin_short_backtrace::readonly_run(&mut **condition, world).unwrap_or_else(
212                |err| {
213                    if let RunSystemError::Failed(err) = err {
214                        error_handler(
215                            err,
216                            ErrorContext::RunCondition {
217                                name: condition.name(),
218                                last_run: condition.get_last_run(),
219                                system: for_system.name(),
220                                on_set,
221                            },
222                        );
223                    };
224                    false
225                },
226            )
227        })
228        .fold(true, |acc, res| acc && res)
229}
230
231#[cfg(test)]
232#[test]
233fn skip_automatic_sync_points() {
234    // Schedules automatically insert ApplyDeferred systems, but these should
235    // not be executed as they only serve as markers and are not initialized
236    use crate::prelude::*;
237    let mut sched = Schedule::default();
238    sched.set_executor_kind(ExecutorKind::Simple);
239    sched.add_systems((|_: Commands| (), || ()).chain());
240    let mut world = World::new();
241    sched.run(&mut world);
242}