bevy_ecs/schedule/executor/
single_threaded.rs

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