2023-06-10 23:23:48 +00:00
|
|
|
//! Contains APIs for ordering systems and executing them on a [`World`](crate::world::World)
|
|
|
|
|
2023-01-17 01:39:17 +00:00
|
|
|
mod condition;
|
|
|
|
mod config;
|
|
|
|
mod executor;
|
2024-12-03 20:01:55 +00:00
|
|
|
mod graph;
|
2023-02-06 18:44:40 +00:00
|
|
|
#[allow(clippy::module_inception)]
|
2023-01-17 01:39:17 +00:00
|
|
|
mod schedule;
|
|
|
|
mod set;
|
System Stepping implemented as Resource (#8453)
# Objective
Add interactive system debugging capabilities to bevy, providing
step/break/continue style capabilities to running system schedules.
* Original implementation: #8063
- `ignore_stepping()` everywhere was too much complexity
* Schedule-config & Resource discussion: #8168
- Decided on selective adding of Schedules & Resource-based control
## Solution
Created `Stepping` Resource. This resource can be used to enable
stepping on a per-schedule basis. Systems within schedules can be
individually configured to:
* AlwaysRun: Ignore any stepping state and run every frame
* NeverRun: Never run while stepping is enabled
- this allows for disabling of systems while debugging
* Break: If we're running the full frame, stop before this system is run
Stepping provides two modes of execution that reflect traditional
debuggers:
* Step-based: Only execute one system at a time
* Continue/Break: Run all systems, but stop before running a system
marked as Break
### Demo
https://user-images.githubusercontent.com/857742/233630981-99f3bbda-9ca6-4cc4-a00f-171c4946dc47.mov
Breakout has been modified to use Stepping. The game runs normally for a
couple of seconds, then stepping is enabled and the game appears to
pause. A list of Schedules & Systems appears with a cursor at the first
System in the list. The demo then steps forward full frames using the
spacebar until the ball is about to hit a brick. Then we step system by
system as the ball impacts a brick, showing the cursor moving through
the individual systems. Finally the demo switches back to frame stepping
as the ball changes course.
### Limitations
Due to architectural constraints in bevy, there are some cases systems
stepping will not function as a user would expect.
#### Event-driven systems
Stepping does not support systems that are driven by `Event`s as events
are flushed after 1-2 frames. Although game systems are not running
while stepping, ignored systems are still running every frame, so events
will be flushed.
This presents to the user as stepping the event-driven system never
executes the system. It does execute, but the events have already been
flushed.
This can be resolved by changing event handling to use a buffer for
events, and only dropping an event once all readers have read it.
The work-around to allow these systems to properly execute during
stepping is to have them ignore stepping:
`app.add_systems(event_driven_system.ignore_stepping())`. This was done
in the breakout example to ensure sound played even while stepping.
#### Conditional Systems
When a system is stepped, it is given an opportunity to run. If the
conditions of the system say it should not run, it will not.
Similar to Event-driven systems, if a system is conditional, and that
condition is only true for a very small time window, then stepping the
system may not execute the system. This includes depending on any sort
of external clock.
This exhibits to the user as the system not always running when it is
stepped.
A solution to this limitation is to ensure any conditions are consistent
while stepping is enabled. For example, all systems that modify any
state the condition uses should also enable stepping.
#### State-transition Systems
Stepping is configured on the per-`Schedule` level, requiring the user
to have a `ScheduleLabel`.
To support state-transition systems, bevy generates needed schedules
dynamically. Currently it’s very difficult (if not impossible, I haven’t
verified) for the user to get the labels for these schedules.
Without ready access to the dynamically generated schedules, and a
resolution for the `Event` lifetime, **stepping of the state-transition
systems is not supported**
---
## Changelog
- `Schedule::run()` updated to consult `Stepping` Resource to determine
which Systems to run each frame
- Added `Schedule.label` as a `BoxedSystemLabel`, along with supporting
`Schedule::set_label()` and `Schedule::label()` methods
- `Stepping` needed to know which `Schedule` was running, and prior to
this PR, `Schedule` didn't track its own label
- Would have preferred to add `Schedule::with_label()` and remove
`Schedule::new()`, but this PR touches enough already
- Added calls to `Schedule.set_label()` to `App` and `World` as needed
- Added `Stepping` resource
- Added `Stepping::begin_frame()` system to `MainSchedulePlugin`
- Run before `Main::run_main()`
- Notifies any `Stepping` Resource a new render frame is starting
## Migration Guide
- Add a call to `Schedule::set_label()` for any custom `Schedule`
- This is only required if the `Schedule` will be stepped
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-02-03 05:18:38 +00:00
|
|
|
mod stepping;
|
2023-01-17 01:39:17 +00:00
|
|
|
|
2024-12-03 20:01:55 +00:00
|
|
|
use self::graph::*;
|
2024-09-24 11:42:59 +00:00
|
|
|
pub use self::{condition::*, config::*, executor::*, schedule::*, set::*};
|
2023-01-17 01:39:17 +00:00
|
|
|
|
2024-12-03 20:01:55 +00:00
|
|
|
pub use self::graph::NodeId;
|
2023-02-16 17:09:45 +00:00
|
|
|
|
2023-01-17 01:39:17 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2024-09-27 00:59:59 +00:00
|
|
|
use core::sync::atomic::{AtomicU32, Ordering};
|
2023-01-17 01:39:17 +00:00
|
|
|
|
|
|
|
pub use crate as bevy_ecs;
|
2024-09-24 11:42:59 +00:00
|
|
|
pub use crate::{
|
|
|
|
prelude::World,
|
|
|
|
schedule::{Schedule, SystemSet},
|
|
|
|
system::{Res, ResMut, Resource},
|
|
|
|
};
|
2023-01-17 01:39:17 +00:00
|
|
|
|
|
|
|
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
|
|
|
|
enum TestSet {
|
|
|
|
A,
|
|
|
|
B,
|
|
|
|
C,
|
|
|
|
D,
|
|
|
|
X,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Resource, Default)]
|
|
|
|
struct SystemOrder(Vec<u32>);
|
|
|
|
|
|
|
|
#[derive(Resource, Default)]
|
|
|
|
struct RunConditionBool(pub bool);
|
|
|
|
|
|
|
|
#[derive(Resource, Default)]
|
|
|
|
struct Counter(pub AtomicU32);
|
|
|
|
|
|
|
|
fn make_exclusive_system(tag: u32) -> impl FnMut(&mut World) {
|
|
|
|
move |world| world.resource_mut::<SystemOrder>().0.push(tag)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn make_function_system(tag: u32) -> impl FnMut(ResMut<SystemOrder>) {
|
|
|
|
move |mut resource: ResMut<SystemOrder>| resource.0.push(tag)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn named_system(mut resource: ResMut<SystemOrder>) {
|
|
|
|
resource.0.push(u32::MAX);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn named_exclusive_system(world: &mut World) {
|
|
|
|
world.resource_mut::<SystemOrder>().0.push(u32::MAX);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn counting_system(counter: Res<Counter>) {
|
|
|
|
counter.0.fetch_add(1, Ordering::Relaxed);
|
|
|
|
}
|
|
|
|
|
|
|
|
mod system_execution {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn run_system() {
|
|
|
|
let mut world = World::default();
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
|
|
|
|
world.init_resource::<SystemOrder>();
|
|
|
|
|
2023-03-18 01:45:34 +00:00
|
|
|
schedule.add_systems(make_function_system(0));
|
2023-01-17 01:39:17 +00:00
|
|
|
schedule.run(&mut world);
|
|
|
|
|
|
|
|
assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn run_exclusive_system() {
|
|
|
|
let mut world = World::default();
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
|
|
|
|
world.init_resource::<SystemOrder>();
|
|
|
|
|
2023-03-18 01:45:34 +00:00
|
|
|
schedule.add_systems(make_exclusive_system(0));
|
2023-01-17 01:39:17 +00:00
|
|
|
schedule.run(&mut world);
|
|
|
|
|
|
|
|
assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[cfg(not(miri))]
|
|
|
|
fn parallel_execution() {
|
2024-09-27 00:59:59 +00:00
|
|
|
use alloc::sync::Arc;
|
2023-01-17 01:39:17 +00:00
|
|
|
use bevy_tasks::{ComputeTaskPool, TaskPool};
|
2024-09-27 00:59:59 +00:00
|
|
|
use std::sync::Barrier;
|
2023-01-17 01:39:17 +00:00
|
|
|
|
|
|
|
let mut world = World::default();
|
|
|
|
let mut schedule = Schedule::default();
|
2023-10-23 20:48:48 +00:00
|
|
|
let thread_count = ComputeTaskPool::get_or_init(TaskPool::default).thread_num();
|
2023-01-17 01:39:17 +00:00
|
|
|
|
|
|
|
let barrier = Arc::new(Barrier::new(thread_count));
|
|
|
|
|
|
|
|
for _ in 0..thread_count {
|
|
|
|
let inner = barrier.clone();
|
2023-03-18 01:45:34 +00:00
|
|
|
schedule.add_systems(move || {
|
2023-01-17 01:39:17 +00:00
|
|
|
inner.wait();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
schedule.run(&mut world);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mod system_ordering {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn order_systems() {
|
|
|
|
let mut world = World::default();
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
|
|
|
|
world.init_resource::<SystemOrder>();
|
|
|
|
|
2023-03-10 18:15:22 +00:00
|
|
|
schedule.add_systems((
|
|
|
|
named_system,
|
|
|
|
make_function_system(1).before(named_system),
|
2023-01-17 01:39:17 +00:00
|
|
|
make_function_system(0)
|
|
|
|
.after(named_system)
|
|
|
|
.in_set(TestSet::A),
|
2023-03-10 18:15:22 +00:00
|
|
|
));
|
2023-01-17 01:39:17 +00:00
|
|
|
schedule.run(&mut world);
|
|
|
|
|
|
|
|
assert_eq!(world.resource::<SystemOrder>().0, vec![1, u32::MAX, 0]);
|
|
|
|
|
|
|
|
world.insert_resource(SystemOrder::default());
|
|
|
|
|
|
|
|
assert_eq!(world.resource::<SystemOrder>().0, vec![]);
|
|
|
|
|
|
|
|
// modify the schedule after it's been initialized and test ordering with sets
|
2023-09-05 17:15:27 +00:00
|
|
|
schedule.configure_sets(TestSet::A.after(named_system));
|
2023-03-10 18:15:22 +00:00
|
|
|
schedule.add_systems((
|
2023-01-17 01:39:17 +00:00
|
|
|
make_function_system(3)
|
|
|
|
.before(TestSet::A)
|
|
|
|
.after(named_system),
|
2023-03-10 18:15:22 +00:00
|
|
|
make_function_system(4).after(TestSet::A),
|
|
|
|
));
|
2023-01-17 01:39:17 +00:00
|
|
|
schedule.run(&mut world);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
world.resource::<SystemOrder>().0,
|
|
|
|
vec![1, u32::MAX, 3, 0, 4]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn order_exclusive_systems() {
|
|
|
|
let mut world = World::default();
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
|
|
|
|
world.init_resource::<SystemOrder>();
|
|
|
|
|
Migrate engine to Schedule v3 (#7267)
Huge thanks to @maniwani, @devil-ira, @hymm, @cart, @superdump and @jakobhellermann for the help with this PR.
# Objective
- Followup #6587.
- Minimal integration for the Stageless Scheduling RFC: https://github.com/bevyengine/rfcs/pull/45
## Solution
- [x] Remove old scheduling module
- [x] Migrate new methods to no longer use extension methods
- [x] Fix compiler errors
- [x] Fix benchmarks
- [x] Fix examples
- [x] Fix docs
- [x] Fix tests
## Changelog
### Added
- a large number of methods on `App` to work with schedules ergonomically
- the `CoreSchedule` enum
- `App::add_extract_system` via the `RenderingAppExtension` trait extension method
- the private `prepare_view_uniforms` system now has a public system set for scheduling purposes, called `ViewSet::PrepareUniforms`
### Removed
- stages, and all code that mentions stages
- states have been dramatically simplified, and no longer use a stack
- `RunCriteriaLabel`
- `AsSystemLabel` trait
- `on_hierarchy_reports_enabled` run criteria (now just uses an ad hoc resource checking run condition)
- systems in `RenderSet/Stage::Extract` no longer warn when they do not read data from the main world
- `RunCriteriaLabel`
- `transform_propagate_system_set`: this was a nonstandard pattern that didn't actually provide enough control. The systems are already `pub`: the docs have been updated to ensure that the third-party usage is clear.
### Changed
- `System::default_labels` is now `System::default_system_sets`.
- `App::add_default_labels` is now `App::add_default_sets`
- `CoreStage` and `StartupStage` enums are now `CoreSet` and `StartupSet`
- `App::add_system_set` was renamed to `App::add_systems`
- The `StartupSchedule` label is now defined as part of the `CoreSchedules` enum
- `.label(SystemLabel)` is now referred to as `.in_set(SystemSet)`
- `SystemLabel` trait was replaced by `SystemSet`
- `SystemTypeIdLabel<T>` was replaced by `SystemSetType<T>`
- The `ReportHierarchyIssue` resource now has a public constructor (`new`), and implements `PartialEq`
- Fixed time steps now use a schedule (`CoreSchedule::FixedTimeStep`) rather than a run criteria.
- Adding rendering extraction systems now panics rather than silently failing if no subapp with the `RenderApp` label is found.
- the `calculate_bounds` system, with the `CalculateBounds` label, is now in `CoreSet::Update`, rather than in `CoreSet::PostUpdate` before commands are applied.
- `SceneSpawnerSystem` now runs under `CoreSet::Update`, rather than `CoreStage::PreUpdate.at_end()`.
- `bevy_pbr::add_clusters` is no longer an exclusive system
- the top level `bevy_ecs::schedule` module was replaced with `bevy_ecs::scheduling`
- `tick_global_task_pools_on_main_thread` is no longer run as an exclusive system. Instead, it has been replaced by `tick_global_task_pools`, which uses a `NonSend` resource to force running on the main thread.
## Migration Guide
- Calls to `.label(MyLabel)` should be replaced with `.in_set(MySet)`
- Stages have been removed. Replace these with system sets, and then add command flushes using the `apply_system_buffers` exclusive system where needed.
- The `CoreStage`, `StartupStage, `RenderStage` and `AssetStage` enums have been replaced with `CoreSet`, `StartupSet, `RenderSet` and `AssetSet`. The same scheduling guarantees have been preserved.
- Systems are no longer added to `CoreSet::Update` by default. Add systems manually if this behavior is needed, although you should consider adding your game logic systems to `CoreSchedule::FixedTimestep` instead for more reliable framerate-independent behavior.
- Similarly, startup systems are no longer part of `StartupSet::Startup` by default. In most cases, this won't matter to you.
- For example, `add_system_to_stage(CoreStage::PostUpdate, my_system)` should be replaced with
- `add_system(my_system.in_set(CoreSet::PostUpdate)`
- When testing systems or otherwise running them in a headless fashion, simply construct and run a schedule using `Schedule::new()` and `World::run_schedule` rather than constructing stages
- Run criteria have been renamed to run conditions. These can now be combined with each other and with states.
- Looping run criteria and state stacks have been removed. Use an exclusive system that runs a schedule if you need this level of control over system control flow.
- For app-level control flow over which schedules get run when (such as for rollback networking), create your own schedule and insert it under the `CoreSchedule::Outer` label.
- Fixed timesteps are now evaluated in a schedule, rather than controlled via run criteria. The `run_fixed_timestep` system runs this schedule between `CoreSet::First` and `CoreSet::PreUpdate` by default.
- Command flush points introduced by `AssetStage` have been removed. If you were relying on these, add them back manually.
- Adding extract systems is now typically done directly on the main app. Make sure the `RenderingAppExtension` trait is in scope, then call `app.add_extract_system(my_system)`.
- the `calculate_bounds` system, with the `CalculateBounds` label, is now in `CoreSet::Update`, rather than in `CoreSet::PostUpdate` before commands are applied. You may need to order your movement systems to occur before this system in order to avoid system order ambiguities in culling behavior.
- the `RenderLabel` `AppLabel` was renamed to `RenderApp` for clarity
- `App::add_state` now takes 0 arguments: the starting state is set based on the `Default` impl.
- Instead of creating `SystemSet` containers for systems that run in stages, simply use `.on_enter::<State::Variant>()` or its `on_exit` or `on_update` siblings.
- `SystemLabel` derives should be replaced with `SystemSet`. You will also need to add the `Debug`, `PartialEq`, `Eq`, and `Hash` traits to satisfy the new trait bounds.
- `with_run_criteria` has been renamed to `run_if`. Run criteria have been renamed to run conditions for clarity, and should now simply return a bool.
- States have been dramatically simplified: there is no longer a "state stack". To queue a transition to the next state, call `NextState::set`
## TODO
- [x] remove dead methods on App and World
- [x] add `App::add_system_to_schedule` and `App::add_systems_to_schedule`
- [x] avoid adding the default system set at inappropriate times
- [x] remove any accidental cycles in the default plugins schedule
- [x] migrate benchmarks
- [x] expose explicit labels for the built-in command flush points
- [x] migrate engine code
- [x] remove all mentions of stages from the docs
- [x] verify docs for States
- [x] fix uses of exclusive systems that use .end / .at_start / .before_commands
- [x] migrate RenderStage and AssetStage
- [x] migrate examples
- [x] ensure that transform propagation is exported in a sufficiently public way (the systems are already pub)
- [x] ensure that on_enter schedules are run at least once before the main app
- [x] re-enable opt-in to execution order ambiguities
- [x] revert change to `update_bounds` to ensure it runs in `PostUpdate`
- [x] test all examples
- [x] unbreak directional lights
- [x] unbreak shadows (see 3d_scene, 3d_shape, lighting, transparaency_3d examples)
- [x] game menu example shows loading screen and menu simultaneously
- [x] display settings menu is a blank screen
- [x] `without_winit` example panics
- [x] ensure all tests pass
- [x] SubApp doc test fails
- [x] runs_spawn_local tasks fails
- [x] [Fix panic_when_hierachy_cycle test hanging](https://github.com/alice-i-cecile/bevy/pull/120)
## Points of Difficulty and Controversy
**Reviewers, please give feedback on these and look closely**
1. Default sets, from the RFC, have been removed. These added a tremendous amount of implicit complexity and result in hard to debug scheduling errors. They're going to be tackled in the form of "base sets" by @cart in a followup.
2. The outer schedule controls which schedule is run when `App::update` is called.
3. I implemented `Label for `Box<dyn Label>` for our label types. This enables us to store schedule labels in concrete form, and then later run them. I ran into the same set of problems when working with one-shot systems. We've previously investigated this pattern in depth, and it does not appear to lead to extra indirection with nested boxes.
4. `SubApp::update` simply runs the default schedule once. This sucks, but this whole API is incomplete and this was the minimal changeset.
5. `time_system` and `tick_global_task_pools_on_main_thread` no longer use exclusive systems to attempt to force scheduling order
6. Implemetnation strategy for fixed timesteps
7. `AssetStage` was migrated to `AssetSet` without reintroducing command flush points. These did not appear to be used, and it's nice to remove these bottlenecks.
8. Migration of `bevy_render/lib.rs` and pipelined rendering. The logic here is unusually tricky, as we have complex scheduling requirements.
## Future Work (ideally before 0.10)
- Rename schedule_v3 module to schedule or scheduling
- Add a derive macro to states, and likely a `EnumIter` trait of some form
- Figure out what exactly to do with the "systems added should basically work by default" problem
- Improve ergonomics for working with fixed timesteps and states
- Polish FixedTime API to match Time
- Rebase and merge #7415
- Resolve all internal ambiguities (blocked on better tools, especially #7442)
- Add "base sets" to replace the removed default sets.
2023-02-06 02:04:50 +00:00
|
|
|
schedule.add_systems((
|
|
|
|
named_exclusive_system,
|
|
|
|
make_exclusive_system(1).before(named_exclusive_system),
|
|
|
|
make_exclusive_system(0).after(named_exclusive_system),
|
|
|
|
));
|
2023-01-17 01:39:17 +00:00
|
|
|
schedule.run(&mut world);
|
|
|
|
|
|
|
|
assert_eq!(world.resource::<SystemOrder>().0, vec![1, u32::MAX, 0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn add_systems_correct_order() {
|
|
|
|
let mut world = World::new();
|
2023-08-28 20:44:48 +00:00
|
|
|
let mut schedule = Schedule::default();
|
2023-02-07 22:59:19 +00:00
|
|
|
|
2023-01-17 01:39:17 +00:00
|
|
|
world.init_resource::<SystemOrder>();
|
|
|
|
|
|
|
|
schedule.add_systems(
|
|
|
|
(
|
|
|
|
make_function_system(0),
|
|
|
|
make_function_system(1),
|
|
|
|
make_exclusive_system(2),
|
|
|
|
make_function_system(3),
|
|
|
|
)
|
|
|
|
.chain(),
|
|
|
|
);
|
|
|
|
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<SystemOrder>().0, vec![0, 1, 2, 3]);
|
|
|
|
}
|
2023-03-18 01:45:34 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn add_systems_correct_order_nested() {
|
|
|
|
let mut world = World::new();
|
2023-08-28 20:44:48 +00:00
|
|
|
let mut schedule = Schedule::default();
|
2023-03-18 01:45:34 +00:00
|
|
|
|
|
|
|
world.init_resource::<SystemOrder>();
|
|
|
|
|
|
|
|
schedule.add_systems(
|
|
|
|
(
|
|
|
|
(make_function_system(0), make_function_system(1)).chain(),
|
|
|
|
make_function_system(2),
|
|
|
|
(make_function_system(3), make_function_system(4)).chain(),
|
|
|
|
(
|
|
|
|
make_function_system(5),
|
|
|
|
(make_function_system(6), make_function_system(7)),
|
|
|
|
),
|
|
|
|
(
|
|
|
|
(make_function_system(8), make_function_system(9)).chain(),
|
|
|
|
make_function_system(10),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.chain(),
|
|
|
|
);
|
|
|
|
|
|
|
|
schedule.run(&mut world);
|
|
|
|
let order = &world.resource::<SystemOrder>().0;
|
|
|
|
assert_eq!(
|
|
|
|
&order[0..5],
|
|
|
|
&[0, 1, 2, 3, 4],
|
|
|
|
"first five items should be exactly ordered"
|
|
|
|
);
|
|
|
|
let unordered = &order[5..8];
|
|
|
|
assert!(
|
|
|
|
unordered.contains(&5) && unordered.contains(&6) && unordered.contains(&7),
|
|
|
|
"unordered must be 5, 6, and 7 in any order"
|
|
|
|
);
|
|
|
|
let partially_ordered = &order[8..11];
|
|
|
|
assert!(
|
|
|
|
partially_ordered == [8, 9, 10] || partially_ordered == [10, 8, 9],
|
|
|
|
"partially_ordered must be [8, 9, 10] or [10, 8, 9]"
|
|
|
|
);
|
2023-12-16 23:58:41 +00:00
|
|
|
assert_eq!(order.len(), 11, "must have exactly 11 order entries");
|
2023-03-18 01:45:34 +00:00
|
|
|
}
|
2023-01-17 01:39:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
mod conditions {
|
2023-01-17 17:54:53 +00:00
|
|
|
use crate::change_detection::DetectChanges;
|
|
|
|
|
2023-01-17 01:39:17 +00:00
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn system_with_condition() {
|
|
|
|
let mut world = World::default();
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
|
|
|
|
world.init_resource::<RunConditionBool>();
|
|
|
|
world.init_resource::<SystemOrder>();
|
|
|
|
|
2023-03-18 01:45:34 +00:00
|
|
|
schedule.add_systems(
|
2023-01-17 01:39:17 +00:00
|
|
|
make_function_system(0).run_if(|condition: Res<RunConditionBool>| condition.0),
|
|
|
|
);
|
|
|
|
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<SystemOrder>().0, vec![]);
|
|
|
|
|
|
|
|
world.resource_mut::<RunConditionBool>().0 = true;
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
|
|
|
|
}
|
|
|
|
|
2023-02-18 21:33:09 +00:00
|
|
|
#[test]
|
|
|
|
fn systems_with_distributive_condition() {
|
|
|
|
let mut world = World::default();
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
|
|
|
|
world.insert_resource(RunConditionBool(true));
|
|
|
|
world.init_resource::<SystemOrder>();
|
|
|
|
|
|
|
|
fn change_condition(mut condition: ResMut<RunConditionBool>) {
|
|
|
|
condition.0 = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
schedule.add_systems(
|
|
|
|
(
|
|
|
|
make_function_system(0),
|
|
|
|
change_condition,
|
|
|
|
make_function_system(1),
|
|
|
|
)
|
|
|
|
.chain()
|
|
|
|
.distributive_run_if(|condition: Res<RunConditionBool>| condition.0),
|
|
|
|
);
|
|
|
|
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
|
|
|
|
}
|
|
|
|
|
2023-01-17 01:39:17 +00:00
|
|
|
#[test]
|
|
|
|
fn run_exclusive_system_with_condition() {
|
|
|
|
let mut world = World::default();
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
|
|
|
|
world.init_resource::<RunConditionBool>();
|
|
|
|
world.init_resource::<SystemOrder>();
|
|
|
|
|
2023-03-18 01:45:34 +00:00
|
|
|
schedule.add_systems(
|
2023-01-17 01:39:17 +00:00
|
|
|
make_exclusive_system(0).run_if(|condition: Res<RunConditionBool>| condition.0),
|
|
|
|
);
|
|
|
|
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<SystemOrder>().0, vec![]);
|
|
|
|
|
|
|
|
world.resource_mut::<RunConditionBool>().0 = true;
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn multiple_conditions_on_system() {
|
|
|
|
let mut world = World::default();
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
|
|
|
|
world.init_resource::<Counter>();
|
|
|
|
|
2023-03-10 18:15:22 +00:00
|
|
|
schedule.add_systems((
|
|
|
|
counting_system.run_if(|| false).run_if(|| false),
|
|
|
|
counting_system.run_if(|| true).run_if(|| false),
|
|
|
|
counting_system.run_if(|| false).run_if(|| true),
|
|
|
|
counting_system.run_if(|| true).run_if(|| true),
|
|
|
|
));
|
2023-01-17 01:39:17 +00:00
|
|
|
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn multiple_conditions_on_system_sets() {
|
|
|
|
let mut world = World::default();
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
|
|
|
|
world.init_resource::<Counter>();
|
|
|
|
|
2023-09-05 17:15:27 +00:00
|
|
|
schedule.configure_sets(TestSet::A.run_if(|| false).run_if(|| false));
|
2023-03-18 01:45:34 +00:00
|
|
|
schedule.add_systems(counting_system.in_set(TestSet::A));
|
2023-09-05 17:15:27 +00:00
|
|
|
schedule.configure_sets(TestSet::B.run_if(|| true).run_if(|| false));
|
2023-03-18 01:45:34 +00:00
|
|
|
schedule.add_systems(counting_system.in_set(TestSet::B));
|
2023-09-05 17:15:27 +00:00
|
|
|
schedule.configure_sets(TestSet::C.run_if(|| false).run_if(|| true));
|
2023-03-18 01:45:34 +00:00
|
|
|
schedule.add_systems(counting_system.in_set(TestSet::C));
|
2023-09-05 17:15:27 +00:00
|
|
|
schedule.configure_sets(TestSet::D.run_if(|| true).run_if(|| true));
|
2023-03-18 01:45:34 +00:00
|
|
|
schedule.add_systems(counting_system.in_set(TestSet::D));
|
2023-01-17 01:39:17 +00:00
|
|
|
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn systems_nested_in_system_sets() {
|
|
|
|
let mut world = World::default();
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
|
|
|
|
world.init_resource::<Counter>();
|
|
|
|
|
2023-09-05 17:15:27 +00:00
|
|
|
schedule.configure_sets(TestSet::A.run_if(|| false));
|
2023-03-18 01:45:34 +00:00
|
|
|
schedule.add_systems(counting_system.in_set(TestSet::A).run_if(|| false));
|
2023-09-05 17:15:27 +00:00
|
|
|
schedule.configure_sets(TestSet::B.run_if(|| true));
|
2023-03-18 01:45:34 +00:00
|
|
|
schedule.add_systems(counting_system.in_set(TestSet::B).run_if(|| false));
|
2023-09-05 17:15:27 +00:00
|
|
|
schedule.configure_sets(TestSet::C.run_if(|| false));
|
2023-03-18 01:45:34 +00:00
|
|
|
schedule.add_systems(counting_system.in_set(TestSet::C).run_if(|| true));
|
2023-09-05 17:15:27 +00:00
|
|
|
schedule.configure_sets(TestSet::D.run_if(|| true));
|
2023-03-18 01:45:34 +00:00
|
|
|
schedule.add_systems(counting_system.in_set(TestSet::D).run_if(|| true));
|
2023-01-17 01:39:17 +00:00
|
|
|
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
|
|
|
|
}
|
2023-01-17 17:54:53 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn system_conditions_and_change_detection() {
|
|
|
|
#[derive(Resource, Default)]
|
|
|
|
struct Bool2(pub bool);
|
|
|
|
|
|
|
|
let mut world = World::default();
|
|
|
|
world.init_resource::<Counter>();
|
|
|
|
world.init_resource::<RunConditionBool>();
|
|
|
|
world.init_resource::<Bool2>();
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
|
2023-03-18 01:45:34 +00:00
|
|
|
schedule.add_systems(
|
2023-01-17 17:54:53 +00:00
|
|
|
counting_system
|
|
|
|
.run_if(|res1: Res<RunConditionBool>| res1.is_changed())
|
|
|
|
.run_if(|res2: Res<Bool2>| res2.is_changed()),
|
|
|
|
);
|
|
|
|
|
|
|
|
// both resource were just added.
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
|
|
|
|
|
|
|
|
// nothing has changed
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
|
|
|
|
|
|
|
|
// RunConditionBool has changed, but counting_system did not run
|
|
|
|
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
|
|
|
|
|
|
|
|
// internal state for the bool2 run criteria was updated in the
|
|
|
|
// previous run, so system still does not run
|
|
|
|
world.get_resource_mut::<Bool2>().unwrap().0 = false;
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
|
|
|
|
|
|
|
|
// internal state for bool2 was updated, so system still does not run
|
|
|
|
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
|
|
|
|
|
|
|
|
// now check that it works correctly changing Bool2 first and then RunConditionBool
|
|
|
|
world.get_resource_mut::<Bool2>().unwrap().0 = false;
|
|
|
|
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn system_set_conditions_and_change_detection() {
|
|
|
|
#[derive(Resource, Default)]
|
|
|
|
struct Bool2(pub bool);
|
|
|
|
|
|
|
|
let mut world = World::default();
|
|
|
|
world.init_resource::<Counter>();
|
|
|
|
world.init_resource::<RunConditionBool>();
|
|
|
|
world.init_resource::<Bool2>();
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
|
2023-09-05 17:15:27 +00:00
|
|
|
schedule.configure_sets(
|
2023-01-17 17:54:53 +00:00
|
|
|
TestSet::A
|
|
|
|
.run_if(|res1: Res<RunConditionBool>| res1.is_changed())
|
|
|
|
.run_if(|res2: Res<Bool2>| res2.is_changed()),
|
|
|
|
);
|
|
|
|
|
2023-03-18 01:45:34 +00:00
|
|
|
schedule.add_systems(counting_system.in_set(TestSet::A));
|
2023-01-17 17:54:53 +00:00
|
|
|
|
|
|
|
// both resource were just added.
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
|
|
|
|
|
|
|
|
// nothing has changed
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
|
|
|
|
|
|
|
|
// RunConditionBool has changed, but counting_system did not run
|
|
|
|
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
|
|
|
|
|
|
|
|
// internal state for the bool2 run criteria was updated in the
|
|
|
|
// previous run, so system still does not run
|
|
|
|
world.get_resource_mut::<Bool2>().unwrap().0 = false;
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
|
|
|
|
|
|
|
|
// internal state for bool2 was updated, so system still does not run
|
|
|
|
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
|
|
|
|
|
|
|
|
// the system only runs when both are changed on the same run
|
|
|
|
world.get_resource_mut::<Bool2>().unwrap().0 = false;
|
|
|
|
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn mixed_conditions_and_change_detection() {
|
|
|
|
#[derive(Resource, Default)]
|
|
|
|
struct Bool2(pub bool);
|
|
|
|
|
|
|
|
let mut world = World::default();
|
|
|
|
world.init_resource::<Counter>();
|
|
|
|
world.init_resource::<RunConditionBool>();
|
|
|
|
world.init_resource::<Bool2>();
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
|
|
|
|
schedule
|
2023-09-05 17:15:27 +00:00
|
|
|
.configure_sets(TestSet::A.run_if(|res1: Res<RunConditionBool>| res1.is_changed()));
|
2023-01-17 17:54:53 +00:00
|
|
|
|
2023-03-18 01:45:34 +00:00
|
|
|
schedule.add_systems(
|
2023-01-17 17:54:53 +00:00
|
|
|
counting_system
|
|
|
|
.run_if(|res2: Res<Bool2>| res2.is_changed())
|
|
|
|
.in_set(TestSet::A),
|
|
|
|
);
|
|
|
|
|
|
|
|
// both resource were just added.
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
|
|
|
|
|
|
|
|
// nothing has changed
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
|
|
|
|
|
|
|
|
// RunConditionBool has changed, but counting_system did not run
|
|
|
|
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
|
|
|
|
|
|
|
|
// we now only change bool2 and the system also should not run
|
|
|
|
world.get_resource_mut::<Bool2>().unwrap().0 = false;
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
|
|
|
|
|
|
|
|
// internal state for the bool2 run criteria was updated in the
|
|
|
|
// previous run, so system still does not run
|
|
|
|
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
|
|
|
|
|
|
|
|
// the system only runs when both are changed on the same run
|
|
|
|
world.get_resource_mut::<Bool2>().unwrap().0 = false;
|
|
|
|
world.get_resource_mut::<RunConditionBool>().unwrap().0 = false;
|
|
|
|
schedule.run(&mut world);
|
|
|
|
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 2);
|
|
|
|
}
|
2023-01-17 01:39:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
mod schedule_build_errors {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[should_panic]
|
|
|
|
fn dependency_loop() {
|
2023-08-28 20:44:48 +00:00
|
|
|
let mut schedule = Schedule::default();
|
2023-09-05 17:15:27 +00:00
|
|
|
schedule.configure_sets(TestSet::X.after(TestSet::X));
|
2023-01-17 01:39:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn dependency_cycle() {
|
|
|
|
let mut world = World::new();
|
2023-08-28 20:44:48 +00:00
|
|
|
let mut schedule = Schedule::default();
|
2023-01-17 01:39:17 +00:00
|
|
|
|
2023-09-05 17:15:27 +00:00
|
|
|
schedule.configure_sets(TestSet::A.after(TestSet::B));
|
|
|
|
schedule.configure_sets(TestSet::B.after(TestSet::A));
|
2023-01-17 01:39:17 +00:00
|
|
|
|
|
|
|
let result = schedule.initialize(&mut world);
|
Refactor build_schedule and related errors (#9579)
# Objective
- break up large build_schedule system to make it easier to read
- Clean up related error messages.
- I have a follow up PR that adds the schedule name to the error
messages, but wanted to break this up from that.
## Changelog
- refactor `build_schedule` to be easier to read
## Sample Error Messages
Dependency Cycle
```text
thread 'main' panicked at 'System dependencies contain cycle(s).
schedule has 1 before/after cycle(s):
cycle 1: system set 'A' must run before itself
system set 'A'
... which must run before system set 'B'
... which must run before system set 'A'
', crates\bevy_ecs\src\schedule\schedule.rs:228:13
```
```text
thread 'main' panicked at 'System dependencies contain cycle(s).
schedule has 1 before/after cycle(s):
cycle 1: system 'foo' must run before itself
system 'foo'
... which must run before system 'bar'
... which must run before system 'foo'
', crates\bevy_ecs\src\schedule\schedule.rs:228:13
```
Hierarchy Cycle
```text
thread 'main' panicked at 'System set hierarchy contains cycle(s).
schedule has 1 in_set cycle(s):
cycle 1: set 'A' contains itself
set 'A'
... which contains set 'B'
... which contains set 'A'
', crates\bevy_ecs\src\schedule\schedule.rs:230:13
```
System Type Set
```text
thread 'main' panicked at 'Tried to order against `SystemTypeSet(fn foo())` in a schedule that has more than one `SystemTypeSet(fn foo())` instance. `SystemTypeSet(fn foo())` is a `SystemTypeSet` and cannot be used for ordering if ambiguous. Use a different set without this restriction.', crates\bevy_ecs\src\schedule\schedule.rs:230:13
```
Hierarchy Redundancy
```text
thread 'main' panicked at 'System set hierarchy contains redundant edges.
hierarchy contains redundant edge(s) -- system set 'X' cannot be child of set 'A', longer path exists
', crates\bevy_ecs\src\schedule\schedule.rs:230:13
```
Systems have ordering but interset
```text
thread 'main' panicked at '`A` and `C` have a `before`-`after` relationship (which may be transitive) but share systems.', crates\bevy_ecs\src\schedule\schedule.rs:227:51
```
Cross Dependency
```text
thread 'main' panicked at '`A` and `B` have both `in_set` and `before`-`after` relationships (these might be transitive). This combination is unsolvable as a system cannot run before or after a set it belongs to.', crates\bevy_ecs\src\schedule\schedule.rs:230:13
```
Ambiguity
```text
thread 'main' panicked at 'Systems with conflicting access have indeterminate run order.
1 pairs of systems with conflicting data access have indeterminate execution order. Consider adding `before`, `after`, or `ambiguous_with` relationships between these:
-- res_mut and res_ref
conflict on: ["bevymark::ambiguity::X"]
', crates\bevy_ecs\src\schedule\schedule.rs:230:13
```
2023-08-27 17:54:59 +00:00
|
|
|
assert!(matches!(
|
|
|
|
result,
|
|
|
|
Err(ScheduleBuildError::DependencyCycle(_))
|
|
|
|
));
|
2023-01-17 01:39:17 +00:00
|
|
|
|
|
|
|
fn foo() {}
|
|
|
|
fn bar() {}
|
|
|
|
|
|
|
|
let mut world = World::new();
|
2023-08-28 20:44:48 +00:00
|
|
|
let mut schedule = Schedule::default();
|
2023-01-17 01:39:17 +00:00
|
|
|
|
|
|
|
schedule.add_systems((foo.after(bar), bar.after(foo)));
|
|
|
|
let result = schedule.initialize(&mut world);
|
Refactor build_schedule and related errors (#9579)
# Objective
- break up large build_schedule system to make it easier to read
- Clean up related error messages.
- I have a follow up PR that adds the schedule name to the error
messages, but wanted to break this up from that.
## Changelog
- refactor `build_schedule` to be easier to read
## Sample Error Messages
Dependency Cycle
```text
thread 'main' panicked at 'System dependencies contain cycle(s).
schedule has 1 before/after cycle(s):
cycle 1: system set 'A' must run before itself
system set 'A'
... which must run before system set 'B'
... which must run before system set 'A'
', crates\bevy_ecs\src\schedule\schedule.rs:228:13
```
```text
thread 'main' panicked at 'System dependencies contain cycle(s).
schedule has 1 before/after cycle(s):
cycle 1: system 'foo' must run before itself
system 'foo'
... which must run before system 'bar'
... which must run before system 'foo'
', crates\bevy_ecs\src\schedule\schedule.rs:228:13
```
Hierarchy Cycle
```text
thread 'main' panicked at 'System set hierarchy contains cycle(s).
schedule has 1 in_set cycle(s):
cycle 1: set 'A' contains itself
set 'A'
... which contains set 'B'
... which contains set 'A'
', crates\bevy_ecs\src\schedule\schedule.rs:230:13
```
System Type Set
```text
thread 'main' panicked at 'Tried to order against `SystemTypeSet(fn foo())` in a schedule that has more than one `SystemTypeSet(fn foo())` instance. `SystemTypeSet(fn foo())` is a `SystemTypeSet` and cannot be used for ordering if ambiguous. Use a different set without this restriction.', crates\bevy_ecs\src\schedule\schedule.rs:230:13
```
Hierarchy Redundancy
```text
thread 'main' panicked at 'System set hierarchy contains redundant edges.
hierarchy contains redundant edge(s) -- system set 'X' cannot be child of set 'A', longer path exists
', crates\bevy_ecs\src\schedule\schedule.rs:230:13
```
Systems have ordering but interset
```text
thread 'main' panicked at '`A` and `C` have a `before`-`after` relationship (which may be transitive) but share systems.', crates\bevy_ecs\src\schedule\schedule.rs:227:51
```
Cross Dependency
```text
thread 'main' panicked at '`A` and `B` have both `in_set` and `before`-`after` relationships (these might be transitive). This combination is unsolvable as a system cannot run before or after a set it belongs to.', crates\bevy_ecs\src\schedule\schedule.rs:230:13
```
Ambiguity
```text
thread 'main' panicked at 'Systems with conflicting access have indeterminate run order.
1 pairs of systems with conflicting data access have indeterminate execution order. Consider adding `before`, `after`, or `ambiguous_with` relationships between these:
-- res_mut and res_ref
conflict on: ["bevymark::ambiguity::X"]
', crates\bevy_ecs\src\schedule\schedule.rs:230:13
```
2023-08-27 17:54:59 +00:00
|
|
|
assert!(matches!(
|
|
|
|
result,
|
|
|
|
Err(ScheduleBuildError::DependencyCycle(_))
|
|
|
|
));
|
2023-01-17 01:39:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[should_panic]
|
|
|
|
fn hierarchy_loop() {
|
2023-08-28 20:44:48 +00:00
|
|
|
let mut schedule = Schedule::default();
|
2023-09-05 17:15:27 +00:00
|
|
|
schedule.configure_sets(TestSet::X.in_set(TestSet::X));
|
2023-01-17 01:39:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn hierarchy_cycle() {
|
|
|
|
let mut world = World::new();
|
2023-08-28 20:44:48 +00:00
|
|
|
let mut schedule = Schedule::default();
|
2023-01-17 01:39:17 +00:00
|
|
|
|
2023-09-05 17:15:27 +00:00
|
|
|
schedule.configure_sets(TestSet::A.in_set(TestSet::B));
|
|
|
|
schedule.configure_sets(TestSet::B.in_set(TestSet::A));
|
2023-01-17 01:39:17 +00:00
|
|
|
|
|
|
|
let result = schedule.initialize(&mut world);
|
Refactor build_schedule and related errors (#9579)
# Objective
- break up large build_schedule system to make it easier to read
- Clean up related error messages.
- I have a follow up PR that adds the schedule name to the error
messages, but wanted to break this up from that.
## Changelog
- refactor `build_schedule` to be easier to read
## Sample Error Messages
Dependency Cycle
```text
thread 'main' panicked at 'System dependencies contain cycle(s).
schedule has 1 before/after cycle(s):
cycle 1: system set 'A' must run before itself
system set 'A'
... which must run before system set 'B'
... which must run before system set 'A'
', crates\bevy_ecs\src\schedule\schedule.rs:228:13
```
```text
thread 'main' panicked at 'System dependencies contain cycle(s).
schedule has 1 before/after cycle(s):
cycle 1: system 'foo' must run before itself
system 'foo'
... which must run before system 'bar'
... which must run before system 'foo'
', crates\bevy_ecs\src\schedule\schedule.rs:228:13
```
Hierarchy Cycle
```text
thread 'main' panicked at 'System set hierarchy contains cycle(s).
schedule has 1 in_set cycle(s):
cycle 1: set 'A' contains itself
set 'A'
... which contains set 'B'
... which contains set 'A'
', crates\bevy_ecs\src\schedule\schedule.rs:230:13
```
System Type Set
```text
thread 'main' panicked at 'Tried to order against `SystemTypeSet(fn foo())` in a schedule that has more than one `SystemTypeSet(fn foo())` instance. `SystemTypeSet(fn foo())` is a `SystemTypeSet` and cannot be used for ordering if ambiguous. Use a different set without this restriction.', crates\bevy_ecs\src\schedule\schedule.rs:230:13
```
Hierarchy Redundancy
```text
thread 'main' panicked at 'System set hierarchy contains redundant edges.
hierarchy contains redundant edge(s) -- system set 'X' cannot be child of set 'A', longer path exists
', crates\bevy_ecs\src\schedule\schedule.rs:230:13
```
Systems have ordering but interset
```text
thread 'main' panicked at '`A` and `C` have a `before`-`after` relationship (which may be transitive) but share systems.', crates\bevy_ecs\src\schedule\schedule.rs:227:51
```
Cross Dependency
```text
thread 'main' panicked at '`A` and `B` have both `in_set` and `before`-`after` relationships (these might be transitive). This combination is unsolvable as a system cannot run before or after a set it belongs to.', crates\bevy_ecs\src\schedule\schedule.rs:230:13
```
Ambiguity
```text
thread 'main' panicked at 'Systems with conflicting access have indeterminate run order.
1 pairs of systems with conflicting data access have indeterminate execution order. Consider adding `before`, `after`, or `ambiguous_with` relationships between these:
-- res_mut and res_ref
conflict on: ["bevymark::ambiguity::X"]
', crates\bevy_ecs\src\schedule\schedule.rs:230:13
```
2023-08-27 17:54:59 +00:00
|
|
|
assert!(matches!(result, Err(ScheduleBuildError::HierarchyCycle(_))));
|
2023-01-17 01:39:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn system_type_set_ambiguity() {
|
|
|
|
// Define some systems.
|
|
|
|
fn foo() {}
|
|
|
|
fn bar() {}
|
|
|
|
|
|
|
|
let mut world = World::new();
|
2023-08-28 20:44:48 +00:00
|
|
|
let mut schedule = Schedule::default();
|
2023-01-17 01:39:17 +00:00
|
|
|
|
|
|
|
// Schedule `bar` to run after `foo`.
|
2023-03-10 18:15:22 +00:00
|
|
|
schedule.add_systems((foo, bar.after(foo)));
|
2023-01-17 01:39:17 +00:00
|
|
|
|
|
|
|
// There's only one `foo`, so it's fine.
|
|
|
|
let result = schedule.initialize(&mut world);
|
|
|
|
assert!(result.is_ok());
|
|
|
|
|
|
|
|
// Schedule another `foo`.
|
2023-03-18 01:45:34 +00:00
|
|
|
schedule.add_systems(foo);
|
2023-01-17 01:39:17 +00:00
|
|
|
|
|
|
|
// When there are multiple instances of `foo`, dependencies on
|
|
|
|
// `foo` are no longer allowed. Too much ambiguity.
|
|
|
|
let result = schedule.initialize(&mut world);
|
|
|
|
assert!(matches!(
|
|
|
|
result,
|
|
|
|
Err(ScheduleBuildError::SystemTypeSetAmbiguity(_))
|
|
|
|
));
|
|
|
|
|
|
|
|
// same goes for `ambiguous_with`
|
2023-08-28 20:44:48 +00:00
|
|
|
let mut schedule = Schedule::default();
|
2023-03-18 01:45:34 +00:00
|
|
|
schedule.add_systems(foo);
|
|
|
|
schedule.add_systems(bar.ambiguous_with(foo));
|
2023-01-17 01:39:17 +00:00
|
|
|
let result = schedule.initialize(&mut world);
|
|
|
|
assert!(result.is_ok());
|
2023-03-18 01:45:34 +00:00
|
|
|
schedule.add_systems(foo);
|
2023-01-17 01:39:17 +00:00
|
|
|
let result = schedule.initialize(&mut world);
|
|
|
|
assert!(matches!(
|
|
|
|
result,
|
|
|
|
Err(ScheduleBuildError::SystemTypeSetAmbiguity(_))
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[should_panic]
|
|
|
|
fn configure_system_type_set() {
|
|
|
|
fn foo() {}
|
2023-08-28 20:44:48 +00:00
|
|
|
let mut schedule = Schedule::default();
|
2023-09-05 17:15:27 +00:00
|
|
|
schedule.configure_sets(foo.into_system_set());
|
2023-01-17 01:39:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn hierarchy_redundancy() {
|
|
|
|
let mut world = World::new();
|
2023-08-28 20:44:48 +00:00
|
|
|
let mut schedule = Schedule::default();
|
2023-01-17 01:39:17 +00:00
|
|
|
|
2023-02-17 15:17:52 +00:00
|
|
|
schedule.set_build_settings(ScheduleBuildSettings {
|
|
|
|
hierarchy_detection: LogLevel::Error,
|
|
|
|
..Default::default()
|
|
|
|
});
|
2023-01-17 01:39:17 +00:00
|
|
|
|
|
|
|
// Add `A`.
|
2023-09-05 17:15:27 +00:00
|
|
|
schedule.configure_sets(TestSet::A);
|
2023-01-17 01:39:17 +00:00
|
|
|
|
|
|
|
// Add `B` as child of `A`.
|
2023-09-05 17:15:27 +00:00
|
|
|
schedule.configure_sets(TestSet::B.in_set(TestSet::A));
|
2023-01-17 01:39:17 +00:00
|
|
|
|
|
|
|
// Add `X` as child of both `A` and `B`.
|
2023-09-05 17:15:27 +00:00
|
|
|
schedule.configure_sets(TestSet::X.in_set(TestSet::A).in_set(TestSet::B));
|
2023-01-17 01:39:17 +00:00
|
|
|
|
|
|
|
// `X` cannot be the `A`'s child and grandchild at the same time.
|
|
|
|
let result = schedule.initialize(&mut world);
|
|
|
|
assert!(matches!(
|
|
|
|
result,
|
Refactor build_schedule and related errors (#9579)
# Objective
- break up large build_schedule system to make it easier to read
- Clean up related error messages.
- I have a follow up PR that adds the schedule name to the error
messages, but wanted to break this up from that.
## Changelog
- refactor `build_schedule` to be easier to read
## Sample Error Messages
Dependency Cycle
```text
thread 'main' panicked at 'System dependencies contain cycle(s).
schedule has 1 before/after cycle(s):
cycle 1: system set 'A' must run before itself
system set 'A'
... which must run before system set 'B'
... which must run before system set 'A'
', crates\bevy_ecs\src\schedule\schedule.rs:228:13
```
```text
thread 'main' panicked at 'System dependencies contain cycle(s).
schedule has 1 before/after cycle(s):
cycle 1: system 'foo' must run before itself
system 'foo'
... which must run before system 'bar'
... which must run before system 'foo'
', crates\bevy_ecs\src\schedule\schedule.rs:228:13
```
Hierarchy Cycle
```text
thread 'main' panicked at 'System set hierarchy contains cycle(s).
schedule has 1 in_set cycle(s):
cycle 1: set 'A' contains itself
set 'A'
... which contains set 'B'
... which contains set 'A'
', crates\bevy_ecs\src\schedule\schedule.rs:230:13
```
System Type Set
```text
thread 'main' panicked at 'Tried to order against `SystemTypeSet(fn foo())` in a schedule that has more than one `SystemTypeSet(fn foo())` instance. `SystemTypeSet(fn foo())` is a `SystemTypeSet` and cannot be used for ordering if ambiguous. Use a different set without this restriction.', crates\bevy_ecs\src\schedule\schedule.rs:230:13
```
Hierarchy Redundancy
```text
thread 'main' panicked at 'System set hierarchy contains redundant edges.
hierarchy contains redundant edge(s) -- system set 'X' cannot be child of set 'A', longer path exists
', crates\bevy_ecs\src\schedule\schedule.rs:230:13
```
Systems have ordering but interset
```text
thread 'main' panicked at '`A` and `C` have a `before`-`after` relationship (which may be transitive) but share systems.', crates\bevy_ecs\src\schedule\schedule.rs:227:51
```
Cross Dependency
```text
thread 'main' panicked at '`A` and `B` have both `in_set` and `before`-`after` relationships (these might be transitive). This combination is unsolvable as a system cannot run before or after a set it belongs to.', crates\bevy_ecs\src\schedule\schedule.rs:230:13
```
Ambiguity
```text
thread 'main' panicked at 'Systems with conflicting access have indeterminate run order.
1 pairs of systems with conflicting data access have indeterminate execution order. Consider adding `before`, `after`, or `ambiguous_with` relationships between these:
-- res_mut and res_ref
conflict on: ["bevymark::ambiguity::X"]
', crates\bevy_ecs\src\schedule\schedule.rs:230:13
```
2023-08-27 17:54:59 +00:00
|
|
|
Err(ScheduleBuildError::HierarchyRedundancy(_))
|
2023-01-17 01:39:17 +00:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn cross_dependency() {
|
|
|
|
let mut world = World::new();
|
2023-08-28 20:44:48 +00:00
|
|
|
let mut schedule = Schedule::default();
|
2023-01-17 01:39:17 +00:00
|
|
|
|
|
|
|
// Add `B` and give it both kinds of relationships with `A`.
|
2023-09-05 17:15:27 +00:00
|
|
|
schedule.configure_sets(TestSet::B.in_set(TestSet::A));
|
|
|
|
schedule.configure_sets(TestSet::B.after(TestSet::A));
|
2023-01-17 01:39:17 +00:00
|
|
|
let result = schedule.initialize(&mut world);
|
|
|
|
assert!(matches!(
|
|
|
|
result,
|
|
|
|
Err(ScheduleBuildError::CrossDependency(_, _))
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
Migrate engine to Schedule v3 (#7267)
Huge thanks to @maniwani, @devil-ira, @hymm, @cart, @superdump and @jakobhellermann for the help with this PR.
# Objective
- Followup #6587.
- Minimal integration for the Stageless Scheduling RFC: https://github.com/bevyengine/rfcs/pull/45
## Solution
- [x] Remove old scheduling module
- [x] Migrate new methods to no longer use extension methods
- [x] Fix compiler errors
- [x] Fix benchmarks
- [x] Fix examples
- [x] Fix docs
- [x] Fix tests
## Changelog
### Added
- a large number of methods on `App` to work with schedules ergonomically
- the `CoreSchedule` enum
- `App::add_extract_system` via the `RenderingAppExtension` trait extension method
- the private `prepare_view_uniforms` system now has a public system set for scheduling purposes, called `ViewSet::PrepareUniforms`
### Removed
- stages, and all code that mentions stages
- states have been dramatically simplified, and no longer use a stack
- `RunCriteriaLabel`
- `AsSystemLabel` trait
- `on_hierarchy_reports_enabled` run criteria (now just uses an ad hoc resource checking run condition)
- systems in `RenderSet/Stage::Extract` no longer warn when they do not read data from the main world
- `RunCriteriaLabel`
- `transform_propagate_system_set`: this was a nonstandard pattern that didn't actually provide enough control. The systems are already `pub`: the docs have been updated to ensure that the third-party usage is clear.
### Changed
- `System::default_labels` is now `System::default_system_sets`.
- `App::add_default_labels` is now `App::add_default_sets`
- `CoreStage` and `StartupStage` enums are now `CoreSet` and `StartupSet`
- `App::add_system_set` was renamed to `App::add_systems`
- The `StartupSchedule` label is now defined as part of the `CoreSchedules` enum
- `.label(SystemLabel)` is now referred to as `.in_set(SystemSet)`
- `SystemLabel` trait was replaced by `SystemSet`
- `SystemTypeIdLabel<T>` was replaced by `SystemSetType<T>`
- The `ReportHierarchyIssue` resource now has a public constructor (`new`), and implements `PartialEq`
- Fixed time steps now use a schedule (`CoreSchedule::FixedTimeStep`) rather than a run criteria.
- Adding rendering extraction systems now panics rather than silently failing if no subapp with the `RenderApp` label is found.
- the `calculate_bounds` system, with the `CalculateBounds` label, is now in `CoreSet::Update`, rather than in `CoreSet::PostUpdate` before commands are applied.
- `SceneSpawnerSystem` now runs under `CoreSet::Update`, rather than `CoreStage::PreUpdate.at_end()`.
- `bevy_pbr::add_clusters` is no longer an exclusive system
- the top level `bevy_ecs::schedule` module was replaced with `bevy_ecs::scheduling`
- `tick_global_task_pools_on_main_thread` is no longer run as an exclusive system. Instead, it has been replaced by `tick_global_task_pools`, which uses a `NonSend` resource to force running on the main thread.
## Migration Guide
- Calls to `.label(MyLabel)` should be replaced with `.in_set(MySet)`
- Stages have been removed. Replace these with system sets, and then add command flushes using the `apply_system_buffers` exclusive system where needed.
- The `CoreStage`, `StartupStage, `RenderStage` and `AssetStage` enums have been replaced with `CoreSet`, `StartupSet, `RenderSet` and `AssetSet`. The same scheduling guarantees have been preserved.
- Systems are no longer added to `CoreSet::Update` by default. Add systems manually if this behavior is needed, although you should consider adding your game logic systems to `CoreSchedule::FixedTimestep` instead for more reliable framerate-independent behavior.
- Similarly, startup systems are no longer part of `StartupSet::Startup` by default. In most cases, this won't matter to you.
- For example, `add_system_to_stage(CoreStage::PostUpdate, my_system)` should be replaced with
- `add_system(my_system.in_set(CoreSet::PostUpdate)`
- When testing systems or otherwise running them in a headless fashion, simply construct and run a schedule using `Schedule::new()` and `World::run_schedule` rather than constructing stages
- Run criteria have been renamed to run conditions. These can now be combined with each other and with states.
- Looping run criteria and state stacks have been removed. Use an exclusive system that runs a schedule if you need this level of control over system control flow.
- For app-level control flow over which schedules get run when (such as for rollback networking), create your own schedule and insert it under the `CoreSchedule::Outer` label.
- Fixed timesteps are now evaluated in a schedule, rather than controlled via run criteria. The `run_fixed_timestep` system runs this schedule between `CoreSet::First` and `CoreSet::PreUpdate` by default.
- Command flush points introduced by `AssetStage` have been removed. If you were relying on these, add them back manually.
- Adding extract systems is now typically done directly on the main app. Make sure the `RenderingAppExtension` trait is in scope, then call `app.add_extract_system(my_system)`.
- the `calculate_bounds` system, with the `CalculateBounds` label, is now in `CoreSet::Update`, rather than in `CoreSet::PostUpdate` before commands are applied. You may need to order your movement systems to occur before this system in order to avoid system order ambiguities in culling behavior.
- the `RenderLabel` `AppLabel` was renamed to `RenderApp` for clarity
- `App::add_state` now takes 0 arguments: the starting state is set based on the `Default` impl.
- Instead of creating `SystemSet` containers for systems that run in stages, simply use `.on_enter::<State::Variant>()` or its `on_exit` or `on_update` siblings.
- `SystemLabel` derives should be replaced with `SystemSet`. You will also need to add the `Debug`, `PartialEq`, `Eq`, and `Hash` traits to satisfy the new trait bounds.
- `with_run_criteria` has been renamed to `run_if`. Run criteria have been renamed to run conditions for clarity, and should now simply return a bool.
- States have been dramatically simplified: there is no longer a "state stack". To queue a transition to the next state, call `NextState::set`
## TODO
- [x] remove dead methods on App and World
- [x] add `App::add_system_to_schedule` and `App::add_systems_to_schedule`
- [x] avoid adding the default system set at inappropriate times
- [x] remove any accidental cycles in the default plugins schedule
- [x] migrate benchmarks
- [x] expose explicit labels for the built-in command flush points
- [x] migrate engine code
- [x] remove all mentions of stages from the docs
- [x] verify docs for States
- [x] fix uses of exclusive systems that use .end / .at_start / .before_commands
- [x] migrate RenderStage and AssetStage
- [x] migrate examples
- [x] ensure that transform propagation is exported in a sufficiently public way (the systems are already pub)
- [x] ensure that on_enter schedules are run at least once before the main app
- [x] re-enable opt-in to execution order ambiguities
- [x] revert change to `update_bounds` to ensure it runs in `PostUpdate`
- [x] test all examples
- [x] unbreak directional lights
- [x] unbreak shadows (see 3d_scene, 3d_shape, lighting, transparaency_3d examples)
- [x] game menu example shows loading screen and menu simultaneously
- [x] display settings menu is a blank screen
- [x] `without_winit` example panics
- [x] ensure all tests pass
- [x] SubApp doc test fails
- [x] runs_spawn_local tasks fails
- [x] [Fix panic_when_hierachy_cycle test hanging](https://github.com/alice-i-cecile/bevy/pull/120)
## Points of Difficulty and Controversy
**Reviewers, please give feedback on these and look closely**
1. Default sets, from the RFC, have been removed. These added a tremendous amount of implicit complexity and result in hard to debug scheduling errors. They're going to be tackled in the form of "base sets" by @cart in a followup.
2. The outer schedule controls which schedule is run when `App::update` is called.
3. I implemented `Label for `Box<dyn Label>` for our label types. This enables us to store schedule labels in concrete form, and then later run them. I ran into the same set of problems when working with one-shot systems. We've previously investigated this pattern in depth, and it does not appear to lead to extra indirection with nested boxes.
4. `SubApp::update` simply runs the default schedule once. This sucks, but this whole API is incomplete and this was the minimal changeset.
5. `time_system` and `tick_global_task_pools_on_main_thread` no longer use exclusive systems to attempt to force scheduling order
6. Implemetnation strategy for fixed timesteps
7. `AssetStage` was migrated to `AssetSet` without reintroducing command flush points. These did not appear to be used, and it's nice to remove these bottlenecks.
8. Migration of `bevy_render/lib.rs` and pipelined rendering. The logic here is unusually tricky, as we have complex scheduling requirements.
## Future Work (ideally before 0.10)
- Rename schedule_v3 module to schedule or scheduling
- Add a derive macro to states, and likely a `EnumIter` trait of some form
- Figure out what exactly to do with the "systems added should basically work by default" problem
- Improve ergonomics for working with fixed timesteps and states
- Polish FixedTime API to match Time
- Rebase and merge #7415
- Resolve all internal ambiguities (blocked on better tools, especially #7442)
- Add "base sets" to replace the removed default sets.
2023-02-06 02:04:50 +00:00
|
|
|
#[test]
|
|
|
|
fn sets_have_order_but_intersect() {
|
|
|
|
let mut world = World::new();
|
2023-08-28 20:44:48 +00:00
|
|
|
let mut schedule = Schedule::default();
|
Migrate engine to Schedule v3 (#7267)
Huge thanks to @maniwani, @devil-ira, @hymm, @cart, @superdump and @jakobhellermann for the help with this PR.
# Objective
- Followup #6587.
- Minimal integration for the Stageless Scheduling RFC: https://github.com/bevyengine/rfcs/pull/45
## Solution
- [x] Remove old scheduling module
- [x] Migrate new methods to no longer use extension methods
- [x] Fix compiler errors
- [x] Fix benchmarks
- [x] Fix examples
- [x] Fix docs
- [x] Fix tests
## Changelog
### Added
- a large number of methods on `App` to work with schedules ergonomically
- the `CoreSchedule` enum
- `App::add_extract_system` via the `RenderingAppExtension` trait extension method
- the private `prepare_view_uniforms` system now has a public system set for scheduling purposes, called `ViewSet::PrepareUniforms`
### Removed
- stages, and all code that mentions stages
- states have been dramatically simplified, and no longer use a stack
- `RunCriteriaLabel`
- `AsSystemLabel` trait
- `on_hierarchy_reports_enabled` run criteria (now just uses an ad hoc resource checking run condition)
- systems in `RenderSet/Stage::Extract` no longer warn when they do not read data from the main world
- `RunCriteriaLabel`
- `transform_propagate_system_set`: this was a nonstandard pattern that didn't actually provide enough control. The systems are already `pub`: the docs have been updated to ensure that the third-party usage is clear.
### Changed
- `System::default_labels` is now `System::default_system_sets`.
- `App::add_default_labels` is now `App::add_default_sets`
- `CoreStage` and `StartupStage` enums are now `CoreSet` and `StartupSet`
- `App::add_system_set` was renamed to `App::add_systems`
- The `StartupSchedule` label is now defined as part of the `CoreSchedules` enum
- `.label(SystemLabel)` is now referred to as `.in_set(SystemSet)`
- `SystemLabel` trait was replaced by `SystemSet`
- `SystemTypeIdLabel<T>` was replaced by `SystemSetType<T>`
- The `ReportHierarchyIssue` resource now has a public constructor (`new`), and implements `PartialEq`
- Fixed time steps now use a schedule (`CoreSchedule::FixedTimeStep`) rather than a run criteria.
- Adding rendering extraction systems now panics rather than silently failing if no subapp with the `RenderApp` label is found.
- the `calculate_bounds` system, with the `CalculateBounds` label, is now in `CoreSet::Update`, rather than in `CoreSet::PostUpdate` before commands are applied.
- `SceneSpawnerSystem` now runs under `CoreSet::Update`, rather than `CoreStage::PreUpdate.at_end()`.
- `bevy_pbr::add_clusters` is no longer an exclusive system
- the top level `bevy_ecs::schedule` module was replaced with `bevy_ecs::scheduling`
- `tick_global_task_pools_on_main_thread` is no longer run as an exclusive system. Instead, it has been replaced by `tick_global_task_pools`, which uses a `NonSend` resource to force running on the main thread.
## Migration Guide
- Calls to `.label(MyLabel)` should be replaced with `.in_set(MySet)`
- Stages have been removed. Replace these with system sets, and then add command flushes using the `apply_system_buffers` exclusive system where needed.
- The `CoreStage`, `StartupStage, `RenderStage` and `AssetStage` enums have been replaced with `CoreSet`, `StartupSet, `RenderSet` and `AssetSet`. The same scheduling guarantees have been preserved.
- Systems are no longer added to `CoreSet::Update` by default. Add systems manually if this behavior is needed, although you should consider adding your game logic systems to `CoreSchedule::FixedTimestep` instead for more reliable framerate-independent behavior.
- Similarly, startup systems are no longer part of `StartupSet::Startup` by default. In most cases, this won't matter to you.
- For example, `add_system_to_stage(CoreStage::PostUpdate, my_system)` should be replaced with
- `add_system(my_system.in_set(CoreSet::PostUpdate)`
- When testing systems or otherwise running them in a headless fashion, simply construct and run a schedule using `Schedule::new()` and `World::run_schedule` rather than constructing stages
- Run criteria have been renamed to run conditions. These can now be combined with each other and with states.
- Looping run criteria and state stacks have been removed. Use an exclusive system that runs a schedule if you need this level of control over system control flow.
- For app-level control flow over which schedules get run when (such as for rollback networking), create your own schedule and insert it under the `CoreSchedule::Outer` label.
- Fixed timesteps are now evaluated in a schedule, rather than controlled via run criteria. The `run_fixed_timestep` system runs this schedule between `CoreSet::First` and `CoreSet::PreUpdate` by default.
- Command flush points introduced by `AssetStage` have been removed. If you were relying on these, add them back manually.
- Adding extract systems is now typically done directly on the main app. Make sure the `RenderingAppExtension` trait is in scope, then call `app.add_extract_system(my_system)`.
- the `calculate_bounds` system, with the `CalculateBounds` label, is now in `CoreSet::Update`, rather than in `CoreSet::PostUpdate` before commands are applied. You may need to order your movement systems to occur before this system in order to avoid system order ambiguities in culling behavior.
- the `RenderLabel` `AppLabel` was renamed to `RenderApp` for clarity
- `App::add_state` now takes 0 arguments: the starting state is set based on the `Default` impl.
- Instead of creating `SystemSet` containers for systems that run in stages, simply use `.on_enter::<State::Variant>()` or its `on_exit` or `on_update` siblings.
- `SystemLabel` derives should be replaced with `SystemSet`. You will also need to add the `Debug`, `PartialEq`, `Eq`, and `Hash` traits to satisfy the new trait bounds.
- `with_run_criteria` has been renamed to `run_if`. Run criteria have been renamed to run conditions for clarity, and should now simply return a bool.
- States have been dramatically simplified: there is no longer a "state stack". To queue a transition to the next state, call `NextState::set`
## TODO
- [x] remove dead methods on App and World
- [x] add `App::add_system_to_schedule` and `App::add_systems_to_schedule`
- [x] avoid adding the default system set at inappropriate times
- [x] remove any accidental cycles in the default plugins schedule
- [x] migrate benchmarks
- [x] expose explicit labels for the built-in command flush points
- [x] migrate engine code
- [x] remove all mentions of stages from the docs
- [x] verify docs for States
- [x] fix uses of exclusive systems that use .end / .at_start / .before_commands
- [x] migrate RenderStage and AssetStage
- [x] migrate examples
- [x] ensure that transform propagation is exported in a sufficiently public way (the systems are already pub)
- [x] ensure that on_enter schedules are run at least once before the main app
- [x] re-enable opt-in to execution order ambiguities
- [x] revert change to `update_bounds` to ensure it runs in `PostUpdate`
- [x] test all examples
- [x] unbreak directional lights
- [x] unbreak shadows (see 3d_scene, 3d_shape, lighting, transparaency_3d examples)
- [x] game menu example shows loading screen and menu simultaneously
- [x] display settings menu is a blank screen
- [x] `without_winit` example panics
- [x] ensure all tests pass
- [x] SubApp doc test fails
- [x] runs_spawn_local tasks fails
- [x] [Fix panic_when_hierachy_cycle test hanging](https://github.com/alice-i-cecile/bevy/pull/120)
## Points of Difficulty and Controversy
**Reviewers, please give feedback on these and look closely**
1. Default sets, from the RFC, have been removed. These added a tremendous amount of implicit complexity and result in hard to debug scheduling errors. They're going to be tackled in the form of "base sets" by @cart in a followup.
2. The outer schedule controls which schedule is run when `App::update` is called.
3. I implemented `Label for `Box<dyn Label>` for our label types. This enables us to store schedule labels in concrete form, and then later run them. I ran into the same set of problems when working with one-shot systems. We've previously investigated this pattern in depth, and it does not appear to lead to extra indirection with nested boxes.
4. `SubApp::update` simply runs the default schedule once. This sucks, but this whole API is incomplete and this was the minimal changeset.
5. `time_system` and `tick_global_task_pools_on_main_thread` no longer use exclusive systems to attempt to force scheduling order
6. Implemetnation strategy for fixed timesteps
7. `AssetStage` was migrated to `AssetSet` without reintroducing command flush points. These did not appear to be used, and it's nice to remove these bottlenecks.
8. Migration of `bevy_render/lib.rs` and pipelined rendering. The logic here is unusually tricky, as we have complex scheduling requirements.
## Future Work (ideally before 0.10)
- Rename schedule_v3 module to schedule or scheduling
- Add a derive macro to states, and likely a `EnumIter` trait of some form
- Figure out what exactly to do with the "systems added should basically work by default" problem
- Improve ergonomics for working with fixed timesteps and states
- Polish FixedTime API to match Time
- Rebase and merge #7415
- Resolve all internal ambiguities (blocked on better tools, especially #7442)
- Add "base sets" to replace the removed default sets.
2023-02-06 02:04:50 +00:00
|
|
|
|
|
|
|
fn foo() {}
|
|
|
|
|
|
|
|
// Add `foo` to both `A` and `C`.
|
2023-03-18 01:45:34 +00:00
|
|
|
schedule.add_systems(foo.in_set(TestSet::A).in_set(TestSet::C));
|
Migrate engine to Schedule v3 (#7267)
Huge thanks to @maniwani, @devil-ira, @hymm, @cart, @superdump and @jakobhellermann for the help with this PR.
# Objective
- Followup #6587.
- Minimal integration for the Stageless Scheduling RFC: https://github.com/bevyengine/rfcs/pull/45
## Solution
- [x] Remove old scheduling module
- [x] Migrate new methods to no longer use extension methods
- [x] Fix compiler errors
- [x] Fix benchmarks
- [x] Fix examples
- [x] Fix docs
- [x] Fix tests
## Changelog
### Added
- a large number of methods on `App` to work with schedules ergonomically
- the `CoreSchedule` enum
- `App::add_extract_system` via the `RenderingAppExtension` trait extension method
- the private `prepare_view_uniforms` system now has a public system set for scheduling purposes, called `ViewSet::PrepareUniforms`
### Removed
- stages, and all code that mentions stages
- states have been dramatically simplified, and no longer use a stack
- `RunCriteriaLabel`
- `AsSystemLabel` trait
- `on_hierarchy_reports_enabled` run criteria (now just uses an ad hoc resource checking run condition)
- systems in `RenderSet/Stage::Extract` no longer warn when they do not read data from the main world
- `RunCriteriaLabel`
- `transform_propagate_system_set`: this was a nonstandard pattern that didn't actually provide enough control. The systems are already `pub`: the docs have been updated to ensure that the third-party usage is clear.
### Changed
- `System::default_labels` is now `System::default_system_sets`.
- `App::add_default_labels` is now `App::add_default_sets`
- `CoreStage` and `StartupStage` enums are now `CoreSet` and `StartupSet`
- `App::add_system_set` was renamed to `App::add_systems`
- The `StartupSchedule` label is now defined as part of the `CoreSchedules` enum
- `.label(SystemLabel)` is now referred to as `.in_set(SystemSet)`
- `SystemLabel` trait was replaced by `SystemSet`
- `SystemTypeIdLabel<T>` was replaced by `SystemSetType<T>`
- The `ReportHierarchyIssue` resource now has a public constructor (`new`), and implements `PartialEq`
- Fixed time steps now use a schedule (`CoreSchedule::FixedTimeStep`) rather than a run criteria.
- Adding rendering extraction systems now panics rather than silently failing if no subapp with the `RenderApp` label is found.
- the `calculate_bounds` system, with the `CalculateBounds` label, is now in `CoreSet::Update`, rather than in `CoreSet::PostUpdate` before commands are applied.
- `SceneSpawnerSystem` now runs under `CoreSet::Update`, rather than `CoreStage::PreUpdate.at_end()`.
- `bevy_pbr::add_clusters` is no longer an exclusive system
- the top level `bevy_ecs::schedule` module was replaced with `bevy_ecs::scheduling`
- `tick_global_task_pools_on_main_thread` is no longer run as an exclusive system. Instead, it has been replaced by `tick_global_task_pools`, which uses a `NonSend` resource to force running on the main thread.
## Migration Guide
- Calls to `.label(MyLabel)` should be replaced with `.in_set(MySet)`
- Stages have been removed. Replace these with system sets, and then add command flushes using the `apply_system_buffers` exclusive system where needed.
- The `CoreStage`, `StartupStage, `RenderStage` and `AssetStage` enums have been replaced with `CoreSet`, `StartupSet, `RenderSet` and `AssetSet`. The same scheduling guarantees have been preserved.
- Systems are no longer added to `CoreSet::Update` by default. Add systems manually if this behavior is needed, although you should consider adding your game logic systems to `CoreSchedule::FixedTimestep` instead for more reliable framerate-independent behavior.
- Similarly, startup systems are no longer part of `StartupSet::Startup` by default. In most cases, this won't matter to you.
- For example, `add_system_to_stage(CoreStage::PostUpdate, my_system)` should be replaced with
- `add_system(my_system.in_set(CoreSet::PostUpdate)`
- When testing systems or otherwise running them in a headless fashion, simply construct and run a schedule using `Schedule::new()` and `World::run_schedule` rather than constructing stages
- Run criteria have been renamed to run conditions. These can now be combined with each other and with states.
- Looping run criteria and state stacks have been removed. Use an exclusive system that runs a schedule if you need this level of control over system control flow.
- For app-level control flow over which schedules get run when (such as for rollback networking), create your own schedule and insert it under the `CoreSchedule::Outer` label.
- Fixed timesteps are now evaluated in a schedule, rather than controlled via run criteria. The `run_fixed_timestep` system runs this schedule between `CoreSet::First` and `CoreSet::PreUpdate` by default.
- Command flush points introduced by `AssetStage` have been removed. If you were relying on these, add them back manually.
- Adding extract systems is now typically done directly on the main app. Make sure the `RenderingAppExtension` trait is in scope, then call `app.add_extract_system(my_system)`.
- the `calculate_bounds` system, with the `CalculateBounds` label, is now in `CoreSet::Update`, rather than in `CoreSet::PostUpdate` before commands are applied. You may need to order your movement systems to occur before this system in order to avoid system order ambiguities in culling behavior.
- the `RenderLabel` `AppLabel` was renamed to `RenderApp` for clarity
- `App::add_state` now takes 0 arguments: the starting state is set based on the `Default` impl.
- Instead of creating `SystemSet` containers for systems that run in stages, simply use `.on_enter::<State::Variant>()` or its `on_exit` or `on_update` siblings.
- `SystemLabel` derives should be replaced with `SystemSet`. You will also need to add the `Debug`, `PartialEq`, `Eq`, and `Hash` traits to satisfy the new trait bounds.
- `with_run_criteria` has been renamed to `run_if`. Run criteria have been renamed to run conditions for clarity, and should now simply return a bool.
- States have been dramatically simplified: there is no longer a "state stack". To queue a transition to the next state, call `NextState::set`
## TODO
- [x] remove dead methods on App and World
- [x] add `App::add_system_to_schedule` and `App::add_systems_to_schedule`
- [x] avoid adding the default system set at inappropriate times
- [x] remove any accidental cycles in the default plugins schedule
- [x] migrate benchmarks
- [x] expose explicit labels for the built-in command flush points
- [x] migrate engine code
- [x] remove all mentions of stages from the docs
- [x] verify docs for States
- [x] fix uses of exclusive systems that use .end / .at_start / .before_commands
- [x] migrate RenderStage and AssetStage
- [x] migrate examples
- [x] ensure that transform propagation is exported in a sufficiently public way (the systems are already pub)
- [x] ensure that on_enter schedules are run at least once before the main app
- [x] re-enable opt-in to execution order ambiguities
- [x] revert change to `update_bounds` to ensure it runs in `PostUpdate`
- [x] test all examples
- [x] unbreak directional lights
- [x] unbreak shadows (see 3d_scene, 3d_shape, lighting, transparaency_3d examples)
- [x] game menu example shows loading screen and menu simultaneously
- [x] display settings menu is a blank screen
- [x] `without_winit` example panics
- [x] ensure all tests pass
- [x] SubApp doc test fails
- [x] runs_spawn_local tasks fails
- [x] [Fix panic_when_hierachy_cycle test hanging](https://github.com/alice-i-cecile/bevy/pull/120)
## Points of Difficulty and Controversy
**Reviewers, please give feedback on these and look closely**
1. Default sets, from the RFC, have been removed. These added a tremendous amount of implicit complexity and result in hard to debug scheduling errors. They're going to be tackled in the form of "base sets" by @cart in a followup.
2. The outer schedule controls which schedule is run when `App::update` is called.
3. I implemented `Label for `Box<dyn Label>` for our label types. This enables us to store schedule labels in concrete form, and then later run them. I ran into the same set of problems when working with one-shot systems. We've previously investigated this pattern in depth, and it does not appear to lead to extra indirection with nested boxes.
4. `SubApp::update` simply runs the default schedule once. This sucks, but this whole API is incomplete and this was the minimal changeset.
5. `time_system` and `tick_global_task_pools_on_main_thread` no longer use exclusive systems to attempt to force scheduling order
6. Implemetnation strategy for fixed timesteps
7. `AssetStage` was migrated to `AssetSet` without reintroducing command flush points. These did not appear to be used, and it's nice to remove these bottlenecks.
8. Migration of `bevy_render/lib.rs` and pipelined rendering. The logic here is unusually tricky, as we have complex scheduling requirements.
## Future Work (ideally before 0.10)
- Rename schedule_v3 module to schedule or scheduling
- Add a derive macro to states, and likely a `EnumIter` trait of some form
- Figure out what exactly to do with the "systems added should basically work by default" problem
- Improve ergonomics for working with fixed timesteps and states
- Polish FixedTime API to match Time
- Rebase and merge #7415
- Resolve all internal ambiguities (blocked on better tools, especially #7442)
- Add "base sets" to replace the removed default sets.
2023-02-06 02:04:50 +00:00
|
|
|
|
|
|
|
// Order `A -> B -> C`.
|
|
|
|
schedule.configure_sets((
|
|
|
|
TestSet::A,
|
|
|
|
TestSet::B.after(TestSet::A),
|
|
|
|
TestSet::C.after(TestSet::B),
|
|
|
|
));
|
|
|
|
|
|
|
|
let result = schedule.initialize(&mut world);
|
|
|
|
// `foo` can't be in both `A` and `C` because they can't run at the same time.
|
|
|
|
assert!(matches!(
|
|
|
|
result,
|
|
|
|
Err(ScheduleBuildError::SetsHaveOrderButIntersect(_, _))
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2023-01-17 01:39:17 +00:00
|
|
|
#[test]
|
|
|
|
fn ambiguity() {
|
|
|
|
#[derive(Resource)]
|
|
|
|
struct X;
|
|
|
|
|
|
|
|
fn res_ref(_x: Res<X>) {}
|
|
|
|
fn res_mut(_x: ResMut<X>) {}
|
|
|
|
|
|
|
|
let mut world = World::new();
|
2023-08-28 20:44:48 +00:00
|
|
|
let mut schedule = Schedule::default();
|
2023-01-17 01:39:17 +00:00
|
|
|
|
2023-02-17 15:17:52 +00:00
|
|
|
schedule.set_build_settings(ScheduleBuildSettings {
|
|
|
|
ambiguity_detection: LogLevel::Error,
|
|
|
|
..Default::default()
|
|
|
|
});
|
2023-01-17 01:39:17 +00:00
|
|
|
|
|
|
|
schedule.add_systems((res_ref, res_mut));
|
|
|
|
let result = schedule.initialize(&mut world);
|
Refactor build_schedule and related errors (#9579)
# Objective
- break up large build_schedule system to make it easier to read
- Clean up related error messages.
- I have a follow up PR that adds the schedule name to the error
messages, but wanted to break this up from that.
## Changelog
- refactor `build_schedule` to be easier to read
## Sample Error Messages
Dependency Cycle
```text
thread 'main' panicked at 'System dependencies contain cycle(s).
schedule has 1 before/after cycle(s):
cycle 1: system set 'A' must run before itself
system set 'A'
... which must run before system set 'B'
... which must run before system set 'A'
', crates\bevy_ecs\src\schedule\schedule.rs:228:13
```
```text
thread 'main' panicked at 'System dependencies contain cycle(s).
schedule has 1 before/after cycle(s):
cycle 1: system 'foo' must run before itself
system 'foo'
... which must run before system 'bar'
... which must run before system 'foo'
', crates\bevy_ecs\src\schedule\schedule.rs:228:13
```
Hierarchy Cycle
```text
thread 'main' panicked at 'System set hierarchy contains cycle(s).
schedule has 1 in_set cycle(s):
cycle 1: set 'A' contains itself
set 'A'
... which contains set 'B'
... which contains set 'A'
', crates\bevy_ecs\src\schedule\schedule.rs:230:13
```
System Type Set
```text
thread 'main' panicked at 'Tried to order against `SystemTypeSet(fn foo())` in a schedule that has more than one `SystemTypeSet(fn foo())` instance. `SystemTypeSet(fn foo())` is a `SystemTypeSet` and cannot be used for ordering if ambiguous. Use a different set without this restriction.', crates\bevy_ecs\src\schedule\schedule.rs:230:13
```
Hierarchy Redundancy
```text
thread 'main' panicked at 'System set hierarchy contains redundant edges.
hierarchy contains redundant edge(s) -- system set 'X' cannot be child of set 'A', longer path exists
', crates\bevy_ecs\src\schedule\schedule.rs:230:13
```
Systems have ordering but interset
```text
thread 'main' panicked at '`A` and `C` have a `before`-`after` relationship (which may be transitive) but share systems.', crates\bevy_ecs\src\schedule\schedule.rs:227:51
```
Cross Dependency
```text
thread 'main' panicked at '`A` and `B` have both `in_set` and `before`-`after` relationships (these might be transitive). This combination is unsolvable as a system cannot run before or after a set it belongs to.', crates\bevy_ecs\src\schedule\schedule.rs:230:13
```
Ambiguity
```text
thread 'main' panicked at 'Systems with conflicting access have indeterminate run order.
1 pairs of systems with conflicting data access have indeterminate execution order. Consider adding `before`, `after`, or `ambiguous_with` relationships between these:
-- res_mut and res_ref
conflict on: ["bevymark::ambiguity::X"]
', crates\bevy_ecs\src\schedule\schedule.rs:230:13
```
2023-08-27 17:54:59 +00:00
|
|
|
assert!(matches!(result, Err(ScheduleBuildError::Ambiguity(_))));
|
2023-01-17 01:39:17 +00:00
|
|
|
}
|
|
|
|
}
|
2023-08-29 14:53:26 +00:00
|
|
|
|
|
|
|
mod system_ambiguity {
|
2024-09-27 00:59:59 +00:00
|
|
|
use alloc::collections::BTreeSet;
|
2023-10-04 02:34:28 +00:00
|
|
|
|
|
|
|
use super::*;
|
2023-08-29 14:53:26 +00:00
|
|
|
// Required to make the derive macro behave
|
|
|
|
use crate as bevy_ecs;
|
|
|
|
use crate::prelude::*;
|
|
|
|
|
|
|
|
#[derive(Resource)]
|
|
|
|
struct R;
|
|
|
|
|
|
|
|
#[derive(Component)]
|
|
|
|
struct A;
|
|
|
|
|
|
|
|
#[derive(Component)]
|
|
|
|
struct B;
|
|
|
|
|
|
|
|
// An event type
|
|
|
|
#[derive(Event)]
|
|
|
|
struct E;
|
|
|
|
|
2024-08-06 01:19:39 +00:00
|
|
|
#[derive(Resource, Component)]
|
|
|
|
struct RC;
|
|
|
|
|
2023-08-29 14:53:26 +00:00
|
|
|
fn empty_system() {}
|
|
|
|
fn res_system(_res: Res<R>) {}
|
|
|
|
fn resmut_system(_res: ResMut<R>) {}
|
|
|
|
fn nonsend_system(_ns: NonSend<R>) {}
|
|
|
|
fn nonsendmut_system(_ns: NonSendMut<R>) {}
|
|
|
|
fn read_component_system(_query: Query<&A>) {}
|
|
|
|
fn write_component_system(_query: Query<&mut A>) {}
|
|
|
|
fn with_filtered_component_system(_query: Query<&mut A, With<B>>) {}
|
|
|
|
fn without_filtered_component_system(_query: Query<&mut A, Without<B>>) {}
|
2024-08-06 01:19:39 +00:00
|
|
|
fn entity_ref_system(_query: Query<EntityRef>) {}
|
|
|
|
fn entity_mut_system(_query: Query<EntityMut>) {}
|
2023-08-29 14:53:26 +00:00
|
|
|
fn event_reader_system(_reader: EventReader<E>) {}
|
|
|
|
fn event_writer_system(_writer: EventWriter<E>) {}
|
|
|
|
fn event_resource_system(_events: ResMut<Events<E>>) {}
|
|
|
|
fn read_world_system(_world: &World) {}
|
|
|
|
fn write_world_system(_world: &mut World) {}
|
|
|
|
|
|
|
|
// Tests for conflict detection
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn one_of_everything() {
|
|
|
|
let mut world = World::new();
|
|
|
|
world.insert_resource(R);
|
|
|
|
world.spawn(A);
|
|
|
|
world.init_resource::<Events<E>>();
|
|
|
|
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
schedule
|
|
|
|
// nonsendmut system deliberately conflicts with resmut system
|
|
|
|
.add_systems((resmut_system, write_component_system, event_writer_system));
|
|
|
|
|
|
|
|
let _ = schedule.initialize(&mut world);
|
|
|
|
|
|
|
|
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn read_only() {
|
|
|
|
let mut world = World::new();
|
|
|
|
world.insert_resource(R);
|
|
|
|
world.spawn(A);
|
|
|
|
world.init_resource::<Events<E>>();
|
|
|
|
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
schedule.add_systems((
|
|
|
|
empty_system,
|
|
|
|
empty_system,
|
|
|
|
res_system,
|
|
|
|
res_system,
|
|
|
|
nonsend_system,
|
|
|
|
nonsend_system,
|
|
|
|
read_component_system,
|
|
|
|
read_component_system,
|
2024-08-06 01:19:39 +00:00
|
|
|
entity_ref_system,
|
|
|
|
entity_ref_system,
|
2023-08-29 14:53:26 +00:00
|
|
|
event_reader_system,
|
|
|
|
event_reader_system,
|
|
|
|
read_world_system,
|
|
|
|
read_world_system,
|
|
|
|
));
|
|
|
|
|
|
|
|
let _ = schedule.initialize(&mut world);
|
|
|
|
|
|
|
|
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn read_world() {
|
|
|
|
let mut world = World::new();
|
|
|
|
world.insert_resource(R);
|
|
|
|
world.spawn(A);
|
|
|
|
world.init_resource::<Events<E>>();
|
|
|
|
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
schedule.add_systems((
|
|
|
|
resmut_system,
|
|
|
|
write_component_system,
|
|
|
|
event_writer_system,
|
|
|
|
read_world_system,
|
|
|
|
));
|
|
|
|
|
|
|
|
let _ = schedule.initialize(&mut world);
|
|
|
|
|
|
|
|
assert_eq!(schedule.graph().conflicting_systems().len(), 3);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn resources() {
|
|
|
|
let mut world = World::new();
|
|
|
|
world.insert_resource(R);
|
|
|
|
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
schedule.add_systems((resmut_system, res_system));
|
|
|
|
|
|
|
|
let _ = schedule.initialize(&mut world);
|
|
|
|
|
|
|
|
assert_eq!(schedule.graph().conflicting_systems().len(), 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn nonsend() {
|
|
|
|
let mut world = World::new();
|
|
|
|
world.insert_resource(R);
|
|
|
|
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
schedule.add_systems((nonsendmut_system, nonsend_system));
|
|
|
|
|
|
|
|
let _ = schedule.initialize(&mut world);
|
|
|
|
|
|
|
|
assert_eq!(schedule.graph().conflicting_systems().len(), 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn components() {
|
|
|
|
let mut world = World::new();
|
|
|
|
world.spawn(A);
|
|
|
|
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
schedule.add_systems((read_component_system, write_component_system));
|
|
|
|
|
|
|
|
let _ = schedule.initialize(&mut world);
|
|
|
|
|
|
|
|
assert_eq!(schedule.graph().conflicting_systems().len(), 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[ignore = "Known failing but fix is non-trivial: https://github.com/bevyengine/bevy/issues/4381"]
|
|
|
|
fn filtered_components() {
|
|
|
|
let mut world = World::new();
|
|
|
|
world.spawn(A);
|
|
|
|
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
schedule.add_systems((
|
|
|
|
with_filtered_component_system,
|
|
|
|
without_filtered_component_system,
|
|
|
|
));
|
|
|
|
|
|
|
|
let _ = schedule.initialize(&mut world);
|
|
|
|
|
|
|
|
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn events() {
|
|
|
|
let mut world = World::new();
|
|
|
|
world.init_resource::<Events<E>>();
|
|
|
|
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
schedule.add_systems((
|
|
|
|
// All of these systems clash
|
|
|
|
event_reader_system,
|
|
|
|
event_writer_system,
|
|
|
|
event_resource_system,
|
|
|
|
));
|
|
|
|
|
|
|
|
let _ = schedule.initialize(&mut world);
|
|
|
|
|
|
|
|
assert_eq!(schedule.graph().conflicting_systems().len(), 3);
|
|
|
|
}
|
|
|
|
|
2024-08-06 01:19:39 +00:00
|
|
|
/// Test that when a struct is both a Resource and a Component, they do not
|
|
|
|
/// conflict with each other.
|
|
|
|
#[test]
|
|
|
|
fn shared_resource_mut_component() {
|
|
|
|
let mut world = World::new();
|
|
|
|
world.insert_resource(RC);
|
|
|
|
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
schedule.add_systems((|_: ResMut<RC>| {}, |_: Query<&mut RC>| {}));
|
|
|
|
|
|
|
|
let _ = schedule.initialize(&mut world);
|
|
|
|
|
|
|
|
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn resource_mut_and_entity_ref() {
|
|
|
|
let mut world = World::new();
|
|
|
|
world.insert_resource(R);
|
|
|
|
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
schedule.add_systems((resmut_system, entity_ref_system));
|
|
|
|
|
|
|
|
let _ = schedule.initialize(&mut world);
|
|
|
|
|
|
|
|
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn resource_and_entity_mut() {
|
|
|
|
let mut world = World::new();
|
|
|
|
world.insert_resource(R);
|
|
|
|
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
schedule.add_systems((res_system, nonsend_system, entity_mut_system));
|
|
|
|
|
|
|
|
let _ = schedule.initialize(&mut world);
|
|
|
|
|
|
|
|
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn write_component_and_entity_ref() {
|
|
|
|
let mut world = World::new();
|
|
|
|
world.insert_resource(R);
|
|
|
|
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
schedule.add_systems((write_component_system, entity_ref_system));
|
|
|
|
|
|
|
|
let _ = schedule.initialize(&mut world);
|
|
|
|
|
|
|
|
assert_eq!(schedule.graph().conflicting_systems().len(), 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn read_component_and_entity_mut() {
|
|
|
|
let mut world = World::new();
|
|
|
|
world.insert_resource(R);
|
|
|
|
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
schedule.add_systems((read_component_system, entity_mut_system));
|
|
|
|
|
|
|
|
let _ = schedule.initialize(&mut world);
|
|
|
|
|
|
|
|
assert_eq!(schedule.graph().conflicting_systems().len(), 1);
|
|
|
|
}
|
|
|
|
|
2023-08-29 14:53:26 +00:00
|
|
|
#[test]
|
|
|
|
fn exclusive() {
|
|
|
|
let mut world = World::new();
|
|
|
|
world.insert_resource(R);
|
|
|
|
world.spawn(A);
|
|
|
|
world.init_resource::<Events<E>>();
|
|
|
|
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
schedule.add_systems((
|
|
|
|
// All 3 of these conflict with each other
|
|
|
|
write_world_system,
|
|
|
|
write_world_system,
|
|
|
|
res_system,
|
|
|
|
));
|
|
|
|
|
|
|
|
let _ = schedule.initialize(&mut world);
|
|
|
|
|
|
|
|
assert_eq!(schedule.graph().conflicting_systems().len(), 3);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tests for silencing and resolving ambiguities
|
|
|
|
#[test]
|
|
|
|
fn before_and_after() {
|
|
|
|
let mut world = World::new();
|
|
|
|
world.init_resource::<Events<E>>();
|
|
|
|
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
schedule.add_systems((
|
|
|
|
event_reader_system.before(event_writer_system),
|
|
|
|
event_writer_system,
|
|
|
|
event_resource_system.after(event_writer_system),
|
|
|
|
));
|
|
|
|
|
|
|
|
let _ = schedule.initialize(&mut world);
|
|
|
|
|
|
|
|
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn ignore_all_ambiguities() {
|
|
|
|
let mut world = World::new();
|
|
|
|
world.insert_resource(R);
|
|
|
|
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
schedule.add_systems((
|
|
|
|
resmut_system.ambiguous_with_all(),
|
|
|
|
res_system,
|
|
|
|
nonsend_system,
|
|
|
|
));
|
|
|
|
|
|
|
|
let _ = schedule.initialize(&mut world);
|
|
|
|
|
|
|
|
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn ambiguous_with_label() {
|
|
|
|
let mut world = World::new();
|
|
|
|
world.insert_resource(R);
|
|
|
|
|
|
|
|
#[derive(SystemSet, Hash, PartialEq, Eq, Debug, Clone)]
|
|
|
|
struct IgnoreMe;
|
|
|
|
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
schedule.add_systems((
|
|
|
|
resmut_system.ambiguous_with(IgnoreMe),
|
|
|
|
res_system.in_set(IgnoreMe),
|
|
|
|
nonsend_system.in_set(IgnoreMe),
|
|
|
|
));
|
|
|
|
|
|
|
|
let _ = schedule.initialize(&mut world);
|
|
|
|
|
|
|
|
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn ambiguous_with_system() {
|
|
|
|
let mut world = World::new();
|
|
|
|
|
|
|
|
let mut schedule = Schedule::default();
|
|
|
|
schedule.add_systems((
|
|
|
|
write_component_system.ambiguous_with(read_component_system),
|
|
|
|
read_component_system,
|
|
|
|
));
|
|
|
|
let _ = schedule.initialize(&mut world);
|
|
|
|
|
|
|
|
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
|
|
|
|
}
|
|
|
|
|
2023-10-04 02:34:28 +00:00
|
|
|
#[derive(ScheduleLabel, Hash, PartialEq, Eq, Debug, Clone)]
|
|
|
|
struct TestSchedule;
|
|
|
|
|
2023-08-29 14:53:26 +00:00
|
|
|
// Tests that the correct ambiguities were reported in the correct order.
|
|
|
|
#[test]
|
|
|
|
fn correct_ambiguities() {
|
|
|
|
fn system_a(_res: ResMut<R>) {}
|
|
|
|
fn system_b(_res: ResMut<R>) {}
|
|
|
|
fn system_c(_res: ResMut<R>) {}
|
|
|
|
fn system_d(_res: ResMut<R>) {}
|
|
|
|
fn system_e(_res: ResMut<R>) {}
|
|
|
|
|
|
|
|
let mut world = World::new();
|
|
|
|
world.insert_resource(R);
|
|
|
|
|
|
|
|
let mut schedule = Schedule::new(TestSchedule);
|
|
|
|
schedule.add_systems((
|
|
|
|
system_a,
|
|
|
|
system_b,
|
|
|
|
system_c.ambiguous_with_all(),
|
|
|
|
system_d.ambiguous_with(system_b),
|
|
|
|
system_e.after(system_a),
|
|
|
|
));
|
|
|
|
|
|
|
|
schedule.graph_mut().initialize(&mut world);
|
2023-10-04 02:34:28 +00:00
|
|
|
let _ = schedule.graph_mut().build_schedule(
|
|
|
|
world.components(),
|
Replace all labels with interned labels (#7762)
# Objective
First of all, this PR took heavy inspiration from #7760 and #5715. It
intends to also fix #5569, but with a slightly different approach.
This also fixes #9335 by reexporting `DynEq`.
## Solution
The advantage of this API is that we can intern a value without
allocating for zero-sized-types and for enum variants that have no
fields. This PR does this automatically in the `SystemSet` and
`ScheduleLabel` derive macros for unit structs and fieldless enum
variants. So this should cover many internal and external use cases of
`SystemSet` and `ScheduleLabel`. In these optimal use cases, no memory
will be allocated.
- The interning returns a `Interned<dyn SystemSet>`, which is just a
wrapper around a `&'static dyn SystemSet`.
- `Hash` and `Eq` are implemented in terms of the pointer value of the
reference, similar to my first approach of anonymous system sets in
#7676.
- Therefore, `Interned<T>` does not implement `Borrow<T>`, only `Deref`.
- The debug output of `Interned<T>` is the same as the interned value.
Edit:
- `AppLabel` is now also interned and the old
`derive_label`/`define_label` macros were replaced with the new
interning implementation.
- Anonymous set ids are reused for different `Schedule`s, reducing the
amount of leaked memory.
### Pros
- `InternedSystemSet` and `InternedScheduleLabel` behave very similar to
the current `BoxedSystemSet` and `BoxedScheduleLabel`, but can be copied
without an allocation.
- Many use cases don't allocate at all.
- Very fast lookups and comparisons when using `InternedSystemSet` and
`InternedScheduleLabel`.
- The `intern` module might be usable in other areas.
- `Interned{ScheduleLabel, SystemSet, AppLabel}` does implement
`{ScheduleLabel, SystemSet, AppLabel}`, increasing ergonomics.
### Cons
- Implementors of `SystemSet` and `ScheduleLabel` still need to
implement `Hash` and `Eq` (and `Clone`) for it to work.
## Changelog
### Added
- Added `intern` module to `bevy_utils`.
- Added reexports of `DynEq` to `bevy_ecs` and `bevy_app`.
### Changed
- Replaced `BoxedSystemSet` and `BoxedScheduleLabel` with
`InternedSystemSet` and `InternedScheduleLabel`.
- Replaced `impl AsRef<dyn ScheduleLabel>` with `impl ScheduleLabel`.
- Replaced `AppLabelId` with `InternedAppLabel`.
- Changed `AppLabel` to use `Debug` for error messages.
- Changed `AppLabel` to use interning.
- Changed `define_label`/`derive_label` to use interning.
- Replaced `define_boxed_label`/`derive_boxed_label` with
`define_label`/`derive_label`.
- Changed anonymous set ids to be only unique inside a schedule, not
globally.
- Made interned label types implement their label trait.
### Removed
- Removed `define_boxed_label` and `derive_boxed_label`.
## Migration guide
- Replace `BoxedScheduleLabel` and `Box<dyn ScheduleLabel>` with
`InternedScheduleLabel` or `Interned<dyn ScheduleLabel>`.
- Replace `BoxedSystemSet` and `Box<dyn SystemSet>` with
`InternedSystemSet` or `Interned<dyn SystemSet>`.
- Replace `AppLabelId` with `InternedAppLabel` or `Interned<dyn
AppLabel>`.
- Types manually implementing `ScheduleLabel`, `AppLabel` or `SystemSet`
need to implement:
- `dyn_hash` directly instead of implementing `DynHash`
- `as_dyn_eq`
- Pass labels to `World::try_schedule_scope`, `World::schedule_scope`,
`World::try_run_schedule`. `World::run_schedule`, `Schedules::remove`,
`Schedules::remove_entry`, `Schedules::contains`, `Schedules::get` and
`Schedules::get_mut` by value instead of by reference.
---------
Co-authored-by: Joseph <21144246+JoJoJet@users.noreply.github.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2023-10-25 21:39:23 +00:00
|
|
|
TestSchedule.intern(),
|
2023-10-04 02:34:28 +00:00
|
|
|
&BTreeSet::new(),
|
|
|
|
);
|
2023-08-29 14:53:26 +00:00
|
|
|
|
|
|
|
let ambiguities: Vec<_> = schedule
|
|
|
|
.graph()
|
2023-08-31 19:06:13 +00:00
|
|
|
.conflicts_to_string(schedule.graph().conflicting_systems(), world.components())
|
2023-08-29 14:53:26 +00:00
|
|
|
.collect();
|
|
|
|
|
|
|
|
let expected = &[
|
|
|
|
(
|
|
|
|
"system_d".to_string(),
|
|
|
|
"system_a".to_string(),
|
|
|
|
vec!["bevy_ecs::schedule::tests::system_ambiguity::R"],
|
|
|
|
),
|
|
|
|
(
|
|
|
|
"system_d".to_string(),
|
|
|
|
"system_e".to_string(),
|
|
|
|
vec!["bevy_ecs::schedule::tests::system_ambiguity::R"],
|
|
|
|
),
|
|
|
|
(
|
|
|
|
"system_b".to_string(),
|
|
|
|
"system_a".to_string(),
|
|
|
|
vec!["bevy_ecs::schedule::tests::system_ambiguity::R"],
|
|
|
|
),
|
|
|
|
(
|
|
|
|
"system_b".to_string(),
|
|
|
|
"system_e".to_string(),
|
|
|
|
vec!["bevy_ecs::schedule::tests::system_ambiguity::R"],
|
|
|
|
),
|
|
|
|
];
|
|
|
|
|
|
|
|
// ordering isn't stable so do this
|
|
|
|
for entry in expected {
|
|
|
|
assert!(ambiguities.contains(entry));
|
|
|
|
}
|
|
|
|
}
|
2023-08-31 22:52:59 +00:00
|
|
|
|
|
|
|
// Test that anonymous set names work properly
|
|
|
|
// Related issue https://github.com/bevyengine/bevy/issues/9641
|
|
|
|
#[test]
|
|
|
|
fn anonymous_set_name() {
|
|
|
|
let mut schedule = Schedule::new(TestSchedule);
|
|
|
|
schedule.add_systems((resmut_system, resmut_system).run_if(|| true));
|
|
|
|
|
|
|
|
let mut world = World::new();
|
|
|
|
schedule.graph_mut().initialize(&mut world);
|
2023-10-04 02:34:28 +00:00
|
|
|
let _ = schedule.graph_mut().build_schedule(
|
|
|
|
world.components(),
|
Replace all labels with interned labels (#7762)
# Objective
First of all, this PR took heavy inspiration from #7760 and #5715. It
intends to also fix #5569, but with a slightly different approach.
This also fixes #9335 by reexporting `DynEq`.
## Solution
The advantage of this API is that we can intern a value without
allocating for zero-sized-types and for enum variants that have no
fields. This PR does this automatically in the `SystemSet` and
`ScheduleLabel` derive macros for unit structs and fieldless enum
variants. So this should cover many internal and external use cases of
`SystemSet` and `ScheduleLabel`. In these optimal use cases, no memory
will be allocated.
- The interning returns a `Interned<dyn SystemSet>`, which is just a
wrapper around a `&'static dyn SystemSet`.
- `Hash` and `Eq` are implemented in terms of the pointer value of the
reference, similar to my first approach of anonymous system sets in
#7676.
- Therefore, `Interned<T>` does not implement `Borrow<T>`, only `Deref`.
- The debug output of `Interned<T>` is the same as the interned value.
Edit:
- `AppLabel` is now also interned and the old
`derive_label`/`define_label` macros were replaced with the new
interning implementation.
- Anonymous set ids are reused for different `Schedule`s, reducing the
amount of leaked memory.
### Pros
- `InternedSystemSet` and `InternedScheduleLabel` behave very similar to
the current `BoxedSystemSet` and `BoxedScheduleLabel`, but can be copied
without an allocation.
- Many use cases don't allocate at all.
- Very fast lookups and comparisons when using `InternedSystemSet` and
`InternedScheduleLabel`.
- The `intern` module might be usable in other areas.
- `Interned{ScheduleLabel, SystemSet, AppLabel}` does implement
`{ScheduleLabel, SystemSet, AppLabel}`, increasing ergonomics.
### Cons
- Implementors of `SystemSet` and `ScheduleLabel` still need to
implement `Hash` and `Eq` (and `Clone`) for it to work.
## Changelog
### Added
- Added `intern` module to `bevy_utils`.
- Added reexports of `DynEq` to `bevy_ecs` and `bevy_app`.
### Changed
- Replaced `BoxedSystemSet` and `BoxedScheduleLabel` with
`InternedSystemSet` and `InternedScheduleLabel`.
- Replaced `impl AsRef<dyn ScheduleLabel>` with `impl ScheduleLabel`.
- Replaced `AppLabelId` with `InternedAppLabel`.
- Changed `AppLabel` to use `Debug` for error messages.
- Changed `AppLabel` to use interning.
- Changed `define_label`/`derive_label` to use interning.
- Replaced `define_boxed_label`/`derive_boxed_label` with
`define_label`/`derive_label`.
- Changed anonymous set ids to be only unique inside a schedule, not
globally.
- Made interned label types implement their label trait.
### Removed
- Removed `define_boxed_label` and `derive_boxed_label`.
## Migration guide
- Replace `BoxedScheduleLabel` and `Box<dyn ScheduleLabel>` with
`InternedScheduleLabel` or `Interned<dyn ScheduleLabel>`.
- Replace `BoxedSystemSet` and `Box<dyn SystemSet>` with
`InternedSystemSet` or `Interned<dyn SystemSet>`.
- Replace `AppLabelId` with `InternedAppLabel` or `Interned<dyn
AppLabel>`.
- Types manually implementing `ScheduleLabel`, `AppLabel` or `SystemSet`
need to implement:
- `dyn_hash` directly instead of implementing `DynHash`
- `as_dyn_eq`
- Pass labels to `World::try_schedule_scope`, `World::schedule_scope`,
`World::try_run_schedule`. `World::run_schedule`, `Schedules::remove`,
`Schedules::remove_entry`, `Schedules::contains`, `Schedules::get` and
`Schedules::get_mut` by value instead of by reference.
---------
Co-authored-by: Joseph <21144246+JoJoJet@users.noreply.github.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2023-10-25 21:39:23 +00:00
|
|
|
TestSchedule.intern(),
|
2023-10-04 02:34:28 +00:00
|
|
|
&BTreeSet::new(),
|
|
|
|
);
|
2023-08-31 22:52:59 +00:00
|
|
|
|
|
|
|
let ambiguities: Vec<_> = schedule
|
|
|
|
.graph()
|
|
|
|
.conflicts_to_string(schedule.graph().conflicting_systems(), world.components())
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
ambiguities[0],
|
|
|
|
(
|
|
|
|
"resmut_system (in set (resmut_system, resmut_system))".to_string(),
|
|
|
|
"resmut_system (in set (resmut_system, resmut_system))".to_string(),
|
|
|
|
vec!["bevy_ecs::schedule::tests::system_ambiguity::R"],
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
2023-10-04 02:34:28 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn ignore_component_resource_ambiguities() {
|
|
|
|
let mut world = World::new();
|
|
|
|
world.insert_resource(R);
|
|
|
|
world.allow_ambiguous_resource::<R>();
|
|
|
|
let mut schedule = Schedule::new(TestSchedule);
|
|
|
|
|
2024-09-24 11:42:59 +00:00
|
|
|
// check resource
|
2023-10-04 02:34:28 +00:00
|
|
|
schedule.add_systems((resmut_system, res_system));
|
|
|
|
schedule.initialize(&mut world).unwrap();
|
|
|
|
assert!(schedule.graph().conflicting_systems().is_empty());
|
|
|
|
|
|
|
|
// check components
|
|
|
|
world.allow_ambiguous_component::<A>();
|
|
|
|
schedule.add_systems((write_component_system, read_component_system));
|
|
|
|
schedule.initialize(&mut world).unwrap();
|
|
|
|
assert!(schedule.graph().conflicting_systems().is_empty());
|
|
|
|
}
|
2023-08-29 14:53:26 +00:00
|
|
|
}
|
System Stepping implemented as Resource (#8453)
# Objective
Add interactive system debugging capabilities to bevy, providing
step/break/continue style capabilities to running system schedules.
* Original implementation: #8063
- `ignore_stepping()` everywhere was too much complexity
* Schedule-config & Resource discussion: #8168
- Decided on selective adding of Schedules & Resource-based control
## Solution
Created `Stepping` Resource. This resource can be used to enable
stepping on a per-schedule basis. Systems within schedules can be
individually configured to:
* AlwaysRun: Ignore any stepping state and run every frame
* NeverRun: Never run while stepping is enabled
- this allows for disabling of systems while debugging
* Break: If we're running the full frame, stop before this system is run
Stepping provides two modes of execution that reflect traditional
debuggers:
* Step-based: Only execute one system at a time
* Continue/Break: Run all systems, but stop before running a system
marked as Break
### Demo
https://user-images.githubusercontent.com/857742/233630981-99f3bbda-9ca6-4cc4-a00f-171c4946dc47.mov
Breakout has been modified to use Stepping. The game runs normally for a
couple of seconds, then stepping is enabled and the game appears to
pause. A list of Schedules & Systems appears with a cursor at the first
System in the list. The demo then steps forward full frames using the
spacebar until the ball is about to hit a brick. Then we step system by
system as the ball impacts a brick, showing the cursor moving through
the individual systems. Finally the demo switches back to frame stepping
as the ball changes course.
### Limitations
Due to architectural constraints in bevy, there are some cases systems
stepping will not function as a user would expect.
#### Event-driven systems
Stepping does not support systems that are driven by `Event`s as events
are flushed after 1-2 frames. Although game systems are not running
while stepping, ignored systems are still running every frame, so events
will be flushed.
This presents to the user as stepping the event-driven system never
executes the system. It does execute, but the events have already been
flushed.
This can be resolved by changing event handling to use a buffer for
events, and only dropping an event once all readers have read it.
The work-around to allow these systems to properly execute during
stepping is to have them ignore stepping:
`app.add_systems(event_driven_system.ignore_stepping())`. This was done
in the breakout example to ensure sound played even while stepping.
#### Conditional Systems
When a system is stepped, it is given an opportunity to run. If the
conditions of the system say it should not run, it will not.
Similar to Event-driven systems, if a system is conditional, and that
condition is only true for a very small time window, then stepping the
system may not execute the system. This includes depending on any sort
of external clock.
This exhibits to the user as the system not always running when it is
stepped.
A solution to this limitation is to ensure any conditions are consistent
while stepping is enabled. For example, all systems that modify any
state the condition uses should also enable stepping.
#### State-transition Systems
Stepping is configured on the per-`Schedule` level, requiring the user
to have a `ScheduleLabel`.
To support state-transition systems, bevy generates needed schedules
dynamically. Currently it’s very difficult (if not impossible, I haven’t
verified) for the user to get the labels for these schedules.
Without ready access to the dynamically generated schedules, and a
resolution for the `Event` lifetime, **stepping of the state-transition
systems is not supported**
---
## Changelog
- `Schedule::run()` updated to consult `Stepping` Resource to determine
which Systems to run each frame
- Added `Schedule.label` as a `BoxedSystemLabel`, along with supporting
`Schedule::set_label()` and `Schedule::label()` methods
- `Stepping` needed to know which `Schedule` was running, and prior to
this PR, `Schedule` didn't track its own label
- Would have preferred to add `Schedule::with_label()` and remove
`Schedule::new()`, but this PR touches enough already
- Added calls to `Schedule.set_label()` to `App` and `World` as needed
- Added `Stepping` resource
- Added `Stepping::begin_frame()` system to `MainSchedulePlugin`
- Run before `Main::run_main()`
- Notifies any `Stepping` Resource a new render frame is starting
## Migration Guide
- Add a call to `Schedule::set_label()` for any custom `Schedule`
- This is only required if the `Schedule` will be stepped
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-02-03 05:18:38 +00:00
|
|
|
|
|
|
|
#[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);
|
|
|
|
}
|
|
|
|
}
|
2023-01-17 01:39:17 +00:00
|
|
|
}
|