use crate::{
resource::Resource,
schedule::{InternedScheduleLabel, NodeId, Schedule, ScheduleLabel, SystemKey},
system::{IntoSystem, ResMut},
};
use alloc::vec::Vec;
use bevy_platform::collections::HashMap;
use bevy_utils::TypeIdMap;
use core::any::TypeId;
use fixedbitset::FixedBitSet;
use log::{info, warn};
use thiserror::Error;
#[cfg(not(feature = "bevy_debug_stepping"))]
use log::error;
#[cfg(test)]
use log::debug;
#[derive(Debug, Default, PartialEq, Eq, Copy, Clone)]
enum Action {
#[default]
RunAll,
Waiting,
Continue,
Step,
}
#[derive(Debug, Copy, Clone)]
enum SystemBehavior {
AlwaysRun,
NeverRun,
Break,
Continue,
}
#[derive(Debug, Default, Clone, Copy)]
struct Cursor {
pub schedule: usize,
pub system: usize,
}
enum SystemIdentifier {
Type(TypeId),
Node(NodeId),
}
enum Update {
SetAction(Action),
AddSchedule(InternedScheduleLabel),
RemoveSchedule(InternedScheduleLabel),
ClearSchedule(InternedScheduleLabel),
SetBehavior(InternedScheduleLabel, SystemIdentifier, SystemBehavior),
ClearBehavior(InternedScheduleLabel, SystemIdentifier),
}
#[derive(Error, Debug)]
#[error("not available until all configured schedules have been run; try again next frame")]
pub struct NotReady;
#[derive(Resource, Default)]
pub struct Stepping {
schedule_states: HashMap<InternedScheduleLabel, ScheduleState>,
schedule_order: Vec<InternedScheduleLabel>,
cursor: Cursor,
previous_schedule: Option<usize>,
action: Action,
updates: Vec<Update>,
}
impl core::fmt::Debug for Stepping {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"Stepping {{ action: {:?}, schedules: {:?}, order: {:?}",
self.action,
self.schedule_states.keys(),
self.schedule_order
)?;
if self.action != Action::RunAll {
let Cursor { schedule, system } = self.cursor;
match self.schedule_order.get(schedule) {
Some(label) => write!(f, "cursor: {label:?}[{system}], ")?,
None => write!(f, "cursor: None, ")?,
};
}
write!(f, "}}")
}
}
impl Stepping {
pub fn new() -> Self {
Stepping::default()
}
pub fn begin_frame(stepping: Option<ResMut<Self>>) {
if let Some(mut stepping) = stepping {
stepping.next_frame();
}
}
pub fn schedules(&self) -> Result<&Vec<InternedScheduleLabel>, NotReady> {
if self.schedule_order.len() == self.schedule_states.len() {
Ok(&self.schedule_order)
} else {
Err(NotReady)
}
}
pub fn cursor(&self) -> Option<(InternedScheduleLabel, NodeId)> {
if self.action == Action::RunAll {
return None;
}
let label = self.schedule_order.get(self.cursor.schedule)?;
let state = self.schedule_states.get(label)?;
state
.node_ids
.get(self.cursor.system)
.map(|node_id| (*label, NodeId::System(*node_id)))
}
pub fn add_schedule(&mut self, schedule: impl ScheduleLabel) -> &mut Self {
self.updates.push(Update::AddSchedule(schedule.intern()));
self
}
pub fn remove_schedule(&mut self, schedule: impl ScheduleLabel) -> &mut Self {
self.updates.push(Update::RemoveSchedule(schedule.intern()));
self
}
pub fn clear_schedule(&mut self, schedule: impl ScheduleLabel) -> &mut Self {
self.updates.push(Update::ClearSchedule(schedule.intern()));
self
}
pub fn enable(&mut self) -> &mut Self {
#[cfg(feature = "bevy_debug_stepping")]
self.updates.push(Update::SetAction(Action::Waiting));
#[cfg(not(feature = "bevy_debug_stepping"))]
error!(
"Stepping cannot be enabled; \
bevy was compiled without the bevy_debug_stepping feature"
);
self
}
pub fn disable(&mut self) -> &mut Self {
self.updates.push(Update::SetAction(Action::RunAll));
self
}
pub fn is_enabled(&self) -> bool {
self.action != Action::RunAll
}
pub fn step_frame(&mut self) -> &mut Self {
self.updates.push(Update::SetAction(Action::Step));
self
}
pub fn continue_frame(&mut self) -> &mut Self {
self.updates.push(Update::SetAction(Action::Continue));
self
}
pub fn always_run<Marker>(
&mut self,
schedule: impl ScheduleLabel,
system: impl IntoSystem<(), (), Marker>,
) -> &mut Self {
let type_id = system.system_type_id();
self.updates.push(Update::SetBehavior(
schedule.intern(),
SystemIdentifier::Type(type_id),
SystemBehavior::AlwaysRun,
));
self
}
pub fn always_run_node(&mut self, schedule: impl ScheduleLabel, node: NodeId) -> &mut Self {
self.updates.push(Update::SetBehavior(
schedule.intern(),
SystemIdentifier::Node(node),
SystemBehavior::AlwaysRun,
));
self
}
pub fn never_run<Marker>(
&mut self,
schedule: impl ScheduleLabel,
system: impl IntoSystem<(), (), Marker>,
) -> &mut Self {
let type_id = system.system_type_id();
self.updates.push(Update::SetBehavior(
schedule.intern(),
SystemIdentifier::Type(type_id),
SystemBehavior::NeverRun,
));
self
}
pub fn never_run_node(&mut self, schedule: impl ScheduleLabel, node: NodeId) -> &mut Self {
self.updates.push(Update::SetBehavior(
schedule.intern(),
SystemIdentifier::Node(node),
SystemBehavior::NeverRun,
));
self
}
pub fn set_breakpoint<Marker>(
&mut self,
schedule: impl ScheduleLabel,
system: impl IntoSystem<(), (), Marker>,
) -> &mut Self {
let type_id = system.system_type_id();
self.updates.push(Update::SetBehavior(
schedule.intern(),
SystemIdentifier::Type(type_id),
SystemBehavior::Break,
));
self
}
pub fn set_breakpoint_node(&mut self, schedule: impl ScheduleLabel, node: NodeId) -> &mut Self {
self.updates.push(Update::SetBehavior(
schedule.intern(),
SystemIdentifier::Node(node),
SystemBehavior::Break,
));
self
}
pub fn clear_breakpoint<Marker>(
&mut self,
schedule: impl ScheduleLabel,
system: impl IntoSystem<(), (), Marker>,
) -> &mut Self {
self.clear_system(schedule, system);
self
}
pub fn clear_breakpoint_node(
&mut self,
schedule: impl ScheduleLabel,
node: NodeId,
) -> &mut Self {
self.clear_node(schedule, node);
self
}
pub fn clear_system<Marker>(
&mut self,
schedule: impl ScheduleLabel,
system: impl IntoSystem<(), (), Marker>,
) -> &mut Self {
let type_id = system.system_type_id();
self.updates.push(Update::ClearBehavior(
schedule.intern(),
SystemIdentifier::Type(type_id),
));
self
}
pub fn clear_node(&mut self, schedule: impl ScheduleLabel, node: NodeId) -> &mut Self {
self.updates.push(Update::ClearBehavior(
schedule.intern(),
SystemIdentifier::Node(node),
));
self
}
fn first_system_index_for_schedule(&self, index: usize) -> usize {
let Some(label) = self.schedule_order.get(index) else {
return 0;
};
let Some(state) = self.schedule_states.get(label) else {
return 0;
};
state.first.unwrap_or(0)
}
fn reset_cursor(&mut self) {
self.cursor = Cursor {
schedule: 0,
system: self.first_system_index_for_schedule(0),
};
}
fn next_frame(&mut self) {
if self.action != Action::RunAll {
self.action = Action::Waiting;
self.previous_schedule = None;
if self.cursor.schedule >= self.schedule_order.len() {
self.reset_cursor();
}
}
if self.updates.is_empty() {
return;
}
let mut reset_cursor = false;
for update in self.updates.drain(..) {
match update {
Update::SetAction(Action::RunAll) => {
self.action = Action::RunAll;
reset_cursor = true;
}
Update::SetAction(action) => {
#[expect(
clippy::match_same_arms,
reason = "Readability would be negatively impacted by combining the `(Waiting, RunAll)` and `(Continue, RunAll)` match arms."
)]
match (self.action, action) {
(Action::RunAll, Action::RunAll)
| (Action::Waiting, Action::Waiting)
| (Action::Continue, Action::Continue)
| (Action::Step, Action::Step)
| (Action::Continue, Action::Waiting)
| (Action::Step, Action::Waiting) => continue,
(Action::RunAll, Action::Waiting) => info!("enabled stepping"),
(Action::RunAll, _) => {
warn!(
"stepping not enabled; call Stepping::enable() \
before step_frame() or continue_frame()"
);
continue;
}
(Action::Waiting, Action::RunAll) => info!("disabled stepping"),
(Action::Waiting, Action::Continue) => info!("continue frame"),
(Action::Waiting, Action::Step) => info!("step frame"),
(Action::Continue, Action::RunAll) => info!("disabled stepping"),
(Action::Continue, Action::Step) => {
warn!("ignoring step_frame(); already continuing next frame");
continue;
}
(Action::Step, Action::RunAll) => info!("disabled stepping"),
(Action::Step, Action::Continue) => {
warn!("ignoring continue_frame(); already stepping next frame");
continue;
}
}
self.action = action;
}
Update::AddSchedule(l) => {
self.schedule_states.insert(l, ScheduleState::default());
}
Update::RemoveSchedule(label) => {
self.schedule_states.remove(&label);
if let Some(index) = self.schedule_order.iter().position(|l| l == &label) {
self.schedule_order.remove(index);
}
reset_cursor = true;
}
Update::ClearSchedule(label) => match self.schedule_states.get_mut(&label) {
Some(state) => state.clear_behaviors(),
None => {
warn!(
"stepping is not enabled for schedule {label:?}; \
use `.add_stepping({label:?})` to enable stepping"
);
}
},
Update::SetBehavior(label, system, behavior) => {
match self.schedule_states.get_mut(&label) {
Some(state) => state.set_behavior(system, behavior),
None => {
warn!(
"stepping is not enabled for schedule {label:?}; \
use `.add_stepping({label:?})` to enable stepping"
);
}
}
}
Update::ClearBehavior(label, system) => {
match self.schedule_states.get_mut(&label) {
Some(state) => state.clear_behavior(system),
None => {
warn!(
"stepping is not enabled for schedule {label:?}; \
use `.add_stepping({label:?})` to enable stepping"
);
}
}
}
}
}
if reset_cursor {
self.reset_cursor();
}
}
pub fn skipped_systems(&mut self, schedule: &Schedule) -> Option<FixedBitSet> {
if self.action == Action::RunAll {
return None;
}
let label = schedule.label();
let state = self.schedule_states.get_mut(&label)?;
let index = self.schedule_order.iter().position(|l| *l == label);
let index = match (index, self.previous_schedule) {
(Some(index), _) => index,
(None, None) => {
self.schedule_order.insert(0, label);
0
}
(None, Some(last)) => {
self.schedule_order.insert(last + 1, label);
last + 1
}
};
self.previous_schedule = Some(index);
#[cfg(test)]
debug!(
"cursor {:?}, index {}, label {:?}",
self.cursor, index, label
);
let cursor = self.cursor;
let (skip_list, next_system) = if index == cursor.schedule {
let (skip_list, next_system) =
state.skipped_systems(schedule, cursor.system, self.action);
if self.action == Action::Step {
self.action = Action::Waiting;
}
(skip_list, next_system)
} else {
let (skip_list, _) = state.skipped_systems(schedule, 0, Action::Waiting);
(skip_list, Some(cursor.system))
};
match next_system {
Some(i) => self.cursor.system = i,
None => {
let index = cursor.schedule + 1;
self.cursor = Cursor {
schedule: index,
system: self.first_system_index_for_schedule(index),
};
#[cfg(test)]
debug!("advanced schedule index: {} -> {}", cursor.schedule, index);
}
}
Some(skip_list)
}
}
#[derive(Default)]
struct ScheduleState {
behaviors: HashMap<NodeId, SystemBehavior>,
node_ids: Vec<SystemKey>,
behavior_updates: TypeIdMap<Option<SystemBehavior>>,
first: Option<usize>,
}
impl ScheduleState {
fn set_behavior(&mut self, system: SystemIdentifier, behavior: SystemBehavior) {
self.first = None;
match system {
SystemIdentifier::Node(node_id) => {
self.behaviors.insert(node_id, behavior);
}
SystemIdentifier::Type(type_id) => {
self.behavior_updates.insert(type_id, Some(behavior));
}
}
}
fn clear_behavior(&mut self, system: SystemIdentifier) {
self.first = None;
match system {
SystemIdentifier::Node(node_id) => {
self.behaviors.remove(&node_id);
}
SystemIdentifier::Type(type_id) => {
self.behavior_updates.insert(type_id, None);
}
}
}
fn clear_behaviors(&mut self) {
self.behaviors.clear();
self.behavior_updates.clear();
self.first = None;
}
fn apply_behavior_updates(&mut self, schedule: &Schedule) {
for (key, system) in schedule.systems().unwrap() {
let behavior = self.behavior_updates.get(&system.type_id());
match behavior {
None => continue,
Some(None) => {
self.behaviors.remove(&NodeId::System(key));
}
Some(Some(behavior)) => {
self.behaviors.insert(NodeId::System(key), *behavior);
}
}
}
self.behavior_updates.clear();
#[cfg(test)]
debug!("apply_updates(): {:?}", self.behaviors);
}
fn skipped_systems(
&mut self,
schedule: &Schedule,
start: usize,
mut action: Action,
) -> (FixedBitSet, Option<usize>) {
use core::cmp::Ordering;
if self.node_ids.len() != schedule.systems_len() {
self.node_ids.clone_from(&schedule.executable().system_ids);
}
if !self.behavior_updates.is_empty() {
self.apply_behavior_updates(schedule);
}
if self.first.is_none() {
for (i, (key, _)) in schedule.systems().unwrap().enumerate() {
match self.behaviors.get(&NodeId::System(key)) {
Some(SystemBehavior::AlwaysRun | SystemBehavior::NeverRun) => continue,
Some(_) | None => {
self.first = Some(i);
break;
}
}
}
}
let mut skip = FixedBitSet::with_capacity(schedule.systems_len());
let mut pos = start;
for (i, (key, _system)) in schedule.systems().unwrap().enumerate() {
let behavior = self
.behaviors
.get(&NodeId::System(key))
.unwrap_or(&SystemBehavior::Continue);
#[cfg(test)]
debug!(
"skipped_systems(): systems[{}], pos {}, Action::{:?}, Behavior::{:?}, {}",
i,
pos,
action,
behavior,
_system.name()
);
match (action, behavior) {
(_, SystemBehavior::NeverRun) => {
skip.insert(i);
if i == pos {
pos += 1;
}
}
(_, SystemBehavior::AlwaysRun) => {
if i == pos {
pos += 1;
}
}
(Action::Waiting, _) => skip.insert(i),
(Action::Step, _) => match i.cmp(&pos) {
Ordering::Less => skip.insert(i),
Ordering::Equal => {
pos += 1;
action = Action::Waiting;
}
Ordering::Greater => unreachable!(),
},
(Action::Continue, SystemBehavior::Continue) => {
if i < start {
skip.insert(i);
}
}
(Action::Continue, SystemBehavior::Break) => {
if i != start {
skip.insert(i);
if i > start {
action = Action::Waiting;
}
}
}
(Action::RunAll, _) => unreachable!(),
}
if i == pos && action != Action::Waiting {
pos += 1;
}
}
if pos >= schedule.systems_len() {
(skip, None)
} else {
(skip, Some(pos))
}
}
}
#[cfg(all(test, feature = "bevy_debug_stepping"))]
#[expect(clippy::print_stdout, reason = "Allowed in tests.")]
mod tests {
use super::*;
use crate::{prelude::*, schedule::ScheduleLabel};
use alloc::{format, vec};
use slotmap::SlotMap;
use std::println;
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
struct TestSchedule;
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
struct TestScheduleA;
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
struct TestScheduleB;
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
struct TestScheduleC;
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
struct TestScheduleD;
fn first_system() {}
fn second_system() {}
fn third_system() {}
fn setup() -> (Schedule, World) {
let mut world = World::new();
let mut schedule = Schedule::new(TestSchedule);
schedule.add_systems((first_system, second_system).chain());
schedule.initialize(&mut world).unwrap();
(schedule, world)
}
macro_rules! assert_skip_list_eq {
($actual:expr, $expected:expr, $system_names:expr) => {
let actual = $actual;
let expected = $expected;
let systems: &Vec<&str> = $system_names;
if (actual != expected) {
use core::fmt::Write as _;
let mut msg = format!(
"Schedule:\n {:9} {:16}{:6} {:6} {:6}\n",
"index", "name", "expect", "actual", "result"
);
for (i, name) in systems.iter().enumerate() {
let _ = write!(msg, " system[{:1}] {:16}", i, name);
match (expected.contains(i), actual.contains(i)) {
(true, true) => msg.push_str("skip skip pass\n"),
(true, false) => {
msg.push_str("skip run FAILED; system should not have run\n")
}
(false, true) => {
msg.push_str("run skip FAILED; system should have run\n")
}
(false, false) => msg.push_str("run run pass\n"),
}
}
assert_eq!(actual, expected, "{}", msg);
}
};
}
macro_rules! assert_systems_run {
($schedule:expr, $skipped_systems:expr, $($system:expr),*) => {
let systems: Vec<(TypeId, alloc::string::String)> = $schedule.systems().unwrap()
.map(|(_, system)| {
(system.type_id(), system.name().as_string())
})
.collect();
let mut expected = FixedBitSet::with_capacity(systems.len());
$(
let sys = IntoSystem::into_system($system);
for (i, (type_id, _)) in systems.iter().enumerate() {
if sys.type_id() == *type_id {
expected.insert(i);
}
}
)*
expected.toggle_range(..);
let actual = match $skipped_systems {
None => FixedBitSet::with_capacity(systems.len()),
Some(b) => b,
};
let system_names: Vec<&str> = systems
.iter()
.map(|(_,n)| n.rsplit_once("::").unwrap().1)
.collect();
assert_skip_list_eq!(actual, expected, &system_names);
};
}
macro_rules! assert_schedule_runs {
($schedule:expr, $stepping:expr, $($system:expr),*) => {
$stepping.next_frame();
assert_systems_run!($schedule, $stepping.skipped_systems($schedule), $($system),*);
};
}
#[test]
fn stepping_disabled() {
let (schedule, _world) = setup();
let mut stepping = Stepping::new();
stepping.add_schedule(TestSchedule).disable().next_frame();
assert!(stepping.skipped_systems(&schedule).is_none());
assert!(stepping.cursor().is_none());
}
#[test]
fn unknown_schedule() {
let (schedule, _world) = setup();
let mut stepping = Stepping::new();
stepping.enable().next_frame();
assert!(stepping.skipped_systems(&schedule).is_none());
}
#[test]
fn disabled_always_run() {
let (schedule, _world) = setup();
let mut stepping = Stepping::new();
stepping
.add_schedule(TestSchedule)
.disable()
.always_run(TestSchedule, first_system);
assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
}
#[test]
fn waiting_always_run() {
let (schedule, _world) = setup();
let mut stepping = Stepping::new();
stepping
.add_schedule(TestSchedule)
.enable()
.always_run(TestSchedule, first_system);
assert_schedule_runs!(&schedule, &mut stepping, first_system);
}
#[test]
fn step_always_run() {
let (schedule, _world) = setup();
let mut stepping = Stepping::new();
stepping
.add_schedule(TestSchedule)
.enable()
.always_run(TestSchedule, first_system)
.step_frame();
assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
}
#[test]
fn continue_always_run() {
let (schedule, _world) = setup();
let mut stepping = Stepping::new();
stepping
.add_schedule(TestSchedule)
.enable()
.always_run(TestSchedule, first_system)
.continue_frame();
assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
}
#[test]
fn disabled_never_run() {
let (schedule, _world) = setup();
let mut stepping = Stepping::new();
stepping
.add_schedule(TestSchedule)
.never_run(TestSchedule, first_system)
.disable();
assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
}
#[test]
fn waiting_never_run() {
let (schedule, _world) = setup();
let mut stepping = Stepping::new();
stepping
.add_schedule(TestSchedule)
.enable()
.never_run(TestSchedule, first_system);
assert_schedule_runs!(&schedule, &mut stepping,);
}
#[test]
fn step_never_run() {
let (schedule, _world) = setup();
let mut stepping = Stepping::new();
stepping
.add_schedule(TestSchedule)
.enable()
.never_run(TestSchedule, first_system)
.step_frame();
assert_schedule_runs!(&schedule, &mut stepping, second_system);
}
#[test]
fn continue_never_run() {
let (schedule, _world) = setup();
let mut stepping = Stepping::new();
stepping
.add_schedule(TestSchedule)
.enable()
.never_run(TestSchedule, first_system)
.continue_frame();
assert_schedule_runs!(&schedule, &mut stepping, second_system);
}
#[test]
fn disabled_breakpoint() {
let (schedule, _world) = setup();
let mut stepping = Stepping::new();
stepping
.add_schedule(TestSchedule)
.disable()
.set_breakpoint(TestSchedule, second_system);
assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
}
#[test]
fn waiting_breakpoint() {
let (schedule, _world) = setup();
let mut stepping = Stepping::new();
stepping
.add_schedule(TestSchedule)
.enable()
.set_breakpoint(TestSchedule, second_system);
assert_schedule_runs!(&schedule, &mut stepping,);
}
#[test]
fn step_breakpoint() {
let (schedule, _world) = setup();
let mut stepping = Stepping::new();
stepping
.add_schedule(TestSchedule)
.enable()
.set_breakpoint(TestSchedule, second_system)
.step_frame();
assert_schedule_runs!(&schedule, &mut stepping, first_system);
stepping.step_frame();
assert_schedule_runs!(&schedule, &mut stepping, second_system);
stepping.step_frame();
assert_schedule_runs!(&schedule, &mut stepping, first_system);
assert_schedule_runs!(&schedule, &mut stepping,);
}
#[test]
fn continue_breakpoint() {
let (schedule, _world) = setup();
let mut stepping = Stepping::new();
stepping
.add_schedule(TestSchedule)
.enable()
.set_breakpoint(TestSchedule, second_system)
.continue_frame();
assert_schedule_runs!(&schedule, &mut stepping, first_system);
stepping.continue_frame();
assert_schedule_runs!(&schedule, &mut stepping, second_system);
stepping.continue_frame();
assert_schedule_runs!(&schedule, &mut stepping, first_system);
}
#[test]
fn continue_step_continue_with_breakpoint() {
let mut world = World::new();
let mut schedule = Schedule::new(TestSchedule);
schedule.add_systems((first_system, second_system, third_system).chain());
schedule.initialize(&mut world).unwrap();
let mut stepping = Stepping::new();
stepping
.add_schedule(TestSchedule)
.enable()
.set_breakpoint(TestSchedule, second_system);
stepping.continue_frame();
assert_schedule_runs!(&schedule, &mut stepping, first_system);
stepping.step_frame();
assert_schedule_runs!(&schedule, &mut stepping, second_system);
stepping.continue_frame();
assert_schedule_runs!(&schedule, &mut stepping, third_system);
}
#[test]
fn clear_breakpoint() {
let (schedule, _world) = setup();
let mut stepping = Stepping::new();
stepping
.add_schedule(TestSchedule)
.enable()
.set_breakpoint(TestSchedule, second_system)
.continue_frame();
assert_schedule_runs!(&schedule, &mut stepping, first_system);
stepping.continue_frame();
assert_schedule_runs!(&schedule, &mut stepping, second_system);
stepping.clear_breakpoint(TestSchedule, second_system);
stepping.continue_frame();
assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
}
#[test]
fn clear_system() {
let (schedule, _world) = setup();
let mut stepping = Stepping::new();
stepping
.add_schedule(TestSchedule)
.enable()
.never_run(TestSchedule, second_system)
.continue_frame();
assert_schedule_runs!(&schedule, &mut stepping, first_system);
stepping.clear_system(TestSchedule, second_system);
stepping.continue_frame();
assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
}
#[test]
fn clear_schedule() {
let (schedule, _world) = setup();
let mut stepping = Stepping::new();
stepping
.add_schedule(TestSchedule)
.enable()
.never_run(TestSchedule, first_system)
.never_run(TestSchedule, second_system)
.continue_frame();
assert_schedule_runs!(&schedule, &mut stepping,);
stepping.clear_schedule(TestSchedule);
stepping.continue_frame();
assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
}
#[test]
fn set_behavior_then_clear_schedule() {
let (schedule, _world) = setup();
let mut stepping = Stepping::new();
stepping
.add_schedule(TestSchedule)
.enable()
.continue_frame();
assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
stepping.never_run(TestSchedule, first_system);
stepping.clear_schedule(TestSchedule);
stepping.continue_frame();
assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
}
#[test]
fn clear_schedule_then_set_behavior() {
let (schedule, _world) = setup();
let mut stepping = Stepping::new();
stepping
.add_schedule(TestSchedule)
.enable()
.continue_frame();
assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
stepping.clear_schedule(TestSchedule);
stepping.never_run(TestSchedule, first_system);
stepping.continue_frame();
assert_schedule_runs!(&schedule, &mut stepping, second_system);
}
#[test]
fn multiple_calls_per_frame_continue() {
let (schedule, _world) = setup();
let mut stepping = Stepping::new();
stepping
.add_schedule(TestSchedule)
.enable()
.always_run(TestSchedule, second_system)
.continue_frame();
stepping.next_frame();
assert_systems_run!(
&schedule,
stepping.skipped_systems(&schedule),
first_system,
second_system
);
assert_systems_run!(
&schedule,
stepping.skipped_systems(&schedule),
second_system
);
}
#[test]
fn multiple_calls_per_frame_step() {
let (schedule, _world) = setup();
let mut stepping = Stepping::new();
stepping.add_schedule(TestSchedule).enable().step_frame();
stepping.next_frame();
assert_systems_run!(&schedule, stepping.skipped_systems(&schedule), first_system);
assert_systems_run!(&schedule, stepping.skipped_systems(&schedule),);
}
#[test]
fn step_duplicate_systems() {
let mut world = World::new();
let mut schedule = Schedule::new(TestSchedule);
schedule.add_systems((first_system, first_system, second_system).chain());
schedule.initialize(&mut world).unwrap();
let mut stepping = Stepping::new();
stepping.add_schedule(TestSchedule).enable();
let system_names = vec!["first_system", "first_system", "second_system"];
for system_index in 0..3 {
let mut expected = FixedBitSet::with_capacity(3);
expected.set_range(.., true);
expected.set(system_index, false);
stepping.step_frame();
stepping.next_frame();
let skip_list = stepping
.skipped_systems(&schedule)
.expect("TestSchedule has been added to Stepping");
assert_skip_list_eq!(skip_list, expected, &system_names);
}
}
#[test]
fn step_run_if_false() {
let mut world = World::new();
let mut schedule = Schedule::new(TestSchedule);
let first_system: fn() = move || {
panic!("first_system should not be run");
};
#[derive(Resource)]
struct RunCount(usize);
world.insert_resource(RunCount(0));
let second_system = |mut run_count: ResMut<RunCount>| {
println!("I have run!");
run_count.0 += 1;
};
schedule.add_systems((first_system.run_if(|| false), second_system).chain());
schedule.initialize(&mut world).unwrap();
let mut stepping = Stepping::new();
stepping.add_schedule(TestSchedule).enable();
world.insert_resource(stepping);
let mut stepping = world.resource_mut::<Stepping>();
stepping.step_frame();
stepping.next_frame();
schedule.run(&mut world);
assert_eq!(
world.resource::<RunCount>().0,
0,
"second_system should not have run"
);
let mut stepping = world.resource_mut::<Stepping>();
stepping.step_frame();
stepping.next_frame();
schedule.run(&mut world);
assert_eq!(
world.resource::<RunCount>().0,
1,
"second_system should have run"
);
}
#[test]
fn remove_schedule() {
let (schedule, _world) = setup();
let mut stepping = Stepping::new();
stepping.add_schedule(TestSchedule).enable();
assert_schedule_runs!(&schedule, &mut stepping,);
assert!(!stepping.schedules().unwrap().is_empty());
stepping.remove_schedule(TestSchedule);
assert_schedule_runs!(&schedule, &mut stepping, first_system, second_system);
assert!(stepping.schedules().unwrap().is_empty());
}
#[test]
fn schedules() {
let mut world = World::new();
let mut schedule_a = Schedule::new(TestScheduleA);
schedule_a.initialize(&mut world).unwrap();
let mut schedule_b = Schedule::new(TestScheduleB);
schedule_b.initialize(&mut world).unwrap();
let mut schedule_c = Schedule::new(TestScheduleC);
schedule_c.initialize(&mut world).unwrap();
let mut schedule_d = Schedule::new(TestScheduleD);
schedule_d.initialize(&mut world).unwrap();
let mut stepping = Stepping::new();
stepping
.add_schedule(TestScheduleA)
.add_schedule(TestScheduleB)
.add_schedule(TestScheduleC)
.add_schedule(TestScheduleD)
.enable()
.next_frame();
assert!(stepping.schedules().is_err());
stepping.skipped_systems(&schedule_b);
assert!(stepping.schedules().is_err());
stepping.skipped_systems(&schedule_a);
assert!(stepping.schedules().is_err());
stepping.skipped_systems(&schedule_c);
assert!(stepping.schedules().is_err());
stepping.skipped_systems(&schedule_d);
assert!(stepping.schedules().is_ok());
assert_eq!(
*stepping.schedules().unwrap(),
vec![
TestScheduleB.intern(),
TestScheduleA.intern(),
TestScheduleC.intern(),
TestScheduleD.intern(),
]
);
}
#[test]
fn verify_cursor() {
fn cursor(schedule: &Schedule, index: usize) -> (InternedScheduleLabel, NodeId) {
let node_id = schedule.executable().system_ids[index];
(schedule.label(), NodeId::System(node_id))
}
let mut world = World::new();
let mut slotmap = SlotMap::<SystemKey, ()>::with_key();
let mut schedule_a = Schedule::new(TestScheduleA);
schedule_a.add_systems((|| {}, || {}, || {}, || {}).chain());
schedule_a.initialize(&mut world).unwrap();
let mut schedule_b = Schedule::new(TestScheduleB);
schedule_b.add_systems((|| {}, || {}, || {}, || {}).chain());
schedule_b.initialize(&mut world).unwrap();
let mut stepping = Stepping::new();
stepping
.add_schedule(TestScheduleA)
.add_schedule(TestScheduleB)
.enable();
assert!(stepping.cursor().is_none());
let mut cursors = Vec::new();
for _ in 0..9 {
stepping.step_frame().next_frame();
cursors.push(stepping.cursor());
stepping.skipped_systems(&schedule_a);
stepping.skipped_systems(&schedule_b);
cursors.push(stepping.cursor());
}
#[rustfmt::skip]
assert_eq!(
cursors,
vec![
None, Some(cursor(&schedule_a, 1)),
Some(cursor(&schedule_a, 1)), Some(cursor(&schedule_a, 2)),
Some(cursor(&schedule_a, 2)), Some(cursor(&schedule_a, 3)),
Some(cursor(&schedule_a, 3)), Some(cursor(&schedule_b, 0)),
Some(cursor(&schedule_b, 0)), Some(cursor(&schedule_b, 1)),
Some(cursor(&schedule_b, 1)), Some(cursor(&schedule_b, 2)),
Some(cursor(&schedule_b, 2)), Some(cursor(&schedule_b, 3)),
Some(cursor(&schedule_b, 3)), None,
Some(cursor(&schedule_a, 0)), Some(cursor(&schedule_a, 1)),
]
);
let sys0 = slotmap.insert(());
let sys1 = slotmap.insert(());
let _sys2 = slotmap.insert(());
let sys3 = slotmap.insert(());
stepping
.disable()
.enable()
.set_breakpoint_node(TestScheduleA, NodeId::System(sys1))
.always_run_node(TestScheduleA, NodeId::System(sys3))
.never_run_node(TestScheduleB, NodeId::System(sys0));
let mut cursors = Vec::new();
for _ in 0..9 {
stepping.step_frame().next_frame();
cursors.push(stepping.cursor());
stepping.skipped_systems(&schedule_a);
stepping.skipped_systems(&schedule_b);
cursors.push(stepping.cursor());
}
#[rustfmt::skip]
assert_eq!(
cursors,
vec![
Some(cursor(&schedule_a, 0)), Some(cursor(&schedule_a, 1)),
Some(cursor(&schedule_a, 1)), Some(cursor(&schedule_a, 2)),
Some(cursor(&schedule_a, 2)), Some(cursor(&schedule_b, 1)),
Some(cursor(&schedule_b, 1)), Some(cursor(&schedule_b, 2)),
Some(cursor(&schedule_b, 2)), Some(cursor(&schedule_b, 3)),
Some(cursor(&schedule_b, 3)), None,
Some(cursor(&schedule_a, 0)), Some(cursor(&schedule_a, 1)),
Some(cursor(&schedule_a, 1)), Some(cursor(&schedule_a, 2)),
Some(cursor(&schedule_a, 2)), Some(cursor(&schedule_b, 1)),
]
);
}
}