Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ecs/src/schedule/stepping.rs
6849 views
1
use crate::{
2
resource::Resource,
3
schedule::{InternedScheduleLabel, NodeId, Schedule, ScheduleLabel, SystemKey},
4
system::{IntoSystem, ResMut},
5
};
6
use alloc::vec::Vec;
7
use bevy_platform::collections::HashMap;
8
use bevy_utils::TypeIdMap;
9
use core::any::TypeId;
10
use fixedbitset::FixedBitSet;
11
use log::{info, warn};
12
use thiserror::Error;
13
14
#[cfg(not(feature = "bevy_debug_stepping"))]
15
use log::error;
16
17
#[cfg(test)]
18
use log::debug;
19
20
#[derive(Debug, Default, PartialEq, Eq, Copy, Clone)]
21
enum Action {
22
/// Stepping is disabled; run all systems
23
#[default]
24
RunAll,
25
26
/// Stepping is enabled, but we're only running required systems this frame
27
Waiting,
28
29
/// Stepping is enabled; run all systems until the end of the frame, or
30
/// until we encounter a system marked with [`SystemBehavior::Break`] or all
31
/// systems in the frame have run.
32
Continue,
33
34
/// stepping is enabled; only run the next system in our step list
35
Step,
36
}
37
38
#[derive(Debug, Copy, Clone)]
39
enum SystemBehavior {
40
/// System will always run regardless of stepping action
41
AlwaysRun,
42
43
/// System will never run while stepping is enabled
44
NeverRun,
45
46
/// When [`Action::Waiting`] this system will not be run
47
/// When [`Action::Step`] this system will be stepped
48
/// When [`Action::Continue`] system execution will stop before executing
49
/// this system unless its the first system run when continuing
50
Break,
51
52
/// When [`Action::Waiting`] this system will not be run
53
/// When [`Action::Step`] this system will be stepped
54
/// When [`Action::Continue`] this system will be run
55
Continue,
56
}
57
58
// schedule_order index, and schedule start point
59
#[derive(Debug, Default, Clone, Copy)]
60
struct Cursor {
61
/// index within `Stepping::schedule_order`
62
pub schedule: usize,
63
/// index within the schedule's system list
64
pub system: usize,
65
}
66
67
// Two methods of referring to Systems, via TypeId, or per-Schedule NodeId
68
enum SystemIdentifier {
69
Type(TypeId),
70
Node(NodeId),
71
}
72
73
/// Updates to [`Stepping::schedule_states`] that will be applied at the start
74
/// of the next render frame
75
enum Update {
76
/// Set the action stepping will perform for this render frame
77
SetAction(Action),
78
/// Enable stepping for this schedule
79
AddSchedule(InternedScheduleLabel),
80
/// Disable stepping for this schedule
81
RemoveSchedule(InternedScheduleLabel),
82
/// Clear any system-specific behaviors for this schedule
83
ClearSchedule(InternedScheduleLabel),
84
/// Set a system-specific behavior for this schedule & system
85
SetBehavior(InternedScheduleLabel, SystemIdentifier, SystemBehavior),
86
/// Clear any system-specific behavior for this schedule & system
87
ClearBehavior(InternedScheduleLabel, SystemIdentifier),
88
}
89
90
#[derive(Error, Debug)]
91
#[error("not available until all configured schedules have been run; try again next frame")]
92
pub struct NotReady;
93
94
#[derive(Resource, Default)]
95
/// Resource for controlling system stepping behavior
96
pub struct Stepping {
97
// [`ScheduleState`] for each [`Schedule`] with stepping enabled
98
schedule_states: HashMap<InternedScheduleLabel, ScheduleState>,
99
100
// dynamically generated [`Schedule`] order
101
schedule_order: Vec<InternedScheduleLabel>,
102
103
// current position in the stepping frame
104
cursor: Cursor,
105
106
// index in [`schedule_order`] of the last schedule to call `skipped_systems()`
107
previous_schedule: Option<usize>,
108
109
// Action to perform during this render frame
110
action: Action,
111
112
// Updates apply at the start of the next render frame
113
updates: Vec<Update>,
114
}
115
116
impl core::fmt::Debug for Stepping {
117
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
118
write!(
119
f,
120
"Stepping {{ action: {:?}, schedules: {:?}, order: {:?}",
121
self.action,
122
self.schedule_states.keys(),
123
self.schedule_order
124
)?;
125
if self.action != Action::RunAll {
126
let Cursor { schedule, system } = self.cursor;
127
match self.schedule_order.get(schedule) {
128
Some(label) => write!(f, "cursor: {label:?}[{system}], ")?,
129
None => write!(f, "cursor: None, ")?,
130
};
131
}
132
write!(f, "}}")
133
}
134
}
135
136
impl Stepping {
137
/// Create a new instance of the `Stepping` resource.
138
pub fn new() -> Self {
139
Stepping::default()
140
}
141
142
/// System to call denoting that a new render frame has begun
143
///
144
/// Note: This system is automatically added to the default `MainSchedule`.
145
pub fn begin_frame(stepping: Option<ResMut<Self>>) {
146
if let Some(mut stepping) = stepping {
147
stepping.next_frame();
148
}
149
}
150
151
/// Return the list of schedules with stepping enabled in the order
152
/// they are executed in.
153
pub fn schedules(&self) -> Result<&Vec<InternedScheduleLabel>, NotReady> {
154
if self.schedule_order.len() == self.schedule_states.len() {
155
Ok(&self.schedule_order)
156
} else {
157
Err(NotReady)
158
}
159
}
160
161
/// Return our current position within the stepping frame
162
///
163
/// NOTE: This function **will** return `None` during normal execution with
164
/// stepping enabled. This can happen at the end of the stepping frame
165
/// after the last system has been run, but before the start of the next
166
/// render frame.
167
pub fn cursor(&self) -> Option<(InternedScheduleLabel, NodeId)> {
168
if self.action == Action::RunAll {
169
return None;
170
}
171
let label = self.schedule_order.get(self.cursor.schedule)?;
172
let state = self.schedule_states.get(label)?;
173
state
174
.node_ids
175
.get(self.cursor.system)
176
.map(|node_id| (*label, NodeId::System(*node_id)))
177
}
178
179
/// Enable stepping for the provided schedule
180
pub fn add_schedule(&mut self, schedule: impl ScheduleLabel) -> &mut Self {
181
self.updates.push(Update::AddSchedule(schedule.intern()));
182
self
183
}
184
185
/// Disable stepping for the provided schedule
186
///
187
/// NOTE: This function will also clear any system-specific behaviors that
188
/// may have been configured.
189
pub fn remove_schedule(&mut self, schedule: impl ScheduleLabel) -> &mut Self {
190
self.updates.push(Update::RemoveSchedule(schedule.intern()));
191
self
192
}
193
194
/// Clear behavior set for all systems in the provided [`Schedule`]
195
pub fn clear_schedule(&mut self, schedule: impl ScheduleLabel) -> &mut Self {
196
self.updates.push(Update::ClearSchedule(schedule.intern()));
197
self
198
}
199
200
/// Begin stepping at the start of the next frame
201
pub fn enable(&mut self) -> &mut Self {
202
#[cfg(feature = "bevy_debug_stepping")]
203
self.updates.push(Update::SetAction(Action::Waiting));
204
#[cfg(not(feature = "bevy_debug_stepping"))]
205
error!(
206
"Stepping cannot be enabled; \
207
bevy was compiled without the bevy_debug_stepping feature"
208
);
209
self
210
}
211
212
/// Disable stepping, resume normal systems execution
213
pub fn disable(&mut self) -> &mut Self {
214
self.updates.push(Update::SetAction(Action::RunAll));
215
self
216
}
217
218
/// Check if stepping is enabled
219
pub fn is_enabled(&self) -> bool {
220
self.action != Action::RunAll
221
}
222
223
/// Run the next system during the next render frame
224
///
225
/// NOTE: This will have no impact unless stepping has been enabled
226
pub fn step_frame(&mut self) -> &mut Self {
227
self.updates.push(Update::SetAction(Action::Step));
228
self
229
}
230
231
/// Run all remaining systems in the stepping frame during the next render
232
/// frame
233
///
234
/// NOTE: This will have no impact unless stepping has been enabled
235
pub fn continue_frame(&mut self) -> &mut Self {
236
self.updates.push(Update::SetAction(Action::Continue));
237
self
238
}
239
240
/// Ensure this system always runs when stepping is enabled
241
///
242
/// Note: if the system is run multiple times in the [`Schedule`], this
243
/// will apply for all instances of the system.
244
pub fn always_run<Marker>(
245
&mut self,
246
schedule: impl ScheduleLabel,
247
system: impl IntoSystem<(), (), Marker>,
248
) -> &mut Self {
249
let type_id = system.system_type_id();
250
self.updates.push(Update::SetBehavior(
251
schedule.intern(),
252
SystemIdentifier::Type(type_id),
253
SystemBehavior::AlwaysRun,
254
));
255
256
self
257
}
258
259
/// Ensure this system instance always runs when stepping is enabled
260
pub fn always_run_node(&mut self, schedule: impl ScheduleLabel, node: NodeId) -> &mut Self {
261
self.updates.push(Update::SetBehavior(
262
schedule.intern(),
263
SystemIdentifier::Node(node),
264
SystemBehavior::AlwaysRun,
265
));
266
self
267
}
268
269
/// Ensure this system never runs when stepping is enabled
270
pub fn never_run<Marker>(
271
&mut self,
272
schedule: impl ScheduleLabel,
273
system: impl IntoSystem<(), (), Marker>,
274
) -> &mut Self {
275
let type_id = system.system_type_id();
276
self.updates.push(Update::SetBehavior(
277
schedule.intern(),
278
SystemIdentifier::Type(type_id),
279
SystemBehavior::NeverRun,
280
));
281
282
self
283
}
284
285
/// Ensure this system instance never runs when stepping is enabled
286
pub fn never_run_node(&mut self, schedule: impl ScheduleLabel, node: NodeId) -> &mut Self {
287
self.updates.push(Update::SetBehavior(
288
schedule.intern(),
289
SystemIdentifier::Node(node),
290
SystemBehavior::NeverRun,
291
));
292
self
293
}
294
295
/// Add a breakpoint for system
296
pub fn set_breakpoint<Marker>(
297
&mut self,
298
schedule: impl ScheduleLabel,
299
system: impl IntoSystem<(), (), Marker>,
300
) -> &mut Self {
301
let type_id = system.system_type_id();
302
self.updates.push(Update::SetBehavior(
303
schedule.intern(),
304
SystemIdentifier::Type(type_id),
305
SystemBehavior::Break,
306
));
307
308
self
309
}
310
311
/// Add a breakpoint for system instance
312
pub fn set_breakpoint_node(&mut self, schedule: impl ScheduleLabel, node: NodeId) -> &mut Self {
313
self.updates.push(Update::SetBehavior(
314
schedule.intern(),
315
SystemIdentifier::Node(node),
316
SystemBehavior::Break,
317
));
318
self
319
}
320
321
/// Clear a breakpoint for the system
322
pub fn clear_breakpoint<Marker>(
323
&mut self,
324
schedule: impl ScheduleLabel,
325
system: impl IntoSystem<(), (), Marker>,
326
) -> &mut Self {
327
self.clear_system(schedule, system);
328
329
self
330
}
331
332
/// clear a breakpoint for system instance
333
pub fn clear_breakpoint_node(
334
&mut self,
335
schedule: impl ScheduleLabel,
336
node: NodeId,
337
) -> &mut Self {
338
self.clear_node(schedule, node);
339
self
340
}
341
342
/// Clear any behavior set for the system
343
pub fn clear_system<Marker>(
344
&mut self,
345
schedule: impl ScheduleLabel,
346
system: impl IntoSystem<(), (), Marker>,
347
) -> &mut Self {
348
let type_id = system.system_type_id();
349
self.updates.push(Update::ClearBehavior(
350
schedule.intern(),
351
SystemIdentifier::Type(type_id),
352
));
353
354
self
355
}
356
357
/// clear a breakpoint for system instance
358
pub fn clear_node(&mut self, schedule: impl ScheduleLabel, node: NodeId) -> &mut Self {
359
self.updates.push(Update::ClearBehavior(
360
schedule.intern(),
361
SystemIdentifier::Node(node),
362
));
363
self
364
}
365
366
/// lookup the first system for the supplied schedule index
367
fn first_system_index_for_schedule(&self, index: usize) -> usize {
368
let Some(label) = self.schedule_order.get(index) else {
369
return 0;
370
};
371
let Some(state) = self.schedule_states.get(label) else {
372
return 0;
373
};
374
state.first.unwrap_or(0)
375
}
376
377
/// Move the cursor to the start of the first schedule
378
fn reset_cursor(&mut self) {
379
self.cursor = Cursor {
380
schedule: 0,
381
system: self.first_system_index_for_schedule(0),
382
};
383
}
384
385
/// Advance schedule states for the next render frame
386
fn next_frame(&mut self) {
387
// if stepping is enabled; reset our internal state for the start of
388
// the next frame
389
if self.action != Action::RunAll {
390
self.action = Action::Waiting;
391
self.previous_schedule = None;
392
393
// if the cursor passed the last schedule, reset it
394
if self.cursor.schedule >= self.schedule_order.len() {
395
self.reset_cursor();
396
}
397
}
398
399
if self.updates.is_empty() {
400
return;
401
}
402
403
let mut reset_cursor = false;
404
for update in self.updates.drain(..) {
405
match update {
406
Update::SetAction(Action::RunAll) => {
407
self.action = Action::RunAll;
408
reset_cursor = true;
409
}
410
Update::SetAction(action) => {
411
// This match block is really just to filter out invalid
412
// transitions, and add debugging messages for permitted
413
// transitions. Any action transition that falls through
414
// this match block will be performed.
415
#[expect(
416
clippy::match_same_arms,
417
reason = "Readability would be negatively impacted by combining the `(Waiting, RunAll)` and `(Continue, RunAll)` match arms."
418
)]
419
match (self.action, action) {
420
// ignore non-transition updates, and prevent a call to
421
// enable() from overwriting a step or continue call
422
(Action::RunAll, Action::RunAll)
423
| (Action::Waiting, Action::Waiting)
424
| (Action::Continue, Action::Continue)
425
| (Action::Step, Action::Step)
426
| (Action::Continue, Action::Waiting)
427
| (Action::Step, Action::Waiting) => continue,
428
429
// when stepping is disabled
430
(Action::RunAll, Action::Waiting) => info!("enabled stepping"),
431
(Action::RunAll, _) => {
432
warn!(
433
"stepping not enabled; call Stepping::enable() \
434
before step_frame() or continue_frame()"
435
);
436
continue;
437
}
438
439
// stepping enabled; waiting
440
(Action::Waiting, Action::RunAll) => info!("disabled stepping"),
441
(Action::Waiting, Action::Continue) => info!("continue frame"),
442
(Action::Waiting, Action::Step) => info!("step frame"),
443
444
// stepping enabled; continue frame
445
(Action::Continue, Action::RunAll) => info!("disabled stepping"),
446
(Action::Continue, Action::Step) => {
447
warn!("ignoring step_frame(); already continuing next frame");
448
continue;
449
}
450
451
// stepping enabled; step frame
452
(Action::Step, Action::RunAll) => info!("disabled stepping"),
453
(Action::Step, Action::Continue) => {
454
warn!("ignoring continue_frame(); already stepping next frame");
455
continue;
456
}
457
}
458
459
// permitted action transition; make the change
460
self.action = action;
461
}
462
Update::AddSchedule(l) => {
463
self.schedule_states.insert(l, ScheduleState::default());
464
}
465
Update::RemoveSchedule(label) => {
466
self.schedule_states.remove(&label);
467
if let Some(index) = self.schedule_order.iter().position(|l| l == &label) {
468
self.schedule_order.remove(index);
469
}
470
reset_cursor = true;
471
}
472
Update::ClearSchedule(label) => match self.schedule_states.get_mut(&label) {
473
Some(state) => state.clear_behaviors(),
474
None => {
475
warn!(
476
"stepping is not enabled for schedule {label:?}; \
477
use `.add_stepping({label:?})` to enable stepping"
478
);
479
}
480
},
481
Update::SetBehavior(label, system, behavior) => {
482
match self.schedule_states.get_mut(&label) {
483
Some(state) => state.set_behavior(system, behavior),
484
None => {
485
warn!(
486
"stepping is not enabled for schedule {label:?}; \
487
use `.add_stepping({label:?})` to enable stepping"
488
);
489
}
490
}
491
}
492
Update::ClearBehavior(label, system) => {
493
match self.schedule_states.get_mut(&label) {
494
Some(state) => state.clear_behavior(system),
495
None => {
496
warn!(
497
"stepping is not enabled for schedule {label:?}; \
498
use `.add_stepping({label:?})` to enable stepping"
499
);
500
}
501
}
502
}
503
}
504
}
505
506
if reset_cursor {
507
self.reset_cursor();
508
}
509
}
510
511
/// get the list of systems this schedule should skip for this render
512
/// frame
513
pub fn skipped_systems(&mut self, schedule: &Schedule) -> Option<FixedBitSet> {
514
if self.action == Action::RunAll {
515
return None;
516
}
517
518
// grab the label and state for this schedule
519
let label = schedule.label();
520
let state = self.schedule_states.get_mut(&label)?;
521
522
// Stepping is enabled, and this schedule is supposed to be stepped.
523
//
524
// We need to maintain a list of schedules in the order that they call
525
// this function. We'll check the ordered list now to see if this
526
// schedule is present. If not, we'll add it after the last schedule
527
// that called this function. Finally we want to save off the index of
528
// this schedule in the ordered schedule list. This is used to
529
// determine if this is the schedule the cursor is pointed at.
530
let index = self.schedule_order.iter().position(|l| *l == label);
531
let index = match (index, self.previous_schedule) {
532
(Some(index), _) => index,
533
(None, None) => {
534
self.schedule_order.insert(0, label);
535
0
536
}
537
(None, Some(last)) => {
538
self.schedule_order.insert(last + 1, label);
539
last + 1
540
}
541
};
542
// Update the index of the previous schedule to be the index of this
543
// schedule for the next call
544
self.previous_schedule = Some(index);
545
546
#[cfg(test)]
547
debug!(
548
"cursor {:?}, index {}, label {:?}",
549
self.cursor, index, label
550
);
551
552
// if the stepping frame cursor is pointing at this schedule, we'll run
553
// the schedule with the current stepping action. If this is not the
554
// cursor schedule, we'll run the schedule with the waiting action.
555
let cursor = self.cursor;
556
let (skip_list, next_system) = if index == cursor.schedule {
557
let (skip_list, next_system) =
558
state.skipped_systems(schedule, cursor.system, self.action);
559
560
// if we just stepped this schedule, then we'll switch the action
561
// to be waiting
562
if self.action == Action::Step {
563
self.action = Action::Waiting;
564
}
565
(skip_list, next_system)
566
} else {
567
// we're not supposed to run any systems in this schedule, so pull
568
// the skip list, but ignore any changes it makes to the cursor.
569
let (skip_list, _) = state.skipped_systems(schedule, 0, Action::Waiting);
570
(skip_list, Some(cursor.system))
571
};
572
573
// update the stepping frame cursor based on if there are any systems
574
// remaining to be run in the schedule
575
// Note: Don't try to detect the end of the render frame here using the
576
// schedule index. We don't know all schedules have been added to the
577
// schedule_order, so only next_frame() knows its safe to reset the
578
// cursor.
579
match next_system {
580
Some(i) => self.cursor.system = i,
581
None => {
582
let index = cursor.schedule + 1;
583
self.cursor = Cursor {
584
schedule: index,
585
system: self.first_system_index_for_schedule(index),
586
};
587
588
#[cfg(test)]
589
debug!("advanced schedule index: {} -> {}", cursor.schedule, index);
590
}
591
}
592
593
Some(skip_list)
594
}
595
}
596
597
#[derive(Default)]
598
struct ScheduleState {
599
/// per-system [`SystemBehavior`]
600
behaviors: HashMap<NodeId, SystemBehavior>,
601
602
/// order of [`NodeId`]s in the schedule
603
///
604
/// This is a cached copy of `SystemExecutable::system_ids`. We need it
605
/// available here to be accessed by [`Stepping::cursor()`] so we can return
606
/// [`NodeId`]s to the caller.
607
node_ids: Vec<SystemKey>,
608
609
/// changes to system behavior that should be applied the next time
610
/// [`ScheduleState::skipped_systems()`] is called
611
behavior_updates: TypeIdMap<Option<SystemBehavior>>,
612
613
/// This field contains the first steppable system in the schedule.
614
first: Option<usize>,
615
}
616
617
impl ScheduleState {
618
// set the stepping behavior for a system in this schedule
619
fn set_behavior(&mut self, system: SystemIdentifier, behavior: SystemBehavior) {
620
self.first = None;
621
match system {
622
SystemIdentifier::Node(node_id) => {
623
self.behaviors.insert(node_id, behavior);
624
}
625
// Behaviors are indexed by NodeId, but we cannot map a system
626
// TypeId to a NodeId without the `Schedule`. So queue this update
627
// to be processed the next time `skipped_systems()` is called.
628
SystemIdentifier::Type(type_id) => {
629
self.behavior_updates.insert(type_id, Some(behavior));
630
}
631
}
632
}
633
634
// clear the stepping behavior for a system in this schedule
635
fn clear_behavior(&mut self, system: SystemIdentifier) {
636
self.first = None;
637
match system {
638
SystemIdentifier::Node(node_id) => {
639
self.behaviors.remove(&node_id);
640
}
641
// queue TypeId updates to be processed later when we have Schedule
642
SystemIdentifier::Type(type_id) => {
643
self.behavior_updates.insert(type_id, None);
644
}
645
}
646
}
647
648
// clear all system behaviors
649
fn clear_behaviors(&mut self) {
650
self.behaviors.clear();
651
self.behavior_updates.clear();
652
self.first = None;
653
}
654
655
// apply system behavior updates by looking up the node id of the system in
656
// the schedule, and updating `systems`
657
fn apply_behavior_updates(&mut self, schedule: &Schedule) {
658
// Systems may be present multiple times within a schedule, so we
659
// iterate through all systems in the schedule, and check our behavior
660
// updates for the system TypeId.
661
// PERF: If we add a way to efficiently query schedule systems by their TypeId, we could remove the full
662
// system scan here
663
for (key, system) in schedule.systems().unwrap() {
664
let behavior = self.behavior_updates.get(&system.type_id());
665
match behavior {
666
None => continue,
667
Some(None) => {
668
self.behaviors.remove(&NodeId::System(key));
669
}
670
Some(Some(behavior)) => {
671
self.behaviors.insert(NodeId::System(key), *behavior);
672
}
673
}
674
}
675
self.behavior_updates.clear();
676
677
#[cfg(test)]
678
debug!("apply_updates(): {:?}", self.behaviors);
679
}
680
681
fn skipped_systems(
682
&mut self,
683
schedule: &Schedule,
684
start: usize,
685
mut action: Action,
686
) -> (FixedBitSet, Option<usize>) {
687
use core::cmp::Ordering;
688
689
// if our NodeId list hasn't been populated, copy it over from the
690
// schedule
691
if self.node_ids.len() != schedule.systems_len() {
692
self.node_ids.clone_from(&schedule.executable().system_ids);
693
}
694
695
// Now that we have the schedule, apply any pending system behavior
696
// updates. The schedule is required to map from system `TypeId` to
697
// `NodeId`.
698
if !self.behavior_updates.is_empty() {
699
self.apply_behavior_updates(schedule);
700
}
701
702
// if we don't have a first system set, set it now
703
if self.first.is_none() {
704
for (i, (key, _)) in schedule.systems().unwrap().enumerate() {
705
match self.behaviors.get(&NodeId::System(key)) {
706
Some(SystemBehavior::AlwaysRun | SystemBehavior::NeverRun) => continue,
707
Some(_) | None => {
708
self.first = Some(i);
709
break;
710
}
711
}
712
}
713
}
714
715
let mut skip = FixedBitSet::with_capacity(schedule.systems_len());
716
let mut pos = start;
717
718
for (i, (key, _system)) in schedule.systems().unwrap().enumerate() {
719
let behavior = self
720
.behaviors
721
.get(&NodeId::System(key))
722
.unwrap_or(&SystemBehavior::Continue);
723
724
#[cfg(test)]
725
debug!(
726
"skipped_systems(): systems[{}], pos {}, Action::{:?}, Behavior::{:?}, {}",
727
i,
728
pos,
729
action,
730
behavior,
731
_system.name()
732
);
733
734
match (action, behavior) {
735
// regardless of which action we're performing, if the system
736
// is marked as NeverRun, add it to the skip list.
737
// Also, advance the cursor past this system if it is our
738
// current position
739
(_, SystemBehavior::NeverRun) => {
740
skip.insert(i);
741
if i == pos {
742
pos += 1;
743
}
744
}
745
// similarly, ignore any system marked as AlwaysRun; they should
746
// never be added to the skip list
747
// Also, advance the cursor past this system if it is our
748
// current position
749
(_, SystemBehavior::AlwaysRun) => {
750
if i == pos {
751
pos += 1;
752
}
753
}
754
// if we're waiting, no other systems besides AlwaysRun should
755
// be run, so add systems to the skip list
756
(Action::Waiting, _) => skip.insert(i),
757
758
// If we're stepping, the remaining behaviors don't matter,
759
// we're only going to run the system at our cursor. Any system
760
// prior to the cursor is skipped. Once we encounter the system
761
// at the cursor, we'll advance the cursor, and set behavior to
762
// Waiting to skip remaining systems.
763
(Action::Step, _) => match i.cmp(&pos) {
764
Ordering::Less => skip.insert(i),
765
Ordering::Equal => {
766
pos += 1;
767
action = Action::Waiting;
768
}
769
Ordering::Greater => unreachable!(),
770
},
771
// If we're continuing, and the step behavior is continue, we
772
// want to skip any systems prior to our start position. That's
773
// where the stepping frame left off last time we ran anything.
774
(Action::Continue, SystemBehavior::Continue) => {
775
if i < start {
776
skip.insert(i);
777
}
778
}
779
// If we're continuing, and we encounter a breakpoint we may
780
// want to stop before executing the system. To do this we
781
// skip this system and set the action to Waiting.
782
//
783
// Note: if the cursor is pointing at this system, we will run
784
// it anyway. This allows the user to continue, hit a
785
// breakpoint, then continue again to run the breakpoint system
786
// and any following systems.
787
(Action::Continue, SystemBehavior::Break) => {
788
if i != start {
789
skip.insert(i);
790
791
// stop running systems if the breakpoint isn't the
792
// system under the cursor.
793
if i > start {
794
action = Action::Waiting;
795
}
796
}
797
}
798
// should have never gotten into this method if stepping is
799
// disabled
800
(Action::RunAll, _) => unreachable!(),
801
}
802
803
// If we're at the cursor position, and not waiting, advance the
804
// cursor.
805
if i == pos && action != Action::Waiting {
806
pos += 1;
807
}
808
}
809
810
// output is the skip list, and the index of the next system to run in
811
// this schedule.
812
if pos >= schedule.systems_len() {
813
(skip, None)
814
} else {
815
(skip, Some(pos))
816
}
817
}
818
}
819
820
#[cfg(all(test, feature = "bevy_debug_stepping"))]
821
#[expect(clippy::print_stdout, reason = "Allowed in tests.")]
822
mod tests {
823
use super::*;
824
use crate::{prelude::*, schedule::ScheduleLabel};
825
use alloc::{format, vec};
826
use slotmap::SlotMap;
827
use std::println;
828
829
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
830
struct TestSchedule;
831
832
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
833
struct TestScheduleA;
834
835
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
836
struct TestScheduleB;
837
838
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
839
struct TestScheduleC;
840
841
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
842
struct TestScheduleD;
843
844
fn first_system() {}
845
fn second_system() {}
846
fn third_system() {}
847
848
fn setup() -> (Schedule, World) {
849
let mut world = World::new();
850
let mut schedule = Schedule::new(TestSchedule);
851
schedule.add_systems((first_system, second_system).chain());
852
schedule.initialize(&mut world).unwrap();
853
(schedule, world)
854
}
855
856
// Helper for verifying skip_lists are equal, and if not, printing a human
857
// readable message.
858
macro_rules! assert_skip_list_eq {
859
($actual:expr, $expected:expr, $system_names:expr) => {
860
let actual = $actual;
861
let expected = $expected;
862
let systems: &Vec<&str> = $system_names;
863
864
if (actual != expected) {
865
use core::fmt::Write as _;
866
867
// mismatch, let's construct a human-readable message of what
868
// was returned
869
let mut msg = format!(
870
"Schedule:\n {:9} {:16}{:6} {:6} {:6}\n",
871
"index", "name", "expect", "actual", "result"
872
);
873
for (i, name) in systems.iter().enumerate() {
874
let _ = write!(msg, " system[{:1}] {:16}", i, name);
875
match (expected.contains(i), actual.contains(i)) {
876
(true, true) => msg.push_str("skip skip pass\n"),
877
(true, false) => {
878
msg.push_str("skip run FAILED; system should not have run\n")
879
}
880
(false, true) => {
881
msg.push_str("run skip FAILED; system should have run\n")
882
}
883
(false, false) => msg.push_str("run run pass\n"),
884
}
885
}
886
assert_eq!(actual, expected, "{}", msg);
887
}
888
};
889
}
890
891
// Helper for verifying that a set of systems will be run for a given skip
892
// list
893
macro_rules! assert_systems_run {
894
($schedule:expr, $skipped_systems:expr, $($system:expr),*) => {
895
// pull an ordered list of systems in the schedule, and save the
896
// system TypeId, and name.
897
let systems: Vec<(TypeId, alloc::string::String)> = $schedule.systems().unwrap()
898
.map(|(_, system)| {
899
(system.type_id(), system.name().as_string())
900
})
901
.collect();
902
903
// construct a list of systems that are expected to run
904
let mut expected = FixedBitSet::with_capacity(systems.len());
905
$(
906
let sys = IntoSystem::into_system($system);
907
for (i, (type_id, _)) in systems.iter().enumerate() {
908
if sys.type_id() == *type_id {
909
expected.insert(i);
910
}
911
}
912
)*
913
914
// flip the run list to get our skip list
915
expected.toggle_range(..);
916
917
// grab the list of skipped systems
918
let actual = match $skipped_systems {
919
None => FixedBitSet::with_capacity(systems.len()),
920
Some(b) => b,
921
};
922
let system_names: Vec<&str> = systems
923
.iter()
924
.map(|(_,n)| n.rsplit_once("::").unwrap().1)
925
.collect();
926
927
assert_skip_list_eq!(actual, expected, &system_names);
928
};
929
}
930
931
// Helper for verifying the expected systems will be run by the schedule
932
//
933
// This macro will construct an expected FixedBitSet for the systems that
934
// should be skipped, and compare it with the results from stepping the
935
// provided schedule. If they don't match, it generates a human-readable
936
// error message and asserts.
937
macro_rules! assert_schedule_runs {
938
($schedule:expr, $stepping:expr, $($system:expr),*) => {
939
// advance stepping to the next frame, and build the skip list for
940
// this schedule
941
$stepping.next_frame();
942
assert_systems_run!($schedule, $stepping.skipped_systems($schedule), $($system),*);
943
};
944
}
945
946
#[test]
947
fn stepping_disabled() {
948
let (schedule, _world) = setup();
949
950
let mut stepping = Stepping::new();
951
stepping.add_schedule(TestSchedule).disable().next_frame();
952
953
assert!(stepping.skipped_systems(&schedule).is_none());
954
assert!(stepping.cursor().is_none());
955
}
956
957
#[test]
958
fn unknown_schedule() {
959
let (schedule, _world) = setup();
960
961
let mut stepping = Stepping::new();
962
stepping.enable().next_frame();
963
964
assert!(stepping.skipped_systems(&schedule).is_none());
965
}
966
967
#[test]
968
fn disabled_always_run() {
969
let (schedule, _world) = setup();
970
971
let mut stepping = Stepping::new();
972
stepping
973
.add_schedule(TestSchedule)
974
.disable()
975
.always_run(TestSchedule, first_system);
976
977
assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
978
}
979
980
#[test]
981
fn waiting_always_run() {
982
let (schedule, _world) = setup();
983
984
let mut stepping = Stepping::new();
985
stepping
986
.add_schedule(TestSchedule)
987
.enable()
988
.always_run(TestSchedule, first_system);
989
990
assert_schedule_runs!(&schedule, &mut stepping, first_system);
991
}
992
993
#[test]
994
fn step_always_run() {
995
let (schedule, _world) = setup();
996
997
let mut stepping = Stepping::new();
998
stepping
999
.add_schedule(TestSchedule)
1000
.enable()
1001
.always_run(TestSchedule, first_system)
1002
.step_frame();
1003
1004
assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
1005
}
1006
1007
#[test]
1008
fn continue_always_run() {
1009
let (schedule, _world) = setup();
1010
1011
let mut stepping = Stepping::new();
1012
stepping
1013
.add_schedule(TestSchedule)
1014
.enable()
1015
.always_run(TestSchedule, first_system)
1016
.continue_frame();
1017
1018
assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
1019
}
1020
1021
#[test]
1022
fn disabled_never_run() {
1023
let (schedule, _world) = setup();
1024
1025
let mut stepping = Stepping::new();
1026
stepping
1027
.add_schedule(TestSchedule)
1028
.never_run(TestSchedule, first_system)
1029
.disable();
1030
assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
1031
}
1032
1033
#[test]
1034
fn waiting_never_run() {
1035
let (schedule, _world) = setup();
1036
1037
let mut stepping = Stepping::new();
1038
stepping
1039
.add_schedule(TestSchedule)
1040
.enable()
1041
.never_run(TestSchedule, first_system);
1042
1043
assert_schedule_runs!(&schedule, &mut stepping,);
1044
}
1045
1046
#[test]
1047
fn step_never_run() {
1048
let (schedule, _world) = setup();
1049
1050
let mut stepping = Stepping::new();
1051
stepping
1052
.add_schedule(TestSchedule)
1053
.enable()
1054
.never_run(TestSchedule, first_system)
1055
.step_frame();
1056
1057
assert_schedule_runs!(&schedule, &mut stepping, second_system);
1058
}
1059
1060
#[test]
1061
fn continue_never_run() {
1062
let (schedule, _world) = setup();
1063
1064
let mut stepping = Stepping::new();
1065
stepping
1066
.add_schedule(TestSchedule)
1067
.enable()
1068
.never_run(TestSchedule, first_system)
1069
.continue_frame();
1070
1071
assert_schedule_runs!(&schedule, &mut stepping, second_system);
1072
}
1073
1074
#[test]
1075
fn disabled_breakpoint() {
1076
let (schedule, _world) = setup();
1077
1078
let mut stepping = Stepping::new();
1079
stepping
1080
.add_schedule(TestSchedule)
1081
.disable()
1082
.set_breakpoint(TestSchedule, second_system);
1083
1084
assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
1085
}
1086
1087
#[test]
1088
fn waiting_breakpoint() {
1089
let (schedule, _world) = setup();
1090
1091
let mut stepping = Stepping::new();
1092
stepping
1093
.add_schedule(TestSchedule)
1094
.enable()
1095
.set_breakpoint(TestSchedule, second_system);
1096
1097
assert_schedule_runs!(&schedule, &mut stepping,);
1098
}
1099
1100
#[test]
1101
fn step_breakpoint() {
1102
let (schedule, _world) = setup();
1103
1104
let mut stepping = Stepping::new();
1105
stepping
1106
.add_schedule(TestSchedule)
1107
.enable()
1108
.set_breakpoint(TestSchedule, second_system)
1109
.step_frame();
1110
1111
// since stepping stops at every system, breakpoints are ignored during
1112
// stepping
1113
assert_schedule_runs!(&schedule, &mut stepping, first_system);
1114
stepping.step_frame();
1115
assert_schedule_runs!(&schedule, &mut stepping, second_system);
1116
1117
// let's go again to verify that we wrap back around to the start of
1118
// the frame
1119
stepping.step_frame();
1120
assert_schedule_runs!(&schedule, &mut stepping, first_system);
1121
1122
// should be back in a waiting state now that it ran first_system
1123
assert_schedule_runs!(&schedule, &mut stepping,);
1124
}
1125
1126
#[test]
1127
fn continue_breakpoint() {
1128
let (schedule, _world) = setup();
1129
1130
let mut stepping = Stepping::new();
1131
stepping
1132
.add_schedule(TestSchedule)
1133
.enable()
1134
.set_breakpoint(TestSchedule, second_system)
1135
.continue_frame();
1136
1137
assert_schedule_runs!(&schedule, &mut stepping, first_system);
1138
stepping.continue_frame();
1139
assert_schedule_runs!(&schedule, &mut stepping, second_system);
1140
stepping.continue_frame();
1141
assert_schedule_runs!(&schedule, &mut stepping, first_system);
1142
}
1143
1144
/// regression test for issue encountered while writing `system_stepping`
1145
/// example
1146
#[test]
1147
fn continue_step_continue_with_breakpoint() {
1148
let mut world = World::new();
1149
let mut schedule = Schedule::new(TestSchedule);
1150
schedule.add_systems((first_system, second_system, third_system).chain());
1151
schedule.initialize(&mut world).unwrap();
1152
1153
let mut stepping = Stepping::new();
1154
stepping
1155
.add_schedule(TestSchedule)
1156
.enable()
1157
.set_breakpoint(TestSchedule, second_system);
1158
1159
stepping.continue_frame();
1160
assert_schedule_runs!(&schedule, &mut stepping, first_system);
1161
1162
stepping.step_frame();
1163
assert_schedule_runs!(&schedule, &mut stepping, second_system);
1164
1165
stepping.continue_frame();
1166
assert_schedule_runs!(&schedule, &mut stepping, third_system);
1167
}
1168
1169
#[test]
1170
fn clear_breakpoint() {
1171
let (schedule, _world) = setup();
1172
1173
let mut stepping = Stepping::new();
1174
stepping
1175
.add_schedule(TestSchedule)
1176
.enable()
1177
.set_breakpoint(TestSchedule, second_system)
1178
.continue_frame();
1179
1180
assert_schedule_runs!(&schedule, &mut stepping, first_system);
1181
stepping.continue_frame();
1182
assert_schedule_runs!(&schedule, &mut stepping, second_system);
1183
1184
stepping.clear_breakpoint(TestSchedule, second_system);
1185
stepping.continue_frame();
1186
assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
1187
}
1188
1189
#[test]
1190
fn clear_system() {
1191
let (schedule, _world) = setup();
1192
1193
let mut stepping = Stepping::new();
1194
stepping
1195
.add_schedule(TestSchedule)
1196
.enable()
1197
.never_run(TestSchedule, second_system)
1198
.continue_frame();
1199
assert_schedule_runs!(&schedule, &mut stepping, first_system);
1200
1201
stepping.clear_system(TestSchedule, second_system);
1202
stepping.continue_frame();
1203
assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
1204
}
1205
1206
#[test]
1207
fn clear_schedule() {
1208
let (schedule, _world) = setup();
1209
1210
let mut stepping = Stepping::new();
1211
stepping
1212
.add_schedule(TestSchedule)
1213
.enable()
1214
.never_run(TestSchedule, first_system)
1215
.never_run(TestSchedule, second_system)
1216
.continue_frame();
1217
assert_schedule_runs!(&schedule, &mut stepping,);
1218
1219
stepping.clear_schedule(TestSchedule);
1220
stepping.continue_frame();
1221
assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
1222
}
1223
1224
/// This was discovered in code-review, ensure that `clear_schedule` also
1225
/// clears any pending changes too.
1226
#[test]
1227
fn set_behavior_then_clear_schedule() {
1228
let (schedule, _world) = setup();
1229
1230
let mut stepping = Stepping::new();
1231
stepping
1232
.add_schedule(TestSchedule)
1233
.enable()
1234
.continue_frame();
1235
assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
1236
1237
stepping.never_run(TestSchedule, first_system);
1238
stepping.clear_schedule(TestSchedule);
1239
stepping.continue_frame();
1240
assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
1241
}
1242
1243
/// Ensure that if they `clear_schedule` then make further changes to the
1244
/// schedule, those changes after the clear are applied.
1245
#[test]
1246
fn clear_schedule_then_set_behavior() {
1247
let (schedule, _world) = setup();
1248
1249
let mut stepping = Stepping::new();
1250
stepping
1251
.add_schedule(TestSchedule)
1252
.enable()
1253
.continue_frame();
1254
assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
1255
1256
stepping.clear_schedule(TestSchedule);
1257
stepping.never_run(TestSchedule, first_system);
1258
stepping.continue_frame();
1259
assert_schedule_runs!(&schedule, &mut stepping, second_system);
1260
}
1261
1262
// Schedules such as FixedUpdate can be called multiple times in a single
1263
// render frame. Ensure we only run steppable systems the first time the
1264
// schedule is run
1265
#[test]
1266
fn multiple_calls_per_frame_continue() {
1267
let (schedule, _world) = setup();
1268
1269
let mut stepping = Stepping::new();
1270
stepping
1271
.add_schedule(TestSchedule)
1272
.enable()
1273
.always_run(TestSchedule, second_system)
1274
.continue_frame();
1275
1276
// start a new frame, then run the schedule two times; first system
1277
// should only run on the first one
1278
stepping.next_frame();
1279
assert_systems_run!(
1280
&schedule,
1281
stepping.skipped_systems(&schedule),
1282
first_system,
1283
second_system
1284
);
1285
assert_systems_run!(
1286
&schedule,
1287
stepping.skipped_systems(&schedule),
1288
second_system
1289
);
1290
}
1291
#[test]
1292
fn multiple_calls_per_frame_step() {
1293
let (schedule, _world) = setup();
1294
1295
let mut stepping = Stepping::new();
1296
stepping.add_schedule(TestSchedule).enable().step_frame();
1297
1298
// start a new frame, then run the schedule two times; first system
1299
// should only run on the first one
1300
stepping.next_frame();
1301
assert_systems_run!(&schedule, stepping.skipped_systems(&schedule), first_system);
1302
assert_systems_run!(&schedule, stepping.skipped_systems(&schedule),);
1303
}
1304
1305
#[test]
1306
fn step_duplicate_systems() {
1307
let mut world = World::new();
1308
let mut schedule = Schedule::new(TestSchedule);
1309
schedule.add_systems((first_system, first_system, second_system).chain());
1310
schedule.initialize(&mut world).unwrap();
1311
1312
let mut stepping = Stepping::new();
1313
stepping.add_schedule(TestSchedule).enable();
1314
1315
// needed for assert_skip_list_eq!
1316
let system_names = vec!["first_system", "first_system", "second_system"];
1317
// we're going to step three times, and each system in order should run
1318
// only once
1319
for system_index in 0..3 {
1320
// build the skip list by setting all bits, then clearing our the
1321
// one system that should run this step
1322
let mut expected = FixedBitSet::with_capacity(3);
1323
expected.set_range(.., true);
1324
expected.set(system_index, false);
1325
1326
// step the frame and get the skip list
1327
stepping.step_frame();
1328
stepping.next_frame();
1329
let skip_list = stepping
1330
.skipped_systems(&schedule)
1331
.expect("TestSchedule has been added to Stepping");
1332
1333
assert_skip_list_eq!(skip_list, expected, &system_names);
1334
}
1335
}
1336
1337
#[test]
1338
fn step_run_if_false() {
1339
let mut world = World::new();
1340
let mut schedule = Schedule::new(TestSchedule);
1341
1342
// This needs to be a system test to confirm the interaction between
1343
// the skip list and system conditions in Schedule::run(). That means
1344
// all of our systems need real bodies that do things.
1345
//
1346
// first system will be configured as `run_if(|| false)`, so it can
1347
// just panic if called
1348
let first_system: fn() = move || {
1349
panic!("first_system should not be run");
1350
};
1351
1352
// The second system, we need to know when it has been called, so we'll
1353
// add a resource for tracking if it has been run. The system will
1354
// increment the run count.
1355
#[derive(Resource)]
1356
struct RunCount(usize);
1357
world.insert_resource(RunCount(0));
1358
let second_system = |mut run_count: ResMut<RunCount>| {
1359
println!("I have run!");
1360
run_count.0 += 1;
1361
};
1362
1363
// build our schedule; first_system should never run, followed by
1364
// second_system.
1365
schedule.add_systems((first_system.run_if(|| false), second_system).chain());
1366
schedule.initialize(&mut world).unwrap();
1367
1368
// set up stepping
1369
let mut stepping = Stepping::new();
1370
stepping.add_schedule(TestSchedule).enable();
1371
world.insert_resource(stepping);
1372
1373
// if we step, and the run condition is false, we should not run
1374
// second_system. The stepping cursor is at first_system, and if
1375
// first_system wasn't able to run, that's ok.
1376
let mut stepping = world.resource_mut::<Stepping>();
1377
stepping.step_frame();
1378
stepping.next_frame();
1379
schedule.run(&mut world);
1380
assert_eq!(
1381
world.resource::<RunCount>().0,
1382
0,
1383
"second_system should not have run"
1384
);
1385
1386
// now on the next step, second_system should run
1387
let mut stepping = world.resource_mut::<Stepping>();
1388
stepping.step_frame();
1389
stepping.next_frame();
1390
schedule.run(&mut world);
1391
assert_eq!(
1392
world.resource::<RunCount>().0,
1393
1,
1394
"second_system should have run"
1395
);
1396
}
1397
1398
#[test]
1399
fn remove_schedule() {
1400
let (schedule, _world) = setup();
1401
let mut stepping = Stepping::new();
1402
stepping.add_schedule(TestSchedule).enable();
1403
1404
// run the schedule once and verify all systems are skipped
1405
assert_schedule_runs!(&schedule, &mut stepping,);
1406
assert!(!stepping.schedules().unwrap().is_empty());
1407
1408
// remove the test schedule
1409
stepping.remove_schedule(TestSchedule);
1410
assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
1411
assert!(stepping.schedules().unwrap().is_empty());
1412
}
1413
1414
// verify that Stepping can construct an ordered list of schedules
1415
#[test]
1416
fn schedules() {
1417
let mut world = World::new();
1418
1419
// build & initialize a few schedules
1420
let mut schedule_a = Schedule::new(TestScheduleA);
1421
schedule_a.initialize(&mut world).unwrap();
1422
let mut schedule_b = Schedule::new(TestScheduleB);
1423
schedule_b.initialize(&mut world).unwrap();
1424
let mut schedule_c = Schedule::new(TestScheduleC);
1425
schedule_c.initialize(&mut world).unwrap();
1426
let mut schedule_d = Schedule::new(TestScheduleD);
1427
schedule_d.initialize(&mut world).unwrap();
1428
1429
// setup stepping and add all the schedules
1430
let mut stepping = Stepping::new();
1431
stepping
1432
.add_schedule(TestScheduleA)
1433
.add_schedule(TestScheduleB)
1434
.add_schedule(TestScheduleC)
1435
.add_schedule(TestScheduleD)
1436
.enable()
1437
.next_frame();
1438
1439
assert!(stepping.schedules().is_err());
1440
1441
stepping.skipped_systems(&schedule_b);
1442
assert!(stepping.schedules().is_err());
1443
stepping.skipped_systems(&schedule_a);
1444
assert!(stepping.schedules().is_err());
1445
stepping.skipped_systems(&schedule_c);
1446
assert!(stepping.schedules().is_err());
1447
1448
// when we call the last schedule, Stepping should have enough data to
1449
// return an ordered list of schedules
1450
stepping.skipped_systems(&schedule_d);
1451
assert!(stepping.schedules().is_ok());
1452
1453
assert_eq!(
1454
*stepping.schedules().unwrap(),
1455
vec![
1456
TestScheduleB.intern(),
1457
TestScheduleA.intern(),
1458
TestScheduleC.intern(),
1459
TestScheduleD.intern(),
1460
]
1461
);
1462
}
1463
1464
#[test]
1465
fn verify_cursor() {
1466
// helper to build a cursor tuple for the supplied schedule
1467
fn cursor(schedule: &Schedule, index: usize) -> (InternedScheduleLabel, NodeId) {
1468
let node_id = schedule.executable().system_ids[index];
1469
(schedule.label(), NodeId::System(node_id))
1470
}
1471
1472
let mut world = World::new();
1473
let mut slotmap = SlotMap::<SystemKey, ()>::with_key();
1474
1475
// create two schedules with a number of systems in them
1476
let mut schedule_a = Schedule::new(TestScheduleA);
1477
schedule_a.add_systems((|| {}, || {}, || {}, || {}).chain());
1478
schedule_a.initialize(&mut world).unwrap();
1479
let mut schedule_b = Schedule::new(TestScheduleB);
1480
schedule_b.add_systems((|| {}, || {}, || {}, || {}).chain());
1481
schedule_b.initialize(&mut world).unwrap();
1482
1483
// setup stepping and add all schedules
1484
let mut stepping = Stepping::new();
1485
stepping
1486
.add_schedule(TestScheduleA)
1487
.add_schedule(TestScheduleB)
1488
.enable();
1489
1490
assert!(stepping.cursor().is_none());
1491
1492
// step the system nine times, and verify the cursor before & after
1493
// each step
1494
let mut cursors = Vec::new();
1495
for _ in 0..9 {
1496
stepping.step_frame().next_frame();
1497
cursors.push(stepping.cursor());
1498
stepping.skipped_systems(&schedule_a);
1499
stepping.skipped_systems(&schedule_b);
1500
cursors.push(stepping.cursor());
1501
}
1502
1503
#[rustfmt::skip]
1504
assert_eq!(
1505
cursors,
1506
vec![
1507
// before render frame // after render frame
1508
None, Some(cursor(&schedule_a, 1)),
1509
Some(cursor(&schedule_a, 1)), Some(cursor(&schedule_a, 2)),
1510
Some(cursor(&schedule_a, 2)), Some(cursor(&schedule_a, 3)),
1511
Some(cursor(&schedule_a, 3)), Some(cursor(&schedule_b, 0)),
1512
Some(cursor(&schedule_b, 0)), Some(cursor(&schedule_b, 1)),
1513
Some(cursor(&schedule_b, 1)), Some(cursor(&schedule_b, 2)),
1514
Some(cursor(&schedule_b, 2)), Some(cursor(&schedule_b, 3)),
1515
Some(cursor(&schedule_b, 3)), None,
1516
Some(cursor(&schedule_a, 0)), Some(cursor(&schedule_a, 1)),
1517
]
1518
);
1519
1520
let sys0 = slotmap.insert(());
1521
let sys1 = slotmap.insert(());
1522
let _sys2 = slotmap.insert(());
1523
let sys3 = slotmap.insert(());
1524
1525
// reset our cursor (disable/enable), and update stepping to test if the
1526
// cursor properly skips over AlwaysRun & NeverRun systems. Also set
1527
// a Break system to ensure that shows properly in the cursor
1528
stepping
1529
// disable/enable to reset cursor
1530
.disable()
1531
.enable()
1532
.set_breakpoint_node(TestScheduleA, NodeId::System(sys1))
1533
.always_run_node(TestScheduleA, NodeId::System(sys3))
1534
.never_run_node(TestScheduleB, NodeId::System(sys0));
1535
1536
let mut cursors = Vec::new();
1537
for _ in 0..9 {
1538
stepping.step_frame().next_frame();
1539
cursors.push(stepping.cursor());
1540
stepping.skipped_systems(&schedule_a);
1541
stepping.skipped_systems(&schedule_b);
1542
cursors.push(stepping.cursor());
1543
}
1544
1545
#[rustfmt::skip]
1546
assert_eq!(
1547
cursors,
1548
vec![
1549
// before render frame // after render frame
1550
Some(cursor(&schedule_a, 0)), Some(cursor(&schedule_a, 1)),
1551
Some(cursor(&schedule_a, 1)), Some(cursor(&schedule_a, 2)),
1552
Some(cursor(&schedule_a, 2)), Some(cursor(&schedule_b, 1)),
1553
Some(cursor(&schedule_b, 1)), Some(cursor(&schedule_b, 2)),
1554
Some(cursor(&schedule_b, 2)), Some(cursor(&schedule_b, 3)),
1555
Some(cursor(&schedule_b, 3)), None,
1556
Some(cursor(&schedule_a, 0)), Some(cursor(&schedule_a, 1)),
1557
Some(cursor(&schedule_a, 1)), Some(cursor(&schedule_a, 2)),
1558
Some(cursor(&schedule_a, 2)), Some(cursor(&schedule_b, 1)),
1559
]
1560
);
1561
}
1562
}
1563
1564