Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ecs/src/schedule/executor/single_threaded.rs
6849 views
1
use core::panic::AssertUnwindSafe;
2
use fixedbitset::FixedBitSet;
3
4
#[cfg(feature = "trace")]
5
use tracing::info_span;
6
7
#[cfg(feature = "std")]
8
use std::eprintln;
9
10
use 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")]
20
use crate::{change_detection::DetectChanges, HotPatchChanges};
21
22
use super::__rust_begin_short_backtrace;
23
24
/// Runs the schedule using a single thread.
25
///
26
/// Useful if you're dealing with a single-threaded environment, saving your threads for
27
/// other things, or just trying minimize overhead.
28
#[derive(Default)]
29
pub struct SingleThreadedExecutor {
30
/// System sets whose conditions have been evaluated.
31
evaluated_sets: FixedBitSet,
32
/// Systems that have run or been skipped.
33
completed_systems: FixedBitSet,
34
/// Systems that have run but have not had their buffers applied.
35
unapplied_systems: FixedBitSet,
36
/// Setting when true applies deferred system buffers after all systems have run
37
apply_final_deferred: bool,
38
}
39
40
impl SystemExecutor for SingleThreadedExecutor {
41
fn kind(&self) -> ExecutorKind {
42
ExecutorKind::SingleThreaded
43
}
44
45
fn init(&mut self, schedule: &SystemSchedule) {
46
// pre-allocate space
47
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
// If stepping is enabled, make sure we skip those systems that should
62
// not be run.
63
#[cfg(feature = "bevy_debug_stepping")]
64
if let Some(skipped_systems) = _skip_systems {
65
// mark skipped systems as completed
66
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
// evaluate system set's conditions
90
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
// evaluate system's conditions
108
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
// system has either been skipped or will run
127
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
181
impl SingleThreadedExecutor {
182
/// Creates a new single-threaded executor for use in a [`Schedule`].
183
///
184
/// [`Schedule`]: crate::schedule::Schedule
185
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
204
fn 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
}
247
248