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::{default_error_handler, BevyError, ErrorContext},
12 schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule},
13 world::World,
14};
15
16use super::__rust_begin_short_backtrace;
17
18#[derive(Default)]
23pub struct SingleThreadedExecutor {
24 evaluated_sets: FixedBitSet,
26 completed_systems: FixedBitSet,
28 unapplied_systems: FixedBitSet,
30 apply_final_deferred: bool,
32}
33
34impl SystemExecutor for SingleThreadedExecutor {
35 fn kind(&self) -> ExecutorKind {
36 ExecutorKind::SingleThreaded
37 }
38
39 fn init(&mut self, schedule: &SystemSchedule) {
40 let sys_count = schedule.system_ids.len();
42 let set_count = schedule.set_ids.len();
43 self.evaluated_sets = FixedBitSet::with_capacity(set_count);
44 self.completed_systems = FixedBitSet::with_capacity(sys_count);
45 self.unapplied_systems = FixedBitSet::with_capacity(sys_count);
46 }
47
48 fn run(
49 &mut self,
50 schedule: &mut SystemSchedule,
51 world: &mut World,
52 _skip_systems: Option<&FixedBitSet>,
53 error_handler: fn(BevyError, ErrorContext),
54 ) {
55 #[cfg(feature = "bevy_debug_stepping")]
58 if let Some(skipped_systems) = _skip_systems {
59 self.completed_systems |= skipped_systems;
61 }
62
63 for system_index in 0..schedule.systems.len() {
64 #[cfg(feature = "trace")]
65 let name = schedule.systems[system_index].name();
66 #[cfg(feature = "trace")]
67 let should_run_span = info_span!("check_conditions", name = &*name).entered();
68
69 let mut should_run = !self.completed_systems.contains(system_index);
70 for set_idx in schedule.sets_with_conditions_of_systems[system_index].ones() {
71 if self.evaluated_sets.contains(set_idx) {
72 continue;
73 }
74
75 let set_conditions_met =
77 evaluate_and_fold_conditions(&mut schedule.set_conditions[set_idx], world);
78
79 if !set_conditions_met {
80 self.completed_systems
81 .union_with(&schedule.systems_in_sets_with_conditions[set_idx]);
82 }
83
84 should_run &= set_conditions_met;
85 self.evaluated_sets.insert(set_idx);
86 }
87
88 let system_conditions_met =
90 evaluate_and_fold_conditions(&mut schedule.system_conditions[system_index], world);
91
92 should_run &= system_conditions_met;
93
94 let system = &mut schedule.systems[system_index];
95 if should_run {
96 let valid_params = match system.validate_param(world) {
97 Ok(()) => true,
98 Err(e) => {
99 if !e.skipped {
100 error_handler(
101 e.into(),
102 ErrorContext::System {
103 name: system.name(),
104 last_run: system.get_last_run(),
105 },
106 );
107 }
108 false
109 }
110 };
111
112 should_run &= valid_params;
113 }
114
115 #[cfg(feature = "trace")]
116 should_run_span.exit();
117
118 self.completed_systems.insert(system_index);
120
121 if !should_run {
122 continue;
123 }
124
125 if is_apply_deferred(system) {
126 self.apply_deferred(schedule, world);
127 continue;
128 }
129
130 let f = AssertUnwindSafe(|| {
131 if system.is_exclusive() {
132 if let Err(err) = __rust_begin_short_backtrace::run(system, world) {
133 error_handler(
134 err,
135 ErrorContext::System {
136 name: system.name(),
137 last_run: system.get_last_run(),
138 },
139 );
140 }
141 } else {
142 let world = world.as_unsafe_world_cell();
144 system.update_archetype_component_access(world);
145 unsafe {
148 if let Err(err) = __rust_begin_short_backtrace::run_unsafe(system, world) {
149 error_handler(
150 err,
151 ErrorContext::System {
152 name: system.name(),
153 last_run: system.get_last_run(),
154 },
155 );
156 }
157 };
158 }
159 });
160
161 #[cfg(feature = "std")]
162 #[expect(clippy::print_stderr, reason = "Allowed behind `std` feature gate.")]
163 {
164 if let Err(payload) = std::panic::catch_unwind(f) {
165 eprintln!("Encountered a panic in system `{}`!", &*system.name());
166 std::panic::resume_unwind(payload);
167 }
168 }
169
170 #[cfg(not(feature = "std"))]
171 {
172 (f)();
173 }
174
175 self.unapplied_systems.insert(system_index);
176 }
177
178 if self.apply_final_deferred {
179 self.apply_deferred(schedule, world);
180 }
181 self.evaluated_sets.clear();
182 self.completed_systems.clear();
183 }
184
185 fn set_apply_final_deferred(&mut self, apply_final_deferred: bool) {
186 self.apply_final_deferred = apply_final_deferred;
187 }
188}
189
190impl SingleThreadedExecutor {
191 pub const fn new() -> Self {
195 Self {
196 evaluated_sets: FixedBitSet::new(),
197 completed_systems: FixedBitSet::new(),
198 unapplied_systems: FixedBitSet::new(),
199 apply_final_deferred: true,
200 }
201 }
202
203 fn apply_deferred(&mut self, schedule: &mut SystemSchedule, world: &mut World) {
204 for system_index in self.unapplied_systems.ones() {
205 let system = &mut schedule.systems[system_index];
206 system.apply_deferred(world);
207 }
208
209 self.unapplied_systems.clear();
210 }
211}
212
213fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut World) -> bool {
214 let error_handler: fn(BevyError, ErrorContext) = default_error_handler();
215
216 #[expect(
217 clippy::unnecessary_fold,
218 reason = "Short-circuiting here would prevent conditions from mutating their own state as needed."
219 )]
220 conditions
221 .iter_mut()
222 .map(|condition| {
223 match condition.validate_param(world) {
224 Ok(()) => (),
225 Err(e) => {
226 if !e.skipped {
227 error_handler(
228 e.into(),
229 ErrorContext::System {
230 name: condition.name(),
231 last_run: condition.get_last_run(),
232 },
233 );
234 }
235 return false;
236 }
237 }
238 __rust_begin_short_backtrace::readonly_run(&mut **condition, world)
239 })
240 .fold(true, |acc, res| acc && res)
241}