Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

System Stepping implemented as Resource #8453

Merged
merged 40 commits into from
Feb 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
dce6a9a
Add `Schedule.label`
dmlary Mar 27, 2023
8db83c3
WIP: Add bevy_ecs::schedule::stepping
dmlary Mar 27, 2023
4ac6dfa
Update `ScheduleExecutor` for stepping
dmlary Mar 28, 2023
6714bcf
Unify `Stepping` updates into single enum
dmlary Mar 29, 2023
e5661c7
Merge remote-tracking branch 'upstream/main' into stepping-resource
dmlary Mar 29, 2023
031acd4
Stepping::continue_frame() works; test updates
dmlary Mar 31, 2023
2a96883
Stepping dynamic schedule order detection
dmlary Apr 1, 2023
9004864
combine build step list functions
dmlary Apr 2, 2023
545c5f0
move stepping frame cursor into Stepping
dmlary Apr 4, 2023
9d85b2e
WIP: add stepping to breakout
dmlary Apr 4, 2023
62fa980
Merge remote-tracking branch 'upstream' into stepping-resource
dmlary Apr 4, 2023
bca6c85
Added stepping to breakout
dmlary Apr 17, 2023
315a88c
Merge remote-tracking branch 'upstream/main' into stepping-resource
dmlary Apr 17, 2023
625e690
Stepping cursor changed to use label & NodeId
dmlary Apr 20, 2023
4a6cea5
Add cursor tests, and Stepping _node() methods
dmlary Apr 21, 2023
16a9e59
Updates from code-review
dmlary Apr 22, 2023
a9ebeb5
Merge remote-tracking branch 'upstream/main' into stepping-resource
dmlary Apr 22, 2023
1cb1c58
merge fixes, and CI fixes
dmlary Apr 22, 2023
4b8b507
Added `bevy_debug_stepping` feature
dmlary Apr 27, 2023
d746299
Fix breakout example so stepping help shows on start
dmlary Apr 27, 2023
8fb59a3
Merge branch 'main' into stepping-resource
dmlary Apr 27, 2023
5b3a77c
update docs/cargo_features.md
dmlary Apr 27, 2023
8ec0e20
`pub(crate)` for `Schedule::executor()`
dmlary Apr 27, 2023
bfd76a1
Merge remote-tracking branch 'upstream/main' into stepping-resource
dmlary May 1, 2023
ed8db01
Merge remote-tracking branch 'upstream/main' into stepping-resource
dmlary May 5, 2023
709a23b
Merge remote-tracking branch 'upstream/main' into stepping-resource
dmlary May 21, 2023
2f8ba58
Merge remote-tracking branch 'upstream/main' into stepping-resource
dmlary Jun 8, 2023
0912a84
merge main
dmlary Oct 26, 2023
7e3f2ad
Merge remote-tracking branch 'upstream' into stepping-resource
dmlary Nov 29, 2023
806f0ad
stepping: cleanup clippy warning
dmlary Nov 29, 2023
383c6fc
fix for toml file
dmlary Nov 30, 2023
bd18fb4
Merge remote-tracking branch 'upstream' into stepping-resource
dmlary Dec 24, 2023
7a841c8
add system_stepping example; fix stepping issue
dmlary Jan 6, 2024
4242192
update examples/README.md; add LogPlugin to system_stepping
dmlary Jan 6, 2024
39b48f7
Merge remote-tracking branch 'upstream' into stepping-resource
dmlary Jan 6, 2024
5f88d6b
convert `bevy_internal` use in system_stepping example
dmlary Jan 6, 2024
7cc52fa
Fixes per code-review comments
dmlary Jan 6, 2024
3c767ab
Merge remote-tracking branch 'upstream' into stepping-resource
dmlary Jan 9, 2024
6cdc0c5
Merge remote-tracking branch 'upstream' into stepping-resource
dmlary Jan 28, 2024
b455384
Minor style tweaks
cart Feb 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ default = [
"tonemapping_luts",
"default_font",
"webgl2",
"bevy_debug_stepping",
]

