bevy_ecs/schedule/executor/
single_threaded.rs

1use 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/// Runs the schedule using a single thread.
19///
20/// Useful if you're dealing with a single-threaded environment, saving your threads for
21/// other things, or just trying minimize overhead.
22#[derive(Default)]
23pub struct SingleThreadedExecutor {
24    /// System sets whose conditions have been evaluated.
25    evaluated_sets: FixedBitSet,
26    /// Systems that have run or been skipped.
27    completed_systems: FixedBitSet,
28    /// Systems that have run but have not had their buffers applied.
29    unapplied_systems: FixedBitSet,
30    /// Setting when true applies deferred system buffers after all systems have run
31    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        // pre-allocate space
41        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        // If stepping is enabled, make sure we skip those systems that should
56        // not be run.
57        #[cfg(feature = "bevy_debug_stepping")]
58        if let Some(skipped_systems) = _skip_systems {
59            // mark skipped systems as completed
60            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                // evaluate system set's conditions
76                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            // evaluate system's conditions
89            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            // system has either been skipped or will run
119            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                    // Use run_unsafe to avoid immediately applying deferred buffers
143                    let world = world.as_unsafe_world_cell();
144                    system.update_archetype_component_access(world);
145                    // SAFETY: We have exclusive, single-threaded access to the world and
146                    // update_archetype_component_access is being called immediately before this.
147                    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    /// Creates a new single-threaded executor for use in a [`Schedule`].
192    ///
193    /// [`Schedule`]: crate::schedule::Schedule
194    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}