bevy_ecs/schedule/
stepping.rs

1use crate::{
2    resource::Resource,
3    schedule::{InternedScheduleLabel, NodeId, Schedule, ScheduleLabel, SystemKey},
4    system::{IntoSystem, ResMut},
5};
6use alloc::vec::Vec;
7use bevy_platform::collections::HashMap;
8use bevy_utils::TypeIdMap;
9use core::any::TypeId;
10use fixedbitset::FixedBitSet;
11use log::{info, warn};
12use thiserror::Error;
13
14#[cfg(not(feature = "bevy_debug_stepping"))]
15use log::error;
16
17#[cfg(test)]
18use log::debug;
19
20#[derive(Debug, Default, PartialEq, Eq, Copy, Clone)]
21enum 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)]
39enum 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)]
60struct 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
68enum 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
75enum 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")]
92pub struct NotReady;
93
94#[derive(Resource, Default)]
95/// Resource for controlling system stepping behavior
96pub 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
116impl 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
136impl 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)]
598struct 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
617impl 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.")]
822mod 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}