bevy_ecs/schedule/executor/
single_threaded.rs1#[cfg(feature = "std")]
2use core::panic::AssertUnwindSafe;
3
4use fixedbitset::FixedBitSet;
5
6#[cfg(feature = "trace")]
7use alloc::string::ToString as _;
8#[cfg(feature = "trace")]
9use tracing::info_span;
10
11#[cfg(feature = "std")]
12use crate::{error::BevyError, system::BoxedSystem};
13use crate::{
14 error::{ErrorContext, ErrorHandler},
15 schedule::{
16 is_apply_deferred, BoxedCondition, ConditionWithAccess, SystemExecutor, SystemSchedule,
17 },
18 system::{RunSystemError, ScheduleSystem},
19 world::World,
20};
21
22#[cfg(feature = "hotpatching")]
23use crate::{change_detection::DetectChanges, HotPatchChanges};
24
25use super::__rust_begin_short_backtrace;
26
27#[derive(Default)]
32pub struct SingleThreadedExecutor {
33 evaluated_sets: FixedBitSet,
35 completed_systems: FixedBitSet,
37 unapplied_systems: FixedBitSet,
39 apply_final_deferred: bool,
41}
42
43impl SystemExecutor for SingleThreadedExecutor {
44 fn init(&mut self, schedule: &SystemSchedule) {
45 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 self.unapplied_systems = FixedBitSet::with_capacity(sys_count);
51 }
52
53 fn run(
54 &mut self,
55 schedule: &mut SystemSchedule,
56 world: &mut World,
57 _skip_systems: Option<&FixedBitSet>,
58 error_handler: ErrorHandler,
59 ) {
60 #[cfg(feature = "bevy_debug_stepping")]
63 if let Some(skipped_systems) = _skip_systems {
64 self.completed_systems |= skipped_systems;
66 }
67
68 #[cfg(feature = "hotpatching")]
69 let hotpatch_tick = world
70 .get_resource_ref::<HotPatchChanges>()
71 .map(|r| r.last_changed())
72 .unwrap_or_default();
73
74 for system_index in 0..schedule.systems.len() {
75 let system = &mut schedule.systems[system_index].system;
76
77 #[cfg(feature = "trace")]
78 let name = system.name();
79 #[cfg(feature = "trace")]
80 let should_run_span = info_span!("check_conditions", name = name.to_string()).entered();
81
82 let mut should_run = !self.completed_systems.contains(system_index);
83 for set_idx in schedule.sets_with_conditions_of_systems[system_index].ones() {
84 if self.evaluated_sets.contains(set_idx) {
85 continue;
86 }
87
88 let set_conditions_met = evaluate_and_fold_conditions(
90 &mut schedule.set_conditions[set_idx],
91 world,
92 error_handler,
93 system,
94 true,
95 );
96
97 if !set_conditions_met {
98 self.completed_systems
99 .union_with(&schedule.systems_in_sets_with_conditions[set_idx]);
100 }
101
102 should_run &= set_conditions_met;
103 self.evaluated_sets.insert(set_idx);
104 }
105
106 let system_conditions_met = evaluate_and_fold_conditions(
108 &mut schedule.system_conditions[system_index],
109 world,
110 error_handler,
111 system,
112 false,
113 );
114
115 should_run &= system_conditions_met;
116
117 #[cfg(feature = "trace")]
118 should_run_span.exit();
119
120 #[cfg(feature = "hotpatching")]
121 if hotpatch_tick.is_newer_than(system.get_last_run(), world.change_tick()) {
122 system.refresh_hotpatch();
123 }
124
125 self.completed_systems.insert(system_index);
127
128 if !should_run {
129 continue;
130 }
131
132 if is_apply_deferred(&**system) {
133 self.apply_deferred(schedule, world, error_handler);
134 continue;
135 }
136
137 let f = |system: &mut _| {
138 if let Err(RunSystemError::Failed(err)) =
139 __rust_begin_short_backtrace::run_without_applying_deferred(system, world)
140 {
141 error_handler(
142 err,
143 ErrorContext::System {
144 name: system.name(),
145 last_run: system.get_last_run(),
146 },
147 );
148 }
149 };
150
151 #[cfg(feature = "std")]
152 {
153 handle_unwind(f, system, error_handler, "System panicked");
154 }
155
156 #[cfg(not(feature = "std"))]
157 {
158 let mut f = f;
159 (f)(system);
160 }
161
162 self.unapplied_systems.insert(system_index);
163 }
164
165 if self.apply_final_deferred {
166 self.apply_deferred(schedule, world, error_handler);
167 }
168 self.evaluated_sets.clear();
169 self.completed_systems.clear();
170 }
171
172 fn set_apply_final_deferred(&mut self, apply_final_deferred: bool) {
173 self.apply_final_deferred = apply_final_deferred;
174 }
175}
176
177impl SingleThreadedExecutor {
178 pub const fn new() -> Self {
182 Self {
183 evaluated_sets: FixedBitSet::new(),
184 completed_systems: FixedBitSet::new(),
185 unapplied_systems: FixedBitSet::new(),
186 apply_final_deferred: true,
187 }
188 }
189
190 fn apply_deferred(
191 &mut self,
192 schedule: &mut SystemSchedule,
193 world: &mut World,
194 error_handler: ErrorHandler,
195 ) {
196 for system_index in self.unapplied_systems.ones() {
197 let system = &mut schedule.systems[system_index].system;
198 #[cfg(not(feature = "std"))]
199 {
200 system.apply_deferred(world);
201 let _ = error_handler;
202 }
203
204 #[cfg(feature = "std")]
205 {
206 handle_unwind(
207 |system| system.apply_deferred(world),
208 system,
209 error_handler,
210 "Encountered a panic while applying system buffers",
211 );
212 }
213 }
214
215 self.unapplied_systems.clear();
216 }
217}
218
219fn evaluate_and_fold_conditions(
220 conditions: &mut [ConditionWithAccess],
221 world: &mut World,
222 error_handler: ErrorHandler,
223 for_system: &ScheduleSystem,
224 on_set: bool,
225) -> bool {
226 #[cfg(feature = "hotpatching")]
227 let hotpatch_tick = world
228 .get_resource_ref::<HotPatchChanges>()
229 .map(|r| r.last_changed())
230 .unwrap_or_default();
231
232 #[expect(
233 clippy::unnecessary_fold,
234 reason = "Short-circuiting here would prevent conditions from mutating their own state as needed."
235 )]
236 conditions
237 .iter_mut()
238 .map(|ConditionWithAccess { condition, .. }| {
239 #[cfg(feature = "hotpatching")]
240 if hotpatch_tick.is_newer_than(condition.get_last_run(), world.change_tick()) {
241 condition.refresh_hotpatch();
242 }
243 let f = |condition: &mut BoxedCondition| {
244 __rust_begin_short_backtrace::readonly_run(&mut **condition, world).unwrap_or_else(
245 |err| {
246 if let RunSystemError::Failed(err) = err {
247 error_handler(
248 err,
249 ErrorContext::RunCondition {
250 name: condition.name(),
251 last_run: condition.get_last_run(),
252 system: for_system.name(),
253 on_set,
254 },
255 );
256 };
257 false
258 },
259 )
260 };
261 #[cfg(not(feature = "std"))]
262 let result = {
263 let mut f = f;
264 f(condition)
265 };
266 #[cfg(feature = "std")]
267 let result =
268 handle_unwind_in_run_condition(f, condition, for_system, on_set, error_handler);
269 result
270 })
271 .fold(true, |acc, res| acc && res)
272}
273
274#[cfg(feature = "std")]
276fn handle_unwind(
277 f: impl FnOnce(&mut BoxedSystem),
278 system: &mut BoxedSystem,
279 error_handler: ErrorHandler,
280 error_message: &str,
281) {
282 let potential_unwind = std::panic::catch_unwind(AssertUnwindSafe(|| f(system)));
283 if let Err(payload) = potential_unwind {
284 __rust_begin_short_backtrace::error_handler(
285 error_handler,
286 BevyError::panic(error_message, payload),
287 ErrorContext::System {
288 name: system.name(),
289 last_run: system.get_last_run(),
290 },
291 );
292 }
293}
294
295#[cfg(feature = "std")]
297fn handle_unwind_in_run_condition(
298 f: impl FnOnce(&mut BoxedCondition) -> bool,
299 condition: &mut BoxedCondition,
300 for_system: &ScheduleSystem,
301 on_set: bool,
302 error_handler: ErrorHandler,
303) -> bool {
304 let potential_unwind = std::panic::catch_unwind(AssertUnwindSafe(|| f(condition)));
305 match potential_unwind {
306 Ok(r) => r,
307 Err(payload) => {
308 let err = BevyError::panic("Encountered panic", payload);
309 __rust_begin_short_backtrace::error_handler(
310 error_handler,
311 err,
312 ErrorContext::RunCondition {
313 name: condition.name(),
314 last_run: condition.get_last_run(),
315 system: for_system.name(),
316 on_set,
317 },
318 );
319 false
320 }
321 }
322}