bevy_ecs/schedule/executor/
simple.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::{
13        executor::is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule,
14    },
15    world::World,
16};
17
18use super::__rust_begin_short_backtrace;
19
20/// A variant of [`SingleThreadedExecutor`](crate::schedule::SingleThreadedExecutor) that calls
21/// [`apply_deferred`](crate::system::System::apply_deferred) immediately after running each system.
22#[derive(Default)]
23pub struct SimpleExecutor {
24    /// Systems sets whose conditions have been evaluated.
25    evaluated_sets: FixedBitSet,
26    /// Systems that have run or been skipped.
27    completed_systems: FixedBitSet,
28}
29
30impl SystemExecutor for SimpleExecutor {
31    fn kind(&self) -> ExecutorKind {
32        ExecutorKind::Simple
33    }
34
35    fn init(&mut self, schedule: &SystemSchedule) {
36        let sys_count = schedule.system_ids.len();
37        let set_count = schedule.set_ids.len();
38        self.evaluated_sets = FixedBitSet::with_capacity(set_count);
39        self.completed_systems = FixedBitSet::with_capacity(sys_count);
40    }
41
42    fn run(
43        &mut self,
44        schedule: &mut SystemSchedule,
45        world: &mut World,
46        _skip_systems: Option<&FixedBitSet>,
47        error_handler: fn(BevyError, ErrorContext),
48    ) {
49        // If stepping is enabled, make sure we skip those systems that should
50        // not be run.
51        #[cfg(feature = "bevy_debug_stepping")]
52        if let Some(skipped_systems) = _skip_systems {
53            // mark skipped systems as completed
54            self.completed_systems |= skipped_systems;
55        }
56
57        for system_index in 0..schedule.systems.len() {
58            #[cfg(feature = "trace")]
59            let name = schedule.systems[system_index].name();
60            #[cfg(feature = "trace")]
61            let should_run_span = info_span!("check_conditions", name = &*name).entered();
62
63            let mut should_run = !self.completed_systems.contains(system_index);
64            for set_idx in schedule.sets_with_conditions_of_systems[system_index].ones() {
65                if self.evaluated_sets.contains(set_idx) {
66                    continue;
67                }
68
69                // evaluate system set's conditions
70                let set_conditions_met =
71                    evaluate_and_fold_conditions(&mut schedule.set_conditions[set_idx], world);
72
73                if !set_conditions_met {
74                    self.completed_systems
75                        .union_with(&schedule.systems_in_sets_with_conditions[set_idx]);
76                }
77
78                should_run &= set_conditions_met;
79                self.evaluated_sets.insert(set_idx);
80            }
81
82            // evaluate system's conditions
83            let system_conditions_met =
84                evaluate_and_fold_conditions(&mut schedule.system_conditions[system_index], world);
85
86            should_run &= system_conditions_met;
87
88            let system = &mut schedule.systems[system_index];
89            if should_run {
90                let valid_params = match system.validate_param(world) {
91                    Ok(()) => true,
92                    Err(e) => {
93                        if !e.skipped {
94                            error_handler(
95                                e.into(),
96                                ErrorContext::System {
97                                    name: system.name(),
98                                    last_run: system.get_last_run(),
99                                },
100                            );
101                        }
102                        false
103                    }
104                };
105                should_run &= valid_params;
106            }
107
108            #[cfg(feature = "trace")]
109            should_run_span.exit();
110
111            // system has either been skipped or will run
112            self.completed_systems.insert(system_index);
113
114            if !should_run {
115                continue;
116            }
117
118            if is_apply_deferred(system) {
119                continue;
120            }
121
122            let f = AssertUnwindSafe(|| {
123                if let Err(err) = __rust_begin_short_backtrace::run(system, world) {
124                    error_handler(
125                        err,
126                        ErrorContext::System {
127                            name: system.name(),
128                            last_run: system.get_last_run(),
129                        },
130                    );
131                }
132            });
133
134            #[cfg(feature = "std")]
135            #[expect(clippy::print_stderr, reason = "Allowed behind `std` feature gate.")]
136            {
137                if let Err(payload) = std::panic::catch_unwind(f) {
138                    eprintln!("Encountered a panic in system `{}`!", &*system.name());
139                    std::panic::resume_unwind(payload);
140                }
141            }
142
143            #[cfg(not(feature = "std"))]
144            {
145                (f)();
146            }
147        }
148
149        self.evaluated_sets.clear();
150        self.completed_systems.clear();
151    }
152
153    fn set_apply_final_deferred(&mut self, _: bool) {
154        // do nothing. simple executor does not do a final sync
155    }
156}
157
158impl SimpleExecutor {
159    /// Creates a new simple executor for use in a [`Schedule`](crate::schedule::Schedule).
160    /// This calls each system in order and immediately calls [`System::apply_deferred`](crate::system::System).
161    pub const fn new() -> Self {
162        Self {
163            evaluated_sets: FixedBitSet::new(),
164            completed_systems: FixedBitSet::new(),
165        }
166    }
167}
168
169fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut World) -> bool {
170    let error_handler = default_error_handler();
171
172    #[expect(
173        clippy::unnecessary_fold,
174        reason = "Short-circuiting here would prevent conditions from mutating their own state as needed."
175    )]
176    conditions
177        .iter_mut()
178        .map(|condition| {
179            match condition.validate_param(world) {
180                Ok(()) => (),
181                Err(e) => {
182                    if !e.skipped {
183                        error_handler(
184                            e.into(),
185                            ErrorContext::System {
186                                name: condition.name(),
187                                last_run: condition.get_last_run(),
188                            },
189                        );
190                    }
191                    return false;
192                }
193            }
194            __rust_begin_short_backtrace::readonly_run(&mut **condition, world)
195        })
196        .fold(true, |acc, res| acc && res)
197}
198
199#[cfg(test)]
200#[test]
201fn skip_automatic_sync_points() {
202    // Schedules automatically insert ApplyDeferred systems, but these should
203    // not be executed as they only serve as markers and are not initialized
204    use crate::prelude::*;
205    let mut sched = Schedule::default();
206    sched.set_executor_kind(ExecutorKind::Simple);
207    sched.add_systems((|_: Commands| (), || ()).chain());
208    let mut world = World::new();
209    sched.run(&mut world);
210}