bevy_ecs/schedule/executor/
single_threaded.rs1use core::panic::AssertUnwindSafe;
2use fixedbitset::FixedBitSet;
3
4#[cfg(feature = "trace")]
5use tracing::info_span;
6
7#[cfg(feature = "std")]
8use std::eprintln;
9
10use crate::{
11 error::{ErrorContext, ErrorHandler},
12 schedule::{
13 is_apply_deferred, ConditionWithAccess, ExecutorKind, SystemExecutor, SystemSchedule,
14 },
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 kind(&self) -> ExecutorKind {
42 ExecutorKind::SingleThreaded
43 }
44
45 fn init(&mut self, schedule: &SystemSchedule) {
46 let sys_count = schedule.system_ids.len();
48 let set_count = schedule.set_ids.len();
49 self.evaluated_sets = FixedBitSet::with_capacity(set_count);
50 self.completed_systems = FixedBitSet::with_capacity(sys_count);
51 self.unapplied_systems = FixedBitSet::with_capacity(sys_count);
52 }
53
54 fn run(
55 &mut self,
56 schedule: &mut SystemSchedule,
57 world: &mut World,
58 _skip_systems: Option<&FixedBitSet>,
59 error_handler: ErrorHandler,
60 ) {
61 #[cfg(feature = "bevy_debug_stepping")]
64 if let Some(skipped_systems) = _skip_systems {
65 self.completed_systems |= skipped_systems;
67 }
68
69 #[cfg(feature = "hotpatching")]
70 let hotpatch_tick = world
71 .get_resource_ref::<HotPatchChanges>()
72 .map(|r| r.last_changed())
73 .unwrap_or_default();
74
75 for system_index in 0..schedule.systems.len() {
76 let system = &mut schedule.systems[system_index].system;
77
78 #[cfg(feature = "trace")]
79 let name = system.name();
80 #[cfg(feature = "trace")]
81 let should_run_span = info_span!("check_conditions", name = name.as_string()).entered();
82
83 let mut should_run = !self.completed_systems.contains(system_index);
84 for set_idx in schedule.sets_with_conditions_of_systems[system_index].ones() {
85 if self.evaluated_sets.contains(set_idx) {
86 continue;
87 }
88
89 let set_conditions_met = evaluate_and_fold_conditions(
91 &mut schedule.set_conditions[set_idx],
92 world,
93 error_handler,
94 system,
95 true,
96 );
97
98 if !set_conditions_met {
99 self.completed_systems
100 .union_with(&schedule.systems_in_sets_with_conditions[set_idx]);
101 }
102
103 should_run &= set_conditions_met;
104 self.evaluated_sets.insert(set_idx);
105 }
106
107 let system_conditions_met = evaluate_and_fold_conditions(
109 &mut schedule.system_conditions[system_index],
110 world,
111 error_handler,
112 system,
113 false,
114 );
115
116 should_run &= system_conditions_met;
117
118 #[cfg(feature = "trace")]
119 should_run_span.exit();
120
121 #[cfg(feature = "hotpatching")]
122 if hotpatch_tick.is_newer_than(system.get_last_run(), world.change_tick()) {
123 system.refresh_hotpatch();
124 }
125
126 self.completed_systems.insert(system_index);
128
129 if !should_run {
130 continue;
131 }
132
133 if is_apply_deferred(&**system) {
134 self.apply_deferred(schedule, world);
135 continue;
136 }
137
138 let f = AssertUnwindSafe(|| {
139 if let Err(RunSystemError::Failed(err)) =
140 __rust_begin_short_backtrace::run_without_applying_deferred(system, world)
141 {
142 error_handler(
143 err,
144 ErrorContext::System {
145 name: system.name(),
146 last_run: system.get_last_run(),
147 },
148 );
149 }
150 });
151
152 #[cfg(feature = "std")]
153 #[expect(clippy::print_stderr, reason = "Allowed behind `std` feature gate.")]
154 {
155 if let Err(payload) = std::panic::catch_unwind(f) {
156 eprintln!("Encountered a panic in system `{}`!", system.name());
157 std::panic::resume_unwind(payload);
158 }
159 }
160
161 #[cfg(not(feature = "std"))]
162 {
163 (f)();
164 }
165
166 self.unapplied_systems.insert(system_index);
167 }
168
169 if self.apply_final_deferred {
170 self.apply_deferred(schedule, world);
171 }
172 self.evaluated_sets.clear();
173 self.completed_systems.clear();
174 }
175
176 fn set_apply_final_deferred(&mut self, apply_final_deferred: bool) {
177 self.apply_final_deferred = apply_final_deferred;
178 }
179}
180
181impl SingleThreadedExecutor {
182 pub const fn new() -> Self {
186 Self {
187 evaluated_sets: FixedBitSet::new(),
188 completed_systems: FixedBitSet::new(),
189 unapplied_systems: FixedBitSet::new(),
190 apply_final_deferred: true,
191 }
192 }
193
194 fn apply_deferred(&mut self, schedule: &mut SystemSchedule, world: &mut World) {
195 for system_index in self.unapplied_systems.ones() {
196 let system = &mut schedule.systems[system_index].system;
197 system.apply_deferred(world);
198 }
199
200 self.unapplied_systems.clear();
201 }
202}
203
204fn evaluate_and_fold_conditions(
205 conditions: &mut [ConditionWithAccess],
206 world: &mut World,
207 error_handler: ErrorHandler,
208 for_system: &ScheduleSystem,
209 on_set: bool,
210) -> bool {
211 #[cfg(feature = "hotpatching")]
212 let hotpatch_tick = world
213 .get_resource_ref::<HotPatchChanges>()
214 .map(|r| r.last_changed())
215 .unwrap_or_default();
216
217 #[expect(
218 clippy::unnecessary_fold,
219 reason = "Short-circuiting here would prevent conditions from mutating their own state as needed."
220 )]
221 conditions
222 .iter_mut()
223 .map(|ConditionWithAccess { condition, .. }| {
224 #[cfg(feature = "hotpatching")]
225 if hotpatch_tick.is_newer_than(condition.get_last_run(), world.change_tick()) {
226 condition.refresh_hotpatch();
227 }
228 __rust_begin_short_backtrace::readonly_run(&mut **condition, world).unwrap_or_else(
229 |err| {
230 if let RunSystemError::Failed(err) = err {
231 error_handler(
232 err,
233 ErrorContext::RunCondition {
234 name: condition.name(),
235 last_run: condition.get_last_run(),
236 system: for_system.name(),
237 on_set,
238 },
239 );
240 };
241 false
242 },
243 )
244 })
245 .fold(true, |acc, res| acc && res)
246}