# Force dynamic linking, which improves iterative compile times
Expand Down Expand Up @@ -292,6 +293,9 @@ file_watcher = ["bevy_internal/file_watcher"]
# Enables watching in memory asset providers for Bevy Asset hot-reloading
embedded_watcher = ["bevy_internal/embedded_watcher"]

# Enable stepping-based debugging of Bevy systems
bevy_debug_stepping = ["bevy_internal/bevy_debug_stepping"]

[dependencies]
bevy_dylib = { path = "crates/bevy_dylib", version = "0.12.0", default-features = false, optional = true }
bevy_internal = { path = "crates/bevy_internal", version = "0.12.0", default-features = false }
Expand Down Expand Up @@ -1541,6 +1545,17 @@ description = "Illustrates creating custom system parameters with `SystemParam`"
category = "ECS (Entity Component System)"
wasm = false

[[example]]
name = "system_stepping"
path = "examples/ecs/system_stepping.rs"
doc-scrape-examples = true

[package.metadata.example.system_stepping]
name = "System Stepping"
description = "Demonstrate stepping through systems in order of execution"
category = "ECS (Entity Component System)"
wasm = false

# Time
[[example]]
name = "time"
Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ keywords = ["bevy"]
[features]
trace = []
bevy_ci_testing = ["serde", "ron"]
default = ["bevy_reflect"]
bevy_debug_stepping = []
default = ["bevy_reflect", "bevy_debug_stepping"]
bevy_reflect = ["dep:bevy_reflect", "bevy_ecs/bevy_reflect"]

