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::{
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#[derive(Default)]
31pub struct SingleThreadedExecutor {
32 evaluated_sets: FixedBitSet,
34 completed_systems: FixedBitSet,
36 unapplied_systems: FixedBitSet,
38 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 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 #[cfg(feature = "bevy_debug_stepping")]
66 if let Some(skipped_systems) = _skip_systems {
67 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 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 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 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 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}