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.
This commit is contained in:
Alice Cecile 2023-02-06 02:04:50 +00:00
parent 9adffb7521
commit 206c7ce219
178 changed files with 2285 additions and 7782 deletions

View file

@ -38,7 +38,7 @@ For more advice on writing examples, see the [relevant section](../../CONTRIBUTI
4. In Queries, prefer `With<T>` filters over actually fetching unused data with `&T`.
5. Prefer disjoint queries using `With` and `Without` over param sets when you need more than one query in a single system.
6. Prefer structs with named fields over tuple structs except in the case of single-field wrapper types.
7. Use enum-labels over string-labels for system / stage / etc. labels.
7. Use enum-labels over string-labels for system / schedule / etc. labels.
## "Feature" examples

View file

@ -920,7 +920,7 @@ path = "examples/ecs/removal_detection.rs"
[package.metadata.example.removal_detection]
name = "Removal Detection"
description = "Query for entities that had a specific component removed in a previous stage during the current frame"
description = "Query for entities that had a specific component removed earlier in the current frame"
category = "ECS (Entity Component System)"
wasm = false
@ -974,16 +974,6 @@ description = "Illustrates creating custom system parameters with `SystemParam`"
category = "ECS (Entity Component System)"
wasm = false
[[example]]
name = "system_sets"
path = "examples/ecs/system_sets.rs"
[package.metadata.example.system_sets]
name = "System Sets"
description = "Shows `SystemSet` use along with run criterion"
category = "ECS (Entity Component System)"
wasm = false
[[example]]
name = "timers"
path = "examples/ecs/timers.rs"

View file

@ -1,22 +1,18 @@
use bevy_ecs::{
component::Component,
schedule::{Stage, SystemStage},
world::World,
};
use bevy_ecs::{component::Component, schedule_v3::Schedule, world::World};
use criterion::{BenchmarkId, Criterion};
#[derive(Component)]
struct A<const N: u16>(f32);
fn setup(system_count: usize) -> (World, SystemStage) {
fn setup(system_count: usize) -> (World, Schedule) {
let mut world = World::new();
fn empty() {}
let mut stage = SystemStage::parallel();
let mut schedule = Schedule::new();
for _ in 0..system_count {
stage.add_system(empty);
schedule.add_system(empty);
}
stage.run(&mut world);
(world, stage)
schedule.run(&mut world);
(world, schedule)
}
/// create `count` entities with distinct archetypes
@ -78,13 +74,13 @@ pub fn no_archetypes(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("no_archetypes");
for i in 0..=5 {
let system_count = i * 20;
let (mut world, mut stage) = setup(system_count);
let (mut world, mut schedule) = setup(system_count);
group.bench_with_input(
BenchmarkId::new("system_count", system_count),
&system_count,
|bencher, &_system_count| {
bencher.iter(|| {
stage.run(&mut world);
schedule.run(&mut world);
});
},
);
@ -101,12 +97,12 @@ pub fn added_archetypes(criterion: &mut Criterion) {
|bencher, &archetype_count| {
bencher.iter_batched(
|| {
let (mut world, stage) = setup(SYSTEM_COUNT);
let (mut world, schedule) = setup(SYSTEM_COUNT);
add_archetypes(&mut world, archetype_count);
(world, stage)
(world, schedule)
},
|(mut world, mut stage)| {
stage.run(&mut world);
|(mut world, mut schedule)| {
schedule.run(&mut world);
},
criterion::BatchSize::LargeInput,
);

View file

@ -1,9 +1,4 @@
use bevy_ecs::{
component::Component,
prelude::*,
schedule::{Stage, SystemStage},
world::World,
};
use bevy_ecs::{component::Component, prelude::*, world::World};
use bevy_tasks::{ComputeTaskPool, TaskPool};
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
@ -80,14 +75,14 @@ fn par_for_each(
});
}
fn setup(parallel: bool, setup: impl FnOnce(&mut SystemStage)) -> (World, SystemStage) {
fn setup(parallel: bool, setup: impl FnOnce(&mut Schedule)) -> (World, Schedule) {
let mut world = World::new();
let mut stage = SystemStage::parallel();
let mut schedule = Schedule::new();
if parallel {
world.insert_resource(ComputeTaskPool(TaskPool::default()));
}
setup(&mut stage);
(world, stage)
setup(&mut schedule);
(world, schedule)
}
/// create `count` entities with distinct archetypes
@ -158,8 +153,8 @@ fn add_archetypes(world: &mut World, count: u16) {
fn empty_archetypes(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("empty_archetypes");
for archetype_count in [10, 100, 500, 1000, 2000, 5000, 10000] {
let (mut world, mut stage) = setup(true, |stage| {
stage.add_system(iter);
let (mut world, mut schedule) = setup(true, |schedule| {
schedule.add_system(iter);
});
add_archetypes(&mut world, archetype_count);
world.clear_entities();
@ -177,20 +172,20 @@ fn empty_archetypes(criterion: &mut Criterion) {
e.insert(A::<10>(1.0));
e.insert(A::<11>(1.0));
e.insert(A::<12>(1.0));
stage.run(&mut world);
schedule.run(&mut world);
group.bench_with_input(
BenchmarkId::new("iter", archetype_count),
&archetype_count,
|bencher, &_| {
bencher.iter(|| {
stage.run(&mut world);
schedule.run(&mut world);
})
},
);
}
for archetype_count in [10, 100, 500, 1000, 2000, 5000, 10000] {
let (mut world, mut stage) = setup(true, |stage| {
stage.add_system(for_each);
let (mut world, mut schedule) = setup(true, |schedule| {
schedule.add_system(for_each);
});
add_archetypes(&mut world, archetype_count);
world.clear_entities();
@ -208,20 +203,20 @@ fn empty_archetypes(criterion: &mut Criterion) {
e.insert(A::<10>(1.0));
e.insert(A::<11>(1.0));
e.insert(A::<12>(1.0));
stage.run(&mut world);
schedule.run(&mut world);
group.bench_with_input(
BenchmarkId::new("for_each", archetype_count),
&archetype_count,
|bencher, &_| {
bencher.iter(|| {
stage.run(&mut world);
schedule.run(&mut world);
})
},
);
}
for archetype_count in [10, 100, 500, 1000, 2000, 5000, 10000] {
let (mut world, mut stage) = setup(true, |stage| {
stage.add_system(par_for_each);
let (mut world, mut schedule) = setup(true, |schedule| {
schedule.add_system(par_for_each);
});
add_archetypes(&mut world, archetype_count);
world.clear_entities();
@ -239,13 +234,13 @@ fn empty_archetypes(criterion: &mut Criterion) {
e.insert(A::<10>(1.0));
e.insert(A::<11>(1.0));
e.insert(A::<12>(1.0));
stage.run(&mut world);
schedule.run(&mut world);
group.bench_with_input(
BenchmarkId::new("par_for_each", archetype_count),
&archetype_count,
|bencher, &_| {
bencher.iter(|| {
stage.run(&mut world);
schedule.run(&mut world);
})
},
);

View file

@ -1,19 +1,17 @@
use criterion::criterion_group;
mod run_criteria;
mod running_systems;
mod schedule;
mod stages;
use run_criteria::*;
use running_systems::*;
use schedule::*;
use stages::*;
criterion_group!(
scheduling_benches,
run_criteria_yes,
run_criteria_no,
run_criteria_yes_with_labels,
run_criteria_no_with_labels,
run_criteria_yes_with_query,
run_criteria_yes_with_resource,
empty_systems,

View file

@ -1,15 +1,14 @@
use bevy_ecs::{prelude::*, schedule::ShouldRun};
use bevy_ecs::prelude::*;
use criterion::Criterion;
fn run_stage(stage: &mut SystemStage, world: &mut World) {
stage.run(world);
/// A run `Condition` that always returns true
fn yes() -> bool {
true
}
/// Labels for run criteria which either always return yes, or always return no.
#[derive(RunCriteriaLabel)]
enum Always {
Yes,
No,
/// A run `Condition` that always returns false
fn no() -> bool {
false
}
pub fn run_criteria_yes(criterion: &mut Criterion) {
@ -18,26 +17,22 @@ pub fn run_criteria_yes(criterion: &mut Criterion) {
group.warm_up_time(std::time::Duration::from_millis(500));
group.measurement_time(std::time::Duration::from_secs(3));
fn empty() {}
fn always_yes() -> ShouldRun {
ShouldRun::Yes
}
for amount in 0..21 {
let mut stage = SystemStage::parallel();
stage.add_system(empty.with_run_criteria(always_yes));
let mut schedule = Schedule::new();
schedule.add_system(empty.run_if(yes));
for _ in 0..amount {
// TODO: should change this to use a label or have another bench that uses a label instead
stage
.add_system(empty.with_run_criteria(always_yes))
.add_system(empty.with_run_criteria(always_yes))
.add_system(empty.with_run_criteria(always_yes))
.add_system(empty.with_run_criteria(always_yes))
.add_system(empty.with_run_criteria(always_yes));
schedule
.add_system(empty.run_if(yes))
.add_system(empty.run_if(yes))
.add_system(empty.run_if(yes))
.add_system(empty.run_if(yes))
.add_system(empty.run_if(yes));
}
// run once to initialize systems
run_stage(&mut stage, &mut world);
schedule.run(&mut world);
group.bench_function(&format!("{:03}_systems", 5 * amount + 1), |bencher| {
bencher.iter(|| {
run_stage(&mut stage, &mut world);
schedule.run(&mut world);
});
});
}
@ -50,89 +45,22 @@ pub fn run_criteria_no(criterion: &mut Criterion) {
group.warm_up_time(std::time::Duration::from_millis(500));
group.measurement_time(std::time::Duration::from_secs(3));
fn empty() {}
fn always_no() -> ShouldRun {
ShouldRun::No
}
for amount in 0..21 {
let mut stage = SystemStage::parallel();
stage.add_system(empty.with_run_criteria(always_no));
let mut schedule = Schedule::new();
schedule.add_system(empty.run_if(no));
for _ in 0..amount {
stage
.add_system(empty.with_run_criteria(always_no))
.add_system(empty.with_run_criteria(always_no))
.add_system(empty.with_run_criteria(always_no))
.add_system(empty.with_run_criteria(always_no))
.add_system(empty.with_run_criteria(always_no));
schedule
.add_system(empty.run_if(no))
.add_system(empty.run_if(no))
.add_system(empty.run_if(no))
.add_system(empty.run_if(no))
.add_system(empty.run_if(no));
}
// run once to initialize systems
run_stage(&mut stage, &mut world);
schedule.run(&mut world);
group.bench_function(&format!("{:03}_systems", 5 * amount + 1), |bencher| {
bencher.iter(|| {
run_stage(&mut stage, &mut world);
});
});
}
group.finish();
}
pub fn run_criteria_yes_with_labels(criterion: &mut Criterion) {
let mut world = World::new();
let mut group = criterion.benchmark_group("run_criteria/yes_with_labels");
group.warm_up_time(std::time::Duration::from_millis(500));
group.measurement_time(std::time::Duration::from_secs(3));
fn empty() {}
fn always_yes() -> ShouldRun {
ShouldRun::Yes
}
for amount in 0..21 {
let mut stage = SystemStage::parallel();
stage.add_system(empty.with_run_criteria(always_yes.label(Always::Yes)));
for _ in 0..amount {
stage
.add_system(empty.with_run_criteria(Always::Yes))
.add_system(empty.with_run_criteria(Always::Yes))
.add_system(empty.with_run_criteria(Always::Yes))
.add_system(empty.with_run_criteria(Always::Yes))
.add_system(empty.with_run_criteria(Always::Yes));
}
// run once to initialize systems
run_stage(&mut stage, &mut world);
group.bench_function(&format!("{:03}_systems", 5 * amount + 1), |bencher| {
bencher.iter(|| {
run_stage(&mut stage, &mut world);
});
});
}
group.finish();
}
pub fn run_criteria_no_with_labels(criterion: &mut Criterion) {
let mut world = World::new();
let mut group = criterion.benchmark_group("run_criteria/no_with_labels");
group.warm_up_time(std::time::Duration::from_millis(500));
group.measurement_time(std::time::Duration::from_secs(3));
fn empty() {}
fn always_no() -> ShouldRun {
ShouldRun::No
}
for amount in 0..21 {
let mut stage = SystemStage::parallel();
stage.add_system(empty.with_run_criteria(always_no.label(Always::No)));
for _ in 0..amount {
stage
.add_system(empty.with_run_criteria(Always::No))
.add_system(empty.with_run_criteria(Always::No))
.add_system(empty.with_run_criteria(Always::No))
.add_system(empty.with_run_criteria(Always::No))
.add_system(empty.with_run_criteria(Always::No));
}
// run once to initialize systems
run_stage(&mut stage, &mut world);
group.bench_function(&format!("{:03}_systems", 5 * amount + 1), |bencher| {
bencher.iter(|| {
run_stage(&mut stage, &mut world);
schedule.run(&mut world);
});
});
}
@ -149,25 +77,25 @@ pub fn run_criteria_yes_with_query(criterion: &mut Criterion) {
group.warm_up_time(std::time::Duration::from_millis(500));
group.measurement_time(std::time::Duration::from_secs(3));
fn empty() {}
fn yes_with_query(query: Query<&TestBool>) -> ShouldRun {
query.single().0.into()
fn yes_with_query(query: Query<&TestBool>) -> bool {
query.single().0
}
for amount in 0..21 {
let mut stage = SystemStage::parallel();
stage.add_system(empty.with_run_criteria(yes_with_query));
let mut schedule = Schedule::new();
schedule.add_system(empty.run_if(yes_with_query));
for _ in 0..amount {
stage
.add_system(empty.with_run_criteria(yes_with_query))
.add_system(empty.with_run_criteria(yes_with_query))
.add_system(empty.with_run_criteria(yes_with_query))
.add_system(empty.with_run_criteria(yes_with_query))
.add_system(empty.with_run_criteria(yes_with_query));
schedule
.add_system(empty.run_if(yes_with_query))
.add_system(empty.run_if(yes_with_query))
.add_system(empty.run_if(yes_with_query))
.add_system(empty.run_if(yes_with_query))
.add_system(empty.run_if(yes_with_query));
}
// run once to initialize systems
run_stage(&mut stage, &mut world);
schedule.run(&mut world);
group.bench_function(&format!("{:03}_systems", 5 * amount + 1), |bencher| {
bencher.iter(|| {
run_stage(&mut stage, &mut world);
schedule.run(&mut world);
});
});
}
@ -181,25 +109,25 @@ pub fn run_criteria_yes_with_resource(criterion: &mut Criterion) {
group.warm_up_time(std::time::Duration::from_millis(500));
group.measurement_time(std::time::Duration::from_secs(3));
fn empty() {}
fn yes_with_resource(res: Res<TestBool>) -> ShouldRun {
res.0.into()
fn yes_with_resource(res: Res<TestBool>) -> bool {
res.0
}
for amount in 0..21 {
let mut stage = SystemStage::parallel();
stage.add_system(empty.with_run_criteria(yes_with_resource));
let mut schedule = Schedule::new();
schedule.add_system(empty.run_if(yes_with_resource));
for _ in 0..amount {
stage
.add_system(empty.with_run_criteria(yes_with_resource))
.add_system(empty.with_run_criteria(yes_with_resource))
.add_system(empty.with_run_criteria(yes_with_resource))
.add_system(empty.with_run_criteria(yes_with_resource))
.add_system(empty.with_run_criteria(yes_with_resource));
schedule
.add_system(empty.run_if(yes_with_resource))
.add_system(empty.run_if(yes_with_resource))
.add_system(empty.run_if(yes_with_resource))
.add_system(empty.run_if(yes_with_resource))
.add_system(empty.run_if(yes_with_resource));
}
// run once to initialize systems
run_stage(&mut stage, &mut world);
schedule.run(&mut world);
group.bench_function(&format!("{:03}_systems", 5 * amount + 1), |bencher| {
bencher.iter(|| {
run_stage(&mut stage, &mut world);
schedule.run(&mut world);
});
});
}

View file

@ -1,15 +1,6 @@
use bevy_ecs::{
component::Component,
schedule::{Stage, SystemStage},
system::Query,
world::World,
};
use bevy_ecs::{component::Component, schedule_v3::Schedule, system::Query, world::World};
use criterion::Criterion;
fn run_stage(stage: &mut SystemStage, world: &mut World) {
stage.run(world);
}
#[derive(Component)]
struct A(f32);
#[derive(Component)]
@ -30,31 +21,31 @@ pub fn empty_systems(criterion: &mut Criterion) {
group.measurement_time(std::time::Duration::from_secs(3));
fn empty() {}
for amount in 0..5 {
let mut stage = SystemStage::parallel();
let mut schedule = Schedule::new();
for _ in 0..amount {
stage.add_system(empty);
schedule.add_system(empty);
}
run_stage(&mut stage, &mut world);
schedule.run(&mut world);
group.bench_function(&format!("{:03}_systems", amount), |bencher| {
bencher.iter(|| {
run_stage(&mut stage, &mut world);
schedule.run(&mut world);
});
});
}
for amount in 1..21 {
let mut stage = SystemStage::parallel();
let mut schedule = Schedule::new();
for _ in 0..amount {
stage
schedule
.add_system(empty)
.add_system(empty)
.add_system(empty)
.add_system(empty)
.add_system(empty);
}
run_stage(&mut stage, &mut world);
schedule.run(&mut world);
group.bench_function(&format!("{:03}_systems", 5 * amount), |bencher| {
bencher.iter(|| {
run_stage(&mut stage, &mut world);
schedule.run(&mut world);
});
});
}
@ -87,12 +78,12 @@ pub fn busy_systems(criterion: &mut Criterion) {
world.spawn_batch((0..ENTITY_BUNCH).map(|_| (A(0.0), B(0.0), C(0.0), D(0.0))));
world.spawn_batch((0..ENTITY_BUNCH).map(|_| (A(0.0), B(0.0), C(0.0), E(0.0))));
for system_amount in 0..5 {
let mut stage = SystemStage::parallel();
stage.add_system(ab).add_system(cd).add_system(ce);
let mut schedule = Schedule::new();
schedule.add_system(ab).add_system(cd).add_system(ce);
for _ in 0..system_amount {
stage.add_system(ab).add_system(cd).add_system(ce);
schedule.add_system(ab).add_system(cd).add_system(ce);
}
run_stage(&mut stage, &mut world);
schedule.run(&mut world);
group.bench_function(
&format!(
"{:02}x_entities_{:02}_systems",
@ -101,7 +92,7 @@ pub fn busy_systems(criterion: &mut Criterion) {
),
|bencher| {
bencher.iter(|| {
run_stage(&mut stage, &mut world);
schedule.run(&mut world);
});
},
);
@ -138,12 +129,12 @@ pub fn contrived(criterion: &mut Criterion) {
world.spawn_batch((0..ENTITY_BUNCH).map(|_| (A(0.0), B(0.0))));
world.spawn_batch((0..ENTITY_BUNCH).map(|_| (C(0.0), D(0.0))));
for system_amount in 0..5 {
let mut stage = SystemStage::parallel();
stage.add_system(s_0).add_system(s_1).add_system(s_2);
let mut schedule = Schedule::new();
schedule.add_system(s_0).add_system(s_1).add_system(s_2);
for _ in 0..system_amount {
stage.add_system(s_0).add_system(s_1).add_system(s_2);
schedule.add_system(s_0).add_system(s_1).add_system(s_2);
}
run_stage(&mut stage, &mut world);
schedule.run(&mut world);
group.bench_function(
&format!(
"{:02}x_entities_{:02}_systems",
@ -152,7 +143,7 @@ pub fn contrived(criterion: &mut Criterion) {
),
|bencher| {
bencher.iter(|| {
run_stage(&mut stage, &mut world);
schedule.run(&mut world);
});
},
);

View file

@ -46,13 +46,13 @@ pub fn schedule(c: &mut Criterion) {
world.spawn_batch((0..10000).map(|_| (A(0.0), B(0.0), C(0.0), E(0.0))));
let mut stage = SystemStage::parallel();
stage.add_system(ab);
stage.add_system(cd);
stage.add_system(ce);
stage.run(&mut world);
let mut schedule = Schedule::new();
schedule.add_system(ab);
schedule.add_system(cd);
schedule.add_system(ce);
schedule.run(&mut world);
b.iter(move || stage.run(&mut world));
b.iter(move || schedule.run(&mut world));
});
group.finish();
}
@ -63,15 +63,14 @@ pub fn build_schedule(criterion: &mut Criterion) {
// Use multiple different kinds of label to ensure that dynamic dispatch
// doesn't somehow get optimized away.
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct NumLabel(usize);
#[derive(Debug, Clone, Copy, SystemLabel)]
#[derive(Debug, Clone, Copy, SystemSet, PartialEq, Eq, Hash)]
struct DummyLabel;
impl SystemLabel for NumLabel {
fn as_str(&self) -> &'static str {
let s = self.0.to_string();
Box::leak(s.into_boxed_str())
impl SystemSet for NumLabel {
fn dyn_clone(&self) -> Box<dyn SystemSet> {
Box::new(self.clone())
}
}
@ -80,12 +79,9 @@ pub fn build_schedule(criterion: &mut Criterion) {
group.measurement_time(std::time::Duration::from_secs(15));
// Method: generate a set of `graph_size` systems which have a One True Ordering.
// Add system to the stage with full constraints. Hopefully this should be maximimally
// Add system to the schedule with full constraints. Hopefully this should be maximimally
// difficult for bevy to figure out.
// Also, we are performing the `as_label` operation outside of the loop since that
// requires an allocation and a leak. This is not something that would be necessary in a
// real scenario, just a contrivance for the benchmark.
let labels: Vec<_> = (0..1000).map(|i| NumLabel(i).as_label()).collect();
let labels: Vec<_> = (0..1000).map(|i| NumLabel(i)).collect();
// Benchmark graphs of different sizes.
for graph_size in [100, 500, 1000] {
@ -104,12 +100,12 @@ pub fn build_schedule(criterion: &mut Criterion) {
group.bench_function(format!("{graph_size}_schedule"), |bencher| {
bencher.iter(|| {
let mut app = App::new();
app.add_system(empty_system.label(DummyLabel));
app.add_system(empty_system.in_set(DummyLabel));
// Build a fully-connected dependency graph describing the One True Ordering.
// Not particularly realistic but this can be refined later.
for i in 0..graph_size {
let mut sys = empty_system.label(labels[i]).before(DummyLabel);
let mut sys = empty_system.in_set(labels[i]).before(DummyLabel);
for label in labels.iter().take(i) {
sys = sys.after(*label);
}

View file

@ -5,18 +5,10 @@
use std::ops::Deref;
use std::time::Duration;
use bevy_app::{App, CoreStage, Plugin};
use bevy_app::{App, CoreSet, Plugin};
use bevy_asset::{AddAsset, Assets, Handle};
use bevy_core::Name;
use bevy_ecs::{
change_detection::{DetectChanges, Mut},
entity::Entity,
prelude::Component,
query::With,
reflect::ReflectComponent,
schedule::IntoSystemDescriptor,
system::{Query, Res},
};
use bevy_ecs::prelude::*;
use bevy_hierarchy::{Children, Parent};
use bevy_math::{Quat, Vec3};
use bevy_reflect::{FromReflect, Reflect, TypeUuid};
@ -558,9 +550,10 @@ impl Plugin for AnimationPlugin {
app.add_asset::<AnimationClip>()
.register_asset_reflect::<AnimationClip>()
.register_type::<AnimationPlayer>()
.add_system_to_stage(
CoreStage::PostUpdate,
animation_player.before(TransformSystem::TransformPropagate),
.add_system(
animation_player
.in_set(CoreSet::PostUpdate)
.before(TransformSystem::TransformPropagate),
);
}
}

View file

@ -1,14 +1,12 @@
use crate::{CoreStage, Plugin, PluginGroup, StartupSchedule, StartupStage};
use crate::{CoreSchedule, CoreSet, Plugin, PluginGroup, StartupSet};
pub use bevy_derive::AppLabel;
use bevy_ecs::{
event::{Event, Events},
prelude::FromWorld,
schedule::{
IntoSystemDescriptor, Schedule, ShouldRun, Stage, StageLabel, State, StateData, SystemSet,
SystemStage,
prelude::*,
schedule_v3::{
apply_state_transition, common_conditions::run_once as run_once_condition,
run_enter_schedule, BoxedScheduleLabel, IntoSystemConfig, IntoSystemSetConfigs,
ScheduleLabel,
},
system::Resource,
world::World,
};
use bevy_utils::{tracing::debug, HashMap, HashSet};
use std::fmt::Debug;
@ -68,8 +66,14 @@ pub struct App {
/// Typically, it is not configured manually, but set by one of Bevy's built-in plugins.
/// See `bevy::winit::WinitPlugin` and [`ScheduleRunnerPlugin`](crate::schedule_runner::ScheduleRunnerPlugin).
pub runner: Box<dyn Fn(App) + Send>, // Send bound is required to make App Send
/// A container of [`Stage`]s set to be run in a linear order.
pub schedule: Schedule,
/// The schedule that systems are added to by default.
///
/// This is initially set to [`CoreSchedule::Main`].
pub default_schedule_label: BoxedScheduleLabel,
/// The schedule that controls the outer loop of schedule execution.
///
/// This is initially set to [`CoreSchedule::Outer`].
pub outer_schedule_label: BoxedScheduleLabel,
sub_apps: HashMap<AppLabelId, SubApp>,
plugin_registry: Vec<Box<dyn Plugin>>,
plugin_name_added: HashSet<String>,
@ -94,8 +98,9 @@ impl Debug for App {
/// # Example
///
/// ```rust
/// # use bevy_app::{App, AppLabel, SubApp};
/// # use bevy_app::{App, AppLabel, SubApp, CoreSchedule};
/// # use bevy_ecs::prelude::*;
/// # use bevy_ecs::schedule_v3::ScheduleLabel;
///
/// #[derive(Resource, Default)]
/// struct Val(pub i32);
@ -103,26 +108,28 @@ impl Debug for App {
/// #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)]
/// struct ExampleApp;
///
/// #[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)]
/// struct ExampleStage;
/// let mut app = App::new();
///
/// let mut app = App::empty();
/// // initialize the main app with a value of 0;
/// app.insert_resource(Val(10));
///
/// // create a app with a resource and a single stage
/// // create a app with a resource and a single schedule
/// let mut sub_app = App::empty();
/// // add an outer schedule that runs the main schedule
/// sub_app.add_simple_outer_schedule();
/// sub_app.insert_resource(Val(100));
/// let mut example_stage = SystemStage::single_threaded();
/// example_stage.add_system(|counter: Res<Val>| {
///
/// // initialize main schedule
/// sub_app.init_schedule(CoreSchedule::Main);
/// sub_app.add_system(|counter: Res<Val>| {
/// // since we assigned the value from the main world in extract
/// // we see that value instead of 100
/// assert_eq!(counter.0, 10);
/// });
/// sub_app.add_stage(ExampleStage, example_stage);
///
/// // add the sub_app to the app
/// app.insert_sub_app(ExampleApp, SubApp::new(sub_app, |main_world, sub_app| {
/// // extract the value from the main app to the sub app
/// sub_app.world.resource_mut::<Val>().0 = main_world.resource::<Val>().0;
/// }));
///
@ -152,9 +159,11 @@ impl SubApp {
}
}
/// Runs the `SubApp`'s schedule.
/// Runs the `SubApp`'s default schedule.
pub fn run(&mut self) {
self.app.schedule.run(&mut self.app.world);
self.app
.world
.run_schedule_ref(&*self.app.outer_schedule_label);
self.app.world.clear_trackers();
}
@ -180,7 +189,9 @@ impl Default for App {
#[cfg(feature = "bevy_reflect")]
app.init_resource::<AppTypeRegistry>();
app.add_default_stages().add_event::<AppExit>();
app.add_default_schedules();
app.add_event::<AppExit>();
#[cfg(feature = "bevy_ci_testing")]
{
@ -194,21 +205,26 @@ impl Default for App {
impl App {
/// Creates a new [`App`] with some default structure to enable core engine features.
/// This is the preferred constructor for most use cases.
///
/// This calls [`App::add_default_schedules`].
pub fn new() -> App {
App::default()
}
/// Creates a new empty [`App`] with minimal default configuration.
///
/// This constructor should be used if you wish to provide a custom schedule, exit handling, cleanup, etc.
/// This constructor should be used if you wish to provide custom scheduling, exit handling, cleanup, etc.
pub fn empty() -> App {
let mut world = World::new();
world.init_resource::<Schedules>();
Self {
world: Default::default(),
schedule: Default::default(),
world,
runner: Box::new(run_once),
sub_apps: HashMap::default(),
plugin_registry: Vec::default(),
plugin_name_added: Default::default(),
default_schedule_label: Box::new(CoreSchedule::Main),
outer_schedule_label: Box::new(CoreSchedule::Outer),
is_building_plugin: false,
}
}
@ -216,13 +232,20 @@ impl App {
/// Advances the execution of the [`Schedule`] by one cycle.
///
/// This method also updates sub apps.
/// See [`insert_sub_app`](Self::insert_sub_app) for more details.
///
/// See [`insert_sub_app`](Self::insert_sub_app) and [`run_once`](Schedule::run_once) for more details.
/// The schedule run by this method is determined by the [`outer_schedule_label`](App) field.
/// In normal usage, this is [`CoreSchedule::Outer`], which will run [`CoreSchedule::Startup`]
/// the first time the app is run, then [`CoreSchedule::Main`] on every call of this method.
///
/// # Panics
///
/// The active schedule of the app must be set before this method is called.
pub fn update(&mut self) {
{
#[cfg(feature = "trace")]
let _bevy_frame_update_span = info_span!("main app").entered();
self.schedule.run(&mut self.world);
self.world.run_schedule_ref(&*self.outer_schedule_label);
}
for (_label, sub_app) in self.sub_apps.iter_mut() {
#[cfg(feature = "trace")]
@ -280,195 +303,54 @@ impl App {
(runner)(app);
}
/// Adds a [`Stage`] with the given `label` to the last position of the app's
/// [`Schedule`].
/// Adds [`State<S>`] and [`NextState<S>`] resources, [`OnEnter`] and [`OnExit`] schedules
/// for each state variant, an instance of [`apply_state_transition::<S>`] in
/// [`CoreSet::StateTransitions`] so that transitions happen before [`CoreSet::Update`] and
/// a instance of [`run_enter_schedule::<S>`] in [`CoreSet::StateTransitions`] with a
/// with a [`run_once`](`run_once_condition`) condition to run the on enter schedule of the
/// initial state.
///
/// # Examples
/// This also adds an [`OnUpdate`] system set for each state variant,
/// which run during [`CoreSet::StateTransitions`] after the transitions are applied.
/// These systems sets only run if the [`State<S>`] resource matches their label.
///
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// # let mut app = App::new();
/// #
/// #[derive(StageLabel)]
/// struct MyStage;
/// app.add_stage(MyStage, SystemStage::parallel());
/// ```
pub fn add_stage<S: Stage>(&mut self, label: impl StageLabel, stage: S) -> &mut Self {
self.schedule.add_stage(label, stage);
/// If you would like to control how other systems run based on the current state,
/// you can emulate this behavior using the [`state_equals`] [`Condition`](bevy_ecs::schedule_v3::Condition).
///
/// Note that you can also apply state transitions at other points in the schedule
/// by adding the [`apply_state_transition`] system manually.
pub fn add_state<S: States>(&mut self) -> &mut Self {
self.init_resource::<State<S>>();
self.init_resource::<NextState<S>>();
self.add_systems(
(
run_enter_schedule::<S>.run_if(run_once_condition()),
apply_state_transition::<S>,
)
.chain()
.in_set(CoreSet::StateTransitions),
);
let main_schedule = self.get_schedule_mut(CoreSchedule::Main).unwrap();
for variant in S::variants() {
main_schedule.configure_set(
OnUpdate(variant.clone())
.in_set(CoreSet::StateTransitions)
.run_if(state_equals(variant))
.after(apply_state_transition::<S>),
);
}
// These are different for loops to avoid conflicting access to self
for variant in S::variants() {
self.add_schedule(OnEnter(variant.clone()), Schedule::new());
self.add_schedule(OnExit(variant), Schedule::new());
}
self
}
/// Adds a [`Stage`] with the given `label` to the app's [`Schedule`], located
/// immediately after the stage labeled by `target`.
///
/// # Examples
///
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// # let mut app = App::new();
/// #
/// #[derive(StageLabel)]
/// struct MyStage;
/// app.add_stage_after(CoreStage::Update, MyStage, SystemStage::parallel());
/// ```
pub fn add_stage_after<S: Stage>(
&mut self,
target: impl StageLabel,
label: impl StageLabel,
stage: S,
) -> &mut Self {
self.schedule.add_stage_after(target, label, stage);
self
}
/// Adds a [`Stage`] with the given `label` to the app's [`Schedule`], located
/// immediately before the stage labeled by `target`.
///
/// # Examples
///
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// # let mut app = App::new();
/// #
/// #[derive(StageLabel)]
/// struct MyStage;
/// app.add_stage_before(CoreStage::Update, MyStage, SystemStage::parallel());
/// ```
pub fn add_stage_before<S: Stage>(
&mut self,
target: impl StageLabel,
label: impl StageLabel,
stage: S,
) -> &mut Self {
self.schedule.add_stage_before(target, label, stage);
self
}
/// Adds a [`Stage`] with the given `label` to the last position of the
/// [startup schedule](Self::add_default_stages).
///
/// # Examples
///
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// # let mut app = App::new();
/// #
/// #[derive(StageLabel)]
/// struct MyStartupStage;
/// app.add_startup_stage(MyStartupStage, SystemStage::parallel());
/// ```
pub fn add_startup_stage<S: Stage>(&mut self, label: impl StageLabel, stage: S) -> &mut Self {
self.schedule
.stage(StartupSchedule, |schedule: &mut Schedule| {
schedule.add_stage(label, stage)
});
self
}
/// Adds a [startup stage](Self::add_default_stages) with the given `label`, immediately
/// after the stage labeled by `target`.
///
/// The `target` label must refer to a stage inside the startup schedule.
///
/// # Examples
///
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// # let mut app = App::new();
/// #
/// #[derive(StageLabel)]
/// struct MyStartupStage;
/// app.add_startup_stage_after(
/// StartupStage::Startup,
/// MyStartupStage,
/// SystemStage::parallel()
/// );
/// ```
pub fn add_startup_stage_after<S: Stage>(
&mut self,
target: impl StageLabel,
label: impl StageLabel,
stage: S,
) -> &mut Self {
self.schedule
.stage(StartupSchedule, |schedule: &mut Schedule| {
schedule.add_stage_after(target, label, stage)
});
self
}
/// Adds a [startup stage](Self::add_default_stages) with the given `label`, immediately
/// before the stage labeled by `target`.
///
/// The `target` label must refer to a stage inside the startup schedule.
///
/// # Examples
///
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// # let mut app = App::new();
/// #
/// #[derive(StageLabel)]
/// struct MyStartupStage;
/// app.add_startup_stage_before(
/// StartupStage::Startup,
/// MyStartupStage,
/// SystemStage::parallel()
/// );
/// ```
pub fn add_startup_stage_before<S: Stage>(
&mut self,
target: impl StageLabel,
label: impl StageLabel,
stage: S,
) -> &mut Self {
self.schedule
.stage(StartupSchedule, |schedule: &mut Schedule| {
schedule.add_stage_before(target, label, stage)
});
self
}
/// Fetches the [`Stage`] of type `T` marked with `label` from the [`Schedule`], then
/// executes the provided `func` passing the fetched stage to it as an argument.
///
/// The `func` argument should be a function or a closure that accepts a mutable reference
/// to a struct implementing `Stage` and returns the same type. That means that it should
/// also assume that the stage has already been fetched successfully.
///
/// See [`stage`](Schedule::stage) for more details.
///
/// # Examples
///
/// Here the closure is used to add a system to the update stage:
///
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// #
/// # let mut app = App::new();
/// # fn my_system() {}
/// #
/// app.stage(CoreStage::Update, |stage: &mut SystemStage| {
/// stage.add_system(my_system)
/// });
/// ```
pub fn stage<T: Stage, F: FnOnce(&mut T) -> &mut T>(
&mut self,
label: impl StageLabel,
func: F,
) -> &mut Self {
self.schedule.stage(label, func);
self
}
/// Adds a system to the [update stage](Self::add_default_stages) of the app's [`Schedule`].
/// Adds a system to the default system set and schedule of the app's [`Schedules`].
///
/// Refer to the [system module documentation](bevy_ecs::system) to see how a system
/// can be defined.
@ -484,11 +366,20 @@ impl App {
/// #
/// app.add_system(my_system);
/// ```
pub fn add_system<Params>(&mut self, system: impl IntoSystemDescriptor<Params>) -> &mut Self {
self.add_system_to_stage(CoreStage::Update, system)
pub fn add_system<P>(&mut self, system: impl IntoSystemConfig<P>) -> &mut Self {
let mut schedules = self.world.resource_mut::<Schedules>();
if let Some(default_schedule) = schedules.get_mut(&*self.default_schedule_label) {
default_schedule.add_system(system);
} else {
let schedule_label = &self.default_schedule_label;
panic!("Default schedule {schedule_label:?} does not exist.")
}
/// Adds a [`SystemSet`] to the [update stage](Self::add_default_stages).
self
}
/// Adds a system to the default system set and schedule of the app's [`Schedules`].
///
/// # Examples
///
@ -501,84 +392,59 @@ impl App {
/// # fn system_b() {}
/// # fn system_c() {}
/// #
/// app.add_system_set(
/// SystemSet::new()
/// .with_system(system_a)
/// .with_system(system_b)
/// .with_system(system_c),
/// );
/// app.add_systems((system_a, system_b, system_c));
/// ```
pub fn add_system_set(&mut self, system_set: SystemSet) -> &mut Self {
self.add_system_set_to_stage(CoreStage::Update, system_set)
pub fn add_systems<P>(&mut self, systems: impl IntoSystemConfigs<P>) -> &mut Self {
let mut schedules = self.world.resource_mut::<Schedules>();
if let Some(default_schedule) = schedules.get_mut(&*self.default_schedule_label) {
default_schedule.add_systems(systems);
} else {
let schedule_label = &self.default_schedule_label;
panic!("Default schedule {schedule_label:?} does not exist.")
}
/// Adds a system to the [`Stage`] identified by `stage_label`.
///
/// # Examples
///
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// #
/// # let mut app = App::new();
/// # fn my_system() {}
/// #
/// app.add_system_to_stage(CoreStage::PostUpdate, my_system);
/// ```
pub fn add_system_to_stage<Params>(
&mut self,
stage_label: impl StageLabel,
system: impl IntoSystemDescriptor<Params>,
) -> &mut Self {
use std::any::TypeId;
assert!(
stage_label.type_id() != TypeId::of::<StartupStage>(),
"use `add_startup_system_to_stage` instead of `add_system_to_stage` to add a system to a StartupStage"
);
self.schedule.add_system_to_stage(stage_label, system);
self
}
/// Adds a [`SystemSet`] to the [`Stage`] identified by `stage_label`.
///
/// # Examples
///
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// #
/// # let mut app = App::new();
/// # fn system_a() {}
/// # fn system_b() {}
/// # fn system_c() {}
/// #
/// app.add_system_set_to_stage(
/// CoreStage::PostUpdate,
/// SystemSet::new()
/// .with_system(system_a)
/// .with_system(system_b)
/// .with_system(system_c),
/// );
/// ```
pub fn add_system_set_to_stage(
/// Adds a system to the provided [`Schedule`].
pub fn add_system_to_schedule<P>(
&mut self,
stage_label: impl StageLabel,
system_set: SystemSet,
schedule_label: impl ScheduleLabel,
system: impl IntoSystemConfig<P>,
) -> &mut Self {
use std::any::TypeId;
assert!(
stage_label.type_id() != TypeId::of::<StartupStage>(),
"use `add_startup_system_set_to_stage` instead of `add_system_set_to_stage` to add system sets to a StartupStage"
);
self.schedule
.add_system_set_to_stage(stage_label, system_set);
let mut schedules = self.world.resource_mut::<Schedules>();
if let Some(schedule) = schedules.get_mut(&schedule_label) {
schedule.add_system(system);
} else {
panic!("Provided schedule {schedule_label:?} does not exist.")
}
self
}
/// Adds a system to the [startup stage](Self::add_default_stages) of the app's [`Schedule`].
/// Adds a collection of system to the provided [`Schedule`].
pub fn add_systems_to_schedule<P>(
&mut self,
schedule_label: impl ScheduleLabel,
systems: impl IntoSystemConfigs<P>,
) -> &mut Self {
let mut schedules = self.world.resource_mut::<Schedules>();
if let Some(schedule) = schedules.get_mut(&schedule_label) {
schedule.add_systems(systems);
} else {
panic!("Provided schedule {schedule_label:?} does not exist.")
}
self
}
/// Adds a system to [`CoreSchedule::Startup`].
///
/// * For adding a system that runs every frame, see [`add_system`](Self::add_system).
/// * For adding a system to a specific stage, see [`add_system_to_stage`](Self::add_system_to_stage).
/// These systems will run exactly once, at the start of the [`App`]'s lifecycle.
/// To add a system that runs every frame, see [`add_system`](Self::add_system).
///
/// # Examples
///
@ -593,14 +459,11 @@ impl App {
/// App::new()
/// .add_startup_system(my_startup_system);
/// ```
pub fn add_startup_system<Params>(
&mut self,
system: impl IntoSystemDescriptor<Params>,
) -> &mut Self {
self.add_startup_system_to_stage(StartupStage::Startup, system)
pub fn add_startup_system<P>(&mut self, system: impl IntoSystemConfig<P>) -> &mut Self {
self.add_system_to_schedule(CoreSchedule::Startup, system)
}
/// Adds a [`SystemSet`] to the [startup stage](Self::add_default_stages).
/// Adds a collection of systems to [`CoreSchedule::Startup`].
///
/// # Examples
///
@ -613,161 +476,90 @@ impl App {
/// # fn startup_system_b() {}
/// # fn startup_system_c() {}
/// #
/// app.add_startup_system_set(
/// SystemSet::new()
/// .with_system(startup_system_a)
/// .with_system(startup_system_b)
/// .with_system(startup_system_c),
/// app.add_startup_systems(
/// (
/// startup_system_a,
/// startup_system_b,
/// startup_system_c,
/// )
/// );
/// ```
pub fn add_startup_system_set(&mut self, system_set: SystemSet) -> &mut Self {
self.add_startup_system_set_to_stage(StartupStage::Startup, system_set)
pub fn add_startup_systems<P>(&mut self, systems: impl IntoSystemConfigs<P>) -> &mut Self {
self.add_systems_to_schedule(CoreSchedule::Startup, systems)
}
/// Adds a system to the [startup schedule](Self::add_default_stages), in the stage
/// identified by `stage_label`.
///
/// `stage_label` must refer to a stage inside the startup schedule.
///
/// # Examples
///
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// #
/// # let mut app = App::new();
/// # fn my_startup_system() {}
/// #
/// app.add_startup_system_to_stage(StartupStage::PreStartup, my_startup_system);
/// ```
pub fn add_startup_system_to_stage<Params>(
&mut self,
stage_label: impl StageLabel,
system: impl IntoSystemDescriptor<Params>,
) -> &mut Self {
self.schedule
.stage(StartupSchedule, |schedule: &mut Schedule| {
schedule.add_system_to_stage(stage_label, system)
});
/// Configures a system set in the default schedule, adding the set if it does not exist.
pub fn configure_set(&mut self, set: impl IntoSystemSetConfig) -> &mut Self {
self.world
.resource_mut::<Schedules>()
.get_mut(&*self.default_schedule_label)
.unwrap()
.configure_set(set);
self
}
/// Adds a [`SystemSet`] to the [startup schedule](Self::add_default_stages), in the stage
/// identified by `stage_label`.
///
/// `stage_label` must refer to a stage inside the startup schedule.
///
/// # Examples
///
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// #
/// # let mut app = App::new();
/// # fn startup_system_a() {}
/// # fn startup_system_b() {}
/// # fn startup_system_c() {}
/// #
/// app.add_startup_system_set_to_stage(
/// StartupStage::PreStartup,
/// SystemSet::new()
/// .with_system(startup_system_a)
/// .with_system(startup_system_b)
/// .with_system(startup_system_c),
/// );
/// ```
pub fn add_startup_system_set_to_stage(
&mut self,
stage_label: impl StageLabel,
system_set: SystemSet,
) -> &mut Self {
self.schedule
.stage(StartupSchedule, |schedule: &mut Schedule| {
schedule.add_system_set_to_stage(stage_label, system_set)
});
/// Configures a collection of system sets in the default schedule, adding any sets that do not exist.
pub fn configure_sets(&mut self, sets: impl IntoSystemSetConfigs) -> &mut Self {
self.world
.resource_mut::<Schedules>()
.get_mut(&*self.default_schedule_label)
.unwrap()
.configure_sets(sets);
self
}
/// Adds a new [`State`] with the given `initial` value.
/// This inserts a new `State<T>` resource and adds a new "driver" to [`CoreStage::Update`].
/// Each stage that uses `State<T>` for system run criteria needs a driver. If you need to use
/// your state in a different stage, consider using [`Self::add_state_to_stage`] or manually
/// adding [`State::get_driver`] to additional stages you need it in.
pub fn add_state<T>(&mut self, initial: T) -> &mut Self
where
T: StateData,
{
self.add_state_to_stage(CoreStage::Update, initial)
}
/// Adds a new [`State`] with the given `initial` value.
/// This inserts a new `State<T>` resource and adds a new "driver" to the given stage.
/// Each stage that uses `State<T>` for system run criteria needs a driver. If you need to use
/// your state in more than one stage, consider manually adding [`State::get_driver`] to the
/// stages you need it in.
pub fn add_state_to_stage<T>(&mut self, stage: impl StageLabel, initial: T) -> &mut Self
where
T: StateData,
{
self.insert_resource(State::new(initial))
.add_system_set_to_stage(stage, State::<T>::get_driver())
}
/// Adds utility stages to the [`Schedule`], giving it a standardized structure.
/// Adds standardized schedules and labels to an [`App`].
///
/// Adding those stages is necessary to make some core engine features work, like
/// adding systems without specifying a stage, or registering events. This is however
/// done by default by calling `App::default`, which is in turn called by
/// Adding these schedules is necessary to make almost all core engine features work.
/// This is typically done implicitly by calling `App::default`, which is in turn called by
/// [`App::new`].
///
/// # The stages
/// The schedules added are defined in the [`CoreSchedule`] enum,
/// and have a starting configuration defined by:
///
/// All the added stages, with the exception of the startup stage, run every time the
/// schedule is invoked. The stages are the following, in order of execution:
///
/// - **First:** Runs at the very start of the schedule execution cycle, even before the
/// startup stage.
/// - **Startup:** This is actually a schedule containing sub-stages. Runs only once
/// when the app starts.
/// - **Pre-startup:** Intended for systems that need to run before other startup systems.
/// - **Startup:** The main startup stage. Startup systems are added here by default.
/// - **Post-startup:** Intended for systems that need to run after other startup systems.
/// - **Pre-update:** Often used by plugins to prepare their internal state before the
/// update stage begins.
/// - **Update:** Intended for user defined logic. Systems are added here by default.
/// - **Post-update:** Often used by plugins to finalize their internal state after the
/// world changes that happened during the update stage.
/// - **Last:** Runs right before the end of the schedule execution cycle.
///
/// The labels for those stages are defined in the [`CoreStage`] and [`StartupStage`] `enum`s.
/// - [`CoreSchedule::Outer`]: uses [`CoreSchedule::outer_schedule`]
/// - [`CoreSchedule::Startup`]: uses [`StartupSet::base_schedule`]
/// - [`CoreSchedule::Main`]: uses [`CoreSet::base_schedule`]
/// - [`CoreSchedule::FixedUpdate`]: no starting configuration
///
/// # Examples
///
/// ```
/// # use bevy_app::prelude::*;
/// #
/// let app = App::empty().add_default_stages();
/// use bevy_app::App;
/// use bevy_ecs::schedule_v3::Schedules;
///
/// let app = App::empty()
/// .init_resource::<Schedules>()
/// .add_default_schedules()
/// .update();
/// ```
pub fn add_default_stages(&mut self) -> &mut Self {
self.add_stage(CoreStage::First, SystemStage::parallel())
.add_stage(
StartupSchedule,
Schedule::default()
.with_run_criteria(ShouldRun::once)
.with_stage(StartupStage::PreStartup, SystemStage::parallel())
.with_stage(StartupStage::Startup, SystemStage::parallel())
.with_stage(StartupStage::PostStartup, SystemStage::parallel()),
)
.add_stage(CoreStage::PreUpdate, SystemStage::parallel())
.add_stage(CoreStage::Update, SystemStage::parallel())
.add_stage(CoreStage::PostUpdate, SystemStage::parallel())
.add_stage(CoreStage::Last, SystemStage::parallel())
pub fn add_default_schedules(&mut self) -> &mut Self {
self.add_schedule(CoreSchedule::Outer, CoreSchedule::outer_schedule());
self.add_schedule(CoreSchedule::Startup, StartupSet::base_schedule());
self.add_schedule(CoreSchedule::Main, CoreSet::base_schedule());
self.init_schedule(CoreSchedule::FixedUpdate);
self
}
/// adds a single threaded outer schedule to the [`App`] that just runs the main schedule
pub fn add_simple_outer_schedule(&mut self) -> &mut Self {
fn run_main_schedule(world: &mut World) {
world.run_schedule(CoreSchedule::Main);
}
self.edit_schedule(CoreSchedule::Outer, |schedule| {
schedule.set_executor_kind(bevy_ecs::schedule_v3::ExecutorKind::SingleThreaded);
schedule.add_system(run_main_schedule);
});
self
}
/// Setup the application to manage events of type `T`.
///
/// This is done by adding a [`Resource`] of type [`Events::<T>`],
/// and inserting an [`update_system`](Events::update_system) into [`CoreStage::First`].
/// and inserting an [`update_system`](Events::update_system) into [`CoreSet::First`].
///
/// See [`Events`] for defining events.
///
@ -788,7 +580,7 @@ impl App {
{
if !self.world.contains_resource::<Events<T>>() {
self.init_resource::<Events<T>>()
.add_system_to_stage(CoreStage::First, Events::<T>::update_system);
.add_system(Events::<T>::update_system.in_set(CoreSet::First));
}
self
}
@ -1146,6 +938,64 @@ impl App {
.map(|sub_app| &sub_app.app)
.ok_or(label)
}
/// Adds a new `schedule` to the [`App`] under the provided `label`.
///
/// # Warning
/// This method will overwrite any existing schedule at that label.
/// To avoid this behavior, use the `init_schedule` method instead.
pub fn add_schedule(&mut self, label: impl ScheduleLabel, schedule: Schedule) -> &mut Self {
let mut schedules = self.world.resource_mut::<Schedules>();
schedules.insert(label, schedule);
self
}
/// Initializes a new empty `schedule` to the [`App`] under the provided `label` if it does not exists.
///
/// See [`App::add_schedule`] to pass in a pre-constructed schedule.
pub fn init_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self {
let mut schedules = self.world.resource_mut::<Schedules>();
if !schedules.contains(&label) {
schedules.insert(label, Schedule::new());
}
self
}
/// Gets read-only access to the [`Schedule`] with the provided `label` if it exists.
pub fn get_schedule(&self, label: impl ScheduleLabel) -> Option<&Schedule> {
let schedules = self.world.get_resource::<Schedules>()?;
schedules.get(&label)
}
/// Gets read-write access to a [`Schedule`] with the provided `label` if it exists.
pub fn get_schedule_mut(&mut self, label: impl ScheduleLabel) -> Option<&mut Schedule> {
let schedules = self.world.get_resource_mut::<Schedules>()?;
// We need to call .into_inner here to satisfy the borrow checker:
// it can reason about reborrows using ordinary references but not the `Mut` smart pointer.
schedules.into_inner().get_mut(&label)
}
/// Applies the function to the [`Schedule`] associated with `label`.
///
/// **Note:** This will create the schedule if it does not already exist.
pub fn edit_schedule(
&mut self,
label: impl ScheduleLabel,
mut f: impl FnMut(&mut Schedule),
) -> &mut Self {
let mut schedules = self.world.resource_mut::<Schedules>();
if schedules.get(&label).is_none() {
schedules.insert(label.dyn_clone(), Schedule::new());
}
let schedule = schedules.get_mut(&label).unwrap();
// Call the function f, passing in the schedule retrieved
f(schedule);
self
}
}
fn run_once(mut app: App) {

View file

@ -23,44 +23,173 @@ pub mod prelude {
pub use crate::AppTypeRegistry;
#[doc(hidden)]
pub use crate::{
app::App, CoreStage, DynamicPlugin, Plugin, PluginGroup, StartupSchedule, StartupStage,
app::App, CoreSchedule, CoreSet, DynamicPlugin, Plugin, PluginGroup, StartupSet,
};
}
use bevy_ecs::schedule::StageLabel;
use bevy_ecs::{
schedule_v3::{
apply_system_buffers, IntoSystemConfig, IntoSystemSetConfig, Schedule, ScheduleLabel,
SystemSet,
},
system::Local,
world::World,
};
/// The names of the default [`App`] stages.
/// The names of the default [`App`] schedules.
///
/// The relative [`Stages`](bevy_ecs::schedule::Stage) are added by [`App::add_default_stages`].
#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)]
pub enum CoreStage {
/// The [`Stage`](bevy_ecs::schedule::Stage) that runs before all other app stages.
First,
/// The [`Stage`](bevy_ecs::schedule::Stage) that runs before [`CoreStage::Update`].
PreUpdate,
/// The [`Stage`](bevy_ecs::schedule::Stage) responsible for doing most app logic. Systems should be registered here by default.
Update,
/// The [`Stage`](bevy_ecs::schedule::Stage) that runs after [`CoreStage::Update`].
PostUpdate,
/// The [`Stage`](bevy_ecs::schedule::Stage) that runs after all other app stages.
Last,
}
/// The label for the startup [`Schedule`](bevy_ecs::schedule::Schedule),
/// which runs once at the beginning of the [`App`].
///
/// When targeting a [`Stage`](bevy_ecs::schedule::Stage) inside this [`Schedule`](bevy_ecs::schedule::Schedule),
/// you need to use [`StartupStage`] instead.
#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)]
pub struct StartupSchedule;
/// The names of the default [`App`] startup stages.
#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)]
pub enum StartupStage {
/// The [`Stage`](bevy_ecs::schedule::Stage) that runs once before [`StartupStage::Startup`].
PreStartup,
/// The [`Stage`](bevy_ecs::schedule::Stage) that runs once when an [`App`] starts up.
/// The corresponding [`Schedule`](bevy_ecs::schedule_v3::Schedule) objects are added by [`App::add_default_schedules`].
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub enum CoreSchedule {
/// The schedule that runs once when the app starts.
Startup,
/// The [`Stage`](bevy_ecs::schedule::Stage) that runs once after [`StartupStage::Startup`].
PostStartup,
/// The schedule that contains the app logic that is evaluated each tick of [`App::update()`].
Main,
/// The schedule that controls which schedules run.
///
/// This is typically created using the [`CoreSchedule::outer_schedule`] method,
/// and does not need to manipulated during ordinary use.
Outer,
/// The schedule that contains systems which only run after a fixed period of time has elapsed.
///
/// The exclusive `run_fixed_update_schedule` system runs this schedule during the [`CoreSet::FixedUpdate`] system set.
FixedUpdate,
}
impl CoreSchedule {
/// An exclusive system that controls which schedule should be running.
///
/// [`CoreSchedule::Main`] is always run.
///
/// If this is the first time this system has been run, [`CoreSchedule::Startup`] will run before [`CoreSchedule::Main`].
pub fn outer_loop(world: &mut World, mut run_at_least_once: Local<bool>) {
if !*run_at_least_once {
world.run_schedule(CoreSchedule::Startup);
*run_at_least_once = true;
}
world.run_schedule(CoreSchedule::Main);
}
/// Initializes a single threaded schedule for [`CoreSchedule::Outer`] that contains the [`outer_loop`](CoreSchedule::outer_loop) system.
pub fn outer_schedule() -> Schedule {
let mut schedule = Schedule::new();
schedule.set_executor_kind(bevy_ecs::schedule_v3::ExecutorKind::SingleThreaded);
schedule.add_system(Self::outer_loop);
schedule
}
}
/// The names of the default [`App`] system sets.
///
/// These are ordered in the same order they are listed.
///
/// The corresponding [`SystemSets`](bevy_ecs::schedule_v3::SystemSet) are added by [`App::add_default_schedules`].
///
/// The `*Flush` sets are assigned to the copy of [`apply_system_buffers`]
/// that runs immediately after the matching system set.
/// These can be useful for ordering, but you almost never want to add your systems to these sets.
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub enum CoreSet {
/// Runs before all other members of this set.
First,
/// The copy of [`apply_system_buffers`] that runs immediately after `First`.
FirstFlush,
/// Runs before [`CoreSet::Update`].
PreUpdate,
/// The copy of [`apply_system_buffers`] that runs immediately after `PreUpdate`.
PreUpdateFlush,
/// Applies [`State`](bevy_ecs::schedule_v3::State) transitions
StateTransitions,
/// Runs systems that should only occur after a fixed period of time.
///
/// The `run_fixed_update_schedule` system runs the [`CoreSchedule::FixedUpdate`] system in this system set.
FixedUpdate,
/// Responsible for doing most app logic. Systems should be registered here by default.
Update,
/// The copy of [`apply_system_buffers`] that runs immediately after `Update`.
UpdateFlush,
/// Runs after [`CoreSet::Update`].
PostUpdate,
/// The copy of [`apply_system_buffers`] that runs immediately after `PostUpdate`.
PostUpdateFlush,
/// Runs after all other members of this set.
Last,
/// The copy of [`apply_system_buffers`] that runs immediately after `Last`.
LastFlush,
}
impl CoreSet {
/// Sets up the base structure of [`CoreSchedule::Main`].
///
/// The sets defined in this enum are configured to run in order,
/// and a copy of [`apply_system_buffers`] is inserted at each `*Flush` label.
pub fn base_schedule() -> Schedule {
use CoreSet::*;
let mut schedule = Schedule::new();
// Create "stage-like" structure using buffer flushes + ordering
schedule.add_system(apply_system_buffers.in_set(FirstFlush));
schedule.add_system(apply_system_buffers.in_set(PreUpdateFlush));
schedule.add_system(apply_system_buffers.in_set(UpdateFlush));
schedule.add_system(apply_system_buffers.in_set(PostUpdateFlush));
schedule.add_system(apply_system_buffers.in_set(LastFlush));
schedule.configure_set(First.before(FirstFlush));
schedule.configure_set(PreUpdate.after(FirstFlush).before(PreUpdateFlush));
schedule.configure_set(StateTransitions.after(PreUpdateFlush).before(FixedUpdate));
schedule.configure_set(FixedUpdate.after(StateTransitions).before(Update));
schedule.configure_set(Update.after(FixedUpdate).before(UpdateFlush));
schedule.configure_set(PostUpdate.after(UpdateFlush).before(PostUpdateFlush));
schedule.configure_set(Last.after(PostUpdateFlush).before(LastFlush));
schedule
}
}
/// The names of the default [`App`] startup sets, which live in [`CoreSchedule::Startup`].
///
/// The corresponding [`SystemSets`](bevy_ecs::schedule_v3::SystemSet) are added by [`App::add_default_schedules`].
///
/// The `*Flush` sets are assigned to the copy of [`apply_system_buffers`]
/// that runs immediately after the matching system set.
/// These can be useful for ordering, but you almost never want to add your systems to these sets.
///
/// [`apply_system_buffers`]: bevy_ecs::prelude::apply_system_buffers
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub enum StartupSet {
/// Runs once before [`StartupSet::Startup`].
PreStartup,
/// The copy of [`apply_system_buffers`] that runs immediately after `PreStartup`.
PreStartupFlush,
/// Runs once when an [`App`] starts up.
Startup,
/// The copy of [`apply_system_buffers`] that runs immediately after `Startup`.
StartupFlush,
/// Runs once after [`StartupSet::Startup`].
PostStartup,
/// The copy of [`apply_system_buffers`] that runs immediately after `PostStartup`.
PostStartupFlush,
}
impl StartupSet {
/// Sets up the base structure of [`CoreSchedule::Startup`].
///
/// The sets defined in this enum are configured to run in order,
/// and a copy of [`apply_system_buffers`] is inserted at each `*Flush` label.
pub fn base_schedule() -> Schedule {
use StartupSet::*;
let mut schedule = Schedule::new();
// Create "stage-like" structure using buffer flushes + ordering
schedule.add_system(apply_system_buffers.in_set(PreStartupFlush));
schedule.add_system(apply_system_buffers.in_set(StartupFlush));
schedule.add_system(apply_system_buffers.in_set(PostStartupFlush));
schedule.configure_set(PreStartup.before(PreStartupFlush));
schedule.configure_set(Startup.after(PreStartupFlush).before(StartupFlush));
schedule.configure_set(PostStartup.after(StartupFlush).before(PostStartupFlush));
schedule
}
}

View file

@ -11,14 +11,14 @@ use std::{cell::RefCell, rc::Rc};
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::{prelude::*, JsCast};
/// Determines the method used to run an [`App`]'s [`Schedule`](bevy_ecs::schedule::Schedule).
/// Determines the method used to run an [`App`]'s [`Schedule`](bevy_ecs::schedule_v3::Schedule).
///
/// It is used in the [`ScheduleRunnerSettings`].
#[derive(Copy, Clone, Debug)]
pub enum RunMode {
/// Indicates that the [`App`]'s schedule should run repeatedly.
Loop {
/// The minimum [`Duration`] to wait after a [`Schedule`](bevy_ecs::schedule::Schedule)
/// The minimum [`Duration`] to wait after a [`Schedule`](bevy_ecs::schedule_v3::Schedule)
/// has completed before repeating. A value of [`None`] will not wait.
wait: Option<Duration>,
},
@ -37,7 +37,7 @@ impl Default for RunMode {
/// It gets added as a [`Resource`](bevy_ecs::system::Resource) inside of the [`ScheduleRunnerPlugin`].
#[derive(Copy, Clone, Default, Resource)]
pub struct ScheduleRunnerSettings {
/// Determines whether the [`Schedule`](bevy_ecs::schedule::Schedule) is run once or repeatedly.
/// Determines whether the [`Schedule`](bevy_ecs::schedule_v3::Schedule) is run once or repeatedly.
pub run_mode: RunMode,
}
@ -59,7 +59,7 @@ impl ScheduleRunnerSettings {
}
}
/// Configures an [`App`] to run its [`Schedule`](bevy_ecs::schedule::Schedule) according to a given
/// Configures an [`App`] to run its [`Schedule`](bevy_ecs::schedule_v3::Schedule) according to a given
/// [`RunMode`].
///
/// [`ScheduleRunnerPlugin`] is included in the
@ -67,7 +67,7 @@ impl ScheduleRunnerSettings {
///
/// [`ScheduleRunnerPlugin`] is *not* included in the
/// [`DefaultPlugins`](https://docs.rs/bevy/latest/bevy/struct.DefaultPlugins.html) plugin group
/// which assumes that the [`Schedule`](bevy_ecs::schedule::Schedule) will be executed by other means:
/// which assumes that the [`Schedule`](bevy_ecs::schedule_v3::Schedule) will be executed by other means:
/// typically, the `winit` event loop
/// (see [`WinitPlugin`](https://docs.rs/bevy/latest/bevy/winit/struct.WinitPlugin.html))
/// executes the schedule making [`ScheduleRunnerPlugin`] unnecessary.

View file

@ -644,7 +644,7 @@ pub fn free_unused_assets_system(asset_server: Res<AssetServer>) {
mod test {
use super::*;
use crate::{loader::LoadedAsset, update_asset_storage_system};
use bevy_app::App;
use bevy_app::{App, CoreSet};
use bevy_ecs::prelude::*;
use bevy_reflect::TypeUuid;
use bevy_utils::BoxedFuture;
@ -847,13 +847,21 @@ mod test {
asset_server.add_loader(FakePngLoader);
let assets = asset_server.register_asset_type::<PngAsset>();
#[derive(SystemLabel, Clone, Hash, Debug, PartialEq, Eq)]
#[derive(SystemSet, Clone, Hash, Debug, PartialEq, Eq)]
struct FreeUnusedAssets;
let mut app = App::new();
app.insert_resource(assets);
app.insert_resource(asset_server);
app.add_system(free_unused_assets_system.label(FreeUnusedAssets));
app.add_system(update_asset_storage_system::<PngAsset>.after(FreeUnusedAssets));
app.add_system(
free_unused_assets_system
.in_set(FreeUnusedAssets)
.in_set(CoreSet::Update),
);
app.add_system(
update_asset_storage_system::<PngAsset>
.after(FreeUnusedAssets)
.in_set(CoreSet::Update),
);
fn load_asset(path: AssetPath, world: &World) -> HandleUntyped {
let asset_server = world.resource::<AssetServer>();

View file

@ -1,13 +1,9 @@
use crate::{
update_asset_storage_system, Asset, AssetLoader, AssetServer, AssetStage, Handle, HandleId,
update_asset_storage_system, Asset, AssetLoader, AssetServer, AssetSet, Handle, HandleId,
RefChange, ReflectAsset, ReflectHandle,
};
use bevy_app::{App, AppTypeRegistry};
use bevy_ecs::{
event::{EventWriter, Events},
system::{ResMut, Resource},
world::FromWorld,
};
use bevy_ecs::prelude::*;
use bevy_reflect::{FromReflect, GetTypeRegistration, Reflect};
use bevy_utils::HashMap;
use crossbeam_channel::Sender;
@ -335,8 +331,8 @@ impl AddAsset for App {
};
self.insert_resource(assets)
.add_system_to_stage(AssetStage::AssetEvents, Assets::<T>::asset_event_system)
.add_system_to_stage(AssetStage::LoadAssets, update_asset_storage_system::<T>)
.add_system(Assets::<T>::asset_event_system.in_set(AssetSet::AssetEvents))
.add_system(update_asset_storage_system::<T>.in_set(AssetSet::LoadAssets))
.register_type::<Handle<T>>()
.add_event::<AssetEvent<T>>()
}
@ -364,7 +360,9 @@ impl AddAsset for App {
{
#[cfg(feature = "debug_asset_server")]
{
self.add_system(crate::debug_asset_server::sync_debug_assets::<T>);
self.add_system(
crate::debug_asset_server::sync_debug_assets::<T>.in_set(bevy_app::CoreSet::Update),
);
let mut app = self
.world
.non_send_resource_mut::<crate::debug_asset_server::DebugAssetApp>();

View file

@ -2,12 +2,8 @@
//!
//! Internal assets (e.g. shaders) are bundled directly into an application and can't be hot
//! reloaded using the conventional API.
use bevy_app::{App, Plugin};
use bevy_ecs::{
event::Events,
schedule::SystemLabel,
system::{NonSendMut, Res, ResMut, Resource, SystemState},
};
use bevy_app::{App, CoreSet, Plugin};
use bevy_ecs::{prelude::*, system::SystemState};
use bevy_tasks::{IoTaskPool, TaskPoolBuilder};
use bevy_utils::HashMap;
use std::{
@ -37,7 +33,7 @@ impl DerefMut for DebugAssetApp {
}
/// A label describing the system that runs [`DebugAssetApp`].
#[derive(SystemLabel, Debug, Clone, PartialEq, Eq, Hash)]
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
pub struct DebugAssetAppRun;
/// Facilitates the creation of a "debug asset app", whose sole responsibility is hot reloading
@ -79,7 +75,7 @@ impl Plugin for DebugAssetServerPlugin {
watch_for_changes: true,
});
app.insert_non_send_resource(DebugAssetApp(debug_asset_app));
app.add_system(run_debug_asset_app);
app.add_system(run_debug_asset_app.in_set(CoreSet::Update));
}
}

View file

@ -1,7 +1,7 @@
use crate::{Asset, Assets};
use bevy_app::prelude::*;
use bevy_diagnostic::{Diagnostic, DiagnosticId, Diagnostics, MAX_DIAGNOSTIC_NAME_WIDTH};
use bevy_ecs::system::{Res, ResMut};
use bevy_ecs::prelude::*;
/// Adds an asset count diagnostic to an [`App`] for assets of type `T`.
pub struct AssetCountDiagnosticsPlugin<T: Asset> {
@ -18,8 +18,8 @@ impl<T: Asset> Default for AssetCountDiagnosticsPlugin<T> {
impl<T: Asset> Plugin for AssetCountDiagnosticsPlugin<T> {
fn build(&self, app: &mut App) {
app.add_startup_system(Self::setup_system)
.add_system(Self::diagnostic_system);
app.add_startup_system(Self::setup_system.in_set(StartupSet::Startup))
.add_system(Self::diagnostic_system.in_set(CoreSet::Update));
}
}

View file

@ -46,19 +46,19 @@ pub use loader::*;
pub use path::*;
pub use reflect::*;
use bevy_app::{prelude::Plugin, App};
use bevy_ecs::schedule::{StageLabel, SystemStage};
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
/// The names of asset stages in an [`App`] schedule.
#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)]
pub enum AssetStage {
/// The stage where asset storages are updated.
/// [`SystemSet`]s for asset loading in an [`App`] schedule.
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub enum AssetSet {
/// Asset storages are updated.
LoadAssets,
/// The stage where asset events are generated.
/// Asset events are generated.
AssetEvents,
}
/// Adds support for Assets to an App.
/// Adds support for [`Assets`] to an App.
///
/// Assets are typed collections with change tracking, which are added as App Resources. Examples of
/// assets: textures, sounds, 3d models, maps, scenes
@ -105,26 +105,26 @@ impl Plugin for AssetPlugin {
app.insert_resource(asset_server);
}
app.add_stage_before(
bevy_app::CoreStage::PreUpdate,
AssetStage::LoadAssets,
SystemStage::parallel(),
app.register_type::<HandleId>();
app.configure_set(
AssetSet::LoadAssets
.no_default_set()
.before(CoreSet::PreUpdate)
.after(CoreSet::First),
)
.add_stage_after(
bevy_app::CoreStage::PostUpdate,
AssetStage::AssetEvents,
SystemStage::parallel(),
.configure_set(
AssetSet::AssetEvents
.no_default_set()
.after(CoreSet::PostUpdate)
.before(CoreSet::Last),
)
.register_type::<HandleId>()
.add_system_to_stage(
bevy_app::CoreStage::PreUpdate,
asset_server::free_unused_assets_system,
);
.add_system(asset_server::free_unused_assets_system.in_set(CoreSet::PreUpdate));
#[cfg(all(
feature = "filesystem_watcher",
all(not(target_arch = "wasm32"), not(target_os = "android"))
))]
app.add_system_to_stage(AssetStage::LoadAssets, io::filesystem_watcher_system);
app.add_system(io::filesystem_watcher_system.in_set(AssetSet::LoadAssets));
}
}

View file

@ -42,6 +42,7 @@ pub use rodio::Sample;
use bevy_app::prelude::*;
use bevy_asset::{AddAsset, Asset};
use bevy_ecs::prelude::*;
/// Adds support for audio playback to a Bevy Application
///
@ -55,10 +56,7 @@ impl Plugin for AudioPlugin {
.add_asset::<AudioSource>()
.add_asset::<AudioSink>()
.init_resource::<Audio<AudioSource>>()
.add_system_to_stage(
CoreStage::PostUpdate,
play_queued_audio_system::<AudioSource>,
);
.add_system(play_queued_audio_system::<AudioSource>.in_set(CoreSet::PostUpdate));
#[cfg(any(feature = "mp3", feature = "flac", feature = "wav", feature = "vorbis"))]
app.init_asset_loader::<AudioLoader>();
@ -73,6 +71,6 @@ impl AddAudioSource for App {
self.add_asset::<T>()
.init_resource::<Audio<T>>()
.init_resource::<AudioOutput<T>>()
.add_system_to_stage(CoreStage::PostUpdate, play_queued_audio_system::<T>)
.add_system(play_queued_audio_system::<T>.in_set(CoreSet::PostUpdate))
}
}

View file

@ -20,16 +20,16 @@ pub mod prelude {
}
use bevy_app::prelude::*;
use bevy_ecs::entity::Entity;
use bevy_ecs::prelude::*;
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
use bevy_utils::{Duration, HashSet, Instant};
use std::borrow::Cow;
use std::ffi::OsString;
use std::marker::PhantomData;
use std::ops::Range;
use std::path::PathBuf;
#[cfg(not(target_arch = "wasm32"))]
use bevy_ecs::schedule::IntoSystemDescriptor;
#[cfg(not(target_arch = "wasm32"))]
use bevy_tasks::tick_global_task_pools_on_main_thread;
@ -107,16 +107,24 @@ impl Plugin for TaskPoolPlugin {
self.task_pool_options.create_default_pools();
#[cfg(not(target_arch = "wasm32"))]
app.add_system_to_stage(
bevy_app::CoreStage::Last,
tick_global_task_pools_on_main_thread.at_end(),
);
app.add_system(tick_global_task_pools.in_set(bevy_app::CoreSet::Last));
}
}
/// A dummy type that is [`!Send`](Send), to force systems to run on the main thread.
pub struct NonSendMarker(PhantomData<*mut ()>);
/// A system used to check and advanced our task pools.
///
/// Calls [`tick_global_task_pools_on_main_thread`],
/// and uses [`NonSendMarker`] to ensure that this system runs on the main thread
#[cfg(not(target_arch = "wasm32"))]
fn tick_global_task_pools(_main_thread_marker: Option<NonSend<NonSendMarker>>) {
tick_global_task_pools_on_main_thread();
}
/// Maintains a count of frames rendered since the start of the application.
///
/// [`FrameCount`] is incremented during [`CoreStage::Last`], providing predictable
/// [`FrameCount`] is incremented during [`CoreSet::Last`], providing predictable
/// behaviour: it will be 0 during the first update, 1 during the next, and so forth.
///
/// # Overflows
@ -134,7 +142,7 @@ pub struct FrameCountPlugin;
impl Plugin for FrameCountPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<FrameCount>();
app.add_system_to_stage(CoreStage::Last, update_frame_count);
app.add_system(update_frame_count.in_set(CoreSet::Last));
}
}

View file

@ -2,10 +2,8 @@ use crate::{core_2d, core_3d, fullscreen_vertex_shader::fullscreen_shader_vertex
use bevy_app::{App, Plugin};
use bevy_asset::{load_internal_asset, HandleUntyped};
use bevy_ecs::{
prelude::{Component, Entity},
query::{QueryItem, QueryState, With},
system::{Commands, Query, Res, ResMut, Resource},
world::{FromWorld, World},
prelude::*,
query::{QueryItem, QueryState},
};
use bevy_math::{UVec2, UVec4, Vec4};
use bevy_reflect::{Reflect, TypeUuid};
@ -21,7 +19,7 @@ use bevy_render::{
renderer::{RenderContext, RenderDevice},
texture::{CachedTexture, TextureCache},
view::ViewTarget,
RenderApp, RenderStage,
RenderApp, RenderSet,
};
#[cfg(feature = "trace")]
use bevy_utils::tracing::info_span;
@ -48,8 +46,8 @@ impl Plugin for BloomPlugin {
render_app
.init_resource::<BloomPipelines>()
.add_system_to_stage(RenderStage::Prepare, prepare_bloom_textures)
.add_system_to_stage(RenderStage::Queue, queue_bloom_bind_groups);
.add_system(prepare_bloom_textures.in_set(RenderSet::Prepare))
.add_system(queue_bloom_bind_groups.in_set(RenderSet::Queue));
{
let bloom_node = BloomNode::new(&mut render_app.world);

View file

@ -30,7 +30,7 @@ use bevy_render::{
DrawFunctionId, DrawFunctions, PhaseItem, RenderPhase,
},
render_resource::CachedRenderPipelineId,
Extract, RenderApp, RenderStage,
Extract, ExtractSchedule, RenderApp, RenderSet,
};
use bevy_utils::FloatOrd;
use std::ops::Range;
@ -51,11 +51,12 @@ impl Plugin for Core2dPlugin {
render_app
.init_resource::<DrawFunctions<Transparent2d>>()
.add_system_to_stage(RenderStage::Extract, extract_core_2d_camera_phases)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Transparent2d>)
.add_system_to_stage(
RenderStage::PhaseSort,
batch_phase_system::<Transparent2d>.after(sort_phase_system::<Transparent2d>),
.add_system_to_schedule(ExtractSchedule, extract_core_2d_camera_phases)
.add_system(sort_phase_system::<Transparent2d>.in_set(RenderSet::PhaseSort))
.add_system(
batch_phase_system::<Transparent2d>
.after(sort_phase_system::<Transparent2d>)
.in_set(RenderSet::PhaseSort),
);
let pass_node_2d = MainPass2dNode::new(&mut render_app.world);

View file

@ -40,7 +40,7 @@ use bevy_render::{
renderer::RenderDevice,
texture::TextureCache,
view::ViewDepthTexture,
Extract, RenderApp, RenderStage,
Extract, ExtractSchedule, RenderApp, RenderSet,
};
use bevy_utils::{FloatOrd, HashMap};
@ -67,11 +67,11 @@ impl Plugin for Core3dPlugin {
.init_resource::<DrawFunctions<Opaque3d>>()
.init_resource::<DrawFunctions<AlphaMask3d>>()
.init_resource::<DrawFunctions<Transparent3d>>()
.add_system_to_stage(RenderStage::Extract, extract_core_3d_camera_phases)
.add_system_to_stage(RenderStage::Prepare, prepare_core_3d_depth_textures)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Opaque3d>)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<AlphaMask3d>)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Transparent3d>);
.add_system_to_schedule(ExtractSchedule, extract_core_3d_camera_phases)
.add_system(prepare_core_3d_depth_textures.in_set(RenderSet::Prepare))
.add_system(sort_phase_system::<Opaque3d>.in_set(RenderSet::PhaseSort))
.add_system(sort_phase_system::<AlphaMask3d>.in_set(RenderSet::PhaseSort))
.add_system(sort_phase_system::<Transparent3d>.in_set(RenderSet::PhaseSort));
let prepass_node = PrepassNode::new(&mut render_app.world);
let pass_node_3d = MainPass3dNode::new(&mut render_app.world);

View file

@ -12,7 +12,7 @@ use bevy_render::{
renderer::RenderDevice,
texture::BevyDefault,
view::{ExtractedView, ViewTarget},
RenderApp, RenderStage,
RenderApp, RenderSet,
};
mod node;
@ -86,7 +86,7 @@ impl Plugin for FxaaPlugin {
render_app
.init_resource::<FxaaPipeline>()
.init_resource::<SpecializedRenderPipelines<FxaaPipeline>>()
.add_system_to_stage(RenderStage::Prepare, prepare_fxaa_pipelines);
.add_system(prepare_fxaa_pipelines.in_set(RenderSet::Prepare));
{
let fxaa_node = FxaaNode::new(&mut render_app.world);

View file

@ -7,7 +7,7 @@ use bevy_render::camera::Camera;
use bevy_render::extract_component::{ExtractComponent, ExtractComponentPlugin};
use bevy_render::renderer::RenderDevice;
use bevy_render::view::ViewTarget;
use bevy_render::{render_resource::*, RenderApp, RenderStage};
use bevy_render::{render_resource::*, RenderApp, RenderSet};
mod node;
@ -44,7 +44,7 @@ impl Plugin for TonemappingPlugin {
render_app
.init_resource::<TonemappingPipeline>()
.init_resource::<SpecializedRenderPipelines<TonemappingPipeline>>()
.add_system_to_stage(RenderStage::Queue, queue_view_tonemapping_pipelines);
.add_system(queue_view_tonemapping_pipelines.in_set(RenderSet::Queue));
}
}
}

View file

@ -5,7 +5,7 @@ use bevy_ecs::prelude::*;
use bevy_reflect::TypeUuid;
use bevy_render::renderer::RenderDevice;
use bevy_render::view::ViewTarget;
use bevy_render::{render_resource::*, RenderApp, RenderStage};
use bevy_render::{render_resource::*, RenderApp, RenderSet};
mod node;
@ -29,7 +29,7 @@ impl Plugin for UpscalingPlugin {
render_app
.init_resource::<UpscalingPipeline>()
.init_resource::<SpecializedRenderPipelines<UpscalingPipeline>>()
.add_system_to_stage(RenderStage::Queue, queue_view_upscaling_pipelines);
.add_system(queue_view_upscaling_pipelines.in_set(RenderSet::Queue));
}
}
}

View file

@ -1,5 +1,5 @@
use bevy_app::{App, Plugin};
use bevy_ecs::{entity::Entities, system::ResMut};
use bevy_app::prelude::*;
use bevy_ecs::{entity::Entities, prelude::*};
use crate::{Diagnostic, DiagnosticId, Diagnostics};
@ -9,8 +9,8 @@ pub struct EntityCountDiagnosticsPlugin;
impl Plugin for EntityCountDiagnosticsPlugin {
fn build(&self, app: &mut App) {
app.add_startup_system(Self::setup_system)
.add_system(Self::diagnostic_system);
app.add_startup_system(Self::setup_system.in_set(StartupSet::Startup))
.add_system(Self::diagnostic_system.in_set(CoreSet::Update));
}
}

View file

@ -1,7 +1,7 @@
use crate::{Diagnostic, DiagnosticId, Diagnostics};
use bevy_app::prelude::*;
use bevy_core::FrameCount;
use bevy_ecs::system::{Res, ResMut};
use bevy_ecs::prelude::*;
use bevy_time::Time;
/// Adds "frame time" diagnostic to an App, specifically "frame time", "fps" and "frame count"
@ -10,8 +10,8 @@ pub struct FrameTimeDiagnosticsPlugin;
impl Plugin for FrameTimeDiagnosticsPlugin {
fn build(&self, app: &mut bevy_app::App) {
app.add_startup_system(Self::setup_system)
.add_system(Self::diagnostic_system);
app.add_startup_system(Self::setup_system.in_set(StartupSet::Startup))
.add_system(Self::diagnostic_system.in_set(CoreSet::Update));
}
}

View file

@ -5,6 +5,7 @@ mod log_diagnostics_plugin;
mod system_information_diagnostics_plugin;
use bevy_app::prelude::*;
use bevy_ecs::schedule_v3::IntoSystemConfig;
pub use diagnostic::*;
pub use entity_count_diagnostics_plugin::EntityCountDiagnosticsPlugin;
pub use frame_time_diagnostics_plugin::FrameTimeDiagnosticsPlugin;
@ -17,8 +18,10 @@ pub struct DiagnosticsPlugin;
impl Plugin for DiagnosticsPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<Diagnostics>()
.add_startup_system(system_information_diagnostics_plugin::internal::log_system_info);
app.init_resource::<Diagnostics>().add_startup_system(
system_information_diagnostics_plugin::internal::log_system_info
.in_set(StartupSet::Startup),
);
}
}

View file

@ -1,6 +1,6 @@
use super::{Diagnostic, DiagnosticId, Diagnostics};
use bevy_app::prelude::*;
use bevy_ecs::system::{Res, ResMut, Resource};
use bevy_ecs::prelude::*;
use bevy_log::{debug, info};
use bevy_time::{Time, Timer, TimerMode};
use bevy_utils::Duration;
@ -37,9 +37,9 @@ impl Plugin for LogDiagnosticsPlugin {
});
if self.debug {
app.add_system_to_stage(CoreStage::PostUpdate, Self::log_diagnostics_debug_system);
app.add_system(Self::log_diagnostics_debug_system.in_set(CoreSet::PostUpdate));
} else {
app.add_system_to_stage(CoreStage::PostUpdate, Self::log_diagnostics_system);
app.add_system(Self::log_diagnostics_system.in_set(CoreSet::PostUpdate));
}
}
}

View file

@ -1,5 +1,6 @@
use crate::DiagnosticId;
use bevy_app::{App, Plugin};
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
/// Adds a System Information Diagnostic, specifically `cpu_usage` (in %) and `mem_usage` (in %)
///
@ -14,8 +15,8 @@ use bevy_app::{App, Plugin};
pub struct SystemInformationDiagnosticsPlugin;
impl Plugin for SystemInformationDiagnosticsPlugin {
fn build(&self, app: &mut App) {
app.add_startup_system(internal::setup_system)
.add_system(internal::diagnostic_system);
app.add_startup_system(internal::setup_system.in_set(StartupSet::Startup))
.add_system(internal::diagnostic_system.in_set(CoreSet::Update));
}
}

View file

@ -42,7 +42,3 @@ path = "examples/resources.rs"
[[example]]
name = "change_detection"
path = "examples/change_detection.rs"
[[example]]
name = "derive_label"
path = "examples/derive_label.rs"

View file

@ -111,9 +111,10 @@ The [`resources.rs`](examples/resources.rs) example illustrates how to read and
### Schedules
Schedules consist of zero or more Stages, which run a set of Systems according to some execution strategy. Bevy ECS provides a few built in Stage implementations (ex: parallel, serial), but you can also implement your own! Schedules run Stages one-by-one in an order defined by the user.
Schedules run a set of Systems according to some execution strategy.
Systems can be added to any number of System Sets, which are used to control their scheduling metadata.
The built in "parallel stage" considers dependencies between systems and (by default) run as many of them in parallel as possible. This maximizes performance, while keeping the system execution safe. You can also define explicit dependencies between systems.
The built in "parallel executor" considers dependencies between systems and (by default) run as many of them in parallel as possible. This maximizes performance, while keeping the system execution safe. To control the system ordering, define explicit dependencies between systems and their sets.
## Using Bevy ECS
@ -148,15 +149,8 @@ fn main() {
// Create a new Schedule, which defines an execution strategy for Systems
let mut schedule = Schedule::default();
// Define a unique public name for a new Stage.
#[derive(StageLabel)]
pub struct UpdateLabel;
// Add a Stage to our schedule. Each Stage in a schedule runs all of its systems
// before moving on to the next Stage
schedule.add_stage(UpdateLabel, SystemStage::parallel()
.with_system(movement)
);
// Add our system to the schedule
schedule.add_system(movement);
// Run the schedule once. If your app has a "loop", you would run this once per loop
schedule.run(&mut world);

View file

@ -1,4 +1,4 @@
use bevy_ecs::prelude::*;
use bevy_ecs::{prelude::*, schedule_v3::IntoSystemConfig};
use rand::Rng;
use std::ops::Deref;
@ -16,23 +16,16 @@ fn main() {
// Add the counter resource to remember how many entities where spawned
world.insert_resource(EntityCounter { value: 0 });
// Create a new Schedule, which defines an execution strategy for Systems
// Create a new Schedule, which stores systems and controls their relative ordering
let mut schedule = Schedule::default();
// Create a Stage to add to our Schedule. Each Stage in a schedule runs all of its systems
// before moving on to the next Stage
let mut update = SystemStage::parallel();
// Add systems to the Stage to execute our app logic
// We can label our systems to force a specific run-order between some of them
update.add_system(spawn_entities.label(SimulationSystem::Spawn));
update.add_system(print_counter_when_changed.after(SimulationSystem::Spawn));
update.add_system(age_all_entities.label(SimulationSystem::Age));
update.add_system(remove_old_entities.after(SimulationSystem::Age));
update.add_system(print_changed_entities.after(SimulationSystem::Age));
// Add the Stage with our systems to the Schedule
#[derive(StageLabel)]
struct Update;
schedule.add_stage(Update, update);
schedule.add_system(spawn_entities.in_set(SimulationSystem::Spawn));
schedule.add_system(print_counter_when_changed.after(SimulationSystem::Spawn));
schedule.add_system(age_all_entities.in_set(SimulationSystem::Age));
schedule.add_system(remove_old_entities.after(SimulationSystem::Age));
schedule.add_system(print_changed_entities.after(SimulationSystem::Age));
// Simulate 10 frames in our world
for iteration in 1..=10 {
@ -53,8 +46,8 @@ struct Age {
frames: i32,
}
// System labels to enforce a run order of our systems
#[derive(SystemLabel, Debug, Clone, PartialEq, Eq, Hash)]
// System sets can be used to group systems and configured to control relative ordering
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
enum SimulationSystem {
Spawn,
Age,

View file

@ -1,62 +0,0 @@
use std::marker::PhantomData;
use bevy_ecs::prelude::*;
fn main() {
// Unit labels are always equal.
assert_eq!(UnitLabel.as_label(), UnitLabel.as_label());
// Enum labels depend on the variant.
assert_eq!(EnumLabel::One.as_label(), EnumLabel::One.as_label());
assert_ne!(EnumLabel::One.as_label(), EnumLabel::Two.as_label());
// Labels annotated with `ignore_fields` ignore their fields.
assert_eq!(WeirdLabel(1).as_label(), WeirdLabel(2).as_label());
// Labels don't depend only on the variant name but on the full type
assert_ne!(
GenericLabel::<f64>::One.as_label(),
GenericLabel::<char>::One.as_label(),
);
}
#[derive(SystemLabel)]
pub struct UnitLabel;
#[derive(SystemLabel)]
pub enum EnumLabel {
One,
Two,
}
#[derive(SystemLabel)]
#[system_label(ignore_fields)]
pub struct WeirdLabel(i32);
#[derive(SystemLabel)]
pub enum GenericLabel<T> {
One,
#[system_label(ignore_fields)]
Two(PhantomData<T>),
}
// FIXME: this should be a compile_fail test
/*#[derive(SystemLabel)]
pub union Foo {
x: i32,
}*/
// FIXME: this should be a compile_fail test
/*#[derive(SystemLabel)]
#[system_label(ignore_fields)]
pub enum BadLabel {
One,
Two,
}*/
// FIXME: this should be a compile_fail test
/*#[derive(SystemLabel)]
pub struct BadLabel2 {
#[system_label(ignore_fields)]
x: (),
}*/

View file

@ -7,31 +7,20 @@ fn main() {
let mut world = World::new();
world.insert_resource(Events::<MyEvent>::default());
// Create a schedule and a stage
// Create a schedule to store our systems
let mut schedule = Schedule::default();
#[derive(StageLabel)]
enum Stages {
First,
Second,
}
// Events need to be updated in every frame in order to clear our buffers.
// This update should happen before we use the events.
// Here, we use system sets to control the ordering.
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
pub struct FlushEvents;
// Events need to be updated in every frame. This update should happen before we use
// the events. To guarantee this, we can let the update run in an earlier stage than our logic.
// Here we will use a stage called "first" that will always run it's systems before the Stage
// called "second". In "first" we update the events and in "second" we run our systems
// sending and receiving events.
let mut first = SystemStage::parallel();
first.add_system(Events::<MyEvent>::update_system);
schedule.add_stage(Stages::First, first);
schedule.add_system(Events::<MyEvent>::update_system.in_set(FlushEvents));
// Add systems sending and receiving events to a "second" Stage
let mut second = SystemStage::parallel();
second.add_system(sending_system);
second.add_system(receiving_system.after(sending_system));
// Run the "second" Stage after the "first" Stage, so our Events always get updated before we use them
schedule.add_stage_after(Stages::First, Stages::Second, second);
// Add systems sending and receiving events after the events are flushed.
schedule.add_system(sending_system.after(FlushEvents));
schedule.add_system(receiving_system.after(sending_system));
// Simulate 10 frames of our world
for iteration in 1..=10 {

View file

@ -13,18 +13,10 @@ fn main() {
// Create a schedule and a stage
let mut schedule = Schedule::default();
let mut update = SystemStage::parallel();
// Add systems to increase the counter and to print out the current value
update.add_system(increase_counter);
update.add_system(print_counter.after(increase_counter));
// Declare a unique label for the stage.
#[derive(StageLabel)]
struct Update;
// Add the stage to the schedule.
schedule.add_stage(Update, update);
schedule.add_system(increase_counter);
schedule.add_system(print_counter.after(increase_counter));
for iteration in 1..=10 {
println!("Simulating frame {iteration}/10");

View file

@ -4,9 +4,7 @@ mod component;
mod fetch;
use crate::fetch::derive_world_query_impl;
use bevy_macro_utils::{
derive_boxed_label, derive_label, derive_set, get_named_struct_fields, BevyManifest,
};
use bevy_macro_utils::{derive_boxed_label, derive_set, get_named_struct_fields, BevyManifest};
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{format_ident, quote};
@ -524,49 +522,6 @@ pub fn derive_world_query(input: TokenStream) -> TokenStream {
derive_world_query_impl(ast)
}
/// Generates an impl of the `SystemLabel` trait.
///
/// This works only for unit structs, or enums with only unit variants.
/// You may force a struct or variant to behave as if it were fieldless with `#[system_label(ignore_fields)]`.
#[proc_macro_derive(SystemLabel, attributes(system_label))]
pub fn derive_system_label(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let mut trait_path = bevy_ecs_path();
trait_path.segments.push(format_ident!("schedule").into());
trait_path
.segments
.push(format_ident!("SystemLabel").into());
derive_label(input, &trait_path, "system_label")
}
/// Generates an impl of the `StageLabel` trait.
///
/// This works only for unit structs, or enums with only unit variants.
/// You may force a struct or variant to behave as if it were fieldless with `#[stage_label(ignore_fields)]`.
#[proc_macro_derive(StageLabel, attributes(stage_label))]
pub fn derive_stage_label(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let mut trait_path = bevy_ecs_path();
trait_path.segments.push(format_ident!("schedule").into());
trait_path.segments.push(format_ident!("StageLabel").into());
derive_label(input, &trait_path, "stage_label")
}
/// Generates an impl of the `RunCriteriaLabel` trait.
///
/// This works only for unit structs, or enums with only unit variants.
/// You may force a struct or variant to behave as if it were fieldless with `#[run_criteria_label(ignore_fields)]`.
#[proc_macro_derive(RunCriteriaLabel, attributes(run_criteria_label))]
pub fn derive_run_criteria_label(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let mut trait_path = bevy_ecs_path();
trait_path.segments.push(format_ident!("schedule").into());
trait_path
.segments
.push(format_ident!("RunCriteriaLabel").into());
derive_label(input, &trait_path, "run_criteria_label")
}
/// Derive macro generating an impl of the trait `ScheduleLabel`.
#[proc_macro_derive(ScheduleLabel)]
pub fn derive_schedule_label(input: TokenStream) -> TokenStream {

View file

@ -459,6 +459,11 @@ impl Components {
self.components.get(id.0)
}
#[inline]
pub fn get_name(&self, id: ComponentId) -> Option<&str> {
self.get_info(id).map(|descriptor| descriptor.name())
}
/// # Safety
///
/// `id` must be a valid [`ComponentId`]

View file

@ -14,7 +14,6 @@ pub mod query;
#[cfg(feature = "bevy_reflect")]
pub mod reflect;
pub mod removal_detection;
pub mod schedule;
pub mod schedule_v3;
pub mod storage;
pub mod system;
@ -33,12 +32,13 @@ pub mod prelude {
change_detection::{DetectChanges, DetectChangesMut, Mut, Ref},
component::Component,
entity::Entity,
event::{EventReader, EventWriter, Events},
event::{Event, EventReader, EventWriter, Events},
query::{Added, AnyOf, ChangeTrackers, Changed, Or, QueryState, With, Without},
removal_detection::RemovedComponents,
schedule::{
IntoSystemDescriptor, RunCriteria, RunCriteriaDescriptorCoercion, RunCriteriaLabel,
Schedule, Stage, StageLabel, State, SystemLabel, SystemSet, SystemStage,
schedule_v3::{
apply_state_transition, apply_system_buffers, common_conditions::*, IntoSystemConfig,
IntoSystemConfigs, IntoSystemSet, IntoSystemSetConfig, IntoSystemSetConfigs, NextState,
OnEnter, OnExit, OnUpdate, Schedule, Schedules, State, States, SystemSet,
},
system::{
adapter as system_adapter,

View file

@ -1,612 +0,0 @@
use bevy_utils::tracing::info;
use fixedbitset::FixedBitSet;
use crate::component::ComponentId;
use crate::schedule::{AmbiguityDetection, GraphNode, SystemContainer, SystemStage};
use crate::world::World;
use super::SystemLabelId;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct SystemOrderAmbiguity {
segment: SystemStageSegment,
// Note: In order for comparisons to work correctly,
// `system_names` and `conflicts` must be sorted at all times.
system_names: [String; 2],
conflicts: Vec<String>,
}
/// Which part of a [`SystemStage`] was a [`SystemOrderAmbiguity`] detected in?
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)]
enum SystemStageSegment {
Parallel,
ExclusiveAtStart,
ExclusiveBeforeCommands,
ExclusiveAtEnd,
}
impl SystemStageSegment {
pub fn desc(&self) -> &'static str {
match self {
SystemStageSegment::Parallel => "Parallel systems",
SystemStageSegment::ExclusiveAtStart => "Exclusive systems at start of stage",
SystemStageSegment::ExclusiveBeforeCommands => {
"Exclusive systems before commands of stage"
}
SystemStageSegment::ExclusiveAtEnd => "Exclusive systems at end of stage",
}
}
}
impl SystemOrderAmbiguity {
fn from_raw(
system_a_index: usize,
system_b_index: usize,
component_ids: Vec<ComponentId>,
segment: SystemStageSegment,
stage: &SystemStage,
world: &World,
) -> Self {
use SystemStageSegment::*;
let systems = match segment {
Parallel => stage.parallel_systems(),
ExclusiveAtStart => stage.exclusive_at_start_systems(),
ExclusiveBeforeCommands => stage.exclusive_before_commands_systems(),
ExclusiveAtEnd => stage.exclusive_at_end_systems(),
};
let mut system_names = [
systems[system_a_index].name().to_string(),
systems[system_b_index].name().to_string(),
];
system_names.sort();
let mut conflicts: Vec<_> = component_ids
.iter()
.map(|id| world.components().get_info(*id).unwrap().name().to_owned())
.collect();
conflicts.sort();
Self {
system_names,
conflicts,
segment,
}
}
}
impl SystemStage {
/// Logs execution order ambiguities between systems.
///
/// The output may be incorrect if this stage has not been initialized with `world`.
pub fn report_ambiguities(&self, world: &World) {
debug_assert!(!self.systems_modified);
use std::fmt::Write;
let ambiguities = self.ambiguities(world);
if !ambiguities.is_empty() {
let mut string = "Execution order ambiguities detected, you might want to \
add an explicit dependency relation between some of these systems:\n"
.to_owned();
let mut last_segment_kind = None;
for SystemOrderAmbiguity {
system_names: [system_a, system_b],
conflicts,
segment,
} in &ambiguities
{
// If the ambiguity occurred in a different segment than the previous one, write a header for the segment.
if last_segment_kind != Some(segment) {
writeln!(string, " * {}:", segment.desc()).unwrap();
last_segment_kind = Some(segment);
}
writeln!(string, " -- {system_a:?} and {system_b:?}").unwrap();
if !conflicts.is_empty() {
writeln!(string, " conflicts: {conflicts:?}").unwrap();
}
}
info!("{}", string);
}
}
/// Returns all execution order ambiguities between systems.
///
/// Returns 4 vectors of ambiguities for each stage, in the following order:
/// - parallel
/// - exclusive at start,
/// - exclusive before commands
/// - exclusive at end
///
/// The result may be incorrect if this stage has not been initialized with `world`.
fn ambiguities(&self, world: &World) -> Vec<SystemOrderAmbiguity> {
let parallel = find_ambiguities(&self.parallel).into_iter().map(
|(system_a_index, system_b_index, component_ids)| {
SystemOrderAmbiguity::from_raw(
system_a_index,
system_b_index,
component_ids.to_vec(),
SystemStageSegment::Parallel,
self,
world,
)
},
);
let at_start = find_ambiguities(&self.exclusive_at_start).into_iter().map(
|(system_a_index, system_b_index, component_ids)| {
SystemOrderAmbiguity::from_raw(
system_a_index,
system_b_index,
component_ids,
SystemStageSegment::ExclusiveAtStart,
self,
world,
)
},
);
let before_commands = find_ambiguities(&self.exclusive_before_commands)
.into_iter()
.map(|(system_a_index, system_b_index, component_ids)| {
SystemOrderAmbiguity::from_raw(
system_a_index,
system_b_index,
component_ids,
SystemStageSegment::ExclusiveBeforeCommands,
self,
world,
)
});
let at_end = find_ambiguities(&self.exclusive_at_end).into_iter().map(
|(system_a_index, system_b_index, component_ids)| {
SystemOrderAmbiguity::from_raw(
system_a_index,
system_b_index,
component_ids,
SystemStageSegment::ExclusiveAtEnd,
self,
world,
)
},
);
let mut ambiguities: Vec<_> = at_start
.chain(parallel)
.chain(before_commands)
.chain(at_end)
.collect();
ambiguities.sort();
ambiguities
}
/// Returns the number of system order ambiguities between systems in this stage.
///
/// The result may be incorrect if this stage has not been initialized with `world`.
#[cfg(test)]
fn ambiguity_count(&self, world: &World) -> usize {
self.ambiguities(world).len()
}
}
/// Returns vector containing all pairs of indices of systems with ambiguous execution order,
/// along with specific components that have triggered the warning.
/// Systems must be topologically sorted beforehand.
fn find_ambiguities(systems: &[SystemContainer]) -> Vec<(usize, usize, Vec<ComponentId>)> {
// Check if we should ignore ambiguities between `system_a` and `system_b`.
fn should_ignore(system_a: &SystemContainer, system_b: &SystemContainer) -> bool {
fn should_ignore_inner(
system_a_detection: &AmbiguityDetection,
system_b_labels: &[SystemLabelId],
) -> bool {
match system_a_detection {
AmbiguityDetection::Check => false,
AmbiguityDetection::IgnoreAll => true,
AmbiguityDetection::IgnoreWithLabel(labels) => {
labels.iter().any(|l| system_b_labels.contains(l))
}
}
}
should_ignore_inner(&system_a.ambiguity_detection, system_b.labels())
|| should_ignore_inner(&system_b.ambiguity_detection, system_a.labels())
}
let mut all_dependencies = Vec::<FixedBitSet>::with_capacity(systems.len());
let mut all_dependants = Vec::<FixedBitSet>::with_capacity(systems.len());
for (index, container) in systems.iter().enumerate() {
let mut dependencies = FixedBitSet::with_capacity(systems.len());
for &dependency in container.dependencies() {
dependencies.union_with(&all_dependencies[dependency]);
dependencies.insert(dependency);
all_dependants[dependency].insert(index);
}
all_dependants.push(FixedBitSet::with_capacity(systems.len()));
all_dependencies.push(dependencies);
}
for index in (0..systems.len()).rev() {
let mut dependants = FixedBitSet::with_capacity(systems.len());
for dependant in all_dependants[index].ones() {
dependants.union_with(&all_dependants[dependant]);
dependants.insert(dependant);
}
all_dependants[index] = dependants;
}
let all_relations = all_dependencies
.into_iter()
.zip(all_dependants.into_iter())
.enumerate()
.map(|(index, (dependencies, dependants))| {
let mut relations = FixedBitSet::with_capacity(systems.len());
relations.union_with(&dependencies);
relations.union_with(&dependants);
relations.insert(index);
relations
})
.collect::<Vec<FixedBitSet>>();
let mut ambiguities = Vec::new();
let full_bitset: FixedBitSet = (0..systems.len()).collect();
let mut processed = FixedBitSet::with_capacity(systems.len());
for (index_a, relations) in all_relations.into_iter().enumerate() {
// TODO: prove that `.take(index_a)` would be correct here, and uncomment it if so.
for index_b in full_bitset.difference(&relations)
// .take(index_a)
{
if !processed.contains(index_b) && !should_ignore(&systems[index_a], &systems[index_b])
{
let system_a = &systems[index_a];
let system_b = &systems[index_b];
if system_a.is_exclusive() || system_b.is_exclusive() {
ambiguities.push((index_a, index_b, Vec::new()));
} else {
let a_access = systems[index_a].component_access();
let b_access = systems[index_b].component_access();
let conflicts = a_access.get_conflicts(b_access);
if !conflicts.is_empty() {
ambiguities.push((index_a, index_b, conflicts));
}
}
}
}
processed.insert(index_a);
}
ambiguities
}
#[cfg(test)]
mod tests {
// Required to make the derive macro behave
use crate as bevy_ecs;
use crate::event::Events;
use crate::prelude::*;
#[derive(Resource)]
struct R;
#[derive(Component)]
struct A;
#[derive(Component)]
struct B;
// An event type
struct E;
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>>) {}
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 test_stage = SystemStage::parallel();
test_stage
// nonsendmut system deliberately conflicts with resmut system
.add_system(resmut_system)
.add_system(write_component_system)
.add_system(event_writer_system);
test_stage.run(&mut world);
assert_eq!(test_stage.ambiguity_count(&world), 0);
}
#[test]
fn read_only() {
let mut world = World::new();
world.insert_resource(R);
world.insert_non_send_resource(R);
world.spawn(A);
world.init_resource::<Events<E>>();
let mut test_stage = SystemStage::parallel();
test_stage
.add_system(empty_system)
.add_system(empty_system)
.add_system(res_system)
.add_system(res_system)
.add_system(nonsend_system)
.add_system(nonsend_system)
.add_system(read_component_system)
.add_system(read_component_system)
.add_system(event_reader_system)
.add_system(event_reader_system)
.add_system(read_world_system)
.add_system(read_world_system);
test_stage.run(&mut world);
assert_eq!(test_stage.ambiguity_count(&world), 0);
}
#[test]
fn read_world() {
let mut world = World::new();
world.insert_resource(R);
world.spawn(A);
world.init_resource::<Events<E>>();
let mut test_stage = SystemStage::parallel();
test_stage
.add_system(resmut_system)
.add_system(write_component_system)
.add_system(event_writer_system)
.add_system(read_world_system);
test_stage.run(&mut world);
assert_eq!(test_stage.ambiguity_count(&world), 3);
}
#[test]
fn resources() {
let mut world = World::new();
world.insert_resource(R);
let mut test_stage = SystemStage::parallel();
test_stage.add_system(resmut_system).add_system(res_system);
test_stage.run(&mut world);
assert_eq!(test_stage.ambiguity_count(&world), 1);
}
#[test]
fn nonsend() {
let mut world = World::new();
world.insert_resource(R);
world.insert_non_send_resource(R);
let mut test_stage = SystemStage::parallel();
test_stage
.add_system(nonsendmut_system)
.add_system(nonsend_system);
test_stage.run(&mut world);
assert_eq!(test_stage.ambiguity_count(&world), 1);
}
#[test]
fn components() {
let mut world = World::new();
world.spawn(A);
let mut test_stage = SystemStage::parallel();
test_stage
.add_system(read_component_system)
.add_system(write_component_system);
test_stage.run(&mut world);
assert_eq!(test_stage.ambiguity_count(&world), 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 test_stage = SystemStage::parallel();
test_stage
.add_system(with_filtered_component_system)
.add_system(without_filtered_component_system);
test_stage.run(&mut world);
assert_eq!(test_stage.ambiguity_count(&world), 0);
}
#[test]
fn events() {
let mut world = World::new();
world.init_resource::<Events<E>>();
let mut test_stage = SystemStage::parallel();
test_stage
// All of these systems clash
.add_system(event_reader_system)
.add_system(event_writer_system)
.add_system(event_resource_system);
test_stage.run(&mut world);
assert_eq!(test_stage.ambiguity_count(&world), 3);
}
#[test]
fn exclusive() {
let mut world = World::new();
world.insert_resource(R);
world.spawn(A);
world.init_resource::<Events<E>>();
let mut test_stage = SystemStage::parallel();
test_stage
// All 3 of these conflict with each other
.add_system(write_world_system)
.add_system(write_world_system.at_end())
.add_system(res_system.at_start())
// These do not, as they're in different segments of the stage
.add_system(write_world_system.at_start())
.add_system(write_world_system.before_commands());
test_stage.run(&mut world);
assert_eq!(test_stage.ambiguity_count(&world), 3);
}
// Tests for silencing and resolving ambiguities
#[test]
fn before_and_after() {
let mut world = World::new();
world.init_resource::<Events<E>>();
let mut test_stage = SystemStage::parallel();
test_stage
.add_system(event_reader_system.before(event_writer_system))
.add_system(event_writer_system)
.add_system(event_resource_system.after(event_writer_system));
test_stage.run(&mut world);
assert_eq!(test_stage.ambiguity_count(&world), 0);
}
#[test]
fn ignore_all_ambiguities() {
let mut world = World::new();
world.insert_resource(R);
world.insert_non_send_resource(R);
let mut test_stage = SystemStage::parallel();
test_stage
.add_system(resmut_system.ignore_all_ambiguities())
.add_system(res_system)
.add_system(nonsend_system);
test_stage.run(&mut world);
assert_eq!(test_stage.ambiguity_count(&world), 0);
}
#[test]
fn ambiguous_with_label() {
let mut world = World::new();
world.insert_resource(R);
world.insert_non_send_resource(R);
#[derive(SystemLabel)]
struct IgnoreMe;
let mut test_stage = SystemStage::parallel();
test_stage
.add_system(resmut_system.ambiguous_with(IgnoreMe))
.add_system(res_system.label(IgnoreMe))
.add_system(nonsend_system.label(IgnoreMe));
test_stage.run(&mut world);
assert_eq!(test_stage.ambiguity_count(&world), 0);
}
#[test]
fn ambiguous_with_system() {
let mut world = World::new();
let mut test_stage = SystemStage::parallel();
test_stage
.add_system(write_component_system.ambiguous_with(read_component_system))
.add_system(read_component_system);
test_stage.run(&mut world);
assert_eq!(test_stage.ambiguity_count(&world), 0);
}
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>) {}
// Tests that the correct ambiguities were reported in the correct order.
#[test]
fn correct_ambiguities() {
use super::*;
let mut world = World::new();
world.insert_resource(R);
let mut test_stage = SystemStage::parallel();
test_stage
.add_system(system_a)
.add_system(system_b)
.add_system(system_c.ignore_all_ambiguities())
.add_system(system_d.ambiguous_with(system_b))
.add_system(system_e.after(system_a));
test_stage.run(&mut world);
let ambiguities = test_stage.ambiguities(&world);
assert_eq!(
ambiguities,
vec![
SystemOrderAmbiguity {
system_names: [
"bevy_ecs::schedule::ambiguity_detection::tests::system_a".to_string(),
"bevy_ecs::schedule::ambiguity_detection::tests::system_b".to_string()
],
conflicts: vec!["bevy_ecs::schedule::ambiguity_detection::tests::R".to_string()],
segment: SystemStageSegment::Parallel,
},
SystemOrderAmbiguity {
system_names: [
"bevy_ecs::schedule::ambiguity_detection::tests::system_a".to_string(),
"bevy_ecs::schedule::ambiguity_detection::tests::system_d".to_string()
],
conflicts: vec!["bevy_ecs::schedule::ambiguity_detection::tests::R".to_string()],
segment: SystemStageSegment::Parallel,
},
SystemOrderAmbiguity {
system_names: [
"bevy_ecs::schedule::ambiguity_detection::tests::system_b".to_string(),
"bevy_ecs::schedule::ambiguity_detection::tests::system_e".to_string()
],
conflicts: vec!["bevy_ecs::schedule::ambiguity_detection::tests::R".to_string()],
segment: SystemStageSegment::Parallel,
},
SystemOrderAmbiguity {
system_names: [
"bevy_ecs::schedule::ambiguity_detection::tests::system_d".to_string(),
"bevy_ecs::schedule::ambiguity_detection::tests::system_e".to_string()
],
conflicts: vec!["bevy_ecs::schedule::ambiguity_detection::tests::R".to_string()],
segment: SystemStageSegment::Parallel,
},
]
);
}
}

View file

@ -1,36 +0,0 @@
use crate::{schedule::SystemContainer, world::World};
use core::fmt::Debug;
use downcast_rs::{impl_downcast, Downcast};
pub trait ParallelSystemExecutor: Downcast + Send + Sync {
/// Called by `SystemStage` whenever `systems` have been changed.
fn rebuild_cached_data(&mut self, systems: &[SystemContainer]);
fn run_systems(&mut self, systems: &mut [SystemContainer], world: &mut World);
}
impl Debug for dyn ParallelSystemExecutor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "dyn ParallelSystemExecutor")
}
}
impl_downcast!(ParallelSystemExecutor);
#[derive(Debug, Default)]
pub struct SingleThreadedExecutor;
impl ParallelSystemExecutor for SingleThreadedExecutor {
fn rebuild_cached_data(&mut self, _: &[SystemContainer]) {}
fn run_systems(&mut self, systems: &mut [SystemContainer], world: &mut World) {
for system in systems {
if system.should_run() {
#[cfg(feature = "trace")]
let _system_span =
bevy_utils::tracing::info_span!("system", name = &*system.name()).entered();
system.system_mut().run((), world);
}
}
}
}

View file

@ -1,574 +0,0 @@
use crate::{
archetype::ArchetypeComponentId,
query::Access,
schedule::{ParallelSystemExecutor, SystemContainer},
schedule_v3::MainThreadExecutor,
world::World,
};
use async_channel::{Receiver, Sender};
use bevy_tasks::{ComputeTaskPool, Scope, TaskPool};
#[cfg(feature = "trace")]
use bevy_utils::tracing::Instrument;
use event_listener::Event;
use fixedbitset::FixedBitSet;
#[cfg(test)]
use scheduling_event::*;
struct SystemSchedulingMetadata {
/// Used to signal the system's task to start the system.
start: Event,
/// Indices of systems that depend on this one, used to decrement their
/// dependency counters when this system finishes.
dependants: Vec<usize>,
/// Total amount of dependencies this system has.
dependencies_total: usize,
/// Amount of unsatisfied dependencies, when it reaches 0 the system is queued to be started.
dependencies_now: usize,
/// Archetype-component access information.
archetype_component_access: Access<ArchetypeComponentId>,
/// Whether or not this system is send-able
is_send: bool,
}
pub struct ParallelExecutor {
/// Cached metadata of every system.
system_metadata: Vec<SystemSchedulingMetadata>,
/// Used by systems to notify the executor that they have finished.
finish_sender: Sender<usize>,
/// Receives finish events from systems.
finish_receiver: Receiver<usize>,
/// Systems that should be started at next opportunity.
queued: FixedBitSet,
/// Systems that are currently running.
running: FixedBitSet,
/// Whether a non-send system is currently running.
non_send_running: bool,
/// Systems that should run this iteration.
should_run: FixedBitSet,
/// Compound archetype-component access information of currently running systems.
active_archetype_component_access: Access<ArchetypeComponentId>,
/// Scratch space to avoid reallocating a vector when updating dependency counters.
dependants_scratch: Vec<usize>,
#[cfg(test)]
events_sender: Option<Sender<SchedulingEvent>>,
}
impl Default for ParallelExecutor {
fn default() -> Self {
// Using a bounded channel here as it avoids allocations when signaling
// and generally remains hotter in memory. It'll take 128 systems completing
// before the parallel executor runs before this overflows. If it overflows
// all systems will just suspend until the parallel executor runs.
let (finish_sender, finish_receiver) = async_channel::bounded(128);
Self {
system_metadata: Default::default(),
finish_sender,
finish_receiver,
queued: Default::default(),
running: Default::default(),
non_send_running: false,
should_run: Default::default(),
active_archetype_component_access: Default::default(),
dependants_scratch: Default::default(),
#[cfg(test)]
events_sender: None,
}
}
}
impl ParallelSystemExecutor for ParallelExecutor {
fn rebuild_cached_data(&mut self, systems: &[SystemContainer]) {
self.system_metadata.clear();
self.queued.grow(systems.len());
self.running.grow(systems.len());
self.should_run.grow(systems.len());
// Construct scheduling data for systems.
for container in systems {
let dependencies_total = container.dependencies().len();
let system = container.system();
self.system_metadata.push(SystemSchedulingMetadata {
start: Event::new(),
dependants: vec![],
dependencies_total,
dependencies_now: 0,
is_send: system.is_send(),
archetype_component_access: Default::default(),
});
}
// Populate the dependants lists in the scheduling metadata.
for (dependant, container) in systems.iter().enumerate() {
for dependency in container.dependencies() {
self.system_metadata[*dependency].dependants.push(dependant);
}
}
}
fn run_systems(&mut self, systems: &mut [SystemContainer], world: &mut World) {
#[cfg(test)]
if self.events_sender.is_none() {
let (sender, receiver) = async_channel::unbounded::<SchedulingEvent>();
world.insert_resource(SchedulingEvents(receiver));
self.events_sender = Some(sender);
}
{
#[cfg(feature = "trace")]
let _span = bevy_utils::tracing::info_span!("update_archetypes").entered();
for (index, container) in systems.iter_mut().enumerate() {
let meta = &mut self.system_metadata[index];
let system = container.system_mut();
system.update_archetype_component_access(world);
meta.archetype_component_access
.extend(system.archetype_component_access());
}
}
let thread_executor = world
.get_resource::<MainThreadExecutor>()
.map(|e| e.0.clone());
let thread_executor = thread_executor.as_deref();
ComputeTaskPool::init(TaskPool::default).scope_with_executor(
false,
thread_executor,
|scope| {
self.prepare_systems(scope, systems, world);
if self.should_run.count_ones(..) == 0 {
return;
}
let parallel_executor = async {
// All systems have been ran if there are no queued or running systems.
while 0 != self.queued.count_ones(..) + self.running.count_ones(..) {
self.process_queued_systems();
// Avoid deadlocking if no systems were actually started.
if self.running.count_ones(..) != 0 {
// Wait until at least one system has finished.
let index = self
.finish_receiver
.recv()
.await
.unwrap_or_else(|error| unreachable!("{}", error));
self.process_finished_system(index);
// Gather other systems than may have finished.
while let Ok(index) = self.finish_receiver.try_recv() {
self.process_finished_system(index);
}
// At least one system has finished, so active access is outdated.
self.rebuild_active_access();
}
self.update_counters_and_queue_systems();
}
};
#[cfg(feature = "trace")]
let span = bevy_utils::tracing::info_span!("parallel executor");
#[cfg(feature = "trace")]
let parallel_executor = parallel_executor.instrument(span);
scope.spawn(parallel_executor);
},
);
}
}
impl ParallelExecutor {
/// Populates `should_run` bitset, spawns tasks for systems that should run this iteration,
/// queues systems with no dependencies to run (or skip) at next opportunity.
fn prepare_systems<'scope>(
&mut self,
scope: &Scope<'_, 'scope, ()>,
systems: &'scope mut [SystemContainer],
world: &'scope World,
) {
// These are used as a part of a unit test.
#[cfg(test)]
let mut started_systems = 0;
#[cfg(feature = "trace")]
let _span = bevy_utils::tracing::info_span!("prepare_systems").entered();
self.should_run.clear();
for (index, (system_data, system)) in
self.system_metadata.iter_mut().zip(systems).enumerate()
{
let should_run = system.should_run();
let can_start = should_run
&& system_data.dependencies_total == 0
&& Self::can_start_now(
self.non_send_running,
system_data,
&self.active_archetype_component_access,
);
// Queue the system if it has no dependencies, otherwise reset its dependency counter.
if system_data.dependencies_total == 0 {
if !can_start {
self.queued.insert(index);
}
} else {
system_data.dependencies_now = system_data.dependencies_total;
}
if !should_run {
continue;
}
// Spawn the system task.
self.should_run.insert(index);
let finish_sender = self.finish_sender.clone();
let system = system.system_mut();
#[cfg(feature = "trace")] // NB: outside the task to get the TLS current span
let system_span = bevy_utils::tracing::info_span!("system", name = &*system.name());
#[cfg(feature = "trace")]
let overhead_span =
bevy_utils::tracing::info_span!("system overhead", name = &*system.name());
let mut run = move || {
#[cfg(feature = "trace")]
let _system_guard = system_span.enter();
// SAFETY: the executor prevents two systems with conflicting access from running simultaneously.
unsafe { system.run_unsafe((), world) };
};
if can_start {
let task = async move {
run();
// This will never panic:
// - The channel is never closed or dropped.
// - Overflowing the bounded size will just suspend until
// there is capacity.
finish_sender
.send(index)
.await
.unwrap_or_else(|error| unreachable!("{}", error));
};
#[cfg(feature = "trace")]
let task = task.instrument(overhead_span);
if system_data.is_send {
scope.spawn(task);
} else {
scope.spawn_on_external(task);
}
#[cfg(test)]
{
started_systems += 1;
}
self.running.insert(index);
if !system_data.is_send {
self.non_send_running = true;
}
// Add this system's access information to the active access information.
self.active_archetype_component_access
.extend(&system_data.archetype_component_access);
} else {
let start_listener = system_data.start.listen();
let task = async move {
start_listener.await;
run();
// This will never panic:
// - The channel is never closed or dropped.
// - Overflowing the bounded size will just suspend until
// there is capacity.
finish_sender
.send(index)
.await
.unwrap_or_else(|error| unreachable!("{}", error));
};
#[cfg(feature = "trace")]
let task = task.instrument(overhead_span);
if system_data.is_send {
scope.spawn(task);
} else {
scope.spawn_on_external(task);
}
}
}
#[cfg(test)]
if started_systems != 0 {
self.emit_event(SchedulingEvent::StartedSystems(started_systems));
}
}
/// Determines if the system with given index has no conflicts with already running systems.
#[inline]
fn can_start_now(
non_send_running: bool,
system_data: &SystemSchedulingMetadata,
active_archetype_component_access: &Access<ArchetypeComponentId>,
) -> bool {
// Non-send systems are considered conflicting with each other.
(!non_send_running || system_data.is_send)
&& system_data
.archetype_component_access
.is_compatible(active_archetype_component_access)
}
/// Starts all non-conflicting queued systems, moves them from `queued` to `running`,
/// adds their access information to active access information;
/// processes queued systems that shouldn't run this iteration as completed immediately.
fn process_queued_systems(&mut self) {
// These are used as a part of a unit test as seen in `process_queued_systems`.
// Removing them will cause the test to fail.
#[cfg(test)]
let mut started_systems = 0;
for index in self.queued.ones() {
// If the system shouldn't actually run this iteration, process it as completed
// immediately; otherwise, check for conflicts and signal its task to start.
let system_metadata = &self.system_metadata[index];
if !self.should_run[index] {
self.dependants_scratch.extend(&system_metadata.dependants);
} else if Self::can_start_now(
self.non_send_running,
system_metadata,
&self.active_archetype_component_access,
) {
#[cfg(test)]
{
started_systems += 1;
}
system_metadata.start.notify_additional_relaxed(1);
self.running.insert(index);
if !system_metadata.is_send {
self.non_send_running = true;
}
// Add this system's access information to the active access information.
self.active_archetype_component_access
.extend(&system_metadata.archetype_component_access);
}
}
#[cfg(test)]
if started_systems != 0 {
self.emit_event(SchedulingEvent::StartedSystems(started_systems));
}
// Remove now running systems from the queue.
self.queued.difference_with(&self.running);
// Remove immediately processed systems from the queue.
self.queued.intersect_with(&self.should_run);
}
/// Unmarks the system give index as running, caches indices of its dependants
/// in the `dependants_scratch`.
fn process_finished_system(&mut self, index: usize) {
let system_data = &self.system_metadata[index];
if !system_data.is_send {
self.non_send_running = false;
}
self.running.set(index, false);
self.dependants_scratch.extend(&system_data.dependants);
}
/// Discards active access information and builds it again using currently
/// running systems' access information.
fn rebuild_active_access(&mut self) {
self.active_archetype_component_access.clear();
for index in self.running.ones() {
self.active_archetype_component_access
.extend(&self.system_metadata[index].archetype_component_access);
}
}
/// Drains `dependants_scratch`, decrementing dependency counters and enqueueing any
/// systems that become able to run.
fn update_counters_and_queue_systems(&mut self) {
for index in self.dependants_scratch.drain(..) {
let dependant_data = &mut self.system_metadata[index];
dependant_data.dependencies_now -= 1;
if dependant_data.dependencies_now == 0 {
self.queued.insert(index);
}
}
}
#[cfg(test)]
fn emit_event(&self, event: SchedulingEvent) {
let _ = self.events_sender.as_ref().unwrap().try_send(event);
}
}
#[cfg(test)]
mod scheduling_event {
use crate as bevy_ecs;
use crate::system::Resource;
use async_channel::Receiver;
#[derive(Debug, PartialEq, Eq)]
pub(super) enum SchedulingEvent {
StartedSystems(usize),
}
#[derive(Resource)]
pub(super) struct SchedulingEvents(pub(crate) Receiver<SchedulingEvent>);
}
#[cfg(test)]
#[cfg(test)]
mod tests {
use crate::{
self as bevy_ecs,
component::Component,
schedule::{
executor_parallel::scheduling_event::*, SingleThreadedExecutor, Stage, SystemStage,
},
system::{NonSend, Query, Res, ResMut, Resource},
world::World,
};
use SchedulingEvent::StartedSystems;
#[derive(Component)]
struct W<T>(T);
#[derive(Resource, Default)]
struct Counter(usize);
fn receive_events(world: &World) -> Vec<SchedulingEvent> {
let mut events = Vec::new();
while let Ok(event) = world.resource::<SchedulingEvents>().0.try_recv() {
events.push(event);
}
events
}
#[test]
fn trivial() {
let mut world = World::new();
fn wants_for_nothing() {}
let mut stage = SystemStage::parallel()
.with_system(wants_for_nothing)
.with_system(wants_for_nothing)
.with_system(wants_for_nothing);
stage.run(&mut world);
stage.run(&mut world);
assert_eq!(
receive_events(&world),
vec![StartedSystems(3), StartedSystems(3),]
);
}
#[test]
fn resources() {
let mut world = World::new();
world.init_resource::<Counter>();
fn wants_mut(_: ResMut<Counter>) {}
fn wants_ref(_: Res<Counter>) {}
let mut stage = SystemStage::parallel()
.with_system(wants_mut)
.with_system(wants_mut);
stage.run(&mut world);
assert_eq!(
receive_events(&world),
vec![StartedSystems(1), StartedSystems(1),]
);
let mut stage = SystemStage::parallel()
.with_system(wants_mut)
.with_system(wants_ref);
stage.run(&mut world);
assert_eq!(
receive_events(&world),
vec![StartedSystems(1), StartedSystems(1),]
);
let mut stage = SystemStage::parallel()
.with_system(wants_ref)
.with_system(wants_ref);
stage.run(&mut world);
assert_eq!(receive_events(&world), vec![StartedSystems(2),]);
}
#[test]
fn queries() {
let mut world = World::new();
world.spawn(W(0usize));
fn wants_mut(_: Query<&mut W<usize>>) {}
fn wants_ref(_: Query<&W<usize>>) {}
let mut stage = SystemStage::parallel()
.with_system(wants_mut)
.with_system(wants_mut);
stage.run(&mut world);
assert_eq!(
receive_events(&world),
vec![StartedSystems(1), StartedSystems(1),]
);
let mut stage = SystemStage::parallel()
.with_system(wants_mut)
.with_system(wants_ref);
stage.run(&mut world);
assert_eq!(
receive_events(&world),
vec![StartedSystems(1), StartedSystems(1),]
);
let mut stage = SystemStage::parallel()
.with_system(wants_ref)
.with_system(wants_ref);
stage.run(&mut world);
assert_eq!(receive_events(&world), vec![StartedSystems(2),]);
let mut world = World::new();
world.spawn((W(0usize), W(0u32), W(0f32)));
fn wants_mut_usize(_: Query<(&mut W<usize>, &W<f32>)>) {}
fn wants_mut_u32(_: Query<(&mut W<u32>, &W<f32>)>) {}
let mut stage = SystemStage::parallel()
.with_system(wants_mut_usize)
.with_system(wants_mut_u32);
stage.run(&mut world);
assert_eq!(receive_events(&world), vec![StartedSystems(2),]);
}
#[test]
fn world() {
let mut world = World::new();
world.spawn(W(0usize));
fn wants_world(_: &World) {}
fn wants_mut(_: Query<&mut W<usize>>) {}
let mut stage = SystemStage::parallel()
.with_system(wants_mut)
.with_system(wants_mut);
stage.run(&mut world);
assert_eq!(
receive_events(&world),
vec![StartedSystems(1), StartedSystems(1),]
);
let mut stage = SystemStage::parallel()
.with_system(wants_mut)
.with_system(wants_world);
stage.run(&mut world);
assert_eq!(
receive_events(&world),
vec![StartedSystems(1), StartedSystems(1),]
);
let mut stage = SystemStage::parallel()
.with_system(wants_world)
.with_system(wants_world);
stage.run(&mut world);
assert_eq!(receive_events(&world), vec![StartedSystems(2),]);
}
#[test]
fn non_send_resource() {
use std::thread;
let mut world = World::new();
world.insert_non_send_resource(thread::current().id());
fn non_send(thread_id: NonSend<thread::ThreadId>) {
assert_eq!(thread::current().id(), *thread_id);
}
fn empty() {}
let mut stage = SystemStage::parallel()
.with_system(non_send)
.with_system(non_send)
.with_system(empty)
.with_system(empty)
.with_system(non_send)
.with_system(non_send);
stage.run(&mut world);
assert_eq!(
receive_events(&world),
vec![
StartedSystems(3),
StartedSystems(1),
StartedSystems(1),
StartedSystems(1),
]
);
stage.set_executor(Box::<SingleThreadedExecutor>::default());
stage.run(&mut world);
}
}

View file

@ -1,126 +0,0 @@
use bevy_utils::{tracing::warn, HashMap, HashSet};
use fixedbitset::FixedBitSet;
use std::{borrow::Cow, fmt::Debug, hash::Hash};
pub enum DependencyGraphError<Labels> {
GraphCycles(Vec<(usize, Labels)>),
}
pub trait GraphNode {
type Label;
fn name(&self) -> Cow<'static, str>;
fn labels(&self) -> &[Self::Label];
fn before(&self) -> &[Self::Label];
fn after(&self) -> &[Self::Label];
}
/// Constructs a dependency graph of given nodes.
pub fn build_dependency_graph<Node>(
nodes: &[Node],
) -> HashMap<usize, HashMap<usize, HashSet<Node::Label>>>
where
Node: GraphNode,
Node::Label: Debug + Clone + Eq + Hash,
{
let mut labels = HashMap::<Node::Label, FixedBitSet>::default();
for (label, index) in nodes.iter().enumerate().flat_map(|(index, container)| {
container
.labels()
.iter()
.cloned()
.map(move |label| (label, index))
}) {
labels
.entry(label)
.or_insert_with(|| FixedBitSet::with_capacity(nodes.len()))
.insert(index);
}
let mut graph = HashMap::with_capacity_and_hasher(nodes.len(), Default::default());
for (index, node) in nodes.iter().enumerate() {
let dependencies = graph.entry(index).or_insert_with(HashMap::default);
for label in node.after() {
match labels.get(label) {
Some(new_dependencies) => {
for dependency in new_dependencies.ones() {
dependencies
.entry(dependency)
.or_insert_with(HashSet::default)
.insert(label.clone());
}
}
None => warn!(
// TODO: plumb this as proper output?
"{} wants to be after unknown label: {:?}",
nodes[index].name(),
label
),
}
}
for label in node.before() {
match labels.get(label) {
Some(dependants) => {
for dependant in dependants.ones() {
graph
.entry(dependant)
.or_insert_with(HashMap::default)
.entry(index)
.or_insert_with(HashSet::default)
.insert(label.clone());
}
}
None => warn!(
"{} wants to be before unknown label: {:?}",
nodes[index].name(),
label
),
}
}
}
graph
}
/// Generates a topological order for the given graph.
pub fn topological_order<Labels: Clone>(
graph: &HashMap<usize, HashMap<usize, Labels>>,
) -> Result<Vec<usize>, DependencyGraphError<Labels>> {
fn check_if_cycles_and_visit<L>(
node: &usize,
graph: &HashMap<usize, HashMap<usize, L>>,
sorted: &mut Vec<usize>,
unvisited: &mut HashSet<usize>,
current: &mut Vec<usize>,
) -> bool {
if current.contains(node) {
return true;
} else if !unvisited.remove(node) {
return false;
}
current.push(*node);
for dependency in graph.get(node).unwrap().keys() {
if check_if_cycles_and_visit(dependency, graph, sorted, unvisited, current) {
return true;
}
}
sorted.push(*node);
current.pop();
false
}
let mut sorted = Vec::with_capacity(graph.len());
let mut current = Vec::with_capacity(graph.len());
let mut unvisited = HashSet::with_capacity_and_hasher(graph.len(), Default::default());
unvisited.extend(graph.keys().cloned());
while let Some(node) = unvisited.iter().next().cloned() {
if check_if_cycles_and_visit(&node, graph, &mut sorted, &mut unvisited, &mut current) {
let mut cycle = Vec::new();
let last_window = [*current.last().unwrap(), current[0]];
let mut windows = current
.windows(2)
.chain(std::iter::once(&last_window as &[usize]));
while let Some(&[dependant, dependency]) = windows.next() {
cycle.push((dependant, graph[&dependant][&dependency].clone()));
}
return Err(DependencyGraphError::GraphCycles(cycle));
}
}
Ok(sorted)
}

View file

@ -1,21 +0,0 @@
pub use bevy_ecs_macros::{RunCriteriaLabel, StageLabel, SystemLabel};
use bevy_utils::define_label;
define_label!(
/// A strongly-typed class of labels used to identify [`Stage`](crate::schedule::Stage)s.
StageLabel,
/// Strongly-typed identifier for a [`StageLabel`].
StageLabelId,
);
define_label!(
/// A strongly-typed class of labels used to identify [`System`](crate::system::System)s.
SystemLabel,
/// Strongly-typed identifier for a [`SystemLabel`].
SystemLabelId,
);
define_label!(
/// A strongly-typed class of labels used to identify [run criteria](crate::schedule::RunCriteria).
RunCriteriaLabel,
/// Strongly-typed identifier for a [`RunCriteriaLabel`].
RunCriteriaLabelId,
);

View file

@ -1,408 +0,0 @@
//! Tools for controlling system execution.
//!
//! When using Bevy ECS, systems are usually not run directly, but are inserted into a
//! [`Stage`], which then lives within a [`Schedule`].
mod ambiguity_detection;
mod executor;
mod executor_parallel;
pub mod graph_utils;
mod label;
mod run_criteria;
mod stage;
mod state;
mod system_container;
mod system_descriptor;
mod system_set;
pub use executor::*;
pub use executor_parallel::*;
pub use graph_utils::GraphNode;
pub use label::*;
pub use run_criteria::*;
pub use stage::*;
pub use state::*;
pub use system_container::*;
pub use system_descriptor::*;
pub use system_set::*;
use std::fmt::Debug;
use crate::{system::IntoSystem, world::World};
use bevy_utils::HashMap;
/// A container of [`Stage`]s set to be run in a linear order.
///
/// Since `Schedule` implements the [`Stage`] trait, it can be inserted into another schedule.
/// In this way, the properties of the child schedule can be set differently from the parent.
/// For example, it can be set to run only once during app execution, while the parent schedule
/// runs indefinitely.
#[derive(Debug, Default)]
pub struct Schedule {
stages: HashMap<StageLabelId, Box<dyn Stage>>,
stage_order: Vec<StageLabelId>,
run_criteria: BoxedRunCriteria,
}
impl Schedule {
/// Similar to [`add_stage`](Self::add_stage), but it also returns itself.
#[must_use]
pub fn with_stage<S: Stage>(mut self, label: impl StageLabel, stage: S) -> Self {
self.add_stage(label, stage);
self
}
/// Similar to [`add_stage_after`](Self::add_stage_after), but it also returns itself.
#[must_use]
pub fn with_stage_after<S: Stage>(
mut self,
target: impl StageLabel,
label: impl StageLabel,
stage: S,
) -> Self {
self.add_stage_after(target, label, stage);
self
}
/// Similar to [`add_stage_before`](Self::add_stage_before), but it also returns itself.
#[must_use]
pub fn with_stage_before<S: Stage>(
mut self,
target: impl StageLabel,
label: impl StageLabel,
stage: S,
) -> Self {
self.add_stage_before(target, label, stage);
self
}
#[must_use]
pub fn with_run_criteria<S: IntoSystem<(), ShouldRun, P>, P>(mut self, system: S) -> Self {
self.set_run_criteria(system);
self
}
/// Similar to [`add_system_to_stage`](Self::add_system_to_stage), but it also returns itself.
#[must_use]
pub fn with_system_in_stage<Params>(
mut self,
stage_label: impl StageLabel,
system: impl IntoSystemDescriptor<Params>,
) -> Self {
self.add_system_to_stage(stage_label, system);
self
}
pub fn set_run_criteria<S: IntoSystem<(), ShouldRun, P>, P>(&mut self, system: S) -> &mut Self {
self.run_criteria
.set(Box::new(IntoSystem::into_system(system)));
self
}
/// Adds the given `stage` at the last position of the schedule.
///
/// # Example
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// # let mut schedule = Schedule::default();
/// // Define a new label for the stage.
/// #[derive(StageLabel)]
/// struct MyStage;
/// // Add a stage with that label to the schedule.
/// schedule.add_stage(MyStage, SystemStage::parallel());
/// ```
pub fn add_stage<S: Stage>(&mut self, label: impl StageLabel, stage: S) -> &mut Self {
let label = label.as_label();
self.stage_order.push(label);
let prev = self.stages.insert(label, Box::new(stage));
assert!(prev.is_none(), "Stage already exists: {label:?}.");
self
}
/// Adds the given `stage` immediately after the `target` stage.
///
/// # Example
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// # let mut schedule = Schedule::default();
/// # #[derive(StageLabel)]
/// # struct TargetStage;
/// # schedule.add_stage(TargetStage, SystemStage::parallel());
/// // Define a new label for the stage.
/// #[derive(StageLabel)]
/// struct NewStage;
/// // Add a stage with that label to the schedule.
/// schedule.add_stage_after(TargetStage, NewStage, SystemStage::parallel());
/// ```
pub fn add_stage_after<S: Stage>(
&mut self,
target: impl StageLabel,
label: impl StageLabel,
stage: S,
) -> &mut Self {
let label = label.as_label();
let target = target.as_label();
let target_index = self
.stage_order
.iter()
.enumerate()
.find(|(_i, stage_label)| **stage_label == target)
.map(|(i, _)| i)
.unwrap_or_else(|| panic!("Target stage does not exist: {target:?}."));
self.stage_order.insert(target_index + 1, label);
let prev = self.stages.insert(label, Box::new(stage));
assert!(prev.is_none(), "Stage already exists: {label:?}.");
self
}
/// Adds the given `stage` immediately before the `target` stage.
///
/// # Example
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// # let mut schedule = Schedule::default();
/// # #[derive(StageLabel)]
/// # struct TargetStage;
/// # schedule.add_stage(TargetStage, SystemStage::parallel());
/// #
/// // Define a new, private label for the stage.
/// #[derive(StageLabel)]
/// struct NewStage;
/// // Add a stage with that label to the schedule.
/// schedule.add_stage_before(TargetStage, NewStage, SystemStage::parallel());
/// ```
pub fn add_stage_before<S: Stage>(
&mut self,
target: impl StageLabel,
label: impl StageLabel,
stage: S,
) -> &mut Self {
let label = label.as_label();
let target = target.as_label();
let target_index = self
.stage_order
.iter()
.enumerate()
.find(|(_i, stage_label)| **stage_label == target)
.map(|(i, _)| i)
.unwrap_or_else(|| panic!("Target stage does not exist: {target:?}."));
self.stage_order.insert(target_index, label);
let prev = self.stages.insert(label, Box::new(stage));
assert!(prev.is_none(), "Stage already exists: {label:?}.");
self
}
/// Adds the given `system` to the stage identified by `stage_label`.
///
/// # Example
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// # fn my_system() {}
/// # let mut schedule = Schedule::default();
/// # #[derive(StageLabel)]
/// # struct MyStage;
/// # schedule.add_stage(MyStage, SystemStage::parallel());
/// #
/// schedule.add_system_to_stage(MyStage, my_system);
/// ```
pub fn add_system_to_stage<Params>(
&mut self,
stage_label: impl StageLabel,
system: impl IntoSystemDescriptor<Params>,
) -> &mut Self {
// Use a function instead of a closure to ensure that it is codegened inside bevy_ecs instead
// of the game. Closures inherit generic parameters from their enclosing function.
#[cold]
fn stage_not_found(stage_label: &dyn Debug) -> ! {
panic!("Stage '{stage_label:?}' does not exist or is not a SystemStage",)
}
let label = stage_label.as_label();
let stage = self
.get_stage_mut::<SystemStage>(label)
.unwrap_or_else(move || stage_not_found(&label));
stage.add_system(system);
self
}
/// Adds the given `system_set` to the stage identified by `stage_label`.
///
/// # Example
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// # fn my_system() {}
/// # let mut schedule = Schedule::default();
/// # #[derive(StageLabel)]
/// # struct MyStage;
/// # schedule.add_stage(MyStage, SystemStage::parallel());
/// #
/// schedule.add_system_set_to_stage(
/// MyStage,
/// SystemSet::new()
/// .with_system(system_a)
/// .with_system(system_b)
/// .with_system(system_c)
/// );
/// #
/// # fn system_a() {}
/// # fn system_b() {}
/// # fn system_c() {}
/// ```
pub fn add_system_set_to_stage(
&mut self,
stage_label: impl StageLabel,
system_set: SystemSet,
) -> &mut Self {
self.stage(stage_label, |stage: &mut SystemStage| {
stage.add_system_set(system_set)
})
}
/// Fetches the [`Stage`] of type `T` marked with `label`, then executes the provided
/// `func` passing the fetched stage to it as an argument.
///
/// The `func` argument should be a function or a closure that accepts a mutable reference
/// to a struct implementing `Stage` and returns the same type. That means that it should
/// also assume that the stage has already been fetched successfully.
///
/// # Example
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// # let mut schedule = Schedule::default();
/// # #[derive(StageLabel)]
/// # struct MyStage;
/// # schedule.add_stage(MyStage, SystemStage::parallel());
/// #
/// schedule.stage(MyStage, |stage: &mut SystemStage| {
/// stage.add_system(my_system)
/// });
/// #
/// # fn my_system() {}
/// ```
///
/// # Panics
///
/// Panics if `label` refers to a non-existing stage, or if it's not of type `T`.
pub fn stage<T: Stage, F: FnOnce(&mut T) -> &mut T>(
&mut self,
stage_label: impl StageLabel,
func: F,
) -> &mut Self {
let label = stage_label.as_label();
let stage = self.get_stage_mut::<T>(label).unwrap_or_else(move || {
panic!("stage '{label:?}' does not exist or is the wrong type",)
});
func(stage);
self
}
/// Returns a shared reference to the stage identified by `label`, if it exists.
///
/// If the requested stage does not exist, `None` is returned instead.
///
/// # Example
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// # fn my_system() {}
/// # let mut schedule = Schedule::default();
/// # #[derive(StageLabel)]
/// # struct MyStage;
/// # schedule.add_stage(MyStage, SystemStage::parallel());
/// #
/// let stage = schedule.get_stage::<SystemStage>(MyStage).unwrap();
/// ```
pub fn get_stage<T: Stage>(&self, stage_label: impl StageLabel) -> Option<&T> {
let label = stage_label.as_label();
self.stages
.get(&label)
.and_then(|stage| stage.downcast_ref::<T>())
}
/// Returns a unique, mutable reference to the stage identified by `label`, if it exists.
///
/// If the requested stage does not exist, `None` is returned instead.
///
/// # Example
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// # fn my_system() {}
/// # let mut schedule = Schedule::default();
/// # #[derive(StageLabel)]
/// # struct MyStage;
/// # schedule.add_stage(MyStage, SystemStage::parallel());
/// #
/// let stage = schedule.get_stage_mut::<SystemStage>(MyStage).unwrap();
/// ```
pub fn get_stage_mut<T: Stage>(&mut self, stage_label: impl StageLabel) -> Option<&mut T> {
let label = stage_label.as_label();
self.stages
.get_mut(&label)
.and_then(|stage| stage.downcast_mut::<T>())
}
/// Removes a [`Stage`] from the schedule.
pub fn remove_stage(&mut self, stage_label: impl StageLabel) -> Option<Box<dyn Stage>> {
let label = stage_label.as_label();
let Some(index) = self.stage_order.iter().position(|x| *x == label) else {
return None;
};
self.stage_order.remove(index);
self.stages.remove(&label)
}
/// Executes each [`Stage`] contained in the schedule, one at a time.
pub fn run_once(&mut self, world: &mut World) {
for label in &self.stage_order {
#[cfg(feature = "trace")]
let _stage_span = bevy_utils::tracing::info_span!("stage", name = ?label).entered();
let stage = self.stages.get_mut(label).unwrap();
stage.run(world);
}
}
/// Iterates over all of schedule's stages and their labels, in execution order.
pub fn iter_stages(&self) -> impl Iterator<Item = (StageLabelId, &dyn Stage)> {
self.stage_order
.iter()
.map(move |&label| (label, &*self.stages[&label]))
}
}
impl Stage for Schedule {
fn run(&mut self, world: &mut World) {
loop {
match self.run_criteria.should_run(world) {
ShouldRun::No => return,
ShouldRun::Yes => {
self.run_once(world);
return;
}
ShouldRun::YesAndCheckAgain => {
self.run_once(world);
}
ShouldRun::NoAndCheckAgain => {
panic!("`NoAndCheckAgain` would loop infinitely in this situation.")
}
}
}
}
}

View file

@ -1,402 +0,0 @@
use crate::{
prelude::System,
schedule::{GraphNode, RunCriteriaLabel, RunCriteriaLabelId},
system::{BoxedSystem, IntoSystem, Local},
world::World,
};
use core::fmt::Debug;
use std::borrow::Cow;
/// Determines whether a system should be executed or not, and how many times it should be ran each
/// time the stage is executed.
///
/// A stage will loop over its run criteria and systems until no more systems need to be executed
/// and no more run criteria need to be checked.
/// - Any systems with run criteria that returns [`Yes`] will be ran exactly one more time during
/// the stage's execution that tick.
/// - Any systems with run criteria that returns [`No`] are not ran for the rest of the stage's
/// execution that tick.
/// - Any systems with run criteria that returns [`YesAndCheckAgain`] will be ran during this
/// iteration of the loop. After all the systems that need to run are ran, that criteria will be
/// checked again.
/// - Any systems with run criteria that returns [`NoAndCheckAgain`] will not be ran during this
/// iteration of the loop. After all the systems that need to run are ran, that criteria will be
/// checked again.
///
/// [`Yes`]: ShouldRun::Yes
/// [`No`]: ShouldRun::No
/// [`YesAndCheckAgain`]: ShouldRun::YesAndCheckAgain
/// [`NoAndCheckAgain`]: ShouldRun::NoAndCheckAgain
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ShouldRun {
/// Yes, the system should run one more time this tick.
Yes,
/// No, the system should not run for the rest of this tick.
No,
/// Yes, the system should run, and after all systems in this stage have run, the criteria
/// should be checked again. This will cause the stage to loop over the remaining systems and
/// criteria this tick until they no longer need to be checked.
YesAndCheckAgain,
/// No, the system should not run right now, but after all systems in this stage have run, the
/// criteria should be checked again. This will cause the stage to loop over the remaining
/// systems and criteria this tick until they no longer need to be checked.
NoAndCheckAgain,
}
impl ShouldRun {
/// A run criterion which returns [`ShouldRun::Yes`] exactly once.
///
/// This leads to the systems controlled by it only being
/// executed one time only.
pub fn once(mut ran: Local<bool>) -> ShouldRun {
if *ran {
ShouldRun::No
} else {
*ran = true;
ShouldRun::Yes
}
}
}
impl From<bool> for ShouldRun {
fn from(value: bool) -> Self {
if value {
ShouldRun::Yes
} else {
ShouldRun::No
}
}
}
#[derive(Debug, Default)]
pub(crate) struct BoxedRunCriteria {
criteria_system: Option<BoxedSystem<(), ShouldRun>>,
initialized: bool,
}
impl BoxedRunCriteria {
pub(crate) fn set(&mut self, criteria_system: BoxedSystem<(), ShouldRun>) {
self.criteria_system = Some(criteria_system);
self.initialized = false;
}
pub(crate) fn should_run(&mut self, world: &mut World) -> ShouldRun {
if let Some(ref mut run_criteria) = self.criteria_system {
if !self.initialized {
run_criteria.initialize(world);
self.initialized = true;
}
let should_run = run_criteria.run((), world);
run_criteria.apply_buffers(world);
should_run
} else {
ShouldRun::Yes
}
}
}
#[derive(Debug)]
pub(crate) enum RunCriteriaInner {
Single(BoxedSystem<(), ShouldRun>),
Piped {
input: usize,
system: BoxedSystem<ShouldRun, ShouldRun>,
},
}
#[derive(Debug)]
pub(crate) struct RunCriteriaContainer {
pub(crate) should_run: ShouldRun,
pub(crate) inner: RunCriteriaInner,
pub(crate) label: Option<RunCriteriaLabelId>,
pub(crate) before: Vec<RunCriteriaLabelId>,
pub(crate) after: Vec<RunCriteriaLabelId>,
}
impl RunCriteriaContainer {
pub(crate) fn from_descriptor(descriptor: RunCriteriaDescriptor) -> Self {
Self {
should_run: ShouldRun::Yes,
inner: match descriptor.system {
RunCriteriaSystem::Single(system) => RunCriteriaInner::Single(system),
RunCriteriaSystem::Piped(system) => RunCriteriaInner::Piped { input: 0, system },
},
label: descriptor.label,
before: descriptor.before,
after: descriptor.after,
}
}
pub(crate) fn name(&self) -> Cow<'static, str> {
match &self.inner {
RunCriteriaInner::Single(system) => system.name(),
RunCriteriaInner::Piped { system, .. } => system.name(),
}
}
pub(crate) fn initialize(&mut self, world: &mut World) {
match &mut self.inner {
RunCriteriaInner::Single(system) => system.initialize(world),
RunCriteriaInner::Piped { system, .. } => system.initialize(world),
}
}
}
impl GraphNode for RunCriteriaContainer {
type Label = RunCriteriaLabelId;
fn name(&self) -> Cow<'static, str> {
match &self.inner {
RunCriteriaInner::Single(system) => system.name(),
RunCriteriaInner::Piped { system, .. } => system.name(),
}
}
fn labels(&self) -> &[RunCriteriaLabelId] {
if let Some(ref label) = self.label {
std::slice::from_ref(label)
} else {
&[]
}
}
fn before(&self) -> &[RunCriteriaLabelId] {
&self.before
}
fn after(&self) -> &[RunCriteriaLabelId] {
&self.after
}
}
#[derive(Debug)]
pub enum RunCriteriaDescriptorOrLabel {
Descriptor(RunCriteriaDescriptor),
Label(RunCriteriaLabelId),
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum DuplicateLabelStrategy {
Panic,
Discard,
}
#[derive(Debug)]
pub struct RunCriteriaDescriptor {
pub(crate) system: RunCriteriaSystem,
pub(crate) label: Option<RunCriteriaLabelId>,
pub(crate) duplicate_label_strategy: DuplicateLabelStrategy,
pub(crate) before: Vec<RunCriteriaLabelId>,
pub(crate) after: Vec<RunCriteriaLabelId>,
}
#[derive(Debug)]
pub(crate) enum RunCriteriaSystem {
Single(BoxedSystem<(), ShouldRun>),
Piped(BoxedSystem<ShouldRun, ShouldRun>),
}
pub trait IntoRunCriteria<Marker> {
fn into(self) -> RunCriteriaDescriptorOrLabel;
}
impl IntoRunCriteria<RunCriteriaDescriptor> for RunCriteriaDescriptorOrLabel {
fn into(self) -> RunCriteriaDescriptorOrLabel {
self
}
}
impl IntoRunCriteria<RunCriteriaDescriptorOrLabel> for RunCriteriaDescriptor {
fn into(self) -> RunCriteriaDescriptorOrLabel {
RunCriteriaDescriptorOrLabel::Descriptor(self)
}
}
impl IntoRunCriteria<BoxedSystem<(), ShouldRun>> for BoxedSystem<(), ShouldRun> {
fn into(self) -> RunCriteriaDescriptorOrLabel {
RunCriteriaDescriptorOrLabel::Descriptor(new_run_criteria_descriptor(self))
}
}
impl<S, Param> IntoRunCriteria<(BoxedSystem<(), ShouldRun>, Param)> for S
where
S: IntoSystem<(), ShouldRun, Param>,
{
fn into(self) -> RunCriteriaDescriptorOrLabel {
RunCriteriaDescriptorOrLabel::Descriptor(new_run_criteria_descriptor(Box::new(
IntoSystem::into_system(self),
)))
}
}
impl<L> IntoRunCriteria<RunCriteriaLabelId> for L
where
L: RunCriteriaLabel,
{
fn into(self) -> RunCriteriaDescriptorOrLabel {
RunCriteriaDescriptorOrLabel::Label(self.as_label())
}
}
impl IntoRunCriteria<RunCriteria> for RunCriteria {
fn into(self) -> RunCriteriaDescriptorOrLabel {
RunCriteriaDescriptorOrLabel::Label(self.label)
}
}
pub trait RunCriteriaDescriptorCoercion<Param> {
/// Assigns a label to the criteria. Must be unique.
fn label(self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor;
/// Assigns a label to the criteria. If the given label is already in use,
/// this criteria will be discarded before initialization.
fn label_discard_if_duplicate(self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor;
/// Specifies that this criteria must be evaluated before a criteria with the given label.
fn before(self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor;
/// Specifies that this criteria must be evaluated after a criteria with the given label.
fn after(self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor;
}
impl RunCriteriaDescriptorCoercion<()> for RunCriteriaDescriptor {
fn label(mut self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor {
self.label = Some(label.as_label());
self.duplicate_label_strategy = DuplicateLabelStrategy::Panic;
self
}
fn label_discard_if_duplicate(mut self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor {
self.label = Some(label.as_label());
self.duplicate_label_strategy = DuplicateLabelStrategy::Discard;
self
}
fn before(mut self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor {
self.before.push(label.as_label());
self
}
fn after(mut self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor {
self.after.push(label.as_label());
self
}
}
fn new_run_criteria_descriptor(system: BoxedSystem<(), ShouldRun>) -> RunCriteriaDescriptor {
RunCriteriaDescriptor {
system: RunCriteriaSystem::Single(system),
label: None,
duplicate_label_strategy: DuplicateLabelStrategy::Panic,
before: vec![],
after: vec![],
}
}
impl RunCriteriaDescriptorCoercion<()> for BoxedSystem<(), ShouldRun> {
fn label(self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor {
new_run_criteria_descriptor(self).label(label)
}
fn label_discard_if_duplicate(self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor {
new_run_criteria_descriptor(self).label_discard_if_duplicate(label)
}
fn before(self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor {
new_run_criteria_descriptor(self).before(label)
}
fn after(self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor {
new_run_criteria_descriptor(self).after(label)
}
}
impl<S, Param> RunCriteriaDescriptorCoercion<Param> for S
where
S: IntoSystem<(), ShouldRun, Param>,
{
fn label(self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor {
new_run_criteria_descriptor(Box::new(IntoSystem::into_system(self))).label(label)
}
fn label_discard_if_duplicate(self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor {
new_run_criteria_descriptor(Box::new(IntoSystem::into_system(self)))
.label_discard_if_duplicate(label)
}
fn before(self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor {
new_run_criteria_descriptor(Box::new(IntoSystem::into_system(self))).before(label)
}
fn after(self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor {
new_run_criteria_descriptor(Box::new(IntoSystem::into_system(self))).after(label)
}
}
#[derive(Debug)]
pub struct RunCriteria {
label: RunCriteriaLabelId,
}
impl RunCriteria {
/// Constructs a new run criteria that will retrieve the result of the criteria `label`
/// and pipe it as input to `system`.
pub fn pipe<P>(
label: impl RunCriteriaLabel,
system: impl IntoSystem<ShouldRun, ShouldRun, P>,
) -> RunCriteriaDescriptor {
RunCriteriaDescriptor {
system: RunCriteriaSystem::Piped(Box::new(IntoSystem::into_system(system))),
label: None,
duplicate_label_strategy: DuplicateLabelStrategy::Panic,
before: vec![],
after: vec![label.as_label()],
}
}
}
impl Debug for dyn System<In = (), Out = ShouldRun> + 'static {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"System {} with In=(), Out=ShouldRun: {{{}}}",
self.name(),
{
if self.is_send() {
if self.is_exclusive() {
"is_send is_exclusive"
} else {
"is_send"
}
} else if self.is_exclusive() {
"is_exclusive"
} else {
""
}
},
)
}
}
impl Debug for dyn System<In = ShouldRun, Out = ShouldRun> + 'static {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"System {} with In=ShouldRun, Out=ShouldRun: {{{}}}",
self.name(),
{
if self.is_send() {
if self.is_exclusive() {
"is_send is_exclusive"
} else {
"is_send"
}
} else if self.is_exclusive() {
"is_exclusive"
} else {
""
}
},
)
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,757 +0,0 @@
use crate::{
schedule::{
RunCriteriaDescriptor, RunCriteriaDescriptorCoercion, RunCriteriaLabel, ShouldRun,
SystemSet,
},
system::{In, IntoPipeSystem, Local, Res, ResMut, Resource},
};
use std::{
any::TypeId,
fmt::{self, Debug},
hash::Hash,
};
// Required for derive macros
use crate as bevy_ecs;
pub trait StateData: Send + Sync + Clone + Eq + Debug + Hash + 'static {}
impl<T> StateData for T where T: Send + Sync + Clone + Eq + Debug + Hash + 'static {}
/// ### Stack based state machine
///
/// This state machine has four operations: Push, Pop, Set and Replace.
/// * Push pushes a new state to the state stack, pausing the previous state
/// * Pop removes the current state, and unpauses the last paused state
/// * Set replaces the active state with a new one
/// * Replace unwinds the state stack, and replaces the entire stack with a single new state
#[derive(Debug, Resource)]
pub struct State<T: StateData> {
transition: Option<StateTransition<T>>,
/// The current states in the stack.
///
/// There is always guaranteed to be at least one.
stack: Vec<T>,
scheduled: Option<ScheduledOperation<T>>,
end_next_loop: bool,
}
#[derive(Debug)]
enum StateTransition<T: StateData> {
PreStartup,
Startup,
// The parameter order is always (leaving, entering)
ExitingToResume(T, T),
ExitingFull(T, T),
Entering(T, T),
Resuming(T, T),
Pausing(T, T),
}
#[derive(Debug)]
enum ScheduledOperation<T: StateData> {
Set(T),
Replace(T),
Pop,
Push(T),
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
struct DriverLabel(TypeId, &'static str);
impl RunCriteriaLabel for DriverLabel {
fn type_id(&self) -> core::any::TypeId {
self.0
}
fn as_str(&self) -> &'static str {
self.1
}
}
impl DriverLabel {
fn of<T: 'static>() -> Self {
Self(TypeId::of::<T>(), std::any::type_name::<T>())
}
}
impl<T> State<T>
where
T: StateData,
{
pub fn on_update(pred: T) -> RunCriteriaDescriptor {
(move |state: Res<State<T>>| {
state.stack.last().unwrap() == &pred && state.transition.is_none()
})
.pipe(should_run_adapter::<T>)
.after(DriverLabel::of::<T>())
}
pub fn on_inactive_update(pred: T) -> RunCriteriaDescriptor {
(move |state: Res<State<T>>, mut is_inactive: Local<bool>| match &state.transition {
Some(StateTransition::Pausing(ref relevant, _))
| Some(StateTransition::Resuming(_, ref relevant)) => {
if relevant == &pred {
*is_inactive = !*is_inactive;
}
false
}
Some(_) => false,
None => *is_inactive,
})
.pipe(should_run_adapter::<T>)
.after(DriverLabel::of::<T>())
}
pub fn on_in_stack_update(pred: T) -> RunCriteriaDescriptor {
(move |state: Res<State<T>>, mut is_in_stack: Local<bool>| match &state.transition {
Some(StateTransition::Entering(ref relevant, _))
| Some(StateTransition::ExitingToResume(_, ref relevant))
| Some(StateTransition::ExitingFull(_, ref relevant)) => {
if relevant == &pred {
*is_in_stack = !*is_in_stack;
}
false
}
Some(StateTransition::Startup) => {
if state.stack.last().unwrap() == &pred {
*is_in_stack = !*is_in_stack;
}
false
}
Some(_) => false,
None => *is_in_stack,
})
.pipe(should_run_adapter::<T>)
.after(DriverLabel::of::<T>())
}
pub fn on_enter(pred: T) -> RunCriteriaDescriptor {
(move |state: Res<State<T>>| {
state
.transition
.as_ref()
.map_or(false, |transition| match transition {
StateTransition::Entering(_, entering) => entering == &pred,
StateTransition::Startup => state.stack.last().unwrap() == &pred,
_ => false,
})
})
.pipe(should_run_adapter::<T>)
.after(DriverLabel::of::<T>())
}
pub fn on_exit(pred: T) -> RunCriteriaDescriptor {
(move |state: Res<State<T>>| {
state
.transition
.as_ref()
.map_or(false, |transition| match transition {
StateTransition::ExitingToResume(exiting, _)
| StateTransition::ExitingFull(exiting, _) => exiting == &pred,
_ => false,
})
})
.pipe(should_run_adapter::<T>)
.after(DriverLabel::of::<T>())
}
pub fn on_pause(pred: T) -> RunCriteriaDescriptor {
(move |state: Res<State<T>>| {
state
.transition
.as_ref()
.map_or(false, |transition| match transition {
StateTransition::Pausing(pausing, _) => pausing == &pred,
_ => false,
})
})
.pipe(should_run_adapter::<T>)
.after(DriverLabel::of::<T>())
}
pub fn on_resume(pred: T) -> RunCriteriaDescriptor {
(move |state: Res<State<T>>| {
state
.transition
.as_ref()
.map_or(false, |transition| match transition {
StateTransition::Resuming(_, resuming) => resuming == &pred,
_ => false,
})
})
.pipe(should_run_adapter::<T>)
.after(DriverLabel::of::<T>())
}
pub fn on_update_set(s: T) -> SystemSet {
SystemSet::new().with_run_criteria(Self::on_update(s))
}
pub fn on_inactive_update_set(s: T) -> SystemSet {
SystemSet::new().with_run_criteria(Self::on_inactive_update(s))
}
pub fn on_enter_set(s: T) -> SystemSet {
SystemSet::new().with_run_criteria(Self::on_enter(s))
}
pub fn on_exit_set(s: T) -> SystemSet {
SystemSet::new().with_run_criteria(Self::on_exit(s))
}
pub fn on_pause_set(s: T) -> SystemSet {
SystemSet::new().with_run_criteria(Self::on_pause(s))
}
pub fn on_resume_set(s: T) -> SystemSet {
SystemSet::new().with_run_criteria(Self::on_resume(s))
}
/// Creates a driver set for the State.
///
/// Important note: this set must be inserted **before** all other state-dependant sets to work
/// properly!
pub fn get_driver() -> SystemSet {
SystemSet::default().with_run_criteria(state_cleaner::<T>.label(DriverLabel::of::<T>()))
}
pub fn new(initial: T) -> Self {
Self {
stack: vec![initial],
transition: Some(StateTransition::PreStartup),
scheduled: None,
end_next_loop: false,
}
}
/// Schedule a state change that replaces the active state with the given state.
/// This will fail if there is a scheduled operation, pending transition, or if the given
/// `state` matches the current state
pub fn set(&mut self, state: T) -> Result<(), StateError> {
if self.stack.last().unwrap() == &state {
return Err(StateError::AlreadyInState);
}
if self.scheduled.is_some() || self.transition.is_some() {
return Err(StateError::StateAlreadyQueued);
}
self.scheduled = Some(ScheduledOperation::Set(state));
Ok(())
}
/// Same as [`Self::set`], but if there is already a next state, it will be overwritten
/// instead of failing
pub fn overwrite_set(&mut self, state: T) -> Result<(), StateError> {
if self.stack.last().unwrap() == &state {
return Err(StateError::AlreadyInState);
}
self.scheduled = Some(ScheduledOperation::Set(state));
Ok(())
}
/// Schedule a state change that replaces the full stack with the given state.
/// This will fail if there is a scheduled operation, pending transition, or if the given
/// `state` matches the current state
pub fn replace(&mut self, state: T) -> Result<(), StateError> {
if self.stack.last().unwrap() == &state {
return Err(StateError::AlreadyInState);
}
if self.scheduled.is_some() || self.transition.is_some() {
return Err(StateError::StateAlreadyQueued);
}
self.scheduled = Some(ScheduledOperation::Replace(state));
Ok(())
}
/// Same as [`Self::replace`], but if there is already a next state, it will be overwritten
/// instead of failing
pub fn overwrite_replace(&mut self, state: T) -> Result<(), StateError> {
if self.stack.last().unwrap() == &state {
return Err(StateError::AlreadyInState);
}
self.scheduled = Some(ScheduledOperation::Replace(state));
Ok(())
}
/// Same as [`Self::set`], but does a push operation instead of a next operation
pub fn push(&mut self, state: T) -> Result<(), StateError> {
if self.stack.last().unwrap() == &state {
return Err(StateError::AlreadyInState);
}
if self.scheduled.is_some() || self.transition.is_some() {
return Err(StateError::StateAlreadyQueued);
}
self.scheduled = Some(ScheduledOperation::Push(state));
Ok(())
}
/// Same as [`Self::push`], but if there is already a next state, it will be overwritten
/// instead of failing
pub fn overwrite_push(&mut self, state: T) -> Result<(), StateError> {
if self.stack.last().unwrap() == &state {
return Err(StateError::AlreadyInState);
}
self.scheduled = Some(ScheduledOperation::Push(state));
Ok(())
}
/// Same as [`Self::set`], but does a pop operation instead of a set operation
pub fn pop(&mut self) -> Result<(), StateError> {
if self.scheduled.is_some() || self.transition.is_some() {
return Err(StateError::StateAlreadyQueued);
}
if self.stack.len() == 1 {
return Err(StateError::StackEmpty);
}
self.scheduled = Some(ScheduledOperation::Pop);
Ok(())
}
/// Same as [`Self::pop`], but if there is already a next state, it will be overwritten
/// instead of failing
pub fn overwrite_pop(&mut self) -> Result<(), StateError> {
if self.stack.len() == 1 {
return Err(StateError::StackEmpty);
}
self.scheduled = Some(ScheduledOperation::Pop);
Ok(())
}
/// Schedule a state change that restarts the active state.
/// This will fail if there is a scheduled operation or a pending transition
pub fn restart(&mut self) -> Result<(), StateError> {
if self.scheduled.is_some() || self.transition.is_some() {
return Err(StateError::StateAlreadyQueued);
}
let state = self.stack.last().unwrap();
self.scheduled = Some(ScheduledOperation::Set(state.clone()));
Ok(())
}
/// Same as [`Self::restart`], but if there is already a scheduled state operation,
/// it will be overwritten instead of failing
pub fn overwrite_restart(&mut self) {
let state = self.stack.last().unwrap();
self.scheduled = Some(ScheduledOperation::Set(state.clone()));
}
pub fn current(&self) -> &T {
self.stack.last().unwrap()
}
pub fn inactives(&self) -> &[T] {
self.stack.split_last().map(|(_, rest)| rest).unwrap()
}
/// Clears the scheduled state operation.
pub fn clear_schedule(&mut self) {
self.scheduled = None;
}
}
#[derive(Debug)]
pub enum StateError {
AlreadyInState,
StateAlreadyQueued,
StackEmpty,
}
impl std::error::Error for StateError {}
impl fmt::Display for StateError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
StateError::AlreadyInState => {
write!(f, "Attempted to change the state to the current state.")
}
StateError::StateAlreadyQueued => write!(
f,
"Attempted to queue a state change, but there was already a state queued."
),
StateError::StackEmpty => {
write!(f, "Attempted to queue a pop, but there is nothing to pop.")
}
}
}
}
fn should_run_adapter<T: StateData>(In(cmp_result): In<bool>, state: Res<State<T>>) -> ShouldRun {
if state.end_next_loop {
return ShouldRun::No;
}
if cmp_result {
ShouldRun::YesAndCheckAgain
} else {
ShouldRun::NoAndCheckAgain
}
}
fn state_cleaner<T: StateData>(
mut state: ResMut<State<T>>,
mut prep_exit: Local<bool>,
) -> ShouldRun {
if *prep_exit {
*prep_exit = false;
if state.scheduled.is_none() {
state.end_next_loop = true;
return ShouldRun::YesAndCheckAgain;
}
} else if state.end_next_loop {
state.end_next_loop = false;
return ShouldRun::No;
}
match state.scheduled.take() {
Some(ScheduledOperation::Set(next)) => {
state.transition = Some(StateTransition::ExitingFull(
state.stack.last().unwrap().clone(),
next,
));
}
Some(ScheduledOperation::Replace(next)) => {
if state.stack.len() <= 1 {
state.transition = Some(StateTransition::ExitingFull(
state.stack.last().unwrap().clone(),
next,
));
} else {
state.scheduled = Some(ScheduledOperation::Replace(next));
match state.transition.take() {
Some(StateTransition::ExitingToResume(p, n)) => {
state.stack.pop();
state.transition = Some(StateTransition::Resuming(p, n));
}
_ => {
state.transition = Some(StateTransition::ExitingToResume(
state.stack[state.stack.len() - 1].clone(),
state.stack[state.stack.len() - 2].clone(),
));
}
}
}
}
Some(ScheduledOperation::Push(next)) => {
let last_type_id = state.stack.last().unwrap().clone();
state.transition = Some(StateTransition::Pausing(last_type_id, next));
}
Some(ScheduledOperation::Pop) => {
state.transition = Some(StateTransition::ExitingToResume(
state.stack[state.stack.len() - 1].clone(),
state.stack[state.stack.len() - 2].clone(),
));
}
None => match state.transition.take() {
Some(StateTransition::ExitingFull(p, n)) => {
state.transition = Some(StateTransition::Entering(p, n.clone()));
*state.stack.last_mut().unwrap() = n;
}
Some(StateTransition::Pausing(p, n)) => {
state.transition = Some(StateTransition::Entering(p, n.clone()));
state.stack.push(n);
}
Some(StateTransition::ExitingToResume(p, n)) => {
state.stack.pop();
state.transition = Some(StateTransition::Resuming(p, n));
}
Some(StateTransition::PreStartup) => {
state.transition = Some(StateTransition::Startup);
}
_ => {}
},
};
if state.transition.is_none() {
*prep_exit = true;
}
ShouldRun::YesAndCheckAgain
}
#[cfg(test)]
mod test {
use super::*;
use crate::prelude::*;
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum MyState {
S1,
S2,
S3,
S4,
S5,
S6,
Final,
}
#[test]
fn state_test() {
#[derive(Resource, Default)]
struct NameList(Vec<&'static str>);
let mut world = World::default();
world.init_resource::<NameList>();
world.insert_resource(State::new(MyState::S1));
let mut stage = SystemStage::parallel();
#[derive(SystemLabel)]
enum Inactive {
S4,
S5,
}
stage.add_system_set(State::<MyState>::get_driver());
stage
.add_system_set(
State::on_enter_set(MyState::S1)
.with_system(|mut r: ResMut<NameList>| r.0.push("startup")),
)
.add_system_set(State::on_update_set(MyState::S1).with_system(
|mut r: ResMut<NameList>, mut s: ResMut<State<MyState>>| {
r.0.push("update S1");
s.overwrite_replace(MyState::S2).unwrap();
},
))
.add_system_set(
State::on_enter_set(MyState::S2)
.with_system(|mut r: ResMut<NameList>| r.0.push("enter S2")),
)
.add_system_set(State::on_update_set(MyState::S2).with_system(
|mut r: ResMut<NameList>, mut s: ResMut<State<MyState>>| {
r.0.push("update S2");
s.overwrite_replace(MyState::S3).unwrap();
},
))
.add_system_set(
State::on_exit_set(MyState::S2)
.with_system(|mut r: ResMut<NameList>| r.0.push("exit S2")),
)
.add_system_set(
State::on_enter_set(MyState::S3)
.with_system(|mut r: ResMut<NameList>| r.0.push("enter S3")),
)
.add_system_set(State::on_update_set(MyState::S3).with_system(
|mut r: ResMut<NameList>, mut s: ResMut<State<MyState>>| {
r.0.push("update S3");
s.overwrite_push(MyState::S4).unwrap();
},
))
.add_system_set(
State::on_pause_set(MyState::S3)
.with_system(|mut r: ResMut<NameList>| r.0.push("pause S3")),
)
.add_system_set(State::on_update_set(MyState::S4).with_system(
|mut r: ResMut<NameList>, mut s: ResMut<State<MyState>>| {
r.0.push("update S4");
s.overwrite_push(MyState::S5).unwrap();
},
))
.add_system_set(State::on_inactive_update_set(MyState::S4).with_system(
(|mut r: ResMut<NameList>| r.0.push("inactive S4")).label(Inactive::S4),
))
.add_system_set(
State::on_update_set(MyState::S5).with_system(
(|mut r: ResMut<NameList>, mut s: ResMut<State<MyState>>| {
r.0.push("update S5");
s.overwrite_push(MyState::S6).unwrap();
})
.after(Inactive::S4),
),
)
.add_system_set(
State::on_inactive_update_set(MyState::S5).with_system(
(|mut r: ResMut<NameList>| r.0.push("inactive S5"))
.label(Inactive::S5)
.after(Inactive::S4),
),
)
.add_system_set(
State::on_update_set(MyState::S6).with_system(
(|mut r: ResMut<NameList>, mut s: ResMut<State<MyState>>| {
r.0.push("update S6");
s.overwrite_push(MyState::Final).unwrap();
})
.after(Inactive::S5),
),
)
.add_system_set(
State::on_resume_set(MyState::S4)
.with_system(|mut r: ResMut<NameList>| r.0.push("resume S4")),
)
.add_system_set(
State::on_exit_set(MyState::S5)
.with_system(|mut r: ResMut<NameList>| r.0.push("exit S4")),
);
const EXPECTED: &[&str] = &[
//
"startup",
"update S1",
//
"enter S2",
"update S2",
//
"exit S2",
"enter S3",
"update S3",
//
"pause S3",
"update S4",
//
"inactive S4",
"update S5",
//
"inactive S4",
"inactive S5",
"update S6",
//
"inactive S4",
"inactive S5",
];
stage.run(&mut world);
let mut collected = world.resource_mut::<NameList>();
let mut count = 0;
for (found, expected) in collected.0.drain(..).zip(EXPECTED) {
assert_eq!(found, *expected);
count += 1;
}
// If not equal, some elements weren't executed
assert_eq!(EXPECTED.len(), count);
assert_eq!(
world.resource::<State<MyState>>().current(),
&MyState::Final
);
}
#[test]
fn issue_1753() {
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
enum AppState {
Main,
}
#[derive(Resource)]
struct Flag(bool);
#[derive(Resource)]
struct Name(&'static str);
fn should_run_once(mut flag: ResMut<Flag>, test_name: Res<Name>) {
assert!(!flag.0, "{:?}", test_name.0);
flag.0 = true;
}
let mut world = World::new();
world.insert_resource(State::new(AppState::Main));
world.insert_resource(Flag(false));
world.insert_resource(Name("control"));
let mut stage = SystemStage::parallel().with_system(should_run_once);
stage.run(&mut world);
assert!(world.resource::<Flag>().0, "after control");
world.insert_resource(Flag(false));
world.insert_resource(Name("test"));
let mut stage = SystemStage::parallel()
.with_system_set(State::<AppState>::get_driver())
.with_system(should_run_once);
stage.run(&mut world);
assert!(world.resource::<Flag>().0, "after test");
}
#[test]
fn restart_state_tests() {
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
enum LoadState {
Load,
Finish,
}
#[derive(PartialEq, Eq, Debug)]
enum LoadStatus {
EnterLoad,
ExitLoad,
EnterFinish,
}
#[derive(Resource, Default)]
struct LoadStatusStack(Vec<LoadStatus>);
let mut world = World::new();
world.init_resource::<LoadStatusStack>();
world.insert_resource(State::new(LoadState::Load));
let mut stage = SystemStage::parallel();
stage.add_system_set(State::<LoadState>::get_driver());
// Systems to track loading status
stage
.add_system_set(
State::on_enter_set(LoadState::Load)
.with_system(|mut r: ResMut<LoadStatusStack>| r.0.push(LoadStatus::EnterLoad)),
)
.add_system_set(
State::on_exit_set(LoadState::Load)
.with_system(|mut r: ResMut<LoadStatusStack>| r.0.push(LoadStatus::ExitLoad)),
)
.add_system_set(
State::on_enter_set(LoadState::Finish).with_system(
|mut r: ResMut<LoadStatusStack>| r.0.push(LoadStatus::EnterFinish),
),
);
stage.run(&mut world);
// A. Restart state
let mut state = world.resource_mut::<State<LoadState>>();
let result = state.restart();
assert!(matches!(result, Ok(())));
stage.run(&mut world);
// B. Restart state (overwrite schedule)
let mut state = world.resource_mut::<State<LoadState>>();
state.set(LoadState::Finish).unwrap();
state.overwrite_restart();
stage.run(&mut world);
// C. Fail restart state (transition already scheduled)
let mut state = world.resource_mut::<State<LoadState>>();
state.set(LoadState::Finish).unwrap();
let result = state.restart();
assert!(matches!(result, Err(StateError::StateAlreadyQueued)));
stage.run(&mut world);
const EXPECTED: &[LoadStatus] = &[
LoadStatus::EnterLoad,
// A
LoadStatus::ExitLoad,
LoadStatus::EnterLoad,
// B
LoadStatus::ExitLoad,
LoadStatus::EnterLoad,
// C
LoadStatus::ExitLoad,
LoadStatus::EnterFinish,
];
let mut collected = world.resource_mut::<LoadStatusStack>();
let mut count = 0;
for (found, expected) in collected.0.drain(..).zip(EXPECTED) {
assert_eq!(found, *expected);
count += 1;
}
// If not equal, some elements weren't executed
assert_eq!(EXPECTED.len(), count);
assert_eq!(
world.resource::<State<LoadState>>().current(),
&LoadState::Finish
);
}
}

View file

@ -1,111 +0,0 @@
use crate::{
component::ComponentId,
query::Access,
schedule::{
AmbiguityDetection, GraphNode, RunCriteriaLabelId, SystemDescriptor, SystemLabelId,
},
system::System,
};
use core::fmt::Debug;
use std::borrow::Cow;
pub struct SystemContainer {
system: Box<dyn System<In = (), Out = ()>>,
pub(crate) run_criteria_index: Option<usize>,
pub(crate) run_criteria_label: Option<RunCriteriaLabelId>,
pub(crate) should_run: bool,
is_exclusive: bool,
dependencies: Vec<usize>,
labels: Vec<SystemLabelId>,
before: Vec<SystemLabelId>,
after: Vec<SystemLabelId>,
pub(crate) ambiguity_detection: AmbiguityDetection,
}
impl SystemContainer {
pub(crate) fn from_descriptor(descriptor: SystemDescriptor) -> Self {
SystemContainer {
system: descriptor.system,
should_run: false,
run_criteria_index: None,
run_criteria_label: None,
dependencies: Vec::new(),
labels: descriptor.labels,
before: descriptor.before,
after: descriptor.after,
ambiguity_detection: descriptor.ambiguity_detection,
is_exclusive: descriptor.exclusive_insertion_point.is_some(),
}
}
pub fn name(&self) -> Cow<'static, str> {
GraphNode::name(self)
}
pub fn system(&self) -> &dyn System<In = (), Out = ()> {
&*self.system
}
pub fn system_mut(&mut self) -> &mut dyn System<In = (), Out = ()> {
&mut *self.system
}
pub fn should_run(&self) -> bool {
self.should_run
}
pub fn dependencies(&self) -> &[usize] {
&self.dependencies
}
pub fn set_dependencies(&mut self, dependencies: impl IntoIterator<Item = usize>) {
self.dependencies.clear();
self.dependencies.extend(dependencies);
}
pub fn run_criteria(&self) -> Option<usize> {
self.run_criteria_index
}
pub fn set_run_criteria(&mut self, index: usize) {
self.run_criteria_index = Some(index);
}
pub fn run_criteria_label(&self) -> Option<&RunCriteriaLabelId> {
self.run_criteria_label.as_ref()
}
pub fn component_access(&self) -> &Access<ComponentId> {
self.system().component_access()
}
pub fn is_exclusive(&self) -> bool {
self.is_exclusive
}
}
impl Debug for SystemContainer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{{{:?}}}", &self.system())
}
}
impl GraphNode for SystemContainer {
type Label = SystemLabelId;
fn name(&self) -> Cow<'static, str> {
self.system().name()
}
fn labels(&self) -> &[SystemLabelId] {
&self.labels
}
fn before(&self) -> &[SystemLabelId] {
&self.before
}
fn after(&self) -> &[SystemLabelId] {
&self.after
}
}

View file

@ -1,274 +0,0 @@
use crate::{
schedule::{IntoRunCriteria, RunCriteriaDescriptorOrLabel, SystemLabel, SystemLabelId},
system::{AsSystemLabel, BoxedSystem, IntoSystem},
};
/// Configures ambiguity detection for a single system.
#[derive(Debug, Default)]
pub(crate) enum AmbiguityDetection {
#[default]
Check,
IgnoreAll,
/// Ignore systems with any of these labels.
IgnoreWithLabel(Vec<SystemLabelId>),
}
/// Encapsulates a system and information on when it run in a `SystemStage`.
///
/// Systems can be inserted into 4 different groups within the stage:
/// * Parallel, accepts non-exclusive systems.
/// * At start, accepts exclusive systems; runs before parallel systems.
/// * Before commands, accepts exclusive systems; runs after parallel systems, but before their
/// command buffers are applied.
/// * At end, accepts exclusive systems; runs after parallel systems' command buffers have
/// been applied.
///
/// Systems can have one or more labels attached to them; other systems in the same group
/// can then specify that they have to run before or after systems with that label using the
/// `before` and `after` methods.
///
/// # Example
/// ```
/// # use bevy_ecs::prelude::*;
/// # fn do_something() {}
/// # fn do_the_other_thing() {}
/// # fn do_something_else() {}
/// #[derive(SystemLabel, Debug, Clone, PartialEq, Eq, Hash)]
/// struct Something;
///
/// SystemStage::parallel()
/// .with_system(do_something.label(Something))
/// .with_system(do_the_other_thing.after(Something))
/// .with_system(do_something_else.at_end());
/// ```
#[derive(Debug)]
pub struct SystemDescriptor {
pub(crate) system: BoxedSystem<(), ()>,
pub(crate) exclusive_insertion_point: Option<ExclusiveInsertionPoint>,
pub(crate) run_criteria: Option<RunCriteriaDescriptorOrLabel>,
pub(crate) labels: Vec<SystemLabelId>,
pub(crate) before: Vec<SystemLabelId>,
pub(crate) after: Vec<SystemLabelId>,
pub(crate) ambiguity_detection: AmbiguityDetection,
}
impl SystemDescriptor {
fn new(system: BoxedSystem<(), ()>) -> SystemDescriptor {
SystemDescriptor {
labels: system.default_labels(),
exclusive_insertion_point: if system.is_exclusive() {
Some(ExclusiveInsertionPoint::AtStart)
} else {
None
},
system,
run_criteria: None,
before: Vec::new(),
after: Vec::new(),
ambiguity_detection: Default::default(),
}
}
}
pub trait IntoSystemDescriptor<Params> {
fn into_descriptor(self) -> SystemDescriptor;
/// Assigns a run criteria to the system. Can be a new descriptor or a label of a
/// run criteria defined elsewhere.
fn with_run_criteria<Marker>(
self,
run_criteria: impl IntoRunCriteria<Marker>,
) -> SystemDescriptor;
/// Assigns a label to the system; there can be more than one, and it doesn't have to be unique.
fn label(self, label: impl SystemLabel) -> SystemDescriptor;
/// Specifies that the system should run before systems with the given label.
fn before<Marker>(self, label: impl AsSystemLabel<Marker>) -> SystemDescriptor;
/// Specifies that the system should run after systems with the given label.
fn after<Marker>(self, label: impl AsSystemLabel<Marker>) -> SystemDescriptor;
/// Marks this system as ambiguous with any system with the specified label.
/// This means that execution order between these systems does not matter,
/// which allows [some warnings](crate::schedule::ReportExecutionOrderAmbiguities) to be silenced.
fn ambiguous_with<Marker>(self, label: impl AsSystemLabel<Marker>) -> SystemDescriptor;
/// Specifies that this system should opt out of
/// [execution order ambiguity detection](crate::schedule::ReportExecutionOrderAmbiguities).
fn ignore_all_ambiguities(self) -> SystemDescriptor;
/// Specifies that the system should run with other exclusive systems at the start of stage.
fn at_start(self) -> SystemDescriptor;
/// Specifies that the system should run with other exclusive systems after the parallel
/// systems and before command buffer application.
fn before_commands(self) -> SystemDescriptor;
/// Specifies that the system should run with other exclusive systems at the end of stage.
fn at_end(self) -> SystemDescriptor;
}
impl IntoSystemDescriptor<()> for SystemDescriptor {
fn with_run_criteria<Marker>(
mut self,
run_criteria: impl IntoRunCriteria<Marker>,
) -> SystemDescriptor {
self.run_criteria = Some(run_criteria.into());
self
}
fn label(mut self, label: impl SystemLabel) -> SystemDescriptor {
self.labels.push(label.as_label());
self
}
fn before<Marker>(mut self, label: impl AsSystemLabel<Marker>) -> SystemDescriptor {
self.before.push(label.as_system_label().as_label());
self
}
fn after<Marker>(mut self, label: impl AsSystemLabel<Marker>) -> SystemDescriptor {
self.after.push(label.as_system_label().as_label());
self
}
fn ambiguous_with<Marker>(mut self, label: impl AsSystemLabel<Marker>) -> SystemDescriptor {
match &mut self.ambiguity_detection {
detection @ AmbiguityDetection::Check => {
*detection =
AmbiguityDetection::IgnoreWithLabel(vec![label.as_system_label().as_label()]);
}
AmbiguityDetection::IgnoreWithLabel(labels) => {
labels.push(label.as_system_label().as_label());
}
// This descriptor is already ambiguous with everything.
AmbiguityDetection::IgnoreAll => {}
}
self
}
fn ignore_all_ambiguities(mut self) -> SystemDescriptor {
self.ambiguity_detection = AmbiguityDetection::IgnoreAll;
self
}
fn at_start(mut self) -> SystemDescriptor {
self.exclusive_insertion_point = Some(ExclusiveInsertionPoint::AtStart);
self
}
fn before_commands(mut self) -> SystemDescriptor {
self.exclusive_insertion_point = Some(ExclusiveInsertionPoint::BeforeCommands);
self
}
fn at_end(mut self) -> SystemDescriptor {
self.exclusive_insertion_point = Some(ExclusiveInsertionPoint::AtEnd);
self
}
fn into_descriptor(self) -> SystemDescriptor {
self
}
}
impl<S, Params> IntoSystemDescriptor<Params> for S
where
S: IntoSystem<(), (), Params>,
{
fn with_run_criteria<Marker>(
self,
run_criteria: impl IntoRunCriteria<Marker>,
) -> SystemDescriptor {
SystemDescriptor::new(Box::new(IntoSystem::into_system(self)))
.with_run_criteria(run_criteria)
}
fn label(self, label: impl SystemLabel) -> SystemDescriptor {
SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).label(label)
}
fn before<Marker>(self, label: impl AsSystemLabel<Marker>) -> SystemDescriptor {
SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).before(label)
}
fn after<Marker>(self, label: impl AsSystemLabel<Marker>) -> SystemDescriptor {
SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).after(label)
}
fn ambiguous_with<Marker>(self, label: impl AsSystemLabel<Marker>) -> SystemDescriptor {
SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).ambiguous_with(label)
}
fn ignore_all_ambiguities(self) -> SystemDescriptor {
SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).ignore_all_ambiguities()
}
fn at_start(self) -> SystemDescriptor {
SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).at_start()
}
fn before_commands(self) -> SystemDescriptor {
SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).before_commands()
}
fn at_end(self) -> SystemDescriptor {
SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).at_end()
}
fn into_descriptor(self) -> SystemDescriptor {
SystemDescriptor::new(Box::new(IntoSystem::into_system(self)))
}
}
impl IntoSystemDescriptor<()> for BoxedSystem<(), ()> {
fn with_run_criteria<Marker>(
self,
run_criteria: impl IntoRunCriteria<Marker>,
) -> SystemDescriptor {
SystemDescriptor::new(self).with_run_criteria(run_criteria)
}
fn label(self, label: impl SystemLabel) -> SystemDescriptor {
SystemDescriptor::new(self).label(label)
}
fn before<Marker>(self, label: impl AsSystemLabel<Marker>) -> SystemDescriptor {
SystemDescriptor::new(self).before(label)
}
fn after<Marker>(self, label: impl AsSystemLabel<Marker>) -> SystemDescriptor {
SystemDescriptor::new(self).after(label)
}
fn ambiguous_with<Marker>(self, label: impl AsSystemLabel<Marker>) -> SystemDescriptor {
SystemDescriptor::new(self).ambiguous_with(label)
}
fn ignore_all_ambiguities(self) -> SystemDescriptor {
SystemDescriptor::new(self).ignore_all_ambiguities()
}
fn at_start(self) -> SystemDescriptor {
SystemDescriptor::new(self).at_start()
}
fn before_commands(self) -> SystemDescriptor {
SystemDescriptor::new(self).before_commands()
}
fn at_end(self) -> SystemDescriptor {
SystemDescriptor::new(self).at_end()
}
fn into_descriptor(self) -> SystemDescriptor {
SystemDescriptor::new(self)
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum ExclusiveInsertionPoint {
AtStart,
BeforeCommands,
AtEnd,
}

View file

@ -1,116 +0,0 @@
use crate::schedule::{
IntoRunCriteria, IntoSystemDescriptor, RunCriteriaDescriptorOrLabel, State, StateData,
SystemDescriptor, SystemLabel, SystemLabelId,
};
use crate::system::AsSystemLabel;
/// A builder for describing several systems at the same time.
#[derive(Default)]
pub struct SystemSet {
pub(crate) systems: Vec<SystemDescriptor>,
pub(crate) run_criteria: Option<RunCriteriaDescriptorOrLabel>,
pub(crate) labels: Vec<SystemLabelId>,
pub(crate) before: Vec<SystemLabelId>,
pub(crate) after: Vec<SystemLabelId>,
}
impl SystemSet {
pub fn new() -> Self {
Default::default()
}
pub fn on_update<T>(s: T) -> SystemSet
where
T: StateData,
{
Self::new().with_run_criteria(State::<T>::on_update(s))
}
pub fn on_inactive_update<T>(s: T) -> SystemSet
where
T: StateData,
{
Self::new().with_run_criteria(State::<T>::on_inactive_update(s))
}
pub fn on_in_stack_update<T>(s: T) -> SystemSet
where
T: StateData,
{
Self::new().with_run_criteria(State::<T>::on_in_stack_update(s))
}
pub fn on_enter<T>(s: T) -> SystemSet
where
T: StateData,
{
Self::new().with_run_criteria(State::<T>::on_enter(s))
}
pub fn on_exit<T>(s: T) -> SystemSet
where
T: StateData,
{
Self::new().with_run_criteria(State::<T>::on_exit(s))
}
pub fn on_pause<T>(s: T) -> SystemSet
where
T: StateData,
{
Self::new().with_run_criteria(State::<T>::on_pause(s))
}
pub fn on_resume<T>(s: T) -> SystemSet
where
T: StateData,
{
Self::new().with_run_criteria(State::<T>::on_resume(s))
}
#[must_use]
pub fn with_system<Params>(mut self, system: impl IntoSystemDescriptor<Params>) -> Self {
self.systems.push(system.into_descriptor());
self
}
#[must_use]
pub fn with_run_criteria<Marker>(mut self, run_criteria: impl IntoRunCriteria<Marker>) -> Self {
self.run_criteria = Some(run_criteria.into());
self
}
#[must_use]
pub fn label(mut self, label: impl SystemLabel) -> Self {
self.labels.push(label.as_label());
self
}
#[must_use]
pub fn before<Marker>(mut self, label: impl AsSystemLabel<Marker>) -> Self {
self.before.push(label.as_system_label().as_label());
self
}
#[must_use]
pub fn after<Marker>(mut self, label: impl AsSystemLabel<Marker>) -> Self {
self.after.push(label.as_system_label().as_label());
self
}
pub(crate) fn bake(self) -> (Option<RunCriteriaDescriptorOrLabel>, Vec<SystemDescriptor>) {
let SystemSet {
mut systems,
run_criteria,
labels,
before,
after,
} = self;
for descriptor in &mut systems {
descriptor.labels.extend(labels.iter().cloned());
descriptor.before.extend(before.iter().cloned());
descriptor.after.extend(after.iter().cloned());
}
(run_criteria, systems)
}
}

View file

@ -1,5 +1,3 @@
pub use common_conditions::*;
use crate::system::BoxedSystem;
pub type BoxedCondition = BoxedSystem<(), bool>;
@ -26,10 +24,24 @@ mod sealed {
}
}
mod common_conditions {
pub mod common_conditions {
use crate::schedule_v3::{State, States};
use crate::system::{Res, Resource};
/// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true`
/// if the first time the condition is run and false every time after
pub fn run_once() -> impl FnMut() -> bool {
let mut has_run = false;
move || {
if !has_run {
has_run = true;
true
} else {
false
}
}
}
/// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true`
/// if the resource exists.
pub fn resource_exists<T>() -> impl FnMut(Option<Res<T>>) -> bool

View file

@ -1,11 +1,11 @@
use bevy_ecs_macros::all_tuples;
use bevy_utils::default;
use crate::{
schedule_v3::{
condition::{BoxedCondition, Condition},
graph_utils::{Ambiguity, Dependency, DependencyKind, GraphInfo},
set::{BoxedSystemSet, IntoSystemSet, SystemSet},
state::{OnUpdate, States},
},
system::{BoxedSystem, IntoSystem, System},
};
@ -28,11 +28,7 @@ impl SystemSetConfig {
Self {
set,
graph_info: GraphInfo {
sets: Vec::new(),
dependencies: Vec::new(),
ambiguous_with: default(),
},
graph_info: GraphInfo::default(),
conditions: Vec::new(),
}
}
@ -53,8 +49,7 @@ impl SystemConfig {
system,
graph_info: GraphInfo {
sets,
dependencies: Vec::new(),
ambiguous_with: default(),
..Default::default()
},
conditions: Vec::new(),
}
@ -93,6 +88,8 @@ pub trait IntoSystemSetConfig: sealed::IntoSystemSetConfig {
fn into_config(self) -> SystemSetConfig;
/// Add to the provided `set`.
fn in_set(self, set: impl SystemSet) -> SystemSetConfig;
/// Don't add this set to the schedules's default set.
fn no_default_set(self) -> SystemSetConfig;
/// Run before all systems in `set`.
fn before<M>(self, set: impl IntoSystemSet<M>) -> SystemSetConfig;
/// Run after all systems in `set`.
@ -102,6 +99,8 @@ pub trait IntoSystemSetConfig: sealed::IntoSystemSetConfig {
/// The `Condition` will be evaluated at most once (per schedule run),
/// the first time a system in this set prepares to run.
fn run_if<P>(self, condition: impl Condition<P>) -> SystemSetConfig;
/// Add this set to the [`OnUpdate(state)`](OnUpdate) set.
fn on_update(self, state: impl States) -> SystemSetConfig;
/// Suppress warnings and errors that would result from systems in this set having ambiguities
/// (conflicting access but indeterminate order) with systems in `set`.
fn ambiguous_with<M>(self, set: impl IntoSystemSet<M>) -> SystemSetConfig;
@ -119,27 +118,35 @@ where
}
fn in_set(self, set: impl SystemSet) -> SystemSetConfig {
SystemSetConfig::new(Box::new(self)).in_set(set)
self.into_config().in_set(set)
}
fn no_default_set(self) -> SystemSetConfig {
self.into_config().no_default_set()
}
fn before<M>(self, set: impl IntoSystemSet<M>) -> SystemSetConfig {
SystemSetConfig::new(Box::new(self)).before(set)
self.into_config().before(set)
}
fn after<M>(self, set: impl IntoSystemSet<M>) -> SystemSetConfig {
SystemSetConfig::new(Box::new(self)).after(set)
self.into_config().after(set)
}
fn run_if<P>(self, condition: impl Condition<P>) -> SystemSetConfig {
SystemSetConfig::new(Box::new(self)).run_if(condition)
self.into_config().run_if(condition)
}
fn on_update(self, state: impl States) -> SystemSetConfig {
self.into_config().on_update(state)
}
fn ambiguous_with<M>(self, set: impl IntoSystemSet<M>) -> SystemSetConfig {
SystemSetConfig::new(Box::new(self)).ambiguous_with(set)
self.into_config().ambiguous_with(set)
}
fn ambiguous_with_all(self) -> SystemSetConfig {
SystemSetConfig::new(Box::new(self)).ambiguous_with_all()
self.into_config().ambiguous_with_all()
}
}
@ -149,27 +156,35 @@ impl IntoSystemSetConfig for BoxedSystemSet {
}
fn in_set(self, set: impl SystemSet) -> SystemSetConfig {
SystemSetConfig::new(self).in_set(set)
self.into_config().in_set(set)
}
fn no_default_set(self) -> SystemSetConfig {
self.into_config().no_default_set()
}
fn before<M>(self, set: impl IntoSystemSet<M>) -> SystemSetConfig {
SystemSetConfig::new(self).before(set)
self.into_config().before(set)
}
fn after<M>(self, set: impl IntoSystemSet<M>) -> SystemSetConfig {
SystemSetConfig::new(self).after(set)
self.into_config().after(set)
}
fn run_if<P>(self, condition: impl Condition<P>) -> SystemSetConfig {
SystemSetConfig::new(self).run_if(condition)
self.into_config().run_if(condition)
}
fn on_update(self, state: impl States) -> SystemSetConfig {
self.into_config().on_update(state)
}
fn ambiguous_with<M>(self, set: impl IntoSystemSet<M>) -> SystemSetConfig {
SystemSetConfig::new(self).ambiguous_with(set)
self.into_config().ambiguous_with(set)
}
fn ambiguous_with_all(self) -> SystemSetConfig {
SystemSetConfig::new(self).ambiguous_with_all()
self.into_config().ambiguous_with_all()
}
}
@ -187,6 +202,11 @@ impl IntoSystemSetConfig for SystemSetConfig {
self
}
fn no_default_set(mut self) -> SystemSetConfig {
self.graph_info.add_default_set = false;
self
}
fn before<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
self.graph_info.dependencies.push(Dependency::new(
DependencyKind::Before,
@ -208,6 +228,10 @@ impl IntoSystemSetConfig for SystemSetConfig {
self
}
fn on_update(self, state: impl States) -> SystemSetConfig {
self.in_set(OnUpdate(state))
}
fn ambiguous_with<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
ambiguous_with(&mut self.graph_info, Box::new(set.into_system_set()));
self
@ -229,6 +253,8 @@ pub trait IntoSystemConfig<Params>: sealed::IntoSystemConfig<Params> {
fn into_config(self) -> SystemConfig;
/// Add to `set` membership.
fn in_set(self, set: impl SystemSet) -> SystemConfig;
/// Don't add this system to the schedules's default set.
fn no_default_set(self) -> SystemConfig;
/// Run before all systems in `set`.
fn before<M>(self, set: impl IntoSystemSet<M>) -> SystemConfig;
/// Run after all systems in `set`.
@ -238,6 +264,8 @@ pub trait IntoSystemConfig<Params>: sealed::IntoSystemConfig<Params> {
/// The `Condition` will be evaluated at most once (per schedule run),
/// when the system prepares to run.
fn run_if<P>(self, condition: impl Condition<P>) -> SystemConfig;
/// Add this system to the [`OnUpdate(state)`](OnUpdate) set.
fn on_update(self, state: impl States) -> SystemConfig;
/// Suppress warnings and errors that would result from this system having ambiguities
/// (conflicting access but indeterminate order) with systems in `set`.
fn ambiguous_with<M>(self, set: impl IntoSystemSet<M>) -> SystemConfig;
@ -255,27 +283,35 @@ where
}
fn in_set(self, set: impl SystemSet) -> SystemConfig {
SystemConfig::new(Box::new(IntoSystem::into_system(self))).in_set(set)
self.into_config().in_set(set)
}
fn no_default_set(self) -> SystemConfig {
self.into_config().no_default_set()
}
fn before<M>(self, set: impl IntoSystemSet<M>) -> SystemConfig {
SystemConfig::new(Box::new(IntoSystem::into_system(self))).before(set)
self.into_config().before(set)
}
fn after<M>(self, set: impl IntoSystemSet<M>) -> SystemConfig {
SystemConfig::new(Box::new(IntoSystem::into_system(self))).after(set)
self.into_config().after(set)
}
fn run_if<P>(self, condition: impl Condition<P>) -> SystemConfig {
SystemConfig::new(Box::new(IntoSystem::into_system(self))).run_if(condition)
self.into_config().run_if(condition)
}
fn on_update(self, state: impl States) -> SystemConfig {
self.into_config().on_update(state)
}
fn ambiguous_with<M>(self, set: impl IntoSystemSet<M>) -> SystemConfig {
SystemConfig::new(Box::new(IntoSystem::into_system(self))).ambiguous_with(set)
self.into_config().ambiguous_with(set)
}
fn ambiguous_with_all(self) -> SystemConfig {
SystemConfig::new(Box::new(IntoSystem::into_system(self))).ambiguous_with_all()
self.into_config().ambiguous_with_all()
}
}
@ -285,27 +321,35 @@ impl IntoSystemConfig<()> for BoxedSystem<(), ()> {
}
fn in_set(self, set: impl SystemSet) -> SystemConfig {
SystemConfig::new(self).in_set(set)
self.into_config().in_set(set)
}
fn no_default_set(self) -> SystemConfig {
self.into_config().no_default_set()
}
fn before<M>(self, set: impl IntoSystemSet<M>) -> SystemConfig {
SystemConfig::new(self).before(set)
self.into_config().before(set)
}
fn after<M>(self, set: impl IntoSystemSet<M>) -> SystemConfig {
SystemConfig::new(self).after(set)
self.into_config().after(set)
}
fn run_if<P>(self, condition: impl Condition<P>) -> SystemConfig {
SystemConfig::new(self).run_if(condition)
self.into_config().run_if(condition)
}
fn on_update(self, state: impl States) -> SystemConfig {
self.into_config().on_update(state)
}
fn ambiguous_with<M>(self, set: impl IntoSystemSet<M>) -> SystemConfig {
SystemConfig::new(self).ambiguous_with(set)
self.into_config().ambiguous_with(set)
}
fn ambiguous_with_all(self) -> SystemConfig {
SystemConfig::new(self).ambiguous_with_all()
self.into_config().ambiguous_with_all()
}
}
@ -323,6 +367,11 @@ impl IntoSystemConfig<()> for SystemConfig {
self
}
fn no_default_set(mut self) -> SystemConfig {
self.graph_info.add_default_set = false;
self
}
fn before<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
self.graph_info.dependencies.push(Dependency::new(
DependencyKind::Before,
@ -344,6 +393,10 @@ impl IntoSystemConfig<()> for SystemConfig {
self
}
fn on_update(self, state: impl States) -> Self {
self.in_set(OnUpdate(state))
}
fn ambiguous_with<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
ambiguous_with(&mut self.graph_info, Box::new(set.into_system_set()));
self
@ -412,6 +465,11 @@ where
self.into_configs().after(set)
}
/// Add this set to the [`OnUpdate(state)`](OnUpdate) set.
fn on_update(self, state: impl States) -> SystemConfigs {
self.into_configs().on_update(state)
}
/// Suppress warnings and errors that would result from these systems having ambiguities
/// (conflicting access but indeterminate order) with systems in `set`.
fn ambiguous_with<M>(self, set: impl IntoSystemSet<M>) -> SystemConfigs {
@ -490,6 +548,10 @@ impl IntoSystemConfigs<()> for SystemConfigs {
self
}
fn on_update(self, state: impl States) -> Self {
self.in_set(OnUpdate(state))
}
fn chain(mut self) -> Self {
self.chained = true;
self

View file

@ -19,6 +19,7 @@ pub(super) trait SystemExecutor: Send + Sync {
fn kind(&self) -> ExecutorKind;
fn init(&mut self, schedule: &SystemSchedule);
fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World);
fn set_apply_final_buffers(&mut self, value: bool);
}
/// Specifies how a [`Schedule`](super::Schedule) will be run.

View file

@ -1,11 +1,11 @@
use std::panic::AssertUnwindSafe;
use std::sync::Arc;
use bevy_tasks::{ComputeTaskPool, Scope, TaskPool, ThreadExecutor};
use bevy_utils::default;
use bevy_utils::syncunsafecell::SyncUnsafeCell;
#[cfg(feature = "trace")]
use bevy_utils::tracing::{info_span, Instrument};
use std::sync::Arc;
use std::panic::AssertUnwindSafe;
use async_channel::{Receiver, Sender};
use fixedbitset::FixedBitSet;
@ -97,6 +97,8 @@ pub struct MultiThreadedExecutor {
completed_systems: FixedBitSet,
/// Systems that have run but have not had their buffers applied.
unapplied_systems: FixedBitSet,
/// Setting when true applies system buffers after all systems have run
apply_final_buffers: bool,
}
impl Default for MultiThreadedExecutor {
@ -110,6 +112,10 @@ impl SystemExecutor for MultiThreadedExecutor {
ExecutorKind::MultiThreaded
}
fn set_apply_final_buffers(&mut self, value: bool) {
self.apply_final_buffers = value;
}
fn init(&mut self, schedule: &SystemSchedule) {
// pre-allocate space
let sys_count = schedule.system_ids.len();
@ -194,19 +200,22 @@ impl SystemExecutor for MultiThreadedExecutor {
};
#[cfg(feature = "trace")]
let executor_span = info_span!("schedule_task");
let executor_span = info_span!("multithreaded executor");
#[cfg(feature = "trace")]
let executor = executor.instrument(executor_span);
scope.spawn(executor);
},
);
if self.apply_final_buffers {
// Do one final apply buffers after all systems have completed
// SAFETY: all systems have completed, and so no outstanding accesses remain
let world = unsafe { &mut *world.get() };
// Commands should be applied while on the scope's thread, not the executor's thread
apply_system_buffers(&self.unapplied_systems, systems, world);
self.unapplied_systems.clear();
debug_assert!(self.unapplied_systems.is_clear());
}
debug_assert!(self.ready_systems.is_clear());
debug_assert!(self.running_systems.is_clear());
@ -237,6 +246,7 @@ impl MultiThreadedExecutor {
skipped_systems: FixedBitSet::new(),
completed_systems: FixedBitSet::new(),
unapplied_systems: FixedBitSet::new(),
apply_final_buffers: true,
}
}
@ -313,9 +323,6 @@ impl MultiThreadedExecutor {
conditions: &mut Conditions,
world: &World,
) -> bool {
#[cfg(feature = "trace")]
let _span = info_span!("check_access", name = &*system.name()).entered();
let system_meta = &self.system_task_metadata[system_index];
if system_meta.is_exclusive && self.num_running_systems > 0 {
return false;
@ -374,9 +381,6 @@ impl MultiThreadedExecutor {
conditions: &mut Conditions,
world: &World,
) -> bool {
#[cfg(feature = "trace")]
let _span = info_span!("check_conditions", name = &*_system.name()).entered();
let mut should_run = !self.skipped_systems.contains(system_index);
for set_idx in conditions.sets_of_systems[system_index].ones() {
if self.evaluated_sets.contains(set_idx) {
@ -563,8 +567,6 @@ impl MultiThreadedExecutor {
}
fn signal_dependents(&mut self, system_index: usize) {
#[cfg(feature = "trace")]
let _span = info_span!("signal_dependents").entered();
for &dep_idx in &self.system_task_metadata[system_index].dependents {
let remaining = &mut self.num_dependencies_remaining[dep_idx];
debug_assert!(*remaining >= 1);
@ -593,8 +595,6 @@ fn apply_system_buffers(
for system_index in unapplied_systems.ones() {
// SAFETY: none of these systems are running, no other references exist
let system = unsafe { &mut *systems[system_index].get() };
#[cfg(feature = "trace")]
let _apply_buffers_span = info_span!("apply_buffers", name = &*system.name()).entered();
system.apply_buffers(world);
}
}

View file

@ -22,6 +22,10 @@ impl SystemExecutor for SimpleExecutor {
ExecutorKind::Simple
}
fn set_apply_final_buffers(&mut self, _: bool) {
// do nothing. simple executor does not do a final sync
}
fn init(&mut self, schedule: &SystemSchedule) {
let sys_count = schedule.system_ids.len();
let set_count = schedule.set_ids.len();
@ -78,8 +82,6 @@ impl SystemExecutor for SimpleExecutor {
#[cfg(feature = "trace")]
system_span.exit();
#[cfg(feature = "trace")]
let _apply_buffers_span = info_span!("apply_buffers", name = &*name).entered();
system.apply_buffers(world);
}

View file

@ -21,6 +21,8 @@ pub struct SingleThreadedExecutor {
completed_systems: FixedBitSet,
/// Systems that have run but have not had their buffers applied.
unapplied_systems: FixedBitSet,
/// Setting when true applies system buffers after all systems have run
apply_final_buffers: bool,
}
impl SystemExecutor for SingleThreadedExecutor {
@ -28,6 +30,10 @@ impl SystemExecutor for SingleThreadedExecutor {
ExecutorKind::SingleThreaded
}
fn set_apply_final_buffers(&mut self, apply_final_buffers: bool) {
self.apply_final_buffers = apply_final_buffers;
}
fn init(&mut self, schedule: &SystemSchedule) {
// pre-allocate space
let sys_count = schedule.system_ids.len();
@ -96,7 +102,9 @@ impl SystemExecutor for SingleThreadedExecutor {
}
}
if self.apply_final_buffers {
self.apply_system_buffers(schedule, world);
}
self.evaluated_sets.clear();
self.completed_systems.clear();
}
@ -108,14 +116,13 @@ impl SingleThreadedExecutor {
evaluated_sets: FixedBitSet::new(),
completed_systems: FixedBitSet::new(),
unapplied_systems: FixedBitSet::new(),
apply_final_buffers: true,
}
}
fn apply_system_buffers(&mut self, schedule: &mut SystemSchedule, world: &mut World) {
for system_index in self.unapplied_systems.ones() {
let system = &mut schedule.systems[system_index];
#[cfg(feature = "trace")]
let _apply_buffers_span = info_span!("apply_buffers", name = &*system.name()).entered();
system.apply_buffers(world);
}

View file

@ -72,6 +72,18 @@ pub(crate) struct GraphInfo {
pub(crate) sets: Vec<BoxedSystemSet>,
pub(crate) dependencies: Vec<Dependency>,
pub(crate) ambiguous_with: Ambiguity,
pub(crate) add_default_set: bool,
}
impl Default for GraphInfo {
fn default() -> Self {
GraphInfo {
sets: Vec::new(),
dependencies: Vec::new(),
ambiguous_with: Ambiguity::default(),
add_default_set: true,
}
}
}
/// Converts 2D row-major pair of indices into a 1D array index.

View file

@ -1,38 +0,0 @@
use crate::schedule_v3::*;
use crate::world::World;
/// Temporary "stageless" `App` methods.
pub trait AppExt {
/// Sets the [`Schedule`] that will be modified by default when you call `App::add_system`
/// and similar methods.
///
/// **Note:** This will create the schedule if it does not already exist.
fn set_default_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self;
/// Applies the function to the [`Schedule`] associated with `label`.
///
/// **Note:** This will create the schedule if it does not already exist.
fn edit_schedule(
&mut self,
label: impl ScheduleLabel,
f: impl FnMut(&mut Schedule),
) -> &mut Self;
/// Adds [`State<S>`] and [`NextState<S>`] resources, [`OnEnter`] and [`OnExit`] schedules
/// for each state variant, and an instance of [`apply_state_transition::<S>`] in
/// \<insert-`bevy_core`-set-name\> so that transitions happen before `Update`.
fn add_state<S: States>(&mut self) -> &mut Self;
}
/// Temporary "stageless" [`World`] methods.
pub trait WorldExt {
/// Runs the [`Schedule`] associated with `label`.
fn run_schedule(&mut self, label: impl ScheduleLabel);
}
impl WorldExt for World {
fn run_schedule(&mut self, label: impl ScheduleLabel) {
if let Some(mut schedule) = self.resource_mut::<Schedules>().remove(&label) {
schedule.run(self);
self.resource_mut::<Schedules>().insert(label, schedule);
}
}
}

View file

@ -2,7 +2,6 @@ mod condition;
mod config;
mod executor;
mod graph_utils;
mod migration;
mod schedule;
mod set;
mod state;
@ -11,7 +10,6 @@ pub use self::condition::*;
pub use self::config::*;
pub use self::executor::*;
use self::graph_utils::*;
pub use self::migration::*;
pub use self::schedule::*;
pub use self::set::*;
pub use self::state::*;
@ -164,9 +162,11 @@ mod tests {
world.init_resource::<SystemOrder>();
schedule.add_system(named_exclusive_system);
schedule.add_system(make_exclusive_system(1).before(named_exclusive_system));
schedule.add_system(make_exclusive_system(0).after(named_exclusive_system));
schedule.add_systems((
named_exclusive_system,
make_exclusive_system(1).before(named_exclusive_system),
make_exclusive_system(0).after(named_exclusive_system),
));
schedule.run(&mut world);
assert_eq!(world.resource::<SystemOrder>().0, vec![1, u32::MAX, 0]);
@ -599,6 +599,31 @@ mod tests {
));
}
#[test]
fn sets_have_order_but_intersect() {
let mut world = World::new();
let mut schedule = Schedule::new();
fn foo() {}
// Add `foo` to both `A` and `C`.
schedule.add_system(foo.in_set(TestSet::A).in_set(TestSet::C));
// 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(_, _))
));
}
#[test]
fn ambiguity() {
#[derive(Resource)]

View file

@ -9,7 +9,7 @@ use bevy_utils::tracing::info_span;
use bevy_utils::{
petgraph::{algo::tarjan_scc, prelude::*},
thiserror::Error,
tracing::{error, info, warn},
tracing::{error, warn},
HashMap, HashSet,
};
@ -17,7 +17,7 @@ use fixedbitset::FixedBitSet;
use crate::{
self as bevy_ecs,
component::ComponentId,
component::{ComponentId, Components},
schedule_v3::*,
system::{BoxedSystem, Resource},
world::World,
@ -42,7 +42,7 @@ impl Schedules {
/// If the map already had an entry for `label`, `schedule` is inserted,
/// and the old schedule is returned. Otherwise, `None` is returned.
pub fn insert(&mut self, label: impl ScheduleLabel, schedule: Schedule) -> Option<Schedule> {
let label: Box<dyn ScheduleLabel> = Box::new(label);
let label = label.dyn_clone();
if self.inner.contains_key(&label) {
warn!("schedule with label {:?} already exists", label);
}
@ -57,6 +57,22 @@ impl Schedules {
self.inner.remove(label)
}
/// Removes the (schedule, label) pair corresponding to the `label` from the map, returning it if it existed.
pub fn remove_entry(
&mut self,
label: &dyn ScheduleLabel,
) -> Option<(Box<dyn ScheduleLabel>, Schedule)> {
if !self.inner.contains_key(label) {
warn!("schedule with label {:?} not found", label);
}
self.inner.remove_entry(label)
}
/// Does a schedule with the provided label already exist?
pub fn contains(&self, label: &dyn ScheduleLabel) -> bool {
self.inner.contains_key(label)
}
/// Returns a reference to the schedule associated with `label`, if it exists.
pub fn get(&self, label: &dyn ScheduleLabel) -> Option<&Schedule> {
self.inner.get(label)
@ -123,25 +139,18 @@ impl Schedule {
self
}
/// Configure a system set in this schedule.
/// Configures a system set in this schedule, adding it if it does not exist.
pub fn configure_set(&mut self, set: impl IntoSystemSetConfig) -> &mut Self {
self.graph.configure_set(set);
self
}
/// Configure a collection of system sets in this schedule.
/// Configures a collection of system sets in this schedule, adding them if they does not exist.
pub fn configure_sets(&mut self, sets: impl IntoSystemSetConfigs) -> &mut Self {
self.graph.configure_sets(sets);
self
}
/// Changes the system set that new systems and system sets will join by default
/// if they aren't already part of one.
pub fn set_default_set(&mut self, set: impl SystemSet) -> &mut Self {
self.graph.set_default_set(set);
self
}
/// Changes miscellaneous build settings.
pub fn set_build_settings(&mut self, settings: ScheduleBuildSettings) -> &mut Self {
self.graph.settings = settings;
@ -166,13 +175,19 @@ impl Schedule {
self
}
/// Set whether the schedule applies buffers on final time or not. This is a catchall
/// incase a system uses commands but was not explicitly ordered after a
/// [`apply_system_buffers`](crate::prelude::apply_system_buffers). By default this
/// setting is true, but may be disabled if needed.
pub fn set_apply_final_buffers(&mut self, apply_final_buffers: bool) -> &mut Self {
self.executor.set_apply_final_buffers(apply_final_buffers);
self
}
/// Runs all systems in this schedule on the `world`, using its current execution strategy.
pub fn run(&mut self, world: &mut World) {
world.check_change_ticks();
self.initialize(world).unwrap();
// TODO: label
#[cfg(feature = "trace")]
let _span = info_span!("schedule").entered();
self.executor.run(&mut self.executable, world);
}
@ -181,7 +196,8 @@ impl Schedule {
pub fn initialize(&mut self, world: &mut World) -> Result<(), ScheduleBuildError> {
if self.graph.changed {
self.graph.initialize(world);
self.graph.update_schedule(&mut self.executable)?;
self.graph
.update_schedule(&mut self.executable, world.components())?;
self.graph.changed = false;
self.executor_initialized = false;
}
@ -214,6 +230,20 @@ impl Schedule {
}
}
}
/// Directly applies any accumulated system buffers (like [`Commands`](crate::prelude::Commands)) to the `world`.
///
/// Like always, system buffers are applied in the "topological sort order" of the schedule graph.
/// As a result, buffers from one system are only guaranteed to be applied before those of other systems
/// if there is an explicit system ordering between the two systems.
///
/// This is used in rendering to extract data from the main world, storing the data in system buffers,
/// before applying their buffers in a different world.
pub fn apply_system_buffers(&mut self, world: &mut World) {
for system in &mut self.executable.systems {
system.apply_buffers(world);
}
}
}
/// A directed acylic graph structure.
@ -237,16 +267,11 @@ impl Dag {
/// A [`SystemSet`] with metadata, stored in a [`ScheduleGraph`].
struct SystemSetNode {
inner: BoxedSystemSet,
/// `true` if this system set was modified with `configure_set`
configured: bool,
}
impl SystemSetNode {
pub fn new(set: BoxedSystemSet) -> Self {
Self {
inner: set,
configured: false,
}
Self { inner: set }
}
pub fn name(&self) -> String {
@ -273,7 +298,6 @@ struct ScheduleGraph {
ambiguous_with: UnGraphMap<NodeId, ()>,
ambiguous_with_flattened: UnGraphMap<NodeId, ()>,
ambiguous_with_all: HashSet<NodeId>,
default_set: Option<BoxedSystemSet>,
changed: bool,
settings: ScheduleBuildSettings,
}
@ -293,20 +317,11 @@ impl ScheduleGraph {
ambiguous_with: UnGraphMap::new(),
ambiguous_with_flattened: UnGraphMap::new(),
ambiguous_with_all: HashSet::new(),
default_set: None,
changed: false,
settings: default(),
}
}
fn set_default_set(&mut self, set: impl SystemSet) {
assert!(
!set.is_system_type(),
"adding arbitrary systems to a system type set is not allowed"
);
self.default_set = Some(Box::new(set));
}
fn add_systems<P>(&mut self, systems: impl IntoSystemConfigs<P>) {
let SystemConfigs { systems, chained } = systems.into_configs();
let mut system_iter = systems.into_iter();
@ -335,20 +350,12 @@ impl ScheduleGraph {
) -> Result<NodeId, ScheduleBuildError> {
let SystemConfig {
system,
mut graph_info,
graph_info,
conditions,
} = system.into_config();
let id = NodeId::System(self.systems.len());
if let [single_set] = graph_info.sets.as_slice() {
if single_set.is_system_type() {
if let Some(default) = self.default_set.as_ref() {
graph_info.sets.push(default.dyn_clone());
}
}
}
// graph updates are immediate
self.update_graphs(id, graph_info)?;
@ -388,7 +395,7 @@ impl ScheduleGraph {
) -> Result<NodeId, ScheduleBuildError> {
let SystemSetConfig {
set,
mut graph_info,
graph_info,
mut conditions,
} = set.into_config();
@ -397,18 +404,6 @@ impl ScheduleGraph {
None => self.add_set(set.dyn_clone()),
};
let meta = &mut self.system_sets[id.index()];
let already_configured = std::mem::replace(&mut meta.configured, true);
// a system set can be configured multiple times, so this "default check"
// should only happen the first time `configure_set` is called on it
if !already_configured && graph_info.sets.is_empty() {
if let Some(default) = self.default_set.as_ref() {
info!("adding system set `{:?}` to default: `{:?}`", set, default);
graph_info.sets.push(default.dyn_clone());
}
}
// graph updates are immediate
self.update_graphs(id, graph_info)?;
@ -438,7 +433,8 @@ impl ScheduleGraph {
match self.system_set_ids.get(set) {
Some(set_id) => {
if id == set_id {
return Err(ScheduleBuildError::HierarchyLoop(set.dyn_clone()));
let string = format!("{:?}", &set);
return Err(ScheduleBuildError::HierarchyLoop(string));
}
}
None => {
@ -459,7 +455,8 @@ impl ScheduleGraph {
match self.system_set_ids.get(set) {
Some(set_id) => {
if id == set_id {
return Err(ScheduleBuildError::DependencyLoop(set.dyn_clone()));
let string = format!("{:?}", &set);
return Err(ScheduleBuildError::DependencyLoop(string));
}
}
None => {
@ -492,18 +489,17 @@ impl ScheduleGraph {
sets,
dependencies,
ambiguous_with,
..
} = graph_info;
if !self.hierarchy.graph.contains_node(id) {
self.hierarchy.graph.add_node(id);
}
self.dependency.graph.add_node(id);
for set in sets.into_iter().map(|set| self.system_set_ids[&set]) {
self.hierarchy.graph.add_edge(set, id, ());
}
if !self.dependency.graph.contains_node(id) {
self.dependency.graph.add_node(id);
// ensure set also appears in dependency graph
self.dependency.graph.add_node(set);
}
for (kind, set) in dependencies
@ -515,6 +511,9 @@ impl ScheduleGraph {
DependencyKind::After => (set, id),
};
self.dependency.graph.add_edge(lhs, rhs, ());
// ensure set also appears in hierarchy graph
self.hierarchy.graph.add_node(set);
}
match ambiguous_with {
@ -557,7 +556,10 @@ impl ScheduleGraph {
}
}
fn build_schedule(&mut self) -> Result<SystemSchedule, ScheduleBuildError> {
fn build_schedule(
&mut self,
components: &Components,
) -> Result<SystemSchedule, ScheduleBuildError> {
// check hierarchy for cycles
let hier_scc = tarjan_scc(&self.hierarchy.graph);
if self.contains_cycles(&hier_scc) {
@ -568,7 +570,9 @@ impl ScheduleGraph {
self.hierarchy.topsort = hier_scc.into_iter().flatten().rev().collect::<Vec<_>>();
let hier_results = check_graph(&self.hierarchy.graph, &self.hierarchy.topsort);
if self.contains_hierarchy_conflicts(&hier_results.transitive_edges) {
if self.settings.hierarchy_detection != LogLevel::Ignore
&& self.contains_hierarchy_conflicts(&hier_results.transitive_edges)
{
self.report_hierarchy_conflicts(&hier_results.transitive_edges);
if matches!(self.settings.hierarchy_detection, LogLevel::Error) {
return Err(ScheduleBuildError::HierarchyRedundancy);
@ -587,7 +591,7 @@ impl ScheduleGraph {
self.dependency.topsort = dep_scc.into_iter().flatten().rev().collect::<Vec<_>>();
// nodes can have dependent XOR hierarchical relationship
// check for systems or system sets depending on sets they belong to
let dep_results = check_graph(&self.dependency.graph, &self.dependency.topsort);
for &(a, b) in dep_results.connected.iter() {
if hier_results.connected.contains(&(a, b)) || hier_results.connected.contains(&(b, a))
@ -598,93 +602,114 @@ impl ScheduleGraph {
}
}
// map system sets to all their member systems
let mut systems_in_sets = HashMap::with_capacity(self.system_sets.len());
// iterate in reverse topological order (bottom-up)
// map all system sets to their systems
// go in reverse topological order (bottom-up) for efficiency
let mut set_systems: HashMap<NodeId, Vec<NodeId>> =
HashMap::with_capacity(self.system_sets.len());
let mut set_system_bitsets = HashMap::with_capacity(self.system_sets.len());
for &id in self.hierarchy.topsort.iter().rev() {
if id.is_system() {
continue;
}
let set = id;
systems_in_sets.insert(set, Vec::new());
let mut systems = Vec::new();
let mut system_bitset = FixedBitSet::with_capacity(self.systems.len());
for child in self
.hierarchy
.graph
.neighbors_directed(set, Direction::Outgoing)
.neighbors_directed(id, Direction::Outgoing)
{
match child {
NodeId::System(_) => {
systems_in_sets.get_mut(&set).unwrap().push(child);
systems.push(child);
system_bitset.insert(child.index());
}
NodeId::Set(_) => {
let [sys, child_sys] =
systems_in_sets.get_many_mut([&set, &child]).unwrap();
sys.extend_from_slice(child_sys);
}
let child_systems = set_systems.get(&child).unwrap();
let child_system_bitset = set_system_bitsets.get(&child).unwrap();
systems.extend_from_slice(child_systems);
system_bitset.union_with(child_system_bitset);
}
}
}
// can't depend on or be ambiguous with system type sets that have many instances
for (&set, systems) in systems_in_sets.iter() {
let node = &self.system_sets[set.index()];
if node.is_system_type() {
let ambiguities = self.ambiguous_with.edges(set).count();
let mut dependencies = 0;
dependencies += self
set_systems.insert(id, systems);
set_system_bitsets.insert(id, system_bitset);
}
// check that there is no ordering between system sets that intersect
for (a, b) in dep_results.connected.iter() {
if !(a.is_set() && b.is_set()) {
continue;
}
let a_systems = set_system_bitsets.get(a).unwrap();
let b_systems = set_system_bitsets.get(b).unwrap();
if !(a_systems.is_disjoint(b_systems)) {
return Err(ScheduleBuildError::SetsHaveOrderButIntersect(
self.get_node_name(a),
self.get_node_name(b),
));
}
}
// check that there are no edges to system-type sets that have multiple instances
for (&id, systems) in set_systems.iter() {
let set = &self.system_sets[id.index()];
if set.is_system_type() {
let instances = systems.len();
let ambiguous_with = self.ambiguous_with.edges(id);
let before = self
.dependency
.graph
.edges_directed(set, Direction::Incoming)
.count();
dependencies += self
.edges_directed(id, Direction::Incoming);
let after = self
.dependency
.graph
.edges_directed(set, Direction::Outgoing)
.count();
if systems.len() > 1 && (ambiguities > 0 || dependencies > 0) {
.edges_directed(id, Direction::Outgoing);
let relations = before.count() + after.count() + ambiguous_with.count();
if instances > 1 && relations > 0 {
return Err(ScheduleBuildError::SystemTypeSetAmbiguity(
node.inner.dyn_clone(),
self.get_node_name(&id),
));
}
}
}
// flatten dependency graph
let mut dependency_flattened = DiGraphMap::new();
for id in self.dependency.graph.nodes() {
if id.is_system() {
dependency_flattened.add_node(id);
// flatten: combine `in_set` with `before` and `after` information
// have to do it like this to preserve transitivity
let mut dependency_flattened = self.dependency.graph.clone();
let mut temp = Vec::new();
for (&set, systems) in set_systems.iter() {
if systems.is_empty() {
for a in dependency_flattened.neighbors_directed(set, Direction::Incoming) {
for b in dependency_flattened.neighbors_directed(set, Direction::Outgoing) {
temp.push((a, b));
}
}
} else {
for a in dependency_flattened.neighbors_directed(set, Direction::Incoming) {
for &sys in systems {
temp.push((a, sys));
}
}
for (lhs, rhs, _) in self.dependency.graph.all_edges() {
match (lhs, rhs) {
(NodeId::System(_), NodeId::System(_)) => {
dependency_flattened.add_edge(lhs, rhs, ());
}
(NodeId::Set(_), NodeId::System(_)) => {
for &lhs_ in &systems_in_sets[&lhs] {
dependency_flattened.add_edge(lhs_, rhs, ());
}
}
(NodeId::System(_), NodeId::Set(_)) => {
for &rhs_ in &systems_in_sets[&rhs] {
dependency_flattened.add_edge(lhs, rhs_, ());
}
}
(NodeId::Set(_), NodeId::Set(_)) => {
for &lhs_ in &systems_in_sets[&lhs] {
for &rhs_ in &systems_in_sets[&rhs] {
dependency_flattened.add_edge(lhs_, rhs_, ());
}
}
for b in dependency_flattened.neighbors_directed(set, Direction::Outgoing) {
for &sys in systems {
temp.push((sys, b));
}
}
}
// check flattened dependencies for cycles
dependency_flattened.remove_node(set);
for (a, b) in temp.drain(..) {
dependency_flattened.add_edge(a, b, ());
}
}
// topsort
let flat_scc = tarjan_scc(&dependency_flattened);
if self.contains_cycles(&flat_scc) {
self.report_cycles(&flat_scc);
@ -703,7 +728,7 @@ impl ScheduleGraph {
// remove redundant edges
self.dependency_flattened.graph = flat_results.transitive_reduction;
// flatten allowed ambiguities
// flatten: combine `in_set` with `ambiguous_with` information
let mut ambiguous_with_flattened = UnGraphMap::new();
for (lhs, rhs, _) in self.ambiguous_with.all_edges() {
match (lhs, rhs) {
@ -711,18 +736,18 @@ impl ScheduleGraph {
ambiguous_with_flattened.add_edge(lhs, rhs, ());
}
(NodeId::Set(_), NodeId::System(_)) => {
for &lhs_ in &systems_in_sets[&lhs] {
for &lhs_ in set_systems.get(&lhs).unwrap() {
ambiguous_with_flattened.add_edge(lhs_, rhs, ());
}
}
(NodeId::System(_), NodeId::Set(_)) => {
for &rhs_ in &systems_in_sets[&rhs] {
for &rhs_ in set_systems.get(&rhs).unwrap() {
ambiguous_with_flattened.add_edge(lhs, rhs_, ());
}
}
(NodeId::Set(_), NodeId::Set(_)) => {
for &lhs_ in &systems_in_sets[&lhs] {
for &rhs_ in &systems_in_sets[&rhs] {
for &lhs_ in set_systems.get(&lhs).unwrap() {
for &rhs_ in set_systems.get(&rhs).unwrap() {
ambiguous_with_flattened.add_edge(lhs_, rhs_, ());
}
}
@ -756,8 +781,10 @@ impl ScheduleGraph {
}
}
if self.contains_conflicts(&conflicting_systems) {
self.report_conflicts(&conflicting_systems);
if self.settings.ambiguity_detection != LogLevel::Ignore
&& self.contains_conflicts(&conflicting_systems)
{
self.report_conflicts(&conflicting_systems, components);
if matches!(self.settings.ambiguity_detection, LogLevel::Error) {
return Err(ScheduleBuildError::Ambiguity);
}
@ -863,7 +890,11 @@ impl ScheduleGraph {
})
}
fn update_schedule(&mut self, schedule: &mut SystemSchedule) -> Result<(), ScheduleBuildError> {
fn update_schedule(
&mut self,
schedule: &mut SystemSchedule,
components: &Components,
) -> Result<(), ScheduleBuildError> {
if !self.uninit.is_empty() {
return Err(ScheduleBuildError::Uninitialized);
}
@ -887,7 +918,7 @@ impl ScheduleGraph {
self.system_set_conditions[id.index()] = Some(conditions);
}
*schedule = self.build_schedule()?;
*schedule = self.build_schedule(components)?;
// move systems into new schedule
for &id in &schedule.system_ids {
@ -993,21 +1024,33 @@ impl ScheduleGraph {
true
}
fn report_conflicts(&self, ambiguities: &[(NodeId, NodeId, Vec<ComponentId>)]) {
let mut string = String::from(
"Some systems with conflicting access have indeterminate execution order. \
fn report_conflicts(
&self,
ambiguities: &[(NodeId, NodeId, Vec<ComponentId>)],
components: &Components,
) {
let n_ambiguities = ambiguities.len();
let mut string = format!(
"{n_ambiguities} pairs of systems with conflicting data access have indeterminate execution order. \
Consider adding `before`, `after`, or `ambiguous_with` relationships between these:\n",
);
for (system_a, system_b, conflicts) in ambiguities {
debug_assert!(system_a.is_system());
debug_assert!(system_b.is_system());
let name_a = self.get_node_name(system_a);
let name_b = self.get_node_name(system_b);
debug_assert!(system_a.is_system(), "{name_a} is not a system.");
debug_assert!(system_b.is_system(), "{name_b} is not a system.");
writeln!(string, " -- {name_a} and {name_b}").unwrap();
if !conflicts.is_empty() {
writeln!(string, " conflict on: {conflicts:?}").unwrap();
let conflict_names: Vec<_> = conflicts
.iter()
.map(|id| components.get_name(*id).unwrap())
.collect();
writeln!(string, " conflict on: {conflict_names:?}").unwrap();
} else {
// one or both systems must be exclusive
let world = std::any::type_name::<World>();
@ -1025,7 +1068,7 @@ impl ScheduleGraph {
pub enum ScheduleBuildError {
/// A system set contains itself.
#[error("`{0:?}` contains itself.")]
HierarchyLoop(BoxedSystemSet),
HierarchyLoop(String),
/// The hierarchy of system sets contains a cycle.
#[error("System set hierarchy contains cycle(s).")]
HierarchyCycle,
@ -1036,16 +1079,19 @@ pub enum ScheduleBuildError {
HierarchyRedundancy,
/// A system (set) has been told to run before itself.
#[error("`{0:?}` depends on itself.")]
DependencyLoop(BoxedSystemSet),
DependencyLoop(String),
/// The dependency graph contains a cycle.
#[error("System dependencies contain cycle(s).")]
DependencyCycle,
/// Tried to order a system (set) relative to a system set it belongs to.
#[error("`{0:?}` and `{1:?}` 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.")]
CrossDependency(String, String),
/// Tried to order system sets that share systems.
#[error("`{0:?}` and `{1:?}` have a `before`-`after` relationship (which may be transitive) but share systems.")]
SetsHaveOrderButIntersect(String, String),
/// Tried to order a system (set) relative to all instances of some system function.
#[error("Tried to order against `fn {0:?}` in a schedule that has more than one `{0:?}` instance. `fn {0:?}` is a `SystemTypeSet` and cannot be used for ordering if ambiguous. Use a different set without this restriction.")]
SystemTypeSetAmbiguity(BoxedSystemSet),
SystemTypeSetAmbiguity(String),
/// Systems with conflicting access have indeterminate run order.
///
/// This error is disabled by default, but can be opted-in using [`ScheduleBuildSettings`].
@ -1057,7 +1103,10 @@ pub enum ScheduleBuildError {
}
/// Specifies how schedule construction should respond to detecting a certain kind of issue.
#[derive(Debug, Clone, PartialEq)]
pub enum LogLevel {
/// Occurences are completely ignored.
Ignore,
/// Occurrences are logged only.
Warn,
/// Occurrences are logged and result in errors.
@ -1065,6 +1114,7 @@ pub enum LogLevel {
}
/// Specifies miscellaneous settings for schedule construction.
#[derive(Clone, Debug)]
pub struct ScheduleBuildSettings {
ambiguity_detection: LogLevel,
hierarchy_detection: LogLevel,
@ -1079,7 +1129,7 @@ impl Default for ScheduleBuildSettings {
impl ScheduleBuildSettings {
pub const fn new() -> Self {
Self {
ambiguity_detection: LogLevel::Warn,
ambiguity_detection: LogLevel::Ignore,
hierarchy_detection: LogLevel::Warn,
}
}

View file

@ -23,7 +23,7 @@ pub trait SystemSet: DynHash + Debug + Send + Sync + 'static {
false
}
#[doc(hidden)]
/// Creates a boxed clone of the label corresponding to this system set.
fn dyn_clone(&self) -> Box<dyn SystemSet>;
}

View file

@ -3,16 +3,50 @@ use std::hash::Hash;
use std::mem;
use crate as bevy_ecs;
use crate::schedule_v3::{ScheduleLabel, SystemSet, WorldExt};
use crate::schedule_v3::{ScheduleLabel, SystemSet};
use crate::system::Resource;
use crate::world::World;
/// Types that can define states in a finite-state machine.
pub trait States: 'static + Send + Sync + Clone + PartialEq + Eq + Hash + Debug {
/// Types that can define world-wide states in a finite-state machine.
///
/// The [`Default`] trait defines the starting state.
/// Multiple states can be defined for the same world,
/// allowing you to classify the state of the world across orthogonal dimensions.
/// You can access the current state of type `T` with the [`State<T>`] resource,
/// and the queued state with the [`NextState<T>`] resource.
///
/// State transitions typically occur in the [`OnEnter<T::Variant>`] and [`OnExit<T:Varaitn>`] schedules,
/// which can be run via the [`apply_state_transition::<T>`] system.
/// Systems that run each frame in various states are typically stored in the main schedule,
/// and are conventionally part of the [`OnUpdate(T::Variant)`] system set.
///
/// # Example
///
/// ```rust
/// use bevy_ecs::prelude::States;
///
/// #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
/// enum GameState {
/// #[default]
/// MainMenu,
/// SettingsMenu,
/// InGame,
/// }
///
/// impl States for GameState {
/// type Iter = std::array::IntoIter<GameState, 3>;
///
/// fn variants() -> Self::Iter {
/// [GameState::MainMenu, GameState::SettingsMenu, GameState::InGame].into_iter()
/// }
/// }
///
/// ```
pub trait States: 'static + Send + Sync + Clone + PartialEq + Eq + Hash + Debug + Default {
type Iter: Iterator<Item = Self>;
/// Returns an iterator over all the state variants.
fn states() -> Self::Iter;
fn variants() -> Self::Iter;
}
/// The label of a [`Schedule`](super::Schedule) that runs whenever [`State<S>`]
@ -25,9 +59,9 @@ pub struct OnEnter<S: States>(pub S);
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct OnExit<S: States>(pub S);
/// A [`SystemSet`] that will run within \<insert-`bevy_core`-set-name\> when this state is active.
/// A [`SystemSet`] that will run within `CoreSet::StateTransitions` when this state is active.
///
/// This is provided for convenience. A more general [`state_equals`](super::state_equals)
/// This is provided for convenience. A more general [`state_equals`](crate::schedule_v3::common_conditions::state_equals)
/// [condition](super::Condition) also exists for systems that need to run elsewhere.
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
pub struct OnUpdate<S: States>(pub S);
@ -38,15 +72,31 @@ pub struct OnUpdate<S: States>(pub S);
/// The current state value can be accessed through this resource. To *change* the state,
/// queue a transition in the [`NextState<S>`] resource, and it will be applied by the next
/// [`apply_state_transition::<S>`] system.
#[derive(Resource)]
///
/// The starting state is defined via the [`Default`] implementation for `S`.
#[derive(Resource, Default)]
pub struct State<S: States>(pub S);
/// The next state of [`State<S>`].
///
/// To queue a transition, just set the contained value to `Some(next_state)`.
#[derive(Resource)]
/// Note that these transitions can be overriden by other systems:
/// only the actual value of this resource at the time of [`apply_state_transition`] matters.
#[derive(Resource, Default)]
pub struct NextState<S: States>(pub Option<S>);
impl<S: States> NextState<S> {
/// Tentatively set a planned state transition to `Some(state)`.
pub fn set(&mut self, state: S) {
self.0 = Some(state);
}
}
/// Run the enter schedule for the current state
pub fn run_enter_schedule<S: States>(world: &mut World) {
world.run_schedule(OnEnter(world.resource::<State<S>>().0.clone()));
}
/// If a new state is queued in [`NextState<S>`], this system:
/// - Takes the new state value from [`NextState<S>`] and updates [`State<S>`].
/// - Runs the [`OnExit(exited_state)`] schedule.

View file

@ -48,10 +48,11 @@ pub trait Command: Send + 'static {
///
/// Since each command requires exclusive access to the `World`,
/// all queued commands are automatically applied in sequence
/// only after each system in a [stage] has completed.
/// when the [`apply_system_buffers`] system runs.
///
/// The command queue of a system can also be manually applied
/// The command queue of an individual system can also be manually applied
/// by calling [`System::apply_buffers`].
/// Similarly, the command queue of a schedule can be manually applied via [`Schedule::apply_system_buffers`].
///
/// Each command can be used to modify the [`World`] in arbitrary ways:
/// * spawning or despawning entities
@ -61,7 +62,7 @@ pub trait Command: Send + 'static {
///
/// # Usage
///
/// Add `mut commands: Commands` as a function argument to your system to get a copy of this struct that will be applied at the end of the current stage.
/// Add `mut commands: Commands` as a function argument to your system to get a copy of this struct that will be applied the next time a copy of [`apply_system_buffers`] runs.
/// Commands are almost always used as a [`SystemParam`](crate::system::SystemParam).
///
/// ```
@ -93,8 +94,9 @@ pub trait Command: Send + 'static {
/// # }
/// ```
///
/// [stage]: crate::schedule::SystemStage
/// [`System::apply_buffers`]: crate::system::System::apply_buffers
/// [`apply_system_buffers`]: crate::schedule_v3::apply_system_buffers
/// [`Schedule::apply_system_buffers`]: crate::schedule_v3::Schedule::apply_system_buffers
pub struct Commands<'w, 's> {
queue: &'s mut CommandQueue,
entities: &'w Entities,
@ -565,11 +567,13 @@ impl<'w, 's> Commands<'w, 's> {
/// # let mut world = World::new();
/// # world.init_resource::<Counter>();
/// #
/// # let mut setup_stage = SystemStage::single_threaded().with_system(setup);
/// # let mut assert_stage = SystemStage::single_threaded().with_system(assert_names);
/// # let mut setup_schedule = Schedule::new();
/// # setup_schedule.add_system(setup);
/// # let mut assert_schedule = Schedule::new();
/// # assert_schedule.add_system(assert_names);
/// #
/// # setup_stage.run(&mut world);
/// # assert_stage.run(&mut world);
/// # setup_schedule.run(&mut world);
/// # assert_schedule.run(&mut world);
///
/// fn setup(mut commands: Commands) {
/// commands.spawn_empty().add(CountName);

View file

@ -3,10 +3,9 @@ use crate::{
change_detection::MAX_CHANGE_AGE,
component::ComponentId,
query::Access,
schedule::{SystemLabel, SystemLabelId},
system::{
check_system_change_tick, AsSystemLabel, ExclusiveSystemParam, ExclusiveSystemParamItem,
In, InputMarker, IntoSystem, System, SystemMeta, SystemTypeIdLabel,
check_system_change_tick, ExclusiveSystemParam, ExclusiveSystemParamItem, In, InputMarker,
IntoSystem, System, SystemMeta,
},
world::{World, WorldId},
};
@ -156,28 +155,12 @@ where
);
}
fn default_labels(&self) -> Vec<SystemLabelId> {
vec![self.func.as_system_label().as_label()]
}
fn default_system_sets(&self) -> Vec<Box<dyn crate::schedule_v3::SystemSet>> {
let set = crate::schedule_v3::SystemTypeSet::<F>::new();
vec![Box::new(set)]
}
}
impl<In, Out, Param, Marker, T> AsSystemLabel<(In, Out, Param, Marker, IsExclusiveFunctionSystem)>
for T
where
Param: ExclusiveSystemParam,
T: ExclusiveSystemParamFunction<In, Out, Param, Marker>,
{
#[inline]
fn as_system_label(&self) -> SystemLabelId {
SystemTypeIdLabel::<T>(PhantomData).as_label()
}
}
/// A trait implemented for all exclusive system functions that can be used as [`System`]s.
///
/// This trait can be useful for making your own systems which accept other systems,

View file

@ -4,12 +4,11 @@ use crate::{
component::ComponentId,
prelude::FromWorld,
query::{Access, FilteredAccessSet},
schedule::{SystemLabel, SystemLabelId},
system::{check_system_change_tick, ReadOnlySystemParam, System, SystemParam, SystemParamItem},
world::{World, WorldId},
};
use bevy_ecs_macros::all_tuples;
use std::{any::TypeId, borrow::Cow, fmt::Debug, marker::PhantomData};
use std::{any::TypeId, borrow::Cow, marker::PhantomData};
/// The metadata of a [`System`].
#[derive(Clone)]
@ -523,41 +522,12 @@ where
);
}
fn default_labels(&self) -> Vec<SystemLabelId> {
vec![self.func.as_system_label().as_label()]
}
fn default_system_sets(&self) -> Vec<Box<dyn crate::schedule_v3::SystemSet>> {
let set = crate::schedule_v3::SystemTypeSet::<F>::new();
vec![Box::new(set)]
}
}
/// A [`SystemLabel`] that was automatically generated for a system on the basis of its `TypeId`.
pub struct SystemTypeIdLabel<T: 'static>(pub(crate) PhantomData<fn() -> T>);
impl<T: 'static> SystemLabel for SystemTypeIdLabel<T> {
#[inline]
fn as_str(&self) -> &'static str {
std::any::type_name::<T>()
}
}
impl<T> Debug for SystemTypeIdLabel<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("SystemTypeIdLabel")
.field(&std::any::type_name::<T>())
.finish()
}
}
impl<T> Clone for SystemTypeIdLabel<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for SystemTypeIdLabel<T> {}
/// A trait implemented for all functions that can be used as [`System`]s.
///
/// This trait can be useful for making your own systems which accept other systems,
@ -680,25 +650,3 @@ macro_rules! impl_system_function {
// Note that we rely on the highest impl to be <= the highest order of the tuple impls
// of `SystemParam` created.
all_tuples!(impl_system_function, 0, 16, F);
/// Used to implicitly convert systems to their default labels. For example, it will convert
/// "system functions" to their [`SystemTypeIdLabel`].
pub trait AsSystemLabel<Marker> {
fn as_system_label(&self) -> SystemLabelId;
}
impl<In, Out, Param: SystemParam, Marker, T: SystemParamFunction<In, Out, Param, Marker>>
AsSystemLabel<(In, Out, Param, Marker)> for T
{
#[inline]
fn as_system_label(&self) -> SystemLabelId {
SystemTypeIdLabel::<T>(PhantomData).as_label()
}
}
impl<T: SystemLabel> AsSystemLabel<()> for T {
#[inline]
fn as_system_label(&self) -> SystemLabelId {
self.as_label()
}
}

View file

@ -1,8 +1,8 @@
//! Tools for controlling behavior in an ECS application.
//!
//! Systems define how an ECS based application behaves. They have to be registered to a
//! [`SystemStage`](crate::schedule::SystemStage) to be able to run. A system is usually
//! written as a normal function that will be automatically converted into a system.
//! Systems define how an ECS based application behaves.
//! Systems are added to a [`Schedule`](crate::schedule_v3::Schedule), which is then run.
//! A system is usually written as a normal function, which is automatically converted into a system.
//!
//! System functions can have parameters, through which one can query and mutate Bevy ECS state.
//! Only types that implement [`SystemParam`] can be used, automatically fetching data from
@ -36,22 +36,26 @@
//!
//! # System ordering
//!
//! While the execution of systems is usually parallel and not deterministic, there are two
//! ways to determine a certain degree of execution order:
//! By default, the execution of systems is parallel and not deterministic.
//! Not all systems can run together: if a system mutably accesses data,
//! no other system that reads or writes that data can be run at the same time.
//! These systems are said to be **incompatible**.
//!
//! - **System Stages:** They determine hard execution synchronization boundaries inside of
//! which systems run in parallel by default.
//! - **Labels:** Systems may be ordered within a stage using the methods `.before()` and `.after()`,
//! which order systems based on their [`SystemLabel`]s. Each system is implicitly labeled with
//! its `fn` type, and custom labels may be added by calling `.label()`.
//! The relative order in which incompatible systems are run matters.
//! When this is not specified, a **system order ambiguity** exists in your schedule.
//! You can **explicitly order** systems:
//!
//! [`SystemLabel`]: crate::schedule::SystemLabel
//! - by calling the `.before(this_system)` or `.after(that_system)` methods when adding them to your schedule
//! - by adding them to a [`SystemSet`], and then using `.configure_set(ThisSet.before(ThatSet))` syntax to configure many systems at once
//! - through the use of `.add_systems((system_a, system_b, system_c).chain())`
//!
//! [`SystemSet`]: crate::schedule_v3::SystemSet
//!
//! ## Example
//!
//! ```
//! # use bevy_ecs::prelude::*;
//! # let mut app = SystemStage::single_threaded();
//! # let mut app = Schedule::new();
//! // Prints "Hello, World!" each frame.
//! app
//! .add_system(print_first.before(print_mid))
@ -137,10 +141,10 @@ mod tests {
change_detection::DetectChanges,
component::{Component, Components},
entity::{Entities, Entity},
prelude::{AnyOf, StageLabel},
prelude::AnyOf,
query::{Added, Changed, Or, With, Without},
removal_detection::RemovedComponents,
schedule::{Schedule, Stage, SystemStage},
schedule_v3::{apply_system_buffers, IntoSystemConfig, Schedule},
system::{
Commands, IntoSystem, Local, NonSend, NonSendMut, ParamSet, Query, QueryComponentError,
Res, ResMut, Resource, System, SystemState,
@ -170,9 +174,6 @@ mod tests {
#[derive(Component, Debug)]
struct W<T>(T);
#[derive(StageLabel)]
struct UpdateStage;
#[test]
fn simple_system() {
fn sys(query: Query<&A>) {
@ -191,9 +192,7 @@ mod tests {
fn run_system<Param, S: IntoSystem<(), (), Param>>(world: &mut World, system: S) {
let mut schedule = Schedule::default();
let mut update = SystemStage::parallel();
update.add_system(system);
schedule.add_stage(UpdateStage, update);
schedule.add_system(system);
schedule.run(world);
}
@ -315,14 +314,11 @@ mod tests {
world.insert_resource(Added(0));
world.insert_resource(Changed(0));
#[derive(StageLabel)]
struct ClearTrackers;
let mut schedule = Schedule::default();
let mut update = SystemStage::parallel();
update.add_system(incr_e_on_flip);
schedule.add_stage(UpdateStage, update);
schedule.add_stage(ClearTrackers, SystemStage::single(World::clear_trackers));
schedule.add_system(incr_e_on_flip);
schedule.add_system(apply_system_buffers.after(incr_e_on_flip));
schedule.add_system(World::clear_trackers.after(apply_system_buffers));
schedule.run(&mut world);
assert_eq!(world.resource::<Added>().0, 1);

View file

@ -3,13 +3,13 @@ use core::fmt::Debug;
use crate::{
archetype::ArchetypeComponentId, change_detection::MAX_CHANGE_AGE, component::ComponentId,
query::Access, schedule::SystemLabelId, world::World,
query::Access, world::World,
};
use std::any::TypeId;
use std::borrow::Cow;
/// An ECS system that can be added to a [`Schedule`](crate::schedule::Schedule)
/// An ECS system that can be added to a [`Schedule`](crate::schedule_v3::Schedule)
///
/// Systems are functions with all arguments implementing
/// [`SystemParam`](crate::system::SystemParam).
@ -19,7 +19,7 @@ use std::borrow::Cow;
///
/// Systems are executed in parallel, in opportunistic order; data access is managed automatically.
/// It's possible to specify explicit execution order between specific systems,
/// see [`SystemDescriptor`](crate::schedule::SystemDescriptor).
/// see [`IntoSystemConfig`](crate::schedule_v3::IntoSystemConfig).
pub trait System: Send + Sync + 'static {
/// The system's input. See [`In`](crate::system::In) for
/// [`FunctionSystem`](crate::system::FunctionSystem)s.
@ -64,10 +64,6 @@ pub trait System: Send + Sync + 'static {
/// Update the system's archetype component [`Access`].
fn update_archetype_component_access(&mut self, world: &World);
fn check_change_tick(&mut self, change_tick: u32);
/// The default labels for the system
fn default_labels(&self) -> Vec<SystemLabelId> {
Vec::new()
}
/// Returns the system's default [system sets](crate::schedule_v3::SystemSet).
fn default_system_sets(&self) -> Vec<Box<dyn crate::schedule_v3::SystemSet>> {
Vec::new()

View file

@ -152,7 +152,7 @@ pub unsafe trait SystemParam: Sized {
}
/// Applies any deferred mutations stored in this [`SystemParam`]'s state.
/// This is used to apply [`Commands`] at the end of a stage.
/// This is used to apply [`Commands`] during [`apply_system_buffers`](crate::prelude::apply_system_buffers).
#[inline]
#[allow(unused_variables)]
fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {}
@ -384,8 +384,7 @@ impl_param_set!();
///
/// ```
/// # let mut world = World::default();
/// # let mut schedule = Schedule::default();
/// # schedule.add_stage("update", SystemStage::parallel());
/// # let mut schedule = Schedule::new();
/// # use bevy_ecs::prelude::*;
/// #[derive(Resource)]
/// struct MyResource { value: u32 }
@ -401,9 +400,9 @@ impl_param_set!();
/// resource.value = 0;
/// assert_eq!(resource.value, 0);
/// }
/// # schedule.add_system_to_stage("update", read_resource_system.label("first"));
/// # schedule.add_system_to_stage("update", write_resource_system.after("first"));
/// # schedule.run_once(&mut world);
/// # schedule.add_system(read_resource_system);
/// # schedule.add_system(write_resource_system.after(read_resource_system));
/// # schedule.run(&mut world);
/// ```
pub trait Resource: Send + Sync + 'static {}

View file

@ -146,12 +146,6 @@ impl<SystemA: System, SystemB: System<In = SystemA::Out>> System for PipeSystem<
self.system_b.set_last_change_tick(last_change_tick);
}
fn default_labels(&self) -> Vec<crate::schedule::SystemLabelId> {
let mut labels = self.system_a.default_labels();
labels.extend(&self.system_b.default_labels());
labels
}
fn default_system_sets(&self) -> Vec<Box<dyn crate::schedule_v3::SystemSet>> {
let mut system_sets = self.system_a.default_system_sets();
system_sets.extend_from_slice(&self.system_b.default_system_sets());
@ -202,12 +196,13 @@ pub mod adapter {
/// ```
/// use bevy_ecs::prelude::*;
///
/// fn return1() -> u64 { 1 }
///
/// return1
/// .pipe(system_adapter::new(u32::try_from))
/// .pipe(system_adapter::unwrap)
/// .pipe(print);
///
/// fn return1() -> u64 { 1 }
/// fn print(In(x): In<impl std::fmt::Debug>) {
/// println!("{x:?}");
/// }
@ -228,16 +223,10 @@ pub mod adapter {
///
/// ```
/// use bevy_ecs::prelude::*;
/// #
/// # #[derive(StageLabel)]
/// # enum CoreStage { Update };
///
/// // Building a new schedule/app...
/// # use bevy_ecs::schedule::SystemStage;
/// # let mut sched = Schedule::default(); sched
/// # .add_stage(CoreStage::Update, SystemStage::parallel())
/// .add_system_to_stage(
/// CoreStage::Update,
/// let mut sched = Schedule::default();
/// sched.add_system(
/// // Panic if the load system returns an error.
/// load_save_system.pipe(system_adapter::unwrap)
/// )
@ -265,16 +254,10 @@ pub mod adapter {
///
/// ```
/// use bevy_ecs::prelude::*;
/// #
/// # #[derive(StageLabel)]
/// # enum CoreStage { Update };
///
/// // Building a new schedule/app...
/// # use bevy_ecs::schedule::SystemStage;
/// # let mut sched = Schedule::default(); sched
/// # .add_stage(CoreStage::Update, SystemStage::parallel())
/// .add_system_to_stage(
/// CoreStage::Update,
/// let mut sched = Schedule::default();
/// sched.add_system(
/// // Prints system information.
/// data_pipe_system.pipe(system_adapter::info)
/// )
@ -298,16 +281,10 @@ pub mod adapter {
///
/// ```
/// use bevy_ecs::prelude::*;
/// #
/// # #[derive(StageLabel)]
/// # enum CoreStage { Update };
///
/// // Building a new schedule/app...
/// # use bevy_ecs::schedule::SystemStage;
/// # let mut sched = Schedule::default(); sched
/// # .add_stage(CoreStage::Update, SystemStage::parallel())
/// .add_system_to_stage(
/// CoreStage::Update,
/// let mut sched = Schedule::default();
/// sched.add_system(
/// // Prints debug data from system.
/// parse_message_system.pipe(system_adapter::dbg)
/// )
@ -331,16 +308,10 @@ pub mod adapter {
///
/// ```
/// use bevy_ecs::prelude::*;
/// #
/// # #[derive(StageLabel)]
/// # enum CoreStage { Update };
///
/// // Building a new schedule/app...
/// # use bevy_ecs::schedule::SystemStage;
/// # let mut sched = Schedule::default(); sched
/// # .add_stage(CoreStage::Update, SystemStage::parallel())
/// .add_system_to_stage(
/// CoreStage::Update,
/// # let mut sched = Schedule::default();
/// sched.add_system(
/// // Prints system warning if system returns an error.
/// warning_pipe_system.pipe(system_adapter::warn)
/// )
@ -366,16 +337,9 @@ pub mod adapter {
///
/// ```
/// use bevy_ecs::prelude::*;
/// #
/// # #[derive(StageLabel)]
/// # enum CoreStage { Update };
///
/// // Building a new schedule/app...
/// # use bevy_ecs::schedule::SystemStage;
/// # let mut sched = Schedule::default(); sched
/// # .add_stage(CoreStage::Update, SystemStage::parallel())
/// .add_system_to_stage(
/// CoreStage::Update,
/// let mut sched = Schedule::default();
/// sched.add_system(
/// // Prints system error if system fails.
/// parse_error_message_system.pipe(system_adapter::error)
/// )
@ -408,16 +372,10 @@ pub mod adapter {
/// // Marker component for an enemy entity.
/// #[derive(Component)]
/// struct Monster;
/// #
/// # #[derive(StageLabel)]
/// # enum CoreStage { Update };
///
/// // Building a new schedule/app...
/// # use bevy_ecs::schedule::SystemStage;
/// # let mut sched = Schedule::default(); sched
/// # .add_stage(CoreStage::Update, SystemStage::parallel())
/// .add_system_to_stage(
/// CoreStage::Update,
/// .add_system(
/// // If the system fails, just move on and try again next frame.
/// fallible_system.pipe(system_adapter::ignore)
/// )

View file

@ -20,6 +20,7 @@ use crate::{
event::{Event, Events},
query::{DebugCheckedUnwrap, QueryState, ReadOnlyWorldQuery, WorldQuery},
removal_detection::RemovedComponentEvents,
schedule_v3::{Schedule, ScheduleLabel, Schedules},
storage::{Column, ComponentSparseSet, ResourceData, Storages, TableRow},
system::Resource,
};
@ -648,7 +649,7 @@ impl World {
/// of detection to be recorded.
///
/// When using `bevy_ecs` as part of the full Bevy engine, this method is added as a system to the
/// main app, to run during the `CoreStage::Last`, so you don't need to call it manually. When using
/// main app, to run during `CoreSet::Last`, so you don't need to call it manually. When using
/// `bevy_ecs` as a separate standalone crate however, you need to call this manually.
///
/// ```
@ -1917,6 +1918,57 @@ impl World {
}
}
// Schedule-related methods
impl World {
/// Runs the [`Schedule`] associated with the `label` a single time.
///
/// The [`Schedule`] is fetched from the
pub fn add_schedule(&mut self, schedule: Schedule, label: impl ScheduleLabel) {
let mut schedules = self.resource_mut::<Schedules>();
schedules.insert(label, schedule);
}
/// Runs the [`Schedule`] associated with the `label` a single time.
///
/// The [`Schedule`] is fetched from the [`Schedules`] resource of the world by its label,
/// and system state is cached.
///
/// For simple testing use cases, call [`Schedule::run(&mut world)`](Schedule::run) instead.
///
/// # Panics
///
/// Panics if the requested schedule does not exist, or the [`Schedules`] resource was not added.
pub fn run_schedule(&mut self, label: impl ScheduleLabel) {
self.run_schedule_ref(&label);
}
/// Runs the [`Schedule`] associated with the `label` a single time.
///
/// Unlike the `run_schedule` method, this method takes the label by reference, which can save a clone.
///
/// The [`Schedule`] is fetched from the [`Schedules`] resource of the world by its label,
/// and system state is cached.
///
/// For simple testing use cases, call [`Schedule::run(&mut world)`](Schedule::run) instead.
///
/// # Panics
///
/// Panics if the requested schedule does not exist, or the [`Schedules`] resource was not added.
pub fn run_schedule_ref(&mut self, label: &dyn ScheduleLabel) {
let (extracted_label, mut schedule) = self
.resource_mut::<Schedules>()
.remove_entry(label)
.unwrap_or_else(|| panic!("The schedule with the label {label:?} was not found."));
// TODO: move this span to Schdule::run
#[cfg(feature = "trace")]
let _span = bevy_utils::tracing::info_span!("schedule", name = ?extracted_label).entered();
schedule.run(self);
self.resource_mut::<Schedules>()
.insert(extracted_label, schedule);
}
}
impl fmt::Debug for World {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("World")

View file

@ -1,8 +1,8 @@
mod converter;
mod gilrs_system;
use bevy_app::{App, CoreStage, Plugin, StartupStage};
use bevy_ecs::schedule::IntoSystemDescriptor;
use bevy_app::{App, CoreSet, Plugin, StartupSet};
use bevy_ecs::prelude::*;
use bevy_input::InputSystem;
use bevy_utils::tracing::error;
use gilrs::GilrsBuilder;
@ -20,13 +20,11 @@ impl Plugin for GilrsPlugin {
{
Ok(gilrs) => {
app.insert_non_send_resource(gilrs)
.add_startup_system_to_stage(
StartupStage::PreStartup,
gilrs_event_startup_system,
)
.add_system_to_stage(
CoreStage::PreUpdate,
gilrs_event_system.before(InputSystem),
.add_startup_system(gilrs_event_startup_system.in_set(StartupSet::PreStartup))
.add_system(
gilrs_event_system
.before(InputSystem)
.in_set(CoreSet::PreUpdate),
);
}
Err(err) => error!("Failed to start Gilrs. {}", err),

View file

@ -1,8 +1,8 @@
use std::marker::PhantomData;
use bevy_app::{App, CoreStage, Plugin};
use bevy_app::{App, CoreSet, Plugin};
use bevy_core::Name;
use bevy_ecs::{prelude::*, schedule::ShouldRun};
use bevy_ecs::prelude::*;
use bevy_log::warn;
use bevy_utils::{get_short_name, HashSet};
@ -19,6 +19,23 @@ pub struct ReportHierarchyIssue<T> {
pub enabled: bool,
_comp: PhantomData<fn(T)>,
}
impl<T> ReportHierarchyIssue<T> {
/// Constructs a new object
pub fn new(enabled: bool) -> Self {
ReportHierarchyIssue {
enabled,
_comp: Default::default(),
}
}
}
impl<T> PartialEq for ReportHierarchyIssue<T> {
fn eq(&self, other: &Self) -> bool {
self.enabled == other.enabled
}
}
impl<T> Default for ReportHierarchyIssue<T> {
fn default() -> Self {
Self {
@ -59,11 +76,11 @@ pub fn check_hierarchy_component_has_valid_parent<T: Component>(
}
/// Run criteria that only allows running when [`ReportHierarchyIssue<T>`] is enabled.
pub fn on_hierarchy_reports_enabled<T>(report: Res<ReportHierarchyIssue<T>>) -> ShouldRun
pub fn on_hierarchy_reports_enabled<T>(report: Res<ReportHierarchyIssue<T>>) -> bool
where
T: Component,
{
report.enabled.into()
report.enabled
}
/// Print a warning for each `Entity` with a `T` component
@ -79,11 +96,10 @@ impl<T: Component> Default for ValidParentCheckPlugin<T> {
impl<T: Component> Plugin for ValidParentCheckPlugin<T> {
fn build(&self, app: &mut App) {
app.init_resource::<ReportHierarchyIssue<T>>()
.add_system_to_stage(
CoreStage::Last,
app.init_resource::<ReportHierarchyIssue<T>>().add_system(
check_hierarchy_component_has_valid_parent::<T>
.with_run_criteria(on_hierarchy_reports_enabled::<T>),
.run_if(resource_equals(ReportHierarchyIssue::<T>::new(true)))
.in_set(CoreSet::Last),
);
}
}

View file

@ -5,7 +5,7 @@ use std::hash::Hash;
// unused import, but needed for intra doc link to work
#[allow(unused_imports)]
use bevy_ecs::schedule::State;
use bevy_ecs::schedule_v3::State;
/// A "press-able" input of type `T`.
///
@ -22,7 +22,7 @@ use bevy_ecs::schedule::State;
///
/// In case multiple systems are checking for [`Input::just_pressed`] or [`Input::just_released`]
/// but only one should react, for example in the case of triggering
/// [`State`](bevy_ecs::schedule::State) change, you should consider clearing the input state, either by:
/// [`State`](bevy_ecs::schedule_v3::State) change, you should consider clearing the input state, either by:
///
/// * Using [`Input::clear_just_pressed`] or [`Input::clear_just_released`] instead.
/// * Calling [`Input::clear`] or [`Input::reset`] immediately after the state change.

View file

@ -22,7 +22,7 @@ pub mod prelude {
}
use bevy_app::prelude::*;
use bevy_ecs::schedule::{IntoSystemDescriptor, SystemLabel, SystemSet};
use bevy_ecs::prelude::*;
use bevy_reflect::{FromReflect, Reflect};
use keyboard::{keyboard_input_system, KeyCode, KeyboardInput, ScanCode};
use mouse::{
@ -46,29 +46,23 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
#[derive(Default)]
pub struct InputPlugin;
#[derive(Debug, PartialEq, Eq, Clone, Hash, SystemLabel)]
#[derive(Debug, PartialEq, Eq, Clone, Hash, SystemSet)]
pub struct InputSystem;
impl Plugin for InputPlugin {
fn build(&self, app: &mut App) {
app
app.configure_set(InputSystem.in_set(CoreSet::PreUpdate))
// keyboard
.add_event::<KeyboardInput>()
.init_resource::<Input<KeyCode>>()
.init_resource::<Input<ScanCode>>()
.add_system_to_stage(
CoreStage::PreUpdate,
keyboard_input_system.label(InputSystem),
)
.add_system(keyboard_input_system.in_set(InputSystem))
// mouse
.add_event::<MouseButtonInput>()
.add_event::<MouseMotion>()
.add_event::<MouseWheel>()
.init_resource::<Input<MouseButton>>()
.add_system_to_stage(
CoreStage::PreUpdate,
mouse_button_input_system.label(InputSystem),
)
.add_system(mouse_button_input_system.in_set(InputSystem))
// gamepad
.add_event::<GamepadConnectionEvent>()
.add_event::<GamepadButtonChangedEvent>()
@ -79,30 +73,23 @@ impl Plugin for InputPlugin {
.init_resource::<Input<GamepadButton>>()
.init_resource::<Axis<GamepadAxis>>()
.init_resource::<Axis<GamepadButton>>()
.add_system_set_to_stage(
CoreStage::PreUpdate,
SystemSet::new()
.with_system(gamepad_event_system)
.with_system(gamepad_connection_system.after(gamepad_event_system))
.with_system(
.add_systems(
(
gamepad_event_system,
gamepad_connection_system.after(gamepad_event_system),
gamepad_button_event_system
.after(gamepad_event_system)
.after(gamepad_connection_system),
)
.with_system(
gamepad_axis_event_system
.after(gamepad_event_system)
.after(gamepad_connection_system),
)
.label(InputSystem),
.in_set(InputSystem),
)
// touch
.add_event::<TouchInput>()
.init_resource::<Touches>()
.add_system_to_stage(
CoreStage::PreUpdate,
touch_screen_input_system.label(InputSystem),
);
.add_system(touch_screen_input_system.in_set(InputSystem));
// Register common types
app.register_type::<ButtonState>();

View file

@ -10,7 +10,8 @@ mod prepass;
mod render;
pub use alpha::*;
use bevy_utils::default;
use bevy_transform::TransformSystem;
use bevy_window::ModifiesWindows;
pub use bundle::*;
pub use fog::*;
pub use light::*;
@ -19,8 +20,6 @@ pub use pbr_material::*;
pub use prepass::*;
pub use render::*;
use bevy_window::ModifiesWindows;
pub mod prelude {
#[doc(hidden)]
pub use crate::{
@ -54,10 +53,9 @@ use bevy_render::{
render_graph::RenderGraph,
render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions},
render_resource::{Shader, SpecializedMeshPipelines},
view::VisibilitySystems,
RenderApp, RenderStage,
view::{ViewSet, VisibilitySystems},
ExtractSchedule, RenderApp, RenderSet,
};
use bevy_transform::TransformSystem;
pub const PBR_TYPES_SHADER_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 1708015359337029744);
@ -165,42 +163,50 @@ impl Plugin for PbrPlugin {
.add_plugin(MeshRenderPlugin)
.add_plugin(MaterialPlugin::<StandardMaterial> {
prepass_enabled: self.prepass_enabled,
..default()
..Default::default()
})
.init_resource::<AmbientLight>()
.init_resource::<GlobalVisiblePointLights>()
.init_resource::<DirectionalLightShadowMap>()
.init_resource::<PointLightShadowMap>()
.add_plugin(ExtractResourcePlugin::<AmbientLight>::default())
.configure_sets(
(
SimulationLightSystems::AddClusters,
SimulationLightSystems::AddClustersFlush
.after(SimulationLightSystems::AddClusters)
.before(SimulationLightSystems::AssignLightsToClusters),
SimulationLightSystems::AssignLightsToClusters,
SimulationLightSystems::CheckLightVisibility,
SimulationLightSystems::UpdateDirectionalLightCascades,
SimulationLightSystems::UpdateLightFrusta,
)
.in_set(CoreSet::PostUpdate),
)
.add_plugin(FogPlugin)
.add_system_to_stage(
CoreStage::PostUpdate,
.add_system(
// NOTE: Clusters need to have been added before update_clusters is run so
// add as an exclusive system
add_clusters
.at_start()
.label(SimulationLightSystems::AddClusters),
add_clusters.in_set(SimulationLightSystems::AddClusters),
)
.add_system_to_stage(
CoreStage::PostUpdate,
.add_system(apply_system_buffers.in_set(SimulationLightSystems::AddClustersFlush))
.add_system(
assign_lights_to_clusters
.label(SimulationLightSystems::AssignLightsToClusters)
.in_set(SimulationLightSystems::AssignLightsToClusters)
.after(TransformSystem::TransformPropagate)
.after(VisibilitySystems::CheckVisibility)
.after(CameraUpdateSystem)
.after(ModifiesWindows),
)
.add_system_to_stage(
CoreStage::PostUpdate,
.add_system(
update_directional_light_cascades
.label(SimulationLightSystems::UpdateDirectionalLightCascades)
.in_set(SimulationLightSystems::UpdateDirectionalLightCascades)
.after(TransformSystem::TransformPropagate)
.after(CameraUpdateSystem),
)
.add_system_to_stage(
CoreStage::PostUpdate,
.add_system(
update_directional_light_frusta
.label(SimulationLightSystems::UpdateLightFrusta)
.in_set(SimulationLightSystems::UpdateLightFrusta)
// This must run after CheckVisibility because it relies on ComputedVisibility::is_visible()
.after(VisibilitySystems::CheckVisibility)
.after(TransformSystem::TransformPropagate)
@ -210,24 +216,22 @@ impl Plugin for PbrPlugin {
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
.ambiguous_with(update_spot_light_frusta),
)
.add_system_to_stage(
CoreStage::PostUpdate,
.add_system(
update_point_light_frusta
.label(SimulationLightSystems::UpdateLightFrusta)
.in_set(SimulationLightSystems::UpdateLightFrusta)
.after(TransformSystem::TransformPropagate)
.after(SimulationLightSystems::AssignLightsToClusters),
)
.add_system_to_stage(
CoreStage::PostUpdate,
.add_system(
update_spot_light_frusta
.label(SimulationLightSystems::UpdateLightFrusta)
.in_set(SimulationLightSystems::UpdateLightFrusta)
.after(TransformSystem::TransformPropagate)
.after(SimulationLightSystems::AssignLightsToClusters),
)
.add_system_to_stage(
CoreStage::PostUpdate,
.add_system(
check_light_mesh_visibility
.label(SimulationLightSystems::CheckLightVisibility)
.in_set(SimulationLightSystems::CheckLightVisibility)
.after(VisibilitySystems::CalculateBoundsFlush)
.after(TransformSystem::TransformPropagate)
.after(SimulationLightSystems::UpdateLightFrusta)
// NOTE: This MUST be scheduled AFTER the core renderer visibility check
@ -252,36 +256,41 @@ impl Plugin for PbrPlugin {
Err(_) => return,
};
// Extract the required data from the main world
render_app
.add_system_to_stage(
RenderStage::Extract,
render::extract_clusters.label(RenderLightSystems::ExtractClusters),
.add_systems_to_schedule(
ExtractSchedule,
(
render::extract_clusters.in_set(RenderLightSystems::ExtractClusters),
render::extract_lights.in_set(RenderLightSystems::ExtractLights),
),
)
.add_system_to_stage(
RenderStage::Extract,
render::extract_lights.label(RenderLightSystems::ExtractLights),
)
.add_system_to_stage(
RenderStage::Prepare,
// this is added as an exclusive system because it contributes new views. it must run (and have Commands applied)
// _before_ the `prepare_views()` system is run. ideally this becomes a normal system when "stageless" features come out
.add_system(
render::prepare_lights
.at_start()
.label(RenderLightSystems::PrepareLights),
.before(ViewSet::PrepareUniforms)
.in_set(RenderLightSystems::PrepareLights)
.in_set(RenderSet::Prepare),
)
.add_system_to_stage(
RenderStage::Prepare,
// NOTE: This needs to run after prepare_lights. As prepare_lights is an exclusive system,
// just adding it to the non-exclusive systems in the Prepare stage means it runs after
// prepare_lights.
render::prepare_clusters.label(RenderLightSystems::PrepareClusters),
// A sync is needed after prepare_lights, before prepare_view_uniforms,
// because prepare_lights creates new views for shadow mapping
.add_system(
apply_system_buffers
.after(RenderLightSystems::PrepareLights)
.before(ViewSet::PrepareUniforms),
)
.add_system_to_stage(
RenderStage::Queue,
render::queue_shadows.label(RenderLightSystems::QueueShadows),
.add_system(
render::prepare_clusters
.after(render::prepare_lights)
.in_set(RenderLightSystems::PrepareClusters)
.in_set(RenderSet::Prepare),
)
.add_system_to_stage(RenderStage::Queue, render::queue_shadow_view_bind_group)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Shadow>)
.add_system(
render::queue_shadows
.in_set(RenderLightSystems::QueueShadows)
.in_set(RenderSet::Queue),
)
.add_system(render::queue_shadow_view_bind_group.in_set(RenderSet::Queue))
.add_system(sort_phase_system::<Shadow>.in_set(RenderSet::PhaseSort))
.init_resource::<ShadowPipeline>()
.init_resource::<DrawFunctions<Shadow>>()
.init_resource::<LightMeta>()

View file

@ -594,9 +594,10 @@ pub struct NotShadowCaster;
#[reflect(Component, Default)]
pub struct NotShadowReceiver;
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub enum SimulationLightSystems {
AddClusters,
AddClustersFlush,
AssignLightsToClusters,
UpdateDirectionalLightCascades,
UpdateLightFrusta,

View file

@ -10,14 +10,11 @@ use bevy_core_pipeline::{
};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
event::EventReader,
prelude::World,
schedule::IntoSystemDescriptor,
prelude::*,
system::{
lifetimeless::{Read, SRes},
Commands, Local, Query, Res, ResMut, Resource, SystemParamItem,
SystemParamItem,
},
world::FromWorld,
};
use bevy_reflect::TypeUuid;
use bevy_render::{
@ -37,7 +34,7 @@ use bevy_render::{
renderer::RenderDevice,
texture::FallbackImage,
view::{ExtractedView, Msaa, VisibleEntities},
Extract, RenderApp, RenderStage,
Extract, ExtractSchedule, RenderApp, RenderSet,
};
use bevy_utils::{tracing::error, HashMap, HashSet};
use std::hash::Hash;
@ -198,12 +195,13 @@ where
.init_resource::<ExtractedMaterials<M>>()
.init_resource::<RenderMaterials<M>>()
.init_resource::<SpecializedMeshPipelines<MaterialPipeline<M>>>()
.add_system_to_stage(RenderStage::Extract, extract_materials::<M>)
.add_system_to_stage(
RenderStage::Prepare,
prepare_materials::<M>.after(PrepareAssetLabel::PreAssetPrepare),
.add_system_to_schedule(ExtractSchedule, extract_materials::<M>)
.add_system(
prepare_materials::<M>
.after(PrepareAssetLabel::PreAssetPrepare)
.in_set(RenderSet::Prepare),
)
.add_system_to_stage(RenderStage::Queue, queue_material_meshes::<M>);
.add_system(queue_material_meshes::<M>.in_set(RenderSet::Queue));
}
if self.prepass_enabled {

View file

@ -8,13 +8,11 @@ use bevy_core_pipeline::{
},
};
use bevy_ecs::{
prelude::Entity,
query::With,
prelude::*,
system::{
lifetimeless::{Read, SRes},
Commands, Query, Res, ResMut, Resource, SystemParamItem,
SystemParamItem,
},
world::{FromWorld, World},
};
use bevy_reflect::TypeUuid;
use bevy_render::{
@ -39,7 +37,7 @@ use bevy_render::{
renderer::RenderDevice,
texture::TextureCache,
view::{ExtractedView, Msaa, ViewUniform, ViewUniformOffset, ViewUniforms, VisibleEntities},
Extract, RenderApp, RenderStage,
Extract, ExtractSchedule, RenderApp, RenderSet,
};
use bevy_utils::{tracing::error, HashMap};
@ -99,15 +97,12 @@ where
};
render_app
.add_system_to_stage(RenderStage::Extract, extract_camera_prepass_phase)
.add_system_to_stage(RenderStage::Prepare, prepare_prepass_textures)
.add_system_to_stage(RenderStage::Queue, queue_prepass_view_bind_group::<M>)
.add_system_to_stage(RenderStage::Queue, queue_prepass_material_meshes::<M>)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Opaque3dPrepass>)
.add_system_to_stage(
RenderStage::PhaseSort,
sort_phase_system::<AlphaMask3dPrepass>,
)
.add_system_to_schedule(ExtractSchedule, extract_camera_prepass_phase)
.add_system(prepare_prepass_textures.in_set(RenderSet::Prepare))
.add_system(queue_prepass_view_bind_group::<M>.in_set(RenderSet::Queue))
.add_system(queue_prepass_material_meshes::<M>.in_set(RenderSet::Queue))
.add_system(sort_phase_system::<Opaque3dPrepass>.in_set(RenderSet::PhaseSort))
.add_system(sort_phase_system::<AlphaMask3dPrepass>.in_set(RenderSet::PhaseSort))
.init_resource::<PrepassPipeline<M>>()
.init_resource::<DrawFunctions<Opaque3dPrepass>>()
.init_resource::<DrawFunctions<AlphaMask3dPrepass>>()

View file

@ -1,6 +1,6 @@
use bevy_app::{App, Plugin};
use bevy_asset::{load_internal_asset, HandleUntyped};
use bevy_ecs::{prelude::*, schedule::SystemLabel};
use bevy_ecs::prelude::*;
use bevy_math::{Vec3, Vec4};
use bevy_reflect::TypeUuid;
use bevy_render::{
@ -8,7 +8,7 @@ use bevy_render::{
render_resource::{DynamicUniformBuffer, Shader, ShaderType},
renderer::{RenderDevice, RenderQueue},
view::ExtractedView,
RenderApp, RenderStage,
RenderApp, RenderSet,
};
use crate::{FogFalloff, FogSettings};
@ -114,7 +114,7 @@ pub fn prepare_fog(
}
/// Labels for fog-related systems
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub enum RenderFogSystems {
PrepareFog,
}
@ -140,10 +140,10 @@ impl Plugin for FogPlugin {
app.add_plugin(ExtractComponentPlugin::<FogSettings>::default());
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app.init_resource::<FogMeta>().add_system_to_stage(
RenderStage::Prepare,
prepare_fog.label(RenderFogSystems::PrepareFog),
);
render_app
.init_resource::<FogMeta>()
.add_system(prepare_fog.in_set(RenderFogSystems::PrepareFog))
.configure_set(RenderFogSystems::PrepareFog.in_set(RenderSet::Prepare));
}
}
}

View file

@ -39,7 +39,7 @@ use bevy_utils::{
};
use std::num::{NonZeroU32, NonZeroU64};
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub enum RenderLightSystems {
ExtractClusters,
ExtractLights,

View file

@ -31,7 +31,7 @@ use bevy_render::{
ImageSampler, TextureFormatPixelInfo,
},
view::{ComputedVisibility, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms},
Extract, RenderApp, RenderStage,
Extract, ExtractSchedule, RenderApp, RenderSet,
};
use bevy_transform::components::GlobalTransform;
use std::num::NonZeroU64;
@ -102,11 +102,10 @@ impl Plugin for MeshRenderPlugin {
render_app
.init_resource::<MeshPipeline>()
.init_resource::<SkinnedMeshUniform>()
.add_system_to_stage(RenderStage::Extract, extract_meshes)
.add_system_to_stage(RenderStage::Extract, extract_skinned_meshes)
.add_system_to_stage(RenderStage::Prepare, prepare_skinned_meshes)
.add_system_to_stage(RenderStage::Queue, queue_mesh_bind_group)
.add_system_to_stage(RenderStage::Queue, queue_mesh_view_bind_groups);
.add_systems_to_schedule(ExtractSchedule, (extract_meshes, extract_skinned_meshes))
.add_system(prepare_skinned_meshes.in_set(RenderSet::Prepare))
.add_system(queue_mesh_bind_group.in_set(RenderSet::Queue))
.add_system(queue_mesh_view_bind_groups.in_set(RenderSet::Queue));
}
}
}

View file

@ -17,7 +17,7 @@ use bevy_render::{
SpecializedMeshPipelineError, SpecializedMeshPipelines,
},
view::{ExtractedView, Msaa, VisibleEntities},
RenderApp, RenderStage,
RenderApp, RenderSet,
};
use bevy_utils::tracing::error;
@ -47,7 +47,7 @@ impl Plugin for WireframePlugin {
.add_render_command::<Opaque3d, DrawWireframes>()
.init_resource::<WireframePipeline>()
.init_resource::<SpecializedMeshPipelines<WireframePipeline>>()
.add_system_to_stage(RenderStage::Queue, queue_wireframes);
.add_system(queue_wireframes.in_set(RenderSet::Queue));
}
}
}

View file

@ -450,8 +450,8 @@ impl NormalizedRenderTarget {
///
/// The system function is generic over the camera projection type, and only instances of
/// [`OrthographicProjection`] and [`PerspectiveProjection`] are automatically added to
/// the app, as well as the runtime-selected [`Projection`]. The system runs during the
/// [`CoreStage::PostUpdate`] stage.
/// the app, as well as the runtime-selected [`Projection`].
/// The system runs during [`CoreSet::PostUpdate`].
///
/// ## World Resources
///
@ -461,7 +461,7 @@ impl NormalizedRenderTarget {
/// [`OrthographicProjection`]: crate::camera::OrthographicProjection
/// [`PerspectiveProjection`]: crate::camera::PerspectiveProjection
/// [`Projection`]: crate::camera::Projection
/// [`CoreStage::PostUpdate`]: bevy_app::CoreStage::PostUpdate
/// [`CoreSet::PostUpdate`]: bevy_app::CoreSet::PostUpdate
pub fn camera_system<T: CameraProjection + Component>(
mut window_resized_events: EventReader<WindowResized>,
mut window_created_events: EventReader<WindowCreated>,

View file

@ -7,7 +7,7 @@ pub use camera::*;
pub use camera_driver_node::*;
pub use projection::*;
use crate::{render_graph::RenderGraph, RenderApp, RenderStage};
use crate::{render_graph::RenderGraph, ExtractSchedule, RenderApp};
use bevy_app::{App, Plugin};
#[derive(Default)]
@ -27,8 +27,7 @@ impl Plugin for CameraPlugin {
.add_plugin(CameraProjectionPlugin::<PerspectiveProjection>::default());
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app.add_system_to_stage(RenderStage::Extract, extract_cameras);
render_app.add_system_to_schedule(ExtractSchedule, extract_cameras);
let camera_driver_node = CameraDriverNode::new(&mut render_app.world);
let mut render_graph = render_app.world.resource_mut::<RenderGraph>();
render_graph.add_node(crate::main_graph::node::CAMERA_DRIVER, camera_driver_node);

View file

@ -1,6 +1,6 @@
use std::marker::PhantomData;
use bevy_app::{App, CoreStage, Plugin, StartupStage};
use bevy_app::{App, CoreSchedule, CoreSet, Plugin, StartupSet};
use bevy_ecs::{prelude::*, reflect::ReflectComponent};
use bevy_math::Mat4;
use bevy_reflect::{
@ -22,25 +22,27 @@ impl<T: CameraProjection> Default for CameraProjectionPlugin<T> {
/// Label for [`camera_system<T>`], shared across all `T`.
///
/// [`camera_system<T>`]: crate::camera::camera_system
#[derive(SystemLabel, Clone, Eq, PartialEq, Hash, Debug)]
#[derive(SystemSet, Clone, Eq, PartialEq, Hash, Debug)]
pub struct CameraUpdateSystem;
impl<T: CameraProjection + Component + GetTypeRegistration> Plugin for CameraProjectionPlugin<T> {
fn build(&self, app: &mut App) {
app.register_type::<T>()
.add_startup_system_to_stage(
StartupStage::PostStartup,
.edit_schedule(CoreSchedule::Startup, |schedule| {
schedule.configure_set(CameraUpdateSystem.in_set(StartupSet::PostStartup));
})
.configure_set(CameraUpdateSystem.in_set(CoreSet::PostUpdate))
.add_startup_system(
crate::camera::camera_system::<T>
.label(CameraUpdateSystem)
.in_set(CameraUpdateSystem)
// We assume that each camera will only have one projection,
// so we can ignore ambiguities with all other monomorphizations.
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
.ambiguous_with(CameraUpdateSystem),
)
.add_system_to_stage(
CoreStage::PostUpdate,
.add_system(
crate::camera::camera_system::<T>
.label(CameraUpdateSystem)
.in_set(CameraUpdateSystem)
.after(ModifiesWindows)
// We assume that each camera will only have one projection,
// so we can ignore ambiguities with all other monomorphizations.

View file

@ -2,7 +2,7 @@ use crate::{
render_resource::{encase::internal::WriteInto, DynamicUniformBuffer, ShaderType},
renderer::{RenderDevice, RenderQueue},
view::ComputedVisibility,
Extract, RenderApp, RenderStage,
Extract, ExtractSchedule, RenderApp, RenderSet,
};
use bevy_app::{App, Plugin};
use bevy_asset::{Asset, Handle};
@ -33,7 +33,7 @@ impl<C: Component> DynamicUniformIndex<C> {
/// Describes how a component gets extracted for rendering.
///
/// Therefore the component is transferred from the "app world" into the "render world"
/// in the [`RenderStage::Extract`](crate::RenderStage::Extract) step.
/// in the [`ExtractSchedule`](crate::ExtractSchedule) step.
pub trait ExtractComponent: Component {
/// ECS [`WorldQuery`] to fetch the components to extract.
type Query: WorldQuery + ReadOnlyWorldQuery;
@ -68,7 +68,7 @@ pub trait ExtractComponent: Component {
/// For referencing the newly created uniforms a [`DynamicUniformIndex`] is inserted
/// for every processed entity.
///
/// Therefore it sets up the [`RenderStage::Prepare`](crate::RenderStage::Prepare) step
/// Therefore it sets up the [`RenderSet::Prepare`](crate::RenderSet::Prepare) step
/// for the specified [`ExtractComponent`].
pub struct UniformComponentPlugin<C>(PhantomData<fn() -> C>);
@ -83,7 +83,7 @@ impl<C: Component + ShaderType + WriteInto + Clone> Plugin for UniformComponentP
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.insert_resource(ComponentUniforms::<C>::default())
.add_system_to_stage(RenderStage::Prepare, prepare_uniform_components::<C>);
.add_system(prepare_uniform_components::<C>.in_set(RenderSet::Prepare));
}
}
}
@ -151,7 +151,7 @@ fn prepare_uniform_components<C: Component>(
/// This plugin extracts the components into the "render world".
///
/// Therefore it sets up the [`RenderStage::Extract`](crate::RenderStage::Extract) step
/// Therefore it sets up the [`ExtractSchedule`](crate::ExtractSchedule) step
/// for the specified [`ExtractComponent`].
pub struct ExtractComponentPlugin<C, F = ()> {
only_extract_visible: bool,
@ -180,10 +180,9 @@ impl<C: ExtractComponent> Plugin for ExtractComponentPlugin<C> {
fn build(&self, app: &mut App) {
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
if self.only_extract_visible {
render_app
.add_system_to_stage(RenderStage::Extract, extract_visible_components::<C>);
render_app.add_system_to_schedule(ExtractSchedule, extract_visible_components::<C>);
} else {
render_app.add_system_to_stage(RenderStage::Extract, extract_components::<C>);
render_app.add_system_to_schedule(ExtractSchedule, extract_components::<C>);
}
}
}

View file

@ -9,19 +9,19 @@ use std::ops::{Deref, DerefMut};
///
/// A [`SystemParam`] adapter which applies the contained `SystemParam` to the [`World`]
/// contained in [`MainWorld`]. This parameter only works for systems run
/// during [`RenderStage::Extract`].
/// during the [`ExtractSchedule`](crate::ExtractSchedule).
///
/// This requires that the contained [`SystemParam`] does not mutate the world, as it
/// uses a read-only reference to [`MainWorld`] internally.
///
/// ## Context
///
/// [`RenderStage::Extract`] is used to extract (move) data from the simulation world ([`MainWorld`]) to the
/// [`ExtractSchedule`] is used to extract (move) data from the simulation world ([`MainWorld`]) to the
/// render world. The render world drives rendering each frame (generally to a [Window]).
/// This design is used to allow performing calculations related to rendering a prior frame at the same
/// time as the next frame is simulated, which increases throughput (FPS).
///
/// [`Extract`] is used to get data from the main world during [`RenderStage::Extract`].
/// [`Extract`] is used to get data from the main world during [`ExtractSchedule`].
///
/// ## Examples
///
@ -37,7 +37,7 @@ use std::ops::{Deref, DerefMut};
/// }
/// ```
///
/// [`RenderStage::Extract`]: crate::RenderStage::Extract
/// [`ExtractSchedule`]: crate::ExtractSchedule
/// [Window]: bevy_window::Window
pub struct Extract<'w, 's, P>
where

View file

@ -1,18 +1,15 @@
use std::marker::PhantomData;
use bevy_app::{App, Plugin};
use bevy_ecs::change_detection::DetectChanges;
#[cfg(debug_assertions)]
use bevy_ecs::system::Local;
use bevy_ecs::system::{Commands, Res, ResMut, Resource};
use bevy_ecs::prelude::*;
pub use bevy_render_macros::ExtractResource;
use crate::{Extract, RenderApp, RenderStage};
use crate::{Extract, ExtractSchedule, RenderApp};
/// Describes how a resource gets extracted for rendering.
///
/// Therefore the resource is transferred from the "main world" into the "render world"
/// in the [`RenderStage::Extract`](crate::RenderStage::Extract) step.
/// in the [`ExtractSchedule`](crate::ExtractSchedule) step.
pub trait ExtractResource: Resource {
type Source: Resource;
@ -22,7 +19,7 @@ pub trait ExtractResource: Resource {
/// This plugin extracts the resources into the "render world".
///
/// Therefore it sets up the [`RenderStage::Extract`](crate::RenderStage::Extract) step
/// Therefore it sets up the[`ExtractSchedule`](crate::ExtractSchedule) step
/// for the specified [`Resource`].
pub struct ExtractResourcePlugin<R: ExtractResource>(PhantomData<R>);
@ -35,7 +32,7 @@ impl<R: ExtractResource> Default for ExtractResourcePlugin<R> {
impl<R: ExtractResource> Plugin for ExtractResourcePlugin<R> {
fn build(&self, app: &mut App) {
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app.add_system_to_stage(RenderStage::Extract, extract_resource::<R>);
render_app.add_system_to_schedule(ExtractSchedule, extract_resource::<R>);
}
}
}

View file

@ -2,7 +2,7 @@ use crate::{
extract_resource::ExtractResource,
render_resource::{ShaderType, UniformBuffer},
renderer::{RenderDevice, RenderQueue},
Extract, RenderApp, RenderStage,
Extract, ExtractSchedule, RenderApp, RenderSet,
};
use bevy_app::{App, Plugin};
use bevy_core::FrameCount;
@ -15,13 +15,13 @@ pub struct GlobalsPlugin;
impl Plugin for GlobalsPlugin {
fn build(&self, app: &mut App) {
app.register_type::<GlobalsUniform>();
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<GlobalsBuffer>()
.init_resource::<Time>()
.add_system_to_stage(RenderStage::Extract, extract_frame_count)
.add_system_to_stage(RenderStage::Extract, extract_time)
.add_system_to_stage(RenderStage::Prepare, prepare_globals_buffer);
.add_systems_to_schedule(ExtractSchedule, (extract_frame_count, extract_time))
.add_system(prepare_globals_buffer.in_set(RenderSet::Prepare));
}
}
}

View file

@ -35,6 +35,7 @@ pub mod prelude {
spatial_bundle::SpatialBundle,
texture::{Image, ImagePlugin},
view::{ComputedVisibility, Msaa, Visibility, VisibilityBundle},
ExtractSchedule,
};
}
@ -50,14 +51,11 @@ use crate::{
settings::WgpuSettings,
view::{ViewPlugin, WindowRenderPlugin},
};
use bevy_app::{App, AppLabel, Plugin, SubApp};
use bevy_app::{App, AppLabel, CoreSchedule, Plugin, SubApp};
use bevy_asset::{AddAsset, AssetServer};
use bevy_ecs::{prelude::*, system::SystemState};
use bevy_ecs::{prelude::*, schedule_v3::ScheduleLabel, system::SystemState};
use bevy_utils::tracing::debug;
use std::{
any::TypeId,
ops::{Deref, DerefMut},
};
use std::ops::{Deref, DerefMut};
/// Contains the default Bevy rendering backend based on wgpu.
#[derive(Default)]
@ -65,52 +63,90 @@ pub struct RenderPlugin {
pub wgpu_settings: WgpuSettings,
}
/// The labels of the default App rendering stages.
#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)]
pub enum RenderStage {
/// Extract data from the "app world" and insert it into the "render world".
/// This step should be kept as short as possible to increase the "pipelining potential" for
/// running the next frame while rendering the current frame.
Extract,
/// A stage for applying the commands from the [`Extract`] stage
/// The labels of the default App rendering sets.
///
/// The sets run in the order listed, with [`apply_system_buffers`] inserted between each set.
///
/// The `*Flush` sets are assigned to the copy of [`apply_system_buffers`]
/// that runs immediately after the matching system set.
/// These can be useful for ordering, but you almost never want to add your systems to these sets.
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub enum RenderSet {
/// The copy of [`apply_system_buffers`] that runs at the begining of this schedule.
/// This is used for applying the commands from the [`ExtractSchedule`]
ExtractCommands,
/// Prepare render resources from the extracted data for the GPU.
Prepare,
/// The copy of [`apply_system_buffers`] that runs immediately after `Prepare`.
PrepareFlush,
/// Create [`BindGroups`](crate::render_resource::BindGroup) that depend on
/// [`Prepare`](RenderStage::Prepare) data and queue up draw calls to run during the
/// [`Render`](RenderStage::Render) stage.
/// [`Prepare`](RenderSet::Prepare) data and queue up draw calls to run during the
/// [`Render`](RenderSet::Render) step.
Queue,
/// The copy of [`apply_system_buffers`] that runs immediately after `Queue`.
QueueFlush,
// TODO: This could probably be moved in favor of a system ordering abstraction in Render or Queue
/// Sort the [`RenderPhases`](crate::render_phase::RenderPhase) here.
PhaseSort,
/// The copy of [`apply_system_buffers`] that runs immediately after `PhaseSort`.
PhaseSortFlush,
/// Actual rendering happens here.
/// In most cases, only the render backend should insert resources here.
Render,
/// The copy of [`apply_system_buffers`] that runs immediately after `Render`.
RenderFlush,
/// Cleanup render resources here.
Cleanup,
/// The copy of [`apply_system_buffers`] that runs immediately after `Cleanup`.
CleanupFlush,
}
/// Resource for holding the extract stage of the rendering schedule.
#[derive(Resource)]
pub struct ExtractStage(pub SystemStage);
impl RenderSet {
/// Sets up the base structure of the rendering [`Schedule`].
///
/// The sets defined in this enum are configured to run in order,
/// and a copy of [`apply_system_buffers`] is inserted at each `*Flush` label.
pub fn base_schedule() -> Schedule {
use RenderSet::*;
let mut schedule = Schedule::new();
// Create "stage-like" structure using buffer flushes + ordering
schedule.add_system(apply_system_buffers.in_set(ExtractCommands));
schedule.add_system(apply_system_buffers.in_set(PrepareFlush));
schedule.add_system(apply_system_buffers.in_set(QueueFlush));
schedule.add_system(apply_system_buffers.in_set(PhaseSortFlush));
schedule.add_system(apply_system_buffers.in_set(RenderFlush));
schedule.add_system(apply_system_buffers.in_set(CleanupFlush));
schedule.configure_set(ExtractCommands.before(Prepare));
schedule.configure_set(Prepare.after(ExtractCommands).before(PrepareFlush));
schedule.configure_set(Queue.after(PrepareFlush).before(QueueFlush));
schedule.configure_set(PhaseSort.after(QueueFlush).before(PhaseSortFlush));
schedule.configure_set(Render.after(PhaseSortFlush).before(RenderFlush));
schedule.configure_set(Cleanup.after(RenderFlush).before(CleanupFlush));
schedule
}
}
/// Schedule which extract data from the main world and inserts it into the render world.
///
/// This step should be kept as short as possible to increase the "pipelining potential" for
/// running the next frame while rendering the current frame.
///
/// This schedule is run on the main world, but its buffers are not applied
/// via [`Schedule::apply_system_buffers`](bevy_ecs::schedule_v3::Schedule) until it is returned to the render world.
#[derive(ScheduleLabel, PartialEq, Eq, Debug, Clone, Hash)]
pub struct ExtractSchedule;
/// The simulation [`World`] of the application, stored as a resource.
/// This resource is only available during [`RenderStage::Extract`] and not
/// during command application of that stage.
/// This resource is only available during [`ExtractSchedule`] and not
/// during command application of that schedule.
/// See [`Extract`] for more details.
#[derive(Resource, Default)]
pub struct MainWorld(World);
/// The Render App World. This is only available as a resource during the Extract step.
#[derive(Resource, Default)]
pub struct RenderWorld(World);
impl Deref for MainWorld {
type Target = World;
@ -136,7 +172,7 @@ pub mod main_graph {
pub struct RenderApp;
impl Plugin for RenderPlugin {
/// Initializes the renderer, sets up the [`RenderStage`](RenderStage) and creates the rendering sub-app.
/// Initializes the renderer, sets up the [`RenderSet`](RenderSet) and creates the rendering sub-app.
fn build(&self, app: &mut App) {
app.add_asset::<Shader>()
.add_debug_asset::<Shader>()
@ -183,46 +219,31 @@ impl Plugin for RenderPlugin {
let asset_server = app.world.resource::<AssetServer>().clone();
let mut render_app = App::empty();
let mut extract_stage =
SystemStage::parallel().with_system(PipelineCache::extract_shaders);
// Get the ComponentId for MainWorld. This does technically 'waste' a `WorldId`, but that's probably fine
render_app.init_resource::<MainWorld>();
render_app.world.remove_resource::<MainWorld>();
let main_world_in_render = render_app
.world
.components()
.get_resource_id(TypeId::of::<MainWorld>());
// `Extract` systems must read from the main world. We want to emit an error when that doesn't occur
// Safe to unwrap: Ensured it existed just above
extract_stage.set_must_read_resource(main_world_in_render.unwrap());
// don't apply buffers when the stage finishes running
// extract stage runs on the render world, but buffers are applied
// after access to the main world is removed
// See also https://github.com/bevyengine/bevy/issues/5082
extract_stage.set_apply_buffers(false);
render_app.add_simple_outer_schedule();
let mut render_schedule = RenderSet::base_schedule();
// This stage applies the commands from the extract stage while the render schedule
// Prepare the schedule which extracts data from the main world to the render world
render_app.edit_schedule(ExtractSchedule, |schedule| {
schedule
.set_apply_final_buffers(false)
.add_system(PipelineCache::extract_shaders);
});
// This set applies the commands from the extract stage while the render schedule
// is running in parallel with the main app.
let mut extract_commands_stage = SystemStage::parallel();
extract_commands_stage.add_system(apply_extract_commands.at_start());
render_schedule.add_system(apply_extract_commands.in_set(RenderSet::ExtractCommands));
render_schedule.add_system(
PipelineCache::process_pipeline_queue_system
.before(render_system)
.in_set(RenderSet::Render),
);
render_schedule.add_system(render_system.in_set(RenderSet::Render));
render_schedule.add_system(World::clear_entities.in_set(RenderSet::Cleanup));
render_app
.add_stage(RenderStage::Extract, extract_stage)
.add_stage(RenderStage::ExtractCommands, extract_commands_stage)
.add_stage(RenderStage::Prepare, SystemStage::parallel())
.add_stage(RenderStage::Queue, SystemStage::parallel())
.add_stage(RenderStage::PhaseSort, SystemStage::parallel())
.add_stage(
RenderStage::Render,
SystemStage::parallel()
// Note: Must run before `render_system` in order to
// processed newly queued pipelines.
.with_system(PipelineCache::process_pipeline_queue_system)
.with_system(render_system.at_end()),
)
.add_stage(
RenderStage::Cleanup,
SystemStage::parallel().with_system(World::clear_entities.at_end()),
)
.add_schedule(CoreSchedule::Main, render_schedule)
.init_resource::<render_graph::RenderGraph>()
.insert_resource(RenderInstance(instance))
.insert_resource(device)
@ -236,23 +257,23 @@ impl Plugin for RenderPlugin {
app.insert_resource(receiver);
render_app.insert_resource(sender);
app.insert_sub_app(RenderApp, SubApp::new(render_app, move |app_world, render_app| {
app.insert_sub_app(RenderApp, SubApp::new(render_app, move |main_world, render_app| {
#[cfg(feature = "trace")]
let _render_span = bevy_utils::tracing::info_span!("extract main app to render subapp").entered();
{
#[cfg(feature = "trace")]
let _stage_span =
bevy_utils::tracing::info_span!("stage", name = "reserve_and_flush")
bevy_utils::tracing::info_span!("reserve_and_flush")
.entered();
// reserve all existing app entities for use in render_app
// reserve all existing main world entities for use in render_app
// they can only be spawned using `get_or_spawn()`
let total_count = app_world.entities().total_count();
let total_count = main_world.entities().total_count();
assert_eq!(
render_app.world.entities().len(),
0,
"An entity was spawned after the entity list was cleared last frame and before the extract stage began. This is not supported",
"An entity was spawned after the entity list was cleared last frame and before the extract schedule began. This is not supported",
);
// This is safe given the clear_entities call in the past frame and the assert above
@ -264,14 +285,8 @@ impl Plugin for RenderPlugin {
}
}
{
#[cfg(feature = "trace")]
let _stage_span =
bevy_utils::tracing::info_span!("stage", name = "extract").entered();
// extract
extract(app_world, render_app);
}
// run extract schedule
extract(main_world, render_app);
}));
}
@ -288,49 +303,37 @@ impl Plugin for RenderPlugin {
.register_type::<primitives::CubemapFrusta>()
.register_type::<primitives::Frustum>();
}
fn setup(&self, app: &mut App) {
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
// move the extract stage to a resource so render_app.run() does not run it.
let stage = render_app
.schedule
.remove_stage(RenderStage::Extract)
.unwrap()
.downcast::<SystemStage>()
.unwrap();
render_app.world.insert_resource(ExtractStage(*stage));
}
}
}
/// A "scratch" world used to avoid allocating new worlds every frame when
/// swapping out the [`MainWorld`] for [`RenderStage::Extract`].
/// swapping out the [`MainWorld`] for [`ExtractSchedule`].
#[derive(Resource, Default)]
struct ScratchMainWorld(World);
/// Executes the [`Extract`](RenderStage::Extract) stage of the renderer.
/// Executes the [`ExtractSchedule`] step of the renderer.
/// This updates the render world with the extracted ECS data of the current frame.
fn extract(app_world: &mut World, render_app: &mut App) {
render_app
.world
.resource_scope(|render_world, mut extract_stage: Mut<ExtractStage>| {
fn extract(main_world: &mut World, render_app: &mut App) {
// temporarily add the app world to the render world as a resource
let scratch_world = app_world.remove_resource::<ScratchMainWorld>().unwrap();
let inserted_world = std::mem::replace(app_world, scratch_world.0);
render_world.insert_resource(MainWorld(inserted_world));
let scratch_world = main_world.remove_resource::<ScratchMainWorld>().unwrap();
let inserted_world = std::mem::replace(main_world, scratch_world.0);
render_app.world.insert_resource(MainWorld(inserted_world));
render_app.world.run_schedule(ExtractSchedule);
extract_stage.0.run(render_world);
// move the app world back, as if nothing happened.
let inserted_world = render_world.remove_resource::<MainWorld>().unwrap();
let scratch_world = std::mem::replace(app_world, inserted_world.0);
app_world.insert_resource(ScratchMainWorld(scratch_world));
});
let inserted_world = render_app.world.remove_resource::<MainWorld>().unwrap();
let scratch_world = std::mem::replace(main_world, inserted_world.0);
main_world.insert_resource(ScratchMainWorld(scratch_world));
}
// system for render app to apply the extract commands
fn apply_extract_commands(world: &mut World) {
world.resource_scope(|world, mut extract_stage: Mut<ExtractStage>| {
extract_stage.0.apply_buffers(world);
/// Applies the commands from the extract schedule. This happens during
/// the render schedule rather than during extraction to allow the commands to run in parallel with the
/// main app when pipelined rendering is enabled.
fn apply_extract_commands(render_world: &mut World) {
render_world.resource_scope(|render_world, mut schedules: Mut<Schedules>| {
schedules
.get_mut(&ExtractSchedule)
.unwrap()
.apply_system_buffers(render_world);
});
}

View file

@ -1,8 +1,7 @@
use async_channel::{Receiver, Sender};
use bevy_app::{App, AppLabel, Plugin, SubApp};
use bevy_app::{App, AppLabel, CoreSchedule, Plugin, SubApp};
use bevy_ecs::{
schedule::{StageLabel, SystemStage},
schedule_v3::MainThreadExecutor,
system::Resource,
world::{Mut, World},
@ -12,18 +11,12 @@ use bevy_tasks::ComputeTaskPool;
use crate::RenderApp;
/// A Label for the sub app that runs the parts of pipelined rendering that need to run on the main thread.
///
/// The Main schedule of this app can be used to run logic after the render schedule starts, but
/// before I/O processing. This can be useful for something like frame pacing.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)]
pub struct RenderExtractApp;
/// Labels for stages in the [`RenderExtractApp`] sub app. These will run after rendering has started.
#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)]
pub enum RenderExtractStage {
/// When pipelined rendering is enabled this stage runs after the render schedule starts, but
/// before I/O processing and the main app schedule. This can be useful for something like
/// frame pacing.
BeforeIoAfterRenderStart,
}
/// Channel to send the render app from the main thread to the rendering thread
#[derive(Resource)]
pub struct MainToRenderAppSender(pub Sender<SubApp>);
@ -50,20 +43,20 @@ pub struct RenderToMainAppReceiver(pub Receiver<SubApp>);
/// A single frame of execution looks something like below
///
/// ```text
/// |-------------------------------------------------------------------|
/// | | BeforeIoAfterRenderStart | winit events | main schedule |
/// | extract |---------------------------------------------------------|
/// |--------------------------------------------------------------------|
/// | | RenderExtractApp schedule | winit events | main schedule |
/// | extract |----------------------------------------------------------|
/// | | extract commands | rendering schedule |
/// |-------------------------------------------------------------------|
/// |--------------------------------------------------------------------|
/// ```
///
/// - `extract` is the stage where data is copied from the main world to the render world.
/// - `extract` is the step where data is copied from the main world to the render world.
/// This is run on the main app's thread.
/// - On the render thread, we first apply the `extract commands`. This is not run during extract, so the
/// main schedule can start sooner.
/// - Then the `rendering schedule` is run. See [`crate::RenderStage`] for the available stages.
/// - In parallel to the rendering thread we first run the [`RenderExtractStage::BeforeIoAfterRenderStart`] stage. By
/// default this stage is empty. But is useful if you need something to run before I/O processing.
/// - Then the `rendering schedule` is run. See [`RenderSet`](crate::RenderSet) for the standard steps in this process.
/// - In parallel to the rendering thread the [`RenderExtractApp`] schedule runs. By
/// default this schedule is empty. But it is useful if you need something to run before I/O processing.
/// - Next all the `winit events` are processed.
/// - And finally the `main app schedule` is run.
/// - Once both the `main app schedule` and the `render schedule` are finished running, `extract` is run again.
@ -79,10 +72,8 @@ impl Plugin for PipelinedRenderingPlugin {
app.insert_resource(MainThreadExecutor::new());
let mut sub_app = App::empty();
sub_app.add_stage(
RenderExtractStage::BeforeIoAfterRenderStart,
SystemStage::parallel(),
);
sub_app.add_simple_outer_schedule();
sub_app.init_schedule(CoreSchedule::Main);
app.insert_sub_app(RenderExtractApp, SubApp::new(sub_app, update_rendering));
}

View file

@ -1,4 +1,4 @@
use crate::{Extract, RenderApp, RenderStage};
use crate::{Extract, ExtractSchedule, RenderApp, RenderSet};
use bevy_app::{App, Plugin};
use bevy_asset::{Asset, AssetEvent, Assets, Handle};
use bevy_derive::{Deref, DerefMut};
@ -15,12 +15,12 @@ pub enum PrepareAssetError<E: Send + Sync + 'static> {
/// Describes how an asset gets extracted and prepared for rendering.
///
/// In the [`RenderStage::Extract`](crate::RenderStage::Extract) step the asset is transferred
/// from the "app world" into the "render world".
/// In the [`ExtractSchedule`](crate::ExtractSchedule) step the asset is transferred
/// from the "main world" into the "render world".
/// Therefore it is converted into a [`RenderAsset::ExtractedAsset`], which may be the same type
/// as the render asset itself.
///
/// After that in the [`RenderStage::Prepare`](crate::RenderStage::Prepare) step the extracted asset
/// After that in the [`RenderSet::Prepare`](crate::RenderSet::Prepare) step the extracted asset
/// is transformed into its GPU-representation of type [`RenderAsset::PreparedAsset`].
pub trait RenderAsset: Asset {
/// The representation of the asset in the "render world".
@ -40,7 +40,7 @@ pub trait RenderAsset: Asset {
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>>;
}
#[derive(Clone, Hash, Debug, Default, PartialEq, Eq, SystemLabel)]
#[derive(Clone, Hash, Debug, Default, PartialEq, Eq, SystemSet)]
pub enum PrepareAssetLabel {
PreAssetPrepare,
#[default]
@ -51,8 +51,8 @@ pub enum PrepareAssetLabel {
/// This plugin extracts the changed assets from the "app world" into the "render world"
/// and prepares them for the GPU. They can then be accessed from the [`RenderAssets`] resource.
///
/// Therefore it sets up the [`RenderStage::Extract`](crate::RenderStage::Extract) and
/// [`RenderStage::Prepare`](crate::RenderStage::Prepare) steps for the specified [`RenderAsset`].
/// Therefore it sets up the [`ExtractSchedule`](crate::ExtractSchedule) and
/// [`RenderSet::Prepare`](crate::RenderSet::Prepare) steps for the specified [`RenderAsset`].
pub struct RenderAssetPlugin<A: RenderAsset> {
prepare_asset_label: PrepareAssetLabel,
phantom: PhantomData<fn() -> A>,
@ -79,7 +79,7 @@ impl<A: RenderAsset> Default for RenderAssetPlugin<A> {
impl<A: RenderAsset> Plugin for RenderAssetPlugin<A> {
fn build(&self, app: &mut App) {
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
let prepare_asset_system = prepare_assets::<A>.label(self.prepare_asset_label.clone());
let prepare_asset_system = prepare_assets::<A>.in_set(self.prepare_asset_label.clone());
let prepare_asset_system = match self.prepare_asset_label {
PrepareAssetLabel::PreAssetPrepare => prepare_asset_system,
@ -95,8 +95,8 @@ impl<A: RenderAsset> Plugin for RenderAssetPlugin<A> {
.init_resource::<ExtractedAssets<A>>()
.init_resource::<RenderAssets<A>>()
.init_resource::<PrepareNextFrameAssets<A>>()
.add_system_to_stage(RenderStage::Extract, extract_render_asset::<A>)
.add_system_to_stage(RenderStage::Prepare, prepare_asset_system);
.add_system_to_schedule(ExtractSchedule, extract_render_asset::<A>)
.add_system(prepare_asset_system.in_set(RenderSet::Prepare));
}
}
}

View file

@ -10,11 +10,11 @@
//!
//! To draw an entity, a corresponding [`PhaseItem`] has to be added to one or multiple of these
//! render phases for each view that it is visible in.
//! This must be done in the [`RenderStage::Queue`](crate::RenderStage::Queue).
//! This must be done in the [`RenderSet::Queue`](crate::RenderSet::Queue).
//! After that the render phase sorts them in the
//! [`RenderStage::PhaseSort`](crate::RenderStage::PhaseSort).
//! [`RenderSet::PhaseSort`](crate::RenderSet::PhaseSort).
//! Finally the items are rendered using a single [`TrackedRenderPass`], during the
//! [`RenderStage::Render`](crate::RenderStage::Render).
//! [`RenderSet::Render`](crate::RenderSet::Render).
//!
//! Therefore each phase item is assigned a [`Draw`] function.
//! These set up the state of the [`TrackedRenderPass`] (i.e. select the
@ -122,13 +122,13 @@ impl<I: BatchedPhaseItem> RenderPhase<I> {
/// as part of a [`RenderPhase`].
///
/// The data required for rendering an entity is extracted from the main world in the
/// [`RenderStage::Extract`](crate::RenderStage::Extract).
/// [`ExtractSchedule`](crate::ExtractSchedule).
/// Then it has to be queued up for rendering during the
/// [`RenderStage::Queue`](crate::RenderStage::Queue), by adding a corresponding phase item to
/// [`RenderSet::Queue`](crate::RenderSet::Queue), by adding a corresponding phase item to
/// a render phase.
/// Afterwards it will be sorted and rendered automatically in the
/// [`RenderStage::PhaseSort`](crate::RenderStage::PhaseSort) and
/// [`RenderStage::Render`](crate::RenderStage::Render), respectively.
/// [`RenderSet::PhaseSort`](crate::RenderSet::PhaseSort) and
/// [`RenderSet::Render`](crate::RenderSet::Render), respectively.
pub trait PhaseItem: Sized + Send + Sync + 'static {
/// The type used for ordering the items. The smallest values are drawn first.
/// This order can be calculated using the [`ViewRangefinder3d`],

View file

@ -330,13 +330,13 @@ impl LayoutCache {
/// The cache stores existing render and compute pipelines allocated on the GPU, as well as
/// pending creation. Pipelines inserted into the cache are identified by a unique ID, which
/// can be used to retrieve the actual GPU object once it's ready. The creation of the GPU
/// pipeline object is deferred to the [`RenderStage::Render`] stage, just before the render
/// pipeline object is deferred to the [`RenderSet::Render`] step, just before the render
/// graph starts being processed, as this requires access to the GPU.
///
/// Note that the cache do not perform automatic deduplication of identical pipelines. It is
/// up to the user not to insert the same pipeline twice to avoid wasting GPU resources.
///
/// [`RenderStage::Render`]: crate::RenderStage::Render
/// [`RenderSet::Render`]: crate::RenderSet::Render
#[derive(Resource)]
pub struct PipelineCache {
layout_cache: LayoutCache,
@ -630,10 +630,10 @@ impl PipelineCache {
/// Process the pipeline queue and create all pending pipelines if possible.
///
/// This is generally called automatically during the [`RenderStage::Render`] stage, but can
/// This is generally called automatically during the [`RenderSet::Render`] step, but can
/// be called manually to force creation at a different time.
///
/// [`RenderStage::Render`]: crate::RenderStage::Render
/// [`RenderSet::Render`]: crate::RenderSet::Render
pub fn process_queue(&mut self) {
let mut waiting_pipelines = mem::take(&mut self.waiting_pipelines);
let mut pipelines = mem::take(&mut self.pipelines);

View file

@ -29,10 +29,11 @@ pub use texture_cache::*;
use crate::{
render_asset::{PrepareAssetLabel, RenderAssetPlugin},
renderer::RenderDevice,
RenderApp, RenderStage,
RenderApp, RenderSet,
};
use bevy_app::{App, Plugin};
use bevy_asset::{AddAsset, Assets};
use bevy_ecs::prelude::*;
// TODO: replace Texture names with Image names?
/// Adds the [`Image`] as an asset and makes sure that they are extracted and prepared for the GPU.
@ -104,7 +105,7 @@ impl Plugin for ImagePlugin {
.init_resource::<FallbackImage>()
.init_resource::<FallbackImageMsaaCache>()
.init_resource::<FallbackImageDepthCache>()
.add_system_to_stage(RenderStage::Cleanup, update_texture_cache_system);
.add_system(update_texture_cache_system.in_set(RenderSet::Cleanup));
}
}
}

View file

@ -13,7 +13,7 @@ use crate::{
render_resource::{DynamicUniformBuffer, ShaderType, Texture, TextureView},
renderer::{RenderDevice, RenderQueue},
texture::{BevyDefault, TextureCache},
RenderApp, RenderStage,
RenderApp, RenderSet,
};
use bevy_app::{App, Plugin};
use bevy_ecs::prelude::*;
@ -45,10 +45,15 @@ impl Plugin for ViewPlugin {
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<ViewUniforms>()
.add_system_to_stage(RenderStage::Prepare, prepare_view_uniforms)
.add_system_to_stage(
RenderStage::Prepare,
prepare_view_targets.after(WindowSystem::Prepare),
.add_system(
prepare_view_uniforms
.in_set(RenderSet::Prepare)
.in_set(ViewSet::PrepareUniforms),
)
.add_system(
prepare_view_targets
.after(WindowSystem::Prepare)
.in_set(RenderSet::Prepare),
);
}
}
@ -391,3 +396,10 @@ fn prepare_view_targets(
}
}
}
/// System sets for the [`view`](crate::view) module.
#[derive(SystemSet, PartialEq, Eq, Hash, Debug, Clone)]
pub enum ViewSet {
/// Prepares view uniforms
PrepareUniforms,
}

View file

@ -2,7 +2,7 @@ mod render_layers;
pub use render_layers::*;
use bevy_app::{CoreStage, Plugin};
use bevy_app::{CoreSet, Plugin};
use bevy_asset::{Assets, Handle};
use bevy_ecs::prelude::*;
use bevy_hierarchy::{Children, Parent};
@ -91,8 +91,8 @@ impl ComputedVisibility {
/// Whether this entity is visible to something this frame. This is true if and only if [`Self::is_visible_in_hierarchy`] and [`Self::is_visible_in_view`]
/// are true. This is the canonical method to call to determine if an entity should be drawn.
/// This value is updated in [`CoreStage::PostUpdate`] during the [`VisibilitySystems::CheckVisibility`] system label. Reading it from the
/// [`CoreStage::Update`] stage will yield the value from the previous frame.
/// This value is updated in [`CoreSet::PostUpdate`] during the [`VisibilitySystems::CheckVisibility`] system label.
/// Reading it during [`CoreSet::Update`] will yield the value from the previous frame.
#[inline]
pub fn is_visible(&self) -> bool {
self.flags.bits == ComputedVisibilityFlags::all().bits
@ -100,8 +100,7 @@ impl ComputedVisibility {
/// Whether this entity is visible in the entity hierarchy, which is determined by the [`Visibility`] component.
/// This takes into account "visibility inheritance". If any of this entity's ancestors (see [`Parent`]) are hidden, this entity
/// will be hidden as well. This value is updated in the [`CoreStage::PostUpdate`] stage in the
/// [`VisibilitySystems::VisibilityPropagate`] system label.
/// will be hidden as well. This value is updated in the [`VisibilitySystems::VisibilityPropagate`], which lives under the [`CoreSet::PostUpdate`] set.
#[inline]
pub fn is_visible_in_hierarchy(&self) -> bool {
self.flags
@ -111,9 +110,9 @@ impl ComputedVisibility {
/// Whether this entity is visible in _any_ view (Cameras, Lights, etc). Each entity type (and view type) should choose how to set this
/// value. For cameras and drawn entities, this will take into account [`RenderLayers`].
///
/// This value is reset to `false` every frame in [`VisibilitySystems::VisibilityPropagate`] during [`CoreStage::PostUpdate`].
/// Each entity type then chooses how to set this field in the [`CoreStage::PostUpdate`] stage in the
/// [`VisibilitySystems::CheckVisibility`] system label. Meshes might use frustum culling to decide if they are visible in a view.
/// This value is reset to `false` every frame in [`VisibilitySystems::VisibilityPropagate`] during [`CoreSet::PostUpdate`].
/// Each entity type then chooses how to set this field in the [`VisibilitySystems::CheckVisibility`] system set, under [`CoreSet::PostUpdate`].
/// Meshes might use frustum culling to decide if they are visible in a view.
/// Other entities might just set this to `true` every frame.
#[inline]
pub fn is_visible_in_view(&self) -> bool {
@ -192,9 +191,10 @@ impl VisibleEntities {
}
}
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub enum VisibilitySystems {
CalculateBounds,
CalculateBoundsFlush,
UpdateOrthographicFrusta,
UpdatePerspectiveFrusta,
UpdateProjectionFrusta,
@ -210,14 +210,23 @@ impl Plugin for VisibilityPlugin {
fn build(&self, app: &mut bevy_app::App) {
use VisibilitySystems::*;
app.add_system_to_stage(
CoreStage::PostUpdate,
calculate_bounds.label(CalculateBounds).before_commands(),
app.configure_set(CalculateBounds.in_set(CoreSet::PostUpdate))
// We add an AABB component in CaclulateBounds, which must be ready on the same frame.
.add_system(apply_system_buffers.in_set(CalculateBoundsFlush))
.configure_set(
CalculateBoundsFlush
.after(CalculateBounds)
.in_set(CoreSet::PostUpdate),
)
.add_system_to_stage(
CoreStage::PostUpdate,
.configure_set(UpdateOrthographicFrusta.in_set(CoreSet::PostUpdate))
.configure_set(UpdatePerspectiveFrusta.in_set(CoreSet::PostUpdate))
.configure_set(UpdateProjectionFrusta.in_set(CoreSet::PostUpdate))
.configure_set(CheckVisibility.in_set(CoreSet::PostUpdate))
.configure_set(VisibilityPropagate.in_set(CoreSet::PostUpdate))
.add_system(calculate_bounds.in_set(CalculateBounds))
.add_system(
update_frusta::<OrthographicProjection>
.label(UpdateOrthographicFrusta)
.in_set(UpdateOrthographicFrusta)
.after(camera_system::<OrthographicProjection>)
.after(TransformSystem::TransformPropagate)
// We assume that no camera will have more than one projection component,
@ -226,10 +235,9 @@ impl Plugin for VisibilityPlugin {
.ambiguous_with(update_frusta::<PerspectiveProjection>)
.ambiguous_with(update_frusta::<Projection>),
)
.add_system_to_stage(
CoreStage::PostUpdate,
.add_system(
update_frusta::<PerspectiveProjection>
.label(UpdatePerspectiveFrusta)
.in_set(UpdatePerspectiveFrusta)
.after(camera_system::<PerspectiveProjection>)
.after(TransformSystem::TransformPropagate)
// We assume that no camera will have more than one projection component,
@ -237,21 +245,17 @@ impl Plugin for VisibilityPlugin {
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
.ambiguous_with(update_frusta::<Projection>),
)
.add_system_to_stage(
CoreStage::PostUpdate,
.add_system(
update_frusta::<Projection>
.label(UpdateProjectionFrusta)
.in_set(CoreSet::PostUpdate)
.after(camera_system::<Projection>)
.after(TransformSystem::TransformPropagate),
)
.add_system_to_stage(
CoreStage::PostUpdate,
visibility_propagate_system.label(VisibilityPropagate),
)
.add_system_to_stage(
CoreStage::PostUpdate,
.add_system(visibility_propagate_system.in_set(VisibilityPropagate))
.add_system(
check_visibility
.label(CheckVisibility)
.in_set(CheckVisibility)
.after(CalculateBoundsFlush)
.after(UpdateOrthographicFrusta)
.after(UpdatePerspectiveFrusta)
.after(UpdateProjectionFrusta)

View file

@ -1,7 +1,7 @@
use crate::{
render_resource::TextureView,
renderer::{RenderAdapter, RenderDevice, RenderInstance},
Extract, RenderApp, RenderStage,
Extract, ExtractSchedule, RenderApp, RenderSet,
};
use bevy_app::{App, Plugin};
use bevy_ecs::prelude::*;
@ -20,7 +20,7 @@ pub struct NonSendMarker;
pub struct WindowRenderPlugin;
#[derive(SystemLabel, Debug, Clone, PartialEq, Eq, Hash)]
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
pub enum WindowSystem {
Prepare,
}
@ -32,10 +32,11 @@ impl Plugin for WindowRenderPlugin {
.init_resource::<ExtractedWindows>()
.init_resource::<WindowSurfaces>()
.init_non_send_resource::<NonSendMarker>()
.add_system_to_stage(RenderStage::Extract, extract_windows)
.add_system_to_stage(
RenderStage::Prepare,
prepare_windows.label(WindowSystem::Prepare),
.add_system_to_schedule(ExtractSchedule, extract_windows)
.add_system(
prepare_windows
.in_set(WindowSystem::Prepare)
.in_set(RenderSet::Prepare),
);
}
}
@ -151,7 +152,7 @@ pub struct WindowSurfaces {
/// Creates and (re)configures window surfaces, and obtains a swapchain texture for rendering.
///
/// NOTE: `get_current_texture` in `prepare_windows` can take a long time if the GPU workload is
/// the performance bottleneck. This can be seen in profiles as multiple prepare-stage systems all
/// the performance bottleneck. This can be seen in profiles as multiple prepare-set systems all
/// taking an unusually long time to complete, and all finishing at about the same time as the
/// `prepare_windows` system. Improvements in bevy are planned to avoid this happening when it
/// should not but it will still happen as it is easy for a user to create a large GPU workload

View file

@ -36,9 +36,9 @@ impl Plugin for ScenePlugin {
.add_asset::<Scene>()
.init_asset_loader::<SceneLoader>()
.init_resource::<SceneSpawner>()
.add_system_to_stage(CoreStage::PreUpdate, scene_spawner_system.at_end())
.add_system(scene_spawner_system.in_set(CoreSet::Update))
// Systems `*_bundle_spawner` must run before `scene_spawner_system`
.add_system_to_stage(CoreStage::PreUpdate, scene_spawner);
.add_system(scene_spawner.in_set(CoreSet::PreUpdate));
}
}

Some files were not shown because too many files have changed in this diff Show more