[dependencies]
Expand Down
6 changes: 6 additions & 0 deletions crates/bevy_app/src/main_schedule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,12 @@ impl Plugin for MainSchedulePlugin {
.init_resource::<FixedMainScheduleOrder>()
.add_systems(Main, Main::run_main)
.add_systems(FixedMain, FixedMain::run_fixed_main);

#[cfg(feature = "bevy_debug_stepping")]
{
use bevy_ecs::schedule::{IntoSystemConfigs, Stepping};
app.add_systems(Main, Stepping::begin_frame.before(Main::run_main));
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_ecs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ categories = ["game-engines", "data-structures"]
[features]
trace = []
multi-threaded = ["bevy_tasks/multi-threaded"]
default = ["bevy_reflect"]
bevy_debug_stepping = []
default = ["bevy_reflect", "bevy_debug_stepping"]

[dependencies]
bevy_ptr = { path = "../bevy_ptr", version = "0.12.0" }
Expand Down
7 changes: 6 additions & 1 deletion crates/bevy_ecs/src/schedule/executor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ use crate::{
pub(super) trait SystemExecutor: Send + Sync {
fn kind(&self) -> ExecutorKind;
fn init(&mut self, schedule: &SystemSchedule);
fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World);
fn run(
&mut self,
schedule: &mut SystemSchedule,
skip_systems: Option<FixedBitSet>,
world: &mut World,
);
fn set_apply_final_deferred(&mut self, value: bool);
}

Expand Down
32 changes: 31 additions & 1 deletion crates/bevy_ecs/src/schedule/executor/multi_threaded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,12 @@ impl SystemExecutor for MultiThreadedExecutor {
self.num_dependencies_remaining = Vec::with_capacity(sys_count);
}

fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World) {
fn run(
&mut self,
schedule: &mut SystemSchedule,
_skip_systems: Option<FixedBitSet>,
dmlary marked this conversation as resolved.
Show resolved Hide resolved
world: &mut World,
) {
// reset counts
self.num_systems = schedule.systems.len();
if self.num_systems == 0 {
Expand All @@ -181,6 +186,31 @@ impl SystemExecutor for MultiThreadedExecutor {
}
}

// If stepping is enabled, make sure we skip those systems that should
// not be run.
#[cfg(feature = "bevy_debug_stepping")]
dmlary marked this conversation as resolved.
Show resolved Hide resolved
if let Some(mut skipped_systems) = _skip_systems {
debug_assert_eq!(skipped_systems.len(), self.completed_systems.len());
// mark skipped systems as completed
self.completed_systems |= &skipped_systems;
self.num_completed_systems = self.completed_systems.count_ones(..);

// signal the dependencies for each of the skipped systems, as
// though they had run
for system_index in skipped_systems.ones() {
self.signal_dependents(system_index);
}

// Finally, we need to clear all skipped systems from the ready
// list.
//
// We invert the skipped system mask to get the list of systems
// that should be run. Then we bitwise AND it with the ready list,
// resulting in a list of ready systems that aren't skipped.
skipped_systems.toggle_range(..);
self.ready_systems &= skipped_systems;
}

let thread_executor = world
.get_resource::<MainThreadExecutor>()
.map(|e| e.0.clone());
Expand Down
15 changes: 14 additions & 1 deletion crates/bevy_ecs/src/schedule/executor/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,20 @@ impl SystemExecutor for SimpleExecutor {
self.completed_systems = FixedBitSet::with_capacity(sys_count);
}

fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World) {
fn run(
&mut self,
schedule: &mut SystemSchedule,
_skip_systems: Option<FixedBitSet>,
world: &mut World,
) {
// If stepping is enabled, make sure we skip those systems that should
// not be run.
#[cfg(feature = "bevy_debug_stepping")]
if let Some(skipped_systems) = _skip_systems {
// mark skipped systems as completed
self.completed_systems |= &skipped_systems;
}

for system_index in 0..schedule.systems.len() {
#[cfg(feature = "trace")]
let name = schedule.systems[system_index].name();
Expand Down
15 changes: 14 additions & 1 deletion crates/bevy_ecs/src/schedule/executor/single_threaded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,20 @@ impl SystemExecutor for SingleThreadedExecutor {
self.unapplied_systems = FixedBitSet::with_capacity(sys_count);
}

fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World) {
fn run(
&mut self,
schedule: &mut SystemSchedule,
_skip_systems: Option<FixedBitSet>,
world: &mut World,
) {
// If stepping is enabled, make sure we skip those systems that should
// not be run.
#[cfg(feature = "bevy_debug_stepping")]
if let Some(skipped_systems) = _skip_systems {
// mark skipped systems as completed
self.completed_systems |= &skipped_systems;
}

for system_index in 0..schedule.systems.len() {
#[cfg(feature = "trace")]
let name = schedule.systems[system_index].name();
Expand Down
57 changes: 57 additions & 0 deletions crates/bevy_ecs/src/schedule/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod graph_utils;
mod schedule;
mod set;
mod state;
mod stepping;

pub use self::condition::*;
pub use self::config::*;
Expand Down Expand Up @@ -1098,4 +1099,60 @@ mod tests {
assert!(schedule.graph().conflicting_systems().is_empty());
}
}

#[cfg(feature = "bevy_debug_stepping")]
mod stepping {
use super::*;
use bevy_ecs::system::SystemState;

#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct TestSchedule;

macro_rules! assert_executor_supports_stepping {
($executor:expr) => {
// create a test schedule
let mut schedule = Schedule::new(TestSchedule);
schedule
.set_executor_kind($executor)
.add_systems(|| panic!("Executor ignored Stepping"));

// Add our schedule to stepping & and enable stepping; this should
// prevent any systems in the schedule from running
let mut stepping = Stepping::default();
stepping.add_schedule(TestSchedule).enable();

// create a world, and add the stepping resource
let mut world = World::default();
world.insert_resource(stepping);

// start a new frame by running ihe begin_frame() system
let mut system_state: SystemState<Option<ResMut<Stepping>>> =
SystemState::new(&mut world);
let res = system_state.get_mut(&mut world);
Stepping::begin_frame(res);

// now run the schedule; this will panic if the executor doesn't
// handle stepping
schedule.run(&mut world);
};
}

/// verify the [`SimpleExecutor`] supports stepping
#[test]
fn simple_executor() {
assert_executor_supports_stepping!(ExecutorKind::Simple);
}

/// verify the [`SingleThreadedExecutor`] supports stepping
#[test]
fn single_threaded_executor() {
assert_executor_supports_stepping!(ExecutorKind::SingleThreaded);
}

/// verify the [`MultiThreadedExecutor`] supports stepping
#[test]
fn multi_threaded_executor() {
assert_executor_supports_stepping!(ExecutorKind::MultiThreaded);
}
}
}
60 changes: 59 additions & 1 deletion crates/bevy_ecs/src/schedule/schedule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ use crate::{
world::World,
};

pub use stepping::Stepping;

/// Resource that stores [`Schedule`]s mapped to [`ScheduleLabel`]s excluding the current running [`Schedule`].
#[derive(Default, Resource)]
pub struct Schedules {
Expand Down Expand Up @@ -238,6 +240,11 @@ impl Schedule {
}
}

/// Get the `InternedScheduleLabel` for this `Schedule`.
pub fn label(&self) -> InternedScheduleLabel {
self.label
}

/// Add a collection of systems to the schedule.
pub fn add_systems<M>(&mut self, systems: impl IntoSystemConfigs<M>) -> &mut Self {
self.graph.process_configs(systems.into_configs(), false);
Expand Down Expand Up @@ -324,7 +331,17 @@ impl Schedule {
world.check_change_ticks();
self.initialize(world)
.unwrap_or_else(|e| panic!("Error when initializing schedule {:?}: {e}", self.label));
self.executor.run(&mut self.executable, world);

#[cfg(not(feature = "bevy_debug_stepping"))]
let skip_systems = None;

#[cfg(feature = "bevy_debug_stepping")]
let skip_systems = match world.get_resource_mut::<Stepping>() {
None => None,
Some(mut stepping) => stepping.skipped_systems(self),
};

self.executor.run(&mut self.executable, skip_systems, world);
}

/// Initializes any newly-added systems and conditions, rebuilds the executable schedule,
Expand Down Expand Up @@ -366,6 +383,11 @@ impl Schedule {
&mut self.graph
}

/// Returns the [`SystemSchedule`].
pub(crate) fn executable(&self) -> &SystemSchedule {
&self.executable
}

/// Iterates the change ticks of all systems in the schedule and clamps any older than
/// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE).
/// This prevents overflow and thus prevents false positives.
Expand Down Expand Up @@ -402,6 +424,36 @@ impl Schedule {
system.apply_deferred(world);
}
}

/// Returns an iterator over all systems in this schedule.
///
/// Note: this method will return [`ScheduleNotInitialized`] if the
/// schedule has never been initialized or run.
pub fn systems(
&self,
) -> Result<impl Iterator<Item = (NodeId, &BoxedSystem)> + Sized, ScheduleNotInitialized> {
if !self.executor_initialized {
return Err(ScheduleNotInitialized);
}

let iter = self
.executable
.system_ids
.iter()
.zip(&self.executable.systems)
.map(|(node_id, system)| (*node_id, system));

Ok(iter)
}

/// Returns the number of systems in this schedule.
pub fn systems_len(&self) -> usize {
if !self.executor_initialized {
self.graph.systems.len()
} else {
self.executable.systems.len()
}
}
dmlary marked this conversation as resolved.
Show resolved Hide resolved
}

/// A directed acyclic graph structure.
Expand Down Expand Up @@ -1939,6 +1991,12 @@ impl ScheduleBuildSettings {
}
}

/// Error to denote that [`Schedule::initialize`] or [`Schedule::run`] has not yet been called for
/// this schedule.
#[derive(Error, Debug)]
#[error("executable schedule has not been built")]
pub struct ScheduleNotInitialized;

#[cfg(test)]
mod tests {
use crate::{
Expand Down
Loading
Loading