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
9351 views
1
use core::panic::AssertUnwindSafe;
2
use fixedbitset::FixedBitSet;
3
4
#[cfg(feature = "trace")]
5
use alloc::string::ToString as _;
6
#[cfg(feature = "trace")]
7
use tracing::info_span;
8
9
#[cfg(feature = "std")]
10
use std::eprintln;
11
12
use crate::{
13
error::{ErrorContext, ErrorHandler},
14
schedule::{
15
is_apply_deferred, ConditionWithAccess, ExecutorKind, SystemExecutor, SystemSchedule,
16
},
17
system::{RunSystemError, ScheduleSystem},
18
world::World,
19
};
20
21
#[cfg(feature = "hotpatching")]
22
use crate::{change_detection::DetectChanges, HotPatchChanges};
23
24
use super::__rust_begin_short_backtrace;
25
26
/// Runs the schedule using a single thread.
27
///
28
/// Useful if you're dealing with a single-threaded environment, saving your threads for
29
/// other things, or just trying minimize overhead.
30
#[derive(Default)]
31
pub struct SingleThreadedExecutor {
32
/// System sets whose conditions have been evaluated.
33
evaluated_sets: FixedBitSet,
34
/// Systems that have run or been skipped.
35
completed_systems: FixedBitSet,
36
/// Systems that have run but have not had their buffers applied.
37
unapplied_systems: FixedBitSet,
38
/// Setting when true applies deferred system buffers after all systems have run
39
apply_final_deferred: bool,
40
}
41
42
impl SystemExecutor for SingleThreadedExecutor {
43
fn kind(&self) -> ExecutorKind {
44
ExecutorKind::SingleThreaded
45
}
46
47
fn init(&mut self, schedule: &SystemSchedule) {
48
// pre-allocate space
49
let sys_count = schedule.system_ids.len();
50
let set_count = schedule.set_ids.len();
51
self.evaluated_sets = FixedBitSet::with_capacity(set_count);
52
self.completed_systems = FixedBitSet::with_capacity(sys_count);
53
self.unapplied_systems = FixedBitSet::with_capacity(sys_count);
54
}
55
56
fn run(
57
&mut self,
58
schedule: &mut SystemSchedule,
59
world: &mut World,
60
_skip_systems: Option<&FixedBitSet>,
61
error_handler: ErrorHandler,
62
) {
63
// If stepping is enabled, make sure we skip those systems that should
64
// not be run.
65
#[cfg(feature = "bevy_debug_stepping")]
66
if let Some(skipped_systems) = _skip_systems {
67
// mark skipped systems as completed
68
self.completed_systems |= skipped_systems;
69
}
70
71
#[cfg(feature = "hotpatching")]
72
let hotpatch_tick = world
73
.get_resource_ref::<HotPatchChanges>()
74
.map(|r| r.last_changed())
75
.unwrap_or_default();
76
77
for system_index in 0..schedule.systems.len() {
78
let system = &mut schedule.systems[system_index].system;
79
80
#[cfg(feature = "trace")]
81
let name = system.name();
82
#[cfg(feature = "trace")]
83
let should_run_span = info_span!("check_conditions", name = name.to_string()).entered();
84
85
let mut should_run = !self.completed_systems.contains(system_index);
86
for set_idx in schedule.sets_with_conditions_of_systems[system_index].ones() {
87
if self.evaluated_sets.contains(set_idx) {
88
continue;
89
}
90
91
// evaluate system set's conditions
92
let set_conditions_met = evaluate_and_fold_conditions(
93
&mut schedule.set_conditions[set_idx],
94
world,
95
error_handler,
96
system,
97
true,
98
);
99
100
if !set_conditions_met {
101
self.completed_systems
102
.union_with(&schedule.systems_in_sets_with_conditions[set_idx]);
103
}
104
105
should_run &= set_conditions_met;
106
self.evaluated_sets.insert(set_idx);
107
}
108
109
// evaluate system's conditions
110
let system_conditions_met = evaluate_and_fold_conditions(
111
&mut schedule.system_conditions[system_index],
112
world,
113
error_handler,
114
system,
115
false,
116
);
117
118
should_run &= system_conditions_met;
119
120
#[cfg(feature = "trace")]
121
should_run_span.exit();
122
123
#[cfg(feature = "hotpatching")]
124
if hotpatch_tick.is_newer_than(system.get_last_run(), world.change_tick()) {
125
system.refresh_hotpatch();
126
}
127
128
// system has either been skipped or will run
129
self.completed_systems.insert(system_index);
130
131
if !should_run {
132
continue;
133
}
134
135
if is_apply_deferred(&**system) {
136
self.apply_deferred(schedule, world);
137
continue;
138
}
139
140
let f = AssertUnwindSafe(|| {
141
if let Err(RunSystemError::Failed(err)) =
142
__rust_begin_short_backtrace::run_without_applying_deferred(system, world)
143
{
144
error_handler(
145
err,
146
ErrorContext::System {
147
name: system.name(),
148
last_run: system.get_last_run(),
149
},
150
);
151
}
152
});
153
154
#[cfg(feature = "std")]
155
#[expect(clippy::print_stderr, reason = "Allowed behind `std` feature gate.")]
156
{
157
if let Err(payload) = std::panic::catch_unwind(f) {
158
eprintln!("Encountered a panic in system `{}`!", system.name());
159
std::panic::resume_unwind(payload);
160
}
161
}
162
163
#[cfg(not(feature = "std"))]
164
{
165
(f)();
166
}
167
168
self.unapplied_systems.insert(system_index);
169
}
170
171
if self.apply_final_deferred {
172
self.apply_deferred(schedule, world);
173
}
174
self.evaluated_sets.clear();
175
self.completed_systems.clear();
176
}
177
178
fn set_apply_final_deferred(&mut self, apply_final_deferred: bool) {
179
self.apply_final_deferred = apply_final_deferred;
180
}
181
}
182
183
impl SingleThreadedExecutor {
184
/// Creates a new single-threaded executor for use in a [`Schedule`].
185
///
186
/// [`Schedule`]: crate::schedule::Schedule
187
pub const fn new() -> Self {
188
Self {
189
evaluated_sets: FixedBitSet::new(),
190
completed_systems: FixedBitSet::new(),
191
unapplied_systems: FixedBitSet::new(),
192
apply_final_deferred: true,
193
}
194
}
195
196
fn apply_deferred(&mut self, schedule: &mut SystemSchedule, world: &mut World) {
197
for system_index in self.unapplied_systems.ones() {
198
let system = &mut schedule.systems[system_index].system;
199
system.apply_deferred(world);
200
}
201
202
self.unapplied_systems.clear();
203
}
204
}
205
206
fn evaluate_and_fold_conditions(
207
conditions: &mut [ConditionWithAccess],
208
world: &mut World,
209
error_handler: ErrorHandler,
210
for_system: &ScheduleSystem,
211
on_set: bool,
212
) -> bool {
213
#[cfg(feature = "hotpatching")]
214
let hotpatch_tick = world
215
.get_resource_ref::<HotPatchChanges>()
216
.map(|r| r.last_changed())
217
.unwrap_or_default();
218
219
#[expect(
220
clippy::unnecessary_fold,
221
reason = "Short-circuiting here would prevent conditions from mutating their own state as needed."
222
)]
223
conditions
224
.iter_mut()
225
.map(|ConditionWithAccess { condition, .. }| {
226
#[cfg(feature = "hotpatching")]
227
if hotpatch_tick.is_newer_than(condition.get_last_run(), world.change_tick()) {
228
condition.refresh_hotpatch();
229
}
230
__rust_begin_short_backtrace::readonly_run(&mut **condition, world).unwrap_or_else(
231
|err| {
232
if let RunSystemError::Failed(err) = err {
233
error_handler(
234
err,
235
ErrorContext::RunCondition {
236
name: condition.name(),
237
last_run: condition.get_last_run(),
238
system: for_system.name(),
239
on_set,
240
},
241
);
242
};
243
false
244
},
245
)
246
})
247
.fold(true, |acc, res| acc && res)
248
}
249
250