bevy_ecs/schedule/executor/
single_threaded.rs1use 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::{is_apply_deferred, ConditionWithAccess, SystemExecutor, SystemSchedule},
15 system::{RunSystemError, ScheduleSystem},
16 world::World,
17};
18
19#[cfg(feature = "hotpatching")]
20use crate::{change_detection::DetectChanges, HotPatchChanges};
21
22use super::__rust_begin_short_backtrace;
23
24#[derive(Default)]
29pub struct SingleThreadedExecutor {
30 evaluated_sets: FixedBitSet,
32 completed_systems: FixedBitSet,
34 unapplied_systems: FixedBitSet,
36 apply_final_deferred: bool,
38}
39
40impl SystemExecutor for SingleThreadedExecutor {
41 fn init(&mut self, schedule: &SystemSchedule) {
42 let sys_count = schedule.system_ids.len();
44 let set_count = schedule.set_ids.len();
45 self.evaluated_sets = FixedBitSet::with_capacity(set_count);
46 self.completed_systems = FixedBitSet::with_capacity(sys_count);
47 self.unapplied_systems = FixedBitSet::with_capacity(sys_count);
48 }
49
50 fn run(
51 &mut self,
52 schedule: &mut SystemSchedule,
53 world: &mut World,
54 _skip_systems: Option<&FixedBitSet>,
55 error_handler: ErrorHandler,
56 ) {
57 #[cfg(feature = "bevy_debug_stepping")]
60 if let Some(skipped_systems) = _skip_systems {
61 self.completed_systems |= skipped_systems;
63 }
64
65 #[cfg(feature = "hotpatching")]
66 let hotpatch_tick = world
67 .get_resource_ref::<HotPatchChanges>()
68 .map(|r| r.last_changed())
69 .unwrap_or_default();
70
71 for system_index in 0..schedule.systems.len() {
72 let system = &mut schedule.systems[system_index].system;
73
74 #[cfg(feature = "trace")]
75 let name = system.name();
76 #[cfg(feature = "trace")]
77 let should_run_span = info_span!("check_conditions", name = name.to_string()).entered();
78
79 let mut should_run = !self.completed_systems.contains(system_index);
80 for set_idx in schedule.sets_with_conditions_of_systems[system_index].ones() {
81 if self.evaluated_sets.contains(set_idx) {
82 continue;
83 }
84
85 let set_conditions_met = evaluate_and_fold_conditions(
87 &mut schedule.set_conditions[set_idx],
88 world,
89 error_handler,
90 system,
91 true,
92 );
93
94 if !set_conditions_met {
95 self.completed_systems
96 .union_with(&schedule.systems_in_sets_with_conditions[set_idx]);
97 }
98
99 should_run &= set_conditions_met;
100 self.evaluated_sets.insert(set_idx);
101 }
102
103 let system_conditions_met = evaluate_and_fold_conditions(
105 &mut schedule.system_conditions[system_index],
106 world,
107 error_handler,
108 system,
109 false,
110 );
111
112 should_run &= system_conditions_met;
113
114 #[cfg(feature = "trace")]
115 should_run_span.exit();
116
117 #[cfg(feature = "hotpatching")]
118 if hotpatch_tick.is_newer_than(system.get_last_run(), world.change_tick()) {
119 system.refresh_hotpatch();
120 }
121
122 self.completed_systems.insert(system_index);
124
125 if !should_run {
126 continue;
127 }
128
129 if is_apply_deferred(&**system) {
130 self.apply_deferred(schedule, world);
131 continue;
132 }
133
134 let f = AssertUnwindSafe(|| {
135 if let Err(RunSystemError::Failed(err)) =
136 __rust_begin_short_backtrace::run_without_applying_deferred(system, world)
137 {
138 error_handler(
139 err,
140 ErrorContext::System {
141 name: system.name(),
142 last_run: system.get_last_run(),
143 },
144 );
145 }
146 });
147
148 #[cfg(feature = "std")]
149 #[expect(clippy::print_stderr, reason = "Allowed behind `std` feature gate.")]
150 {
151 if let Err(payload) = std::panic::catch_unwind(f) {
152 eprintln!("Encountered a panic in system `{}`!", system.name());
153 std::panic::resume_unwind(payload);
154 }
155 }
156
157 #[cfg(not(feature = "std"))]
158 {
159 (f)();
160 }
161
162 self.unapplied_systems.insert(system_index);
163 }
164
165 if self.apply_final_deferred {
166 self.apply_deferred(schedule, world);
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(&mut self, schedule: &mut SystemSchedule, world: &mut World) {
191 for system_index in self.unapplied_systems.ones() {
192 let system = &mut schedule.systems[system_index].system;
193 system.apply_deferred(world);
194 }
195
196 self.unapplied_systems.clear();
197 }
198}
199
200fn evaluate_and_fold_conditions(
201 conditions: &mut [ConditionWithAccess],
202 world: &mut World,
203 error_handler: ErrorHandler,
204 for_system: &ScheduleSystem,
205 on_set: bool,
206) -> bool {
207 #[cfg(feature = "hotpatching")]
208 let hotpatch_tick = world
209 .get_resource_ref::<HotPatchChanges>()
210 .map(|r| r.last_changed())
211 .unwrap_or_default();
212
213 #[expect(
214 clippy::unnecessary_fold,
215 reason = "Short-circuiting here would prevent conditions from mutating their own state as needed."
216 )]
217 conditions
218 .iter_mut()
219 .map(|ConditionWithAccess { condition, .. }| {
220 #[cfg(feature = "hotpatching")]
221 if hotpatch_tick.is_newer_than(condition.get_last_run(), world.change_tick()) {
222 condition.refresh_hotpatch();
223 }
224 __rust_begin_short_backtrace::readonly_run(&mut **condition, world).unwrap_or_else(
225 |err| {
226 if let RunSystemError::Failed(err) = err {
227 error_handler(
228 err,
229 ErrorContext::RunCondition {
230 name: condition.name(),
231 last_run: condition.get_last_run(),
232 system: for_system.name(),
233 on_set,
234 },
235 );
236 };
237 false
238 },
239 )
240 })
241 .fold(true, |acc, res| acc && res)
242}