bevy_ecs/schedule/
stepping.rs

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