bevy_ecs/schedule/
stepping.rs

1use crate::{
2    resource::Resource,
3    schedule::{InternedScheduleLabel, NodeId, Schedule, ScheduleLabel},
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, *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 label = match self.schedule_order.get(index) {
369            None => return 0,
370            Some(label) => label,
371        };
372        let state = match self.schedule_states.get(label) {
373            None => return 0,
374            Some(state) => state,
375        };
376        state.first.unwrap_or(0)
377    }
378
379    /// Move the cursor to the start of the first schedule
380    fn reset_cursor(&mut self) {
381        self.cursor = Cursor {
382            schedule: 0,
383            system: self.first_system_index_for_schedule(0),
384        };
385    }
386
387    /// Advance schedule states for the next render frame
388    fn next_frame(&mut self) {
389        // if stepping is enabled; reset our internal state for the start of
390        // the next frame
391        if self.action != Action::RunAll {
392            self.action = Action::Waiting;
393            self.previous_schedule = None;
394
395            // if the cursor passed the last schedule, reset it
396            if self.cursor.schedule >= self.schedule_order.len() {
397                self.reset_cursor();
398            }
399        }
400
401        if self.updates.is_empty() {
402            return;
403        }
404
405        let mut reset_cursor = false;
406        for update in self.updates.drain(..) {
407            match update {
408                Update::SetAction(Action::RunAll) => {
409                    self.action = Action::RunAll;
410                    reset_cursor = true;
411                }
412                Update::SetAction(action) => {
413                    // This match block is really just to filter out invalid
414                    // transitions, and add debugging messages for permitted
415                    // transitions.  Any action transition that falls through
416                    // this match block will be performed.
417                    #[expect(
418                        clippy::match_same_arms,
419                        reason = "Readability would be negatively impacted by combining the `(Waiting, RunAll)` and `(Continue, RunAll)` match arms."
420                    )]
421                    match (self.action, action) {
422                        // ignore non-transition updates, and prevent a call to
423                        // enable() from overwriting a step or continue call
424                        (Action::RunAll, Action::RunAll)
425                        | (Action::Waiting, Action::Waiting)
426                        | (Action::Continue, Action::Continue)
427                        | (Action::Step, Action::Step)
428                        | (Action::Continue, Action::Waiting)
429                        | (Action::Step, Action::Waiting) => continue,
430
431                        // when stepping is disabled
432                        (Action::RunAll, Action::Waiting) => info!("enabled stepping"),
433                        (Action::RunAll, _) => {
434                            warn!(
435                                "stepping not enabled; call Stepping::enable() \
436                                before step_frame() or continue_frame()"
437                            );
438                            continue;
439                        }
440
441                        // stepping enabled; waiting
442                        (Action::Waiting, Action::RunAll) => info!("disabled stepping"),
443                        (Action::Waiting, Action::Continue) => info!("continue frame"),
444                        (Action::Waiting, Action::Step) => info!("step frame"),
445
446                        // stepping enabled; continue frame
447                        (Action::Continue, Action::RunAll) => info!("disabled stepping"),
448                        (Action::Continue, Action::Step) => {
449                            warn!("ignoring step_frame(); already continuing next frame");
450                            continue;
451                        }
452
453                        // stepping enabled; step frame
454                        (Action::Step, Action::RunAll) => info!("disabled stepping"),
455                        (Action::Step, Action::Continue) => {
456                            warn!("ignoring continue_frame(); already stepping next frame");
457                            continue;
458                        }
459                    }
460
461                    // permitted action transition; make the change
462                    self.action = action;
463                }
464                Update::AddSchedule(l) => {
465                    self.schedule_states.insert(l, ScheduleState::default());
466                }
467                Update::RemoveSchedule(label) => {
468                    self.schedule_states.remove(&label);
469                    if let Some(index) = self.schedule_order.iter().position(|l| l == &label) {
470                        self.schedule_order.remove(index);
471                    }
472                    reset_cursor = true;
473                }
474                Update::ClearSchedule(label) => match self.schedule_states.get_mut(&label) {
475                    Some(state) => state.clear_behaviors(),
476                    None => {
477                        warn!(
478                            "stepping is not enabled for schedule {:?}; \
479                            use `.add_stepping({:?})` to enable stepping",
480                            label, label
481                        );
482                    }
483                },
484                Update::SetBehavior(label, system, behavior) => {
485                    match self.schedule_states.get_mut(&label) {
486                        Some(state) => state.set_behavior(system, behavior),
487                        None => {
488                            warn!(
489                                "stepping is not enabled for schedule {:?}; \
490                                use `.add_stepping({:?})` to enable stepping",
491                                label, label
492                            );
493                        }
494                    }
495                }
496                Update::ClearBehavior(label, system) => {
497                    match self.schedule_states.get_mut(&label) {
498                        Some(state) => state.clear_behavior(system),
499                        None => {
500                            warn!(
501                                "stepping is not enabled for schedule {:?}; \
502                                use `.add_stepping({:?})` to enable stepping",
503                                label, label
504                            );
505                        }
506                    }
507                }
508            }
509        }
510
511        if reset_cursor {
512            self.reset_cursor();
513        }
514    }
515
516    /// get the list of systems this schedule should skip for this render
517    /// frame
518    pub fn skipped_systems(&mut self, schedule: &Schedule) -> Option<FixedBitSet> {
519        if self.action == Action::RunAll {
520            return None;
521        }
522
523        // grab the label and state for this schedule
524        let label = schedule.label();
525        let state = self.schedule_states.get_mut(&label)?;
526
527        // Stepping is enabled, and this schedule is supposed to be stepped.
528        //
529        // We need to maintain a list of schedules in the order that they call
530        // this function. We'll check the ordered list now to see if this
531        // schedule is present. If not, we'll add it after the last schedule
532        // that called this function. Finally we want to save off the index of
533        // this schedule in the ordered schedule list. This is used to
534        // determine if this is the schedule the cursor is pointed at.
535        let index = self.schedule_order.iter().position(|l| *l == label);
536        let index = match (index, self.previous_schedule) {
537            (Some(index), _) => index,
538            (None, None) => {
539                self.schedule_order.insert(0, label);
540                0
541            }
542            (None, Some(last)) => {
543                self.schedule_order.insert(last + 1, label);
544                last + 1
545            }
546        };
547        // Update the index of the previous schedule to be the index of this
548        // schedule for the next call
549        self.previous_schedule = Some(index);
550
551        #[cfg(test)]
552        debug!(
553            "cursor {:?}, index {}, label {:?}",
554            self.cursor, index, label
555        );
556
557        // if the stepping frame cursor is pointing at this schedule, we'll run
558        // the schedule with the current stepping action.  If this is not the
559        // cursor schedule, we'll run the schedule with the waiting action.
560        let cursor = self.cursor;
561        let (skip_list, next_system) = if index == cursor.schedule {
562            let (skip_list, next_system) =
563                state.skipped_systems(schedule, cursor.system, self.action);
564
565            // if we just stepped this schedule, then we'll switch the action
566            // to be waiting
567            if self.action == Action::Step {
568                self.action = Action::Waiting;
569            }
570            (skip_list, next_system)
571        } else {
572            // we're not supposed to run any systems in this schedule, so pull
573            // the skip list, but ignore any changes it makes to the cursor.
574            let (skip_list, _) = state.skipped_systems(schedule, 0, Action::Waiting);
575            (skip_list, Some(cursor.system))
576        };
577
578        // update the stepping frame cursor based on if there are any systems
579        // remaining to be run in the schedule
580        // Note: Don't try to detect the end of the render frame here using the
581        // schedule index.  We don't know all schedules have been added to the
582        // schedule_order, so only next_frame() knows its safe to reset the
583        // cursor.
584        match next_system {
585            Some(i) => self.cursor.system = i,
586            None => {
587                let index = cursor.schedule + 1;
588                self.cursor = Cursor {
589                    schedule: index,
590                    system: self.first_system_index_for_schedule(index),
591                };
592
593                #[cfg(test)]
594                debug!("advanced schedule index: {} -> {}", cursor.schedule, index);
595            }
596        }
597
598        Some(skip_list)
599    }
600}
601
602#[derive(Default)]
603struct ScheduleState {
604    /// per-system [`SystemBehavior`]
605    behaviors: HashMap<NodeId, SystemBehavior>,
606
607    /// order of [`NodeId`]s in the schedule
608    ///
609    /// This is a cached copy of `SystemExecutable::system_ids`. We need it
610    /// available here to be accessed by [`Stepping::cursor()`] so we can return
611    /// [`NodeId`]s to the caller.
612    node_ids: Vec<NodeId>,
613
614    /// changes to system behavior that should be applied the next time
615    /// [`ScheduleState::skipped_systems()`] is called
616    behavior_updates: TypeIdMap<Option<SystemBehavior>>,
617
618    /// This field contains the first steppable system in the schedule.
619    first: Option<usize>,
620}
621
622impl ScheduleState {
623    // set the stepping behavior for a system in this schedule
624    fn set_behavior(&mut self, system: SystemIdentifier, behavior: SystemBehavior) {
625        self.first = None;
626        match system {
627            SystemIdentifier::Node(node_id) => {
628                self.behaviors.insert(node_id, behavior);
629            }
630            // Behaviors are indexed by NodeId, but we cannot map a system
631            // TypeId to a NodeId without the `Schedule`.  So queue this update
632            // to be processed the next time `skipped_systems()` is called.
633            SystemIdentifier::Type(type_id) => {
634                self.behavior_updates.insert(type_id, Some(behavior));
635            }
636        }
637    }
638
639    // clear the stepping behavior for a system in this schedule
640    fn clear_behavior(&mut self, system: SystemIdentifier) {
641        self.first = None;
642        match system {
643            SystemIdentifier::Node(node_id) => {
644                self.behaviors.remove(&node_id);
645            }
646            // queue TypeId updates to be processed later when we have Schedule
647            SystemIdentifier::Type(type_id) => {
648                self.behavior_updates.insert(type_id, None);
649            }
650        }
651    }
652
653    // clear all system behaviors
654    fn clear_behaviors(&mut self) {
655        self.behaviors.clear();
656        self.behavior_updates.clear();
657        self.first = None;
658    }
659
660    // apply system behavior updates by looking up the node id of the system in
661    // the schedule, and updating `systems`
662    fn apply_behavior_updates(&mut self, schedule: &Schedule) {
663        // Systems may be present multiple times within a schedule, so we
664        // iterate through all systems in the schedule, and check our behavior
665        // updates for the system TypeId.
666        // PERF: If we add a way to efficiently query schedule systems by their TypeId, we could remove the full
667        // system scan here
668        for (node_id, system) in schedule.systems().unwrap() {
669            let behavior = self.behavior_updates.get(&system.type_id());
670            match behavior {
671                None => continue,
672                Some(None) => {
673                    self.behaviors.remove(&node_id);
674                }
675                Some(Some(behavior)) => {
676                    self.behaviors.insert(node_id, *behavior);
677                }
678            }
679        }
680        self.behavior_updates.clear();
681
682        #[cfg(test)]
683        debug!("apply_updates(): {:?}", self.behaviors);
684    }
685
686    fn skipped_systems(
687        &mut self,
688        schedule: &Schedule,
689        start: usize,
690        mut action: Action,
691    ) -> (FixedBitSet, Option<usize>) {
692        use core::cmp::Ordering;
693
694        // if our NodeId list hasn't been populated, copy it over from the
695        // schedule
696        if self.node_ids.len() != schedule.systems_len() {
697            self.node_ids.clone_from(&schedule.executable().system_ids);
698        }
699
700        // Now that we have the schedule, apply any pending system behavior
701        // updates.  The schedule is required to map from system `TypeId` to
702        // `NodeId`.
703        if !self.behavior_updates.is_empty() {
704            self.apply_behavior_updates(schedule);
705        }
706
707        // if we don't have a first system set, set it now
708        if self.first.is_none() {
709            for (i, (node_id, _)) in schedule.systems().unwrap().enumerate() {
710                match self.behaviors.get(&node_id) {
711                    Some(SystemBehavior::AlwaysRun | SystemBehavior::NeverRun) => continue,
712                    Some(_) | None => {
713                        self.first = Some(i);
714                        break;
715                    }
716                }
717            }
718        }
719
720        let mut skip = FixedBitSet::with_capacity(schedule.systems_len());
721        let mut pos = start;
722
723        for (i, (node_id, _system)) in schedule.systems().unwrap().enumerate() {
724            let behavior = self
725                .behaviors
726                .get(&node_id)
727                .unwrap_or(&SystemBehavior::Continue);
728
729            #[cfg(test)]
730            debug!(
731                "skipped_systems(): systems[{}], pos {}, Action::{:?}, Behavior::{:?}, {}",
732                i,
733                pos,
734                action,
735                behavior,
736                _system.name()
737            );
738
739            match (action, behavior) {
740                // regardless of which action we're performing, if the system
741                // is marked as NeverRun, add it to the skip list.
742                // Also, advance the cursor past this system if it is our
743                // current position
744                (_, SystemBehavior::NeverRun) => {
745                    skip.insert(i);
746                    if i == pos {
747                        pos += 1;
748                    }
749                }
750                // similarly, ignore any system marked as AlwaysRun; they should
751                // never be added to the skip list
752                // Also, advance the cursor past this system if it is our
753                // current position
754                (_, SystemBehavior::AlwaysRun) => {
755                    if i == pos {
756                        pos += 1;
757                    }
758                }
759                // if we're waiting, no other systems besides AlwaysRun should
760                // be run, so add systems to the skip list
761                (Action::Waiting, _) => skip.insert(i),
762
763                // If we're stepping, the remaining behaviors don't matter,
764                // we're only going to run the system at our cursor.  Any system
765                // prior to the cursor is skipped.  Once we encounter the system
766                // at the cursor, we'll advance the cursor, and set behavior to
767                // Waiting to skip remaining systems.
768                (Action::Step, _) => match i.cmp(&pos) {
769                    Ordering::Less => skip.insert(i),
770                    Ordering::Equal => {
771                        pos += 1;
772                        action = Action::Waiting;
773                    }
774                    Ordering::Greater => unreachable!(),
775                },
776                // If we're continuing, and the step behavior is continue, we
777                // want to skip any systems prior to our start position.  That's
778                // where the stepping frame left off last time we ran anything.
779                (Action::Continue, SystemBehavior::Continue) => {
780                    if i < start {
781                        skip.insert(i);
782                    }
783                }
784                // If we're continuing, and we encounter a breakpoint we may
785                // want to stop before executing the system.  To do this we
786                // skip this system and set the action to Waiting.
787                //
788                // Note: if the cursor is pointing at this system, we will run
789                // it anyway.  This allows the user to continue, hit a
790                // breakpoint, then continue again to run the breakpoint system
791                // and any following systems.
792                (Action::Continue, SystemBehavior::Break) => {
793                    if i != start {
794                        skip.insert(i);
795
796                        // stop running systems if the breakpoint isn't the
797                        // system under the cursor.
798                        if i > start {
799                            action = Action::Waiting;
800                        }
801                    }
802                }
803                // should have never gotten into this method if stepping is
804                // disabled
805                (Action::RunAll, _) => unreachable!(),
806            }
807
808            // If we're at the cursor position, and not waiting, advance the
809            // cursor.
810            if i == pos && action != Action::Waiting {
811                pos += 1;
812            }
813        }
814
815        // output is the skip list, and the index of the next system to run in
816        // this schedule.
817        if pos >= schedule.systems_len() {
818            (skip, None)
819        } else {
820            (skip, Some(pos))
821        }
822    }
823}
824
825#[cfg(all(test, feature = "bevy_debug_stepping"))]
826#[expect(clippy::print_stdout, reason = "Allowed in tests.")]
827mod tests {
828    use super::*;
829    use crate::{prelude::*, schedule::ScheduleLabel};
830    use alloc::{format, vec};
831    use std::println;
832
833    #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
834    struct TestSchedule;
835
836    #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
837    struct TestScheduleA;
838
839    #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
840    struct TestScheduleB;
841
842    #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
843    struct TestScheduleC;
844
845    #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
846    struct TestScheduleD;
847
848    fn first_system() {}
849    fn second_system() {}
850    fn third_system() {}
851
852    fn setup() -> (Schedule, World) {
853        let mut world = World::new();
854        let mut schedule = Schedule::new(TestSchedule);
855        schedule.add_systems((first_system, second_system).chain());
856        schedule.initialize(&mut world).unwrap();
857        (schedule, world)
858    }
859
860    // Helper for verifying skip_lists are equal, and if not, printing a human
861    // readable message.
862    macro_rules! assert_skip_list_eq {
863        ($actual:expr, $expected:expr, $system_names:expr) => {
864            let actual = $actual;
865            let expected = $expected;
866            let systems: &Vec<&str> = $system_names;
867
868            if (actual != expected) {
869                use core::fmt::Write as _;
870
871                // mismatch, let's construct a human-readable message of what
872                // was returned
873                let mut msg = format!(
874                    "Schedule:\n    {:9} {:16}{:6} {:6} {:6}\n",
875                    "index", "name", "expect", "actual", "result"
876                );
877                for (i, name) in systems.iter().enumerate() {
878                    let _ = write!(msg, "    system[{:1}] {:16}", i, name);
879                    match (expected.contains(i), actual.contains(i)) {
880                        (true, true) => msg.push_str("skip   skip   pass\n"),
881                        (true, false) => {
882                            msg.push_str("skip   run    FAILED; system should not have run\n")
883                        }
884                        (false, true) => {
885                            msg.push_str("run    skip   FAILED; system should have run\n")
886                        }
887                        (false, false) => msg.push_str("run    run    pass\n"),
888                    }
889                }
890                assert_eq!(actual, expected, "{}", msg);
891            }
892        };
893    }
894
895    // Helper for verifying that a set of systems will be run for a given skip
896    // list
897    macro_rules! assert_systems_run {
898        ($schedule:expr, $skipped_systems:expr, $($system:expr),*) => {
899            // pull an ordered list of systems in the schedule, and save the
900            // system TypeId, and name.
901            let systems: Vec<(TypeId, alloc::borrow::Cow<'static, str>)> = $schedule.systems().unwrap()
902                .map(|(_, system)| {
903                    (system.type_id(), system.name())
904                })
905            .collect();
906
907            // construct a list of systems that are expected to run
908            let mut expected = FixedBitSet::with_capacity(systems.len());
909            $(
910                let sys = IntoSystem::into_system($system);
911                for (i, (type_id, _)) in systems.iter().enumerate() {
912                    if sys.type_id() == *type_id {
913                        expected.insert(i);
914                    }
915                }
916            )*
917
918            // flip the run list to get our skip list
919            expected.toggle_range(..);
920
921            // grab the list of skipped systems
922            let actual = match $skipped_systems {
923                None => FixedBitSet::with_capacity(systems.len()),
924                Some(b) => b,
925            };
926            let system_names: Vec<&str> = systems
927                .iter()
928                .map(|(_,n)| n.rsplit_once("::").unwrap().1)
929                .collect();
930
931            assert_skip_list_eq!(actual, expected, &system_names);
932        };
933    }
934
935    // Helper for verifying the expected systems will be run by the schedule
936    //
937    // This macro will construct an expected FixedBitSet for the systems that
938    // should be skipped, and compare it with the results from stepping the
939    // provided schedule.  If they don't match, it generates a human-readable
940    // error message and asserts.
941    macro_rules! assert_schedule_runs {
942        ($schedule:expr, $stepping:expr, $($system:expr),*) => {
943            // advance stepping to the next frame, and build the skip list for
944            // this schedule
945            $stepping.next_frame();
946            assert_systems_run!($schedule, $stepping.skipped_systems($schedule), $($system),*);
947        };
948    }
949
950    #[test]
951    fn stepping_disabled() {
952        let (schedule, _world) = setup();
953
954        let mut stepping = Stepping::new();
955        stepping.add_schedule(TestSchedule).disable().next_frame();
956
957        assert!(stepping.skipped_systems(&schedule).is_none());
958        assert!(stepping.cursor().is_none());
959    }
960
961    #[test]
962    fn unknown_schedule() {
963        let (schedule, _world) = setup();
964
965        let mut stepping = Stepping::new();
966        stepping.enable().next_frame();
967
968        assert!(stepping.skipped_systems(&schedule).is_none());
969    }
970
971    #[test]
972    fn disabled_always_run() {
973        let (schedule, _world) = setup();
974
975        let mut stepping = Stepping::new();
976        stepping
977            .add_schedule(TestSchedule)
978            .disable()
979            .always_run(TestSchedule, first_system);
980
981        assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
982    }
983
984    #[test]
985    fn waiting_always_run() {
986        let (schedule, _world) = setup();
987
988        let mut stepping = Stepping::new();
989        stepping
990            .add_schedule(TestSchedule)
991            .enable()
992            .always_run(TestSchedule, first_system);
993
994        assert_schedule_runs!(&schedule, &mut stepping, first_system);
995    }
996
997    #[test]
998    fn step_always_run() {
999        let (schedule, _world) = setup();
1000
1001        let mut stepping = Stepping::new();
1002        stepping
1003            .add_schedule(TestSchedule)
1004            .enable()
1005            .always_run(TestSchedule, first_system)
1006            .step_frame();
1007
1008        assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
1009    }
1010
1011    #[test]
1012    fn continue_always_run() {
1013        let (schedule, _world) = setup();
1014
1015        let mut stepping = Stepping::new();
1016        stepping
1017            .add_schedule(TestSchedule)
1018            .enable()
1019            .always_run(TestSchedule, first_system)
1020            .continue_frame();
1021
1022        assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
1023    }
1024
1025    #[test]
1026    fn disabled_never_run() {
1027        let (schedule, _world) = setup();
1028
1029        let mut stepping = Stepping::new();
1030        stepping
1031            .add_schedule(TestSchedule)
1032            .never_run(TestSchedule, first_system)
1033            .disable();
1034        assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
1035    }
1036
1037    #[test]
1038    fn waiting_never_run() {
1039        let (schedule, _world) = setup();
1040
1041        let mut stepping = Stepping::new();
1042        stepping
1043            .add_schedule(TestSchedule)
1044            .enable()
1045            .never_run(TestSchedule, first_system);
1046
1047        assert_schedule_runs!(&schedule, &mut stepping,);
1048    }
1049
1050    #[test]
1051    fn step_never_run() {
1052        let (schedule, _world) = setup();
1053
1054        let mut stepping = Stepping::new();
1055        stepping
1056            .add_schedule(TestSchedule)
1057            .enable()
1058            .never_run(TestSchedule, first_system)
1059            .step_frame();
1060
1061        assert_schedule_runs!(&schedule, &mut stepping, second_system);
1062    }
1063
1064    #[test]
1065    fn continue_never_run() {
1066        let (schedule, _world) = setup();
1067
1068        let mut stepping = Stepping::new();
1069        stepping
1070            .add_schedule(TestSchedule)
1071            .enable()
1072            .never_run(TestSchedule, first_system)
1073            .continue_frame();
1074
1075        assert_schedule_runs!(&schedule, &mut stepping, second_system);
1076    }
1077
1078    #[test]
1079    fn disabled_breakpoint() {
1080        let (schedule, _world) = setup();
1081
1082        let mut stepping = Stepping::new();
1083        stepping
1084            .add_schedule(TestSchedule)
1085            .disable()
1086            .set_breakpoint(TestSchedule, second_system);
1087
1088        assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
1089    }
1090
1091    #[test]
1092    fn waiting_breakpoint() {
1093        let (schedule, _world) = setup();
1094
1095        let mut stepping = Stepping::new();
1096        stepping
1097            .add_schedule(TestSchedule)
1098            .enable()
1099            .set_breakpoint(TestSchedule, second_system);
1100
1101        assert_schedule_runs!(&schedule, &mut stepping,);
1102    }
1103
1104    #[test]
1105    fn step_breakpoint() {
1106        let (schedule, _world) = setup();
1107
1108        let mut stepping = Stepping::new();
1109        stepping
1110            .add_schedule(TestSchedule)
1111            .enable()
1112            .set_breakpoint(TestSchedule, second_system)
1113            .step_frame();
1114
1115        // since stepping stops at every system, breakpoints are ignored during
1116        // stepping
1117        assert_schedule_runs!(&schedule, &mut stepping, first_system);
1118        stepping.step_frame();
1119        assert_schedule_runs!(&schedule, &mut stepping, second_system);
1120
1121        // let's go again to verify that we wrap back around to the start of
1122        // the frame
1123        stepping.step_frame();
1124        assert_schedule_runs!(&schedule, &mut stepping, first_system);
1125
1126        // should be back in a waiting state now that it ran first_system
1127        assert_schedule_runs!(&schedule, &mut stepping,);
1128    }
1129
1130    #[test]
1131    fn continue_breakpoint() {
1132        let (schedule, _world) = setup();
1133
1134        let mut stepping = Stepping::new();
1135        stepping
1136            .add_schedule(TestSchedule)
1137            .enable()
1138            .set_breakpoint(TestSchedule, second_system)
1139            .continue_frame();
1140
1141        assert_schedule_runs!(&schedule, &mut stepping, first_system);
1142        stepping.continue_frame();
1143        assert_schedule_runs!(&schedule, &mut stepping, second_system);
1144        stepping.continue_frame();
1145        assert_schedule_runs!(&schedule, &mut stepping, first_system);
1146    }
1147
1148    /// regression test for issue encountered while writing `system_stepping`
1149    /// example
1150    #[test]
1151    fn continue_step_continue_with_breakpoint() {
1152        let mut world = World::new();
1153        let mut schedule = Schedule::new(TestSchedule);
1154        schedule.add_systems((first_system, second_system, third_system).chain());
1155        schedule.initialize(&mut world).unwrap();
1156
1157        let mut stepping = Stepping::new();
1158        stepping
1159            .add_schedule(TestSchedule)
1160            .enable()
1161            .set_breakpoint(TestSchedule, second_system);
1162
1163        stepping.continue_frame();
1164        assert_schedule_runs!(&schedule, &mut stepping, first_system);
1165
1166        stepping.step_frame();
1167        assert_schedule_runs!(&schedule, &mut stepping, second_system);
1168
1169        stepping.continue_frame();
1170        assert_schedule_runs!(&schedule, &mut stepping, third_system);
1171    }
1172
1173    #[test]
1174    fn clear_breakpoint() {
1175        let (schedule, _world) = setup();
1176
1177        let mut stepping = Stepping::new();
1178        stepping
1179            .add_schedule(TestSchedule)
1180            .enable()
1181            .set_breakpoint(TestSchedule, second_system)
1182            .continue_frame();
1183
1184        assert_schedule_runs!(&schedule, &mut stepping, first_system);
1185        stepping.continue_frame();
1186        assert_schedule_runs!(&schedule, &mut stepping, second_system);
1187
1188        stepping.clear_breakpoint(TestSchedule, second_system);
1189        stepping.continue_frame();
1190        assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
1191    }
1192
1193    #[test]
1194    fn clear_system() {
1195        let (schedule, _world) = setup();
1196
1197        let mut stepping = Stepping::new();
1198        stepping
1199            .add_schedule(TestSchedule)
1200            .enable()
1201            .never_run(TestSchedule, second_system)
1202            .continue_frame();
1203        assert_schedule_runs!(&schedule, &mut stepping, first_system);
1204
1205        stepping.clear_system(TestSchedule, second_system);
1206        stepping.continue_frame();
1207        assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
1208    }
1209
1210    #[test]
1211    fn clear_schedule() {
1212        let (schedule, _world) = setup();
1213
1214        let mut stepping = Stepping::new();
1215        stepping
1216            .add_schedule(TestSchedule)
1217            .enable()
1218            .never_run(TestSchedule, first_system)
1219            .never_run(TestSchedule, second_system)
1220            .continue_frame();
1221        assert_schedule_runs!(&schedule, &mut stepping,);
1222
1223        stepping.clear_schedule(TestSchedule);
1224        stepping.continue_frame();
1225        assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
1226    }
1227
1228    /// This was discovered in code-review, ensure that `clear_schedule` also
1229    /// clears any pending changes too.
1230    #[test]
1231    fn set_behavior_then_clear_schedule() {
1232        let (schedule, _world) = setup();
1233
1234        let mut stepping = Stepping::new();
1235        stepping
1236            .add_schedule(TestSchedule)
1237            .enable()
1238            .continue_frame();
1239        assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
1240
1241        stepping.never_run(TestSchedule, first_system);
1242        stepping.clear_schedule(TestSchedule);
1243        stepping.continue_frame();
1244        assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
1245    }
1246
1247    /// Ensure that if they `clear_schedule` then make further changes to the
1248    /// schedule, those changes after the clear are applied.
1249    #[test]
1250    fn clear_schedule_then_set_behavior() {
1251        let (schedule, _world) = setup();
1252
1253        let mut stepping = Stepping::new();
1254        stepping
1255            .add_schedule(TestSchedule)
1256            .enable()
1257            .continue_frame();
1258        assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
1259
1260        stepping.clear_schedule(TestSchedule);
1261        stepping.never_run(TestSchedule, first_system);
1262        stepping.continue_frame();
1263        assert_schedule_runs!(&schedule, &mut stepping, second_system);
1264    }
1265
1266    // Schedules such as FixedUpdate can be called multiple times in a single
1267    // render frame.  Ensure we only run steppable systems the first time the
1268    // schedule is run
1269    #[test]
1270    fn multiple_calls_per_frame_continue() {
1271        let (schedule, _world) = setup();
1272
1273        let mut stepping = Stepping::new();
1274        stepping
1275            .add_schedule(TestSchedule)
1276            .enable()
1277            .always_run(TestSchedule, second_system)
1278            .continue_frame();
1279
1280        // start a new frame, then run the schedule two times; first system
1281        // should only run on the first one
1282        stepping.next_frame();
1283        assert_systems_run!(
1284            &schedule,
1285            stepping.skipped_systems(&schedule),
1286            first_system,
1287            second_system
1288        );
1289        assert_systems_run!(
1290            &schedule,
1291            stepping.skipped_systems(&schedule),
1292            second_system
1293        );
1294    }
1295    #[test]
1296    fn multiple_calls_per_frame_step() {
1297        let (schedule, _world) = setup();
1298
1299        let mut stepping = Stepping::new();
1300        stepping.add_schedule(TestSchedule).enable().step_frame();
1301
1302        // start a new frame, then run the schedule two times; first system
1303        // should only run on the first one
1304        stepping.next_frame();
1305        assert_systems_run!(&schedule, stepping.skipped_systems(&schedule), first_system);
1306        assert_systems_run!(&schedule, stepping.skipped_systems(&schedule),);
1307    }
1308
1309    #[test]
1310    fn step_duplicate_systems() {
1311        let mut world = World::new();
1312        let mut schedule = Schedule::new(TestSchedule);
1313        schedule.add_systems((first_system, first_system, second_system).chain());
1314        schedule.initialize(&mut world).unwrap();
1315
1316        let mut stepping = Stepping::new();
1317        stepping.add_schedule(TestSchedule).enable();
1318
1319        // needed for assert_skip_list_eq!
1320        let system_names = vec!["first_system", "first_system", "second_system"];
1321        // we're going to step three times, and each system in order should run
1322        // only once
1323        for system_index in 0..3 {
1324            // build the skip list by setting all bits, then clearing our the
1325            // one system that should run this step
1326            let mut expected = FixedBitSet::with_capacity(3);
1327            expected.set_range(.., true);
1328            expected.set(system_index, false);
1329
1330            // step the frame and get the skip list
1331            stepping.step_frame();
1332            stepping.next_frame();
1333            let skip_list = stepping
1334                .skipped_systems(&schedule)
1335                .expect("TestSchedule has been added to Stepping");
1336
1337            assert_skip_list_eq!(skip_list, expected, &system_names);
1338        }
1339    }
1340
1341    #[test]
1342    fn step_run_if_false() {
1343        let mut world = World::new();
1344        let mut schedule = Schedule::new(TestSchedule);
1345
1346        // This needs to be a system test to confirm the interaction between
1347        // the skip list and system conditions in Schedule::run().  That means
1348        // all of our systems need real bodies that do things.
1349        //
1350        // first system will be configured as `run_if(|| false)`, so it can
1351        // just panic if called
1352        let first_system: fn() = move || {
1353            panic!("first_system should not be run");
1354        };
1355
1356        // The second system, we need to know when it has been called, so we'll
1357        // add a resource for tracking if it has been run.  The system will
1358        // increment the run count.
1359        #[derive(Resource)]
1360        struct RunCount(usize);
1361        world.insert_resource(RunCount(0));
1362        let second_system = |mut run_count: ResMut<RunCount>| {
1363            println!("I have run!");
1364            run_count.0 += 1;
1365        };
1366
1367        // build our schedule; first_system should never run, followed by
1368        // second_system.
1369        schedule.add_systems((first_system.run_if(|| false), second_system).chain());
1370        schedule.initialize(&mut world).unwrap();
1371
1372        // set up stepping
1373        let mut stepping = Stepping::new();
1374        stepping.add_schedule(TestSchedule).enable();
1375        world.insert_resource(stepping);
1376
1377        // if we step, and the run condition is false, we should not run
1378        // second_system.  The stepping cursor is at first_system, and if
1379        // first_system wasn't able to run, that's ok.
1380        let mut stepping = world.resource_mut::<Stepping>();
1381        stepping.step_frame();
1382        stepping.next_frame();
1383        schedule.run(&mut world);
1384        assert_eq!(
1385            world.resource::<RunCount>().0,
1386            0,
1387            "second_system should not have run"
1388        );
1389
1390        // now on the next step, second_system should run
1391        let mut stepping = world.resource_mut::<Stepping>();
1392        stepping.step_frame();
1393        stepping.next_frame();
1394        schedule.run(&mut world);
1395        assert_eq!(
1396            world.resource::<RunCount>().0,
1397            1,
1398            "second_system should have run"
1399        );
1400    }
1401
1402    #[test]
1403    fn remove_schedule() {
1404        let (schedule, _world) = setup();
1405        let mut stepping = Stepping::new();
1406        stepping.add_schedule(TestSchedule).enable();
1407
1408        // run the schedule once and verify all systems are skipped
1409        assert_schedule_runs!(&schedule, &mut stepping,);
1410        assert!(!stepping.schedules().unwrap().is_empty());
1411
1412        // remove the test schedule
1413        stepping.remove_schedule(TestSchedule);
1414        assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
1415        assert!(stepping.schedules().unwrap().is_empty());
1416    }
1417
1418    // verify that Stepping can construct an ordered list of schedules
1419    #[test]
1420    fn schedules() {
1421        let mut world = World::new();
1422
1423        // build & initialize a few schedules
1424        let mut schedule_a = Schedule::new(TestScheduleA);
1425        schedule_a.initialize(&mut world).unwrap();
1426        let mut schedule_b = Schedule::new(TestScheduleB);
1427        schedule_b.initialize(&mut world).unwrap();
1428        let mut schedule_c = Schedule::new(TestScheduleC);
1429        schedule_c.initialize(&mut world).unwrap();
1430        let mut schedule_d = Schedule::new(TestScheduleD);
1431        schedule_d.initialize(&mut world).unwrap();
1432
1433        // setup stepping and add all the schedules
1434        let mut stepping = Stepping::new();
1435        stepping
1436            .add_schedule(TestScheduleA)
1437            .add_schedule(TestScheduleB)
1438            .add_schedule(TestScheduleC)
1439            .add_schedule(TestScheduleD)
1440            .enable()
1441            .next_frame();
1442
1443        assert!(stepping.schedules().is_err());
1444
1445        stepping.skipped_systems(&schedule_b);
1446        assert!(stepping.schedules().is_err());
1447        stepping.skipped_systems(&schedule_a);
1448        assert!(stepping.schedules().is_err());
1449        stepping.skipped_systems(&schedule_c);
1450        assert!(stepping.schedules().is_err());
1451
1452        // when we call the last schedule, Stepping should have enough data to
1453        // return an ordered list of schedules
1454        stepping.skipped_systems(&schedule_d);
1455        assert!(stepping.schedules().is_ok());
1456
1457        assert_eq!(
1458            *stepping.schedules().unwrap(),
1459            vec![
1460                TestScheduleB.intern(),
1461                TestScheduleA.intern(),
1462                TestScheduleC.intern(),
1463                TestScheduleD.intern(),
1464            ]
1465        );
1466    }
1467
1468    #[test]
1469    fn verify_cursor() {
1470        // helper to build a cursor tuple for the supplied schedule
1471        fn cursor(schedule: &Schedule, index: usize) -> (InternedScheduleLabel, NodeId) {
1472            let node_id = schedule.executable().system_ids[index];
1473            (schedule.label(), node_id)
1474        }
1475
1476        let mut world = World::new();
1477
1478        // create two schedules with a number of systems in them
1479        let mut schedule_a = Schedule::new(TestScheduleA);
1480        schedule_a.add_systems((|| {}, || {}, || {}, || {}).chain());
1481        schedule_a.initialize(&mut world).unwrap();
1482        let mut schedule_b = Schedule::new(TestScheduleB);
1483        schedule_b.add_systems((|| {}, || {}, || {}, || {}).chain());
1484        schedule_b.initialize(&mut world).unwrap();
1485
1486        // setup stepping and add all schedules
1487        let mut stepping = Stepping::new();
1488        stepping
1489            .add_schedule(TestScheduleA)
1490            .add_schedule(TestScheduleB)
1491            .enable();
1492
1493        assert!(stepping.cursor().is_none());
1494
1495        // step the system nine times, and verify the cursor before & after
1496        // each step
1497        let mut cursors = Vec::new();
1498        for _ in 0..9 {
1499            stepping.step_frame().next_frame();
1500            cursors.push(stepping.cursor());
1501            stepping.skipped_systems(&schedule_a);
1502            stepping.skipped_systems(&schedule_b);
1503            cursors.push(stepping.cursor());
1504        }
1505
1506        #[rustfmt::skip]
1507        assert_eq!(
1508            cursors,
1509            vec![
1510                // before render frame        // after render frame
1511                None,                         Some(cursor(&schedule_a, 1)),
1512                Some(cursor(&schedule_a, 1)), Some(cursor(&schedule_a, 2)),
1513                Some(cursor(&schedule_a, 2)), Some(cursor(&schedule_a, 3)),
1514                Some(cursor(&schedule_a, 3)), Some(cursor(&schedule_b, 0)),
1515                Some(cursor(&schedule_b, 0)), Some(cursor(&schedule_b, 1)),
1516                Some(cursor(&schedule_b, 1)), Some(cursor(&schedule_b, 2)),
1517                Some(cursor(&schedule_b, 2)), Some(cursor(&schedule_b, 3)),
1518                Some(cursor(&schedule_b, 3)), None,
1519                Some(cursor(&schedule_a, 0)), Some(cursor(&schedule_a, 1)),
1520            ]
1521        );
1522
1523        // reset our cursor (disable/enable), and update stepping to test if the
1524        // cursor properly skips over AlwaysRun & NeverRun systems.  Also set
1525        // a Break system to ensure that shows properly in the cursor
1526        stepping
1527            // disable/enable to reset cursor
1528            .disable()
1529            .enable()
1530            .set_breakpoint_node(TestScheduleA, NodeId::System(1))
1531            .always_run_node(TestScheduleA, NodeId::System(3))
1532            .never_run_node(TestScheduleB, NodeId::System(0));
1533
1534        let mut cursors = Vec::new();
1535        for _ in 0..9 {
1536            stepping.step_frame().next_frame();
1537            cursors.push(stepping.cursor());
1538            stepping.skipped_systems(&schedule_a);
1539            stepping.skipped_systems(&schedule_b);
1540            cursors.push(stepping.cursor());
1541        }
1542
1543        #[rustfmt::skip]
1544        assert_eq!(
1545            cursors,
1546            vec![
1547                // before render frame        // after render frame
1548                Some(cursor(&schedule_a, 0)), Some(cursor(&schedule_a, 1)),
1549                Some(cursor(&schedule_a, 1)), Some(cursor(&schedule_a, 2)),
1550                Some(cursor(&schedule_a, 2)), Some(cursor(&schedule_b, 1)),
1551                Some(cursor(&schedule_b, 1)), Some(cursor(&schedule_b, 2)),
1552                Some(cursor(&schedule_b, 2)), Some(cursor(&schedule_b, 3)),
1553                Some(cursor(&schedule_b, 3)), None,
1554                Some(cursor(&schedule_a, 0)), Some(cursor(&schedule_a, 1)),
1555                Some(cursor(&schedule_a, 1)), Some(cursor(&schedule_a, 2)),
1556                Some(cursor(&schedule_a, 2)), Some(cursor(&schedule_b, 1)),
1557            ]
1558        );
1559    }
1560}