diff --git a/.github/contributing/example_style_guide.md b/.github/contributing/example_style_guide.md index 8b6ee4c7af0cd..3c36e4e6be0d9 100644 --- a/.github/contributing/example_style_guide.md +++ b/.github/contributing/example_style_guide.md @@ -38,7 +38,7 @@ For more advice on writing examples, see the [relevant section](../../CONTRIBUTI 4. In Queries, prefer `With` 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 diff --git a/Cargo.toml b/Cargo.toml index dec2d5c128464..a95e4d531c1d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/benches/benches/bevy_ecs/components/archetype_updates.rs b/benches/benches/bevy_ecs/components/archetype_updates.rs index 8cb4c5552db0c..a7f8c2bc276a8 100644 --- a/benches/benches/bevy_ecs/components/archetype_updates.rs +++ b/benches/benches/bevy_ecs/components/archetype_updates.rs @@ -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(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, ); diff --git a/benches/benches/bevy_ecs/empty_archetypes.rs b/benches/benches/bevy_ecs/empty_archetypes.rs index 0db82700e691e..0d01bdcc32830 100644 --- a/benches/benches/bevy_ecs/empty_archetypes.rs +++ b/benches/benches/bevy_ecs/empty_archetypes.rs @@ -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); }) }, ); diff --git a/benches/benches/bevy_ecs/scheduling/mod.rs b/benches/benches/bevy_ecs/scheduling/mod.rs index 21ccc56c04f63..7a1ef42b05e4b 100644 --- a/benches/benches/bevy_ecs/scheduling/mod.rs +++ b/benches/benches/bevy_ecs/scheduling/mod.rs @@ -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, diff --git a/benches/benches/bevy_ecs/scheduling/run_criteria.rs b/benches/benches/bevy_ecs/scheduling/run_criteria.rs index a301a66417fcc..d185654cf6e08 100644 --- a/benches/benches/bevy_ecs/scheduling/run_criteria.rs +++ b/benches/benches/bevy_ecs/scheduling/run_criteria.rs @@ -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)); - 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); - }); - }); - } - 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))); + 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); + 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) -> ShouldRun { - res.0.into() + fn yes_with_resource(res: Res) -> 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); }); }); } diff --git a/benches/benches/bevy_ecs/scheduling/stages.rs b/benches/benches/bevy_ecs/scheduling/running_systems.rs similarity index 79% rename from benches/benches/bevy_ecs/scheduling/stages.rs rename to benches/benches/bevy_ecs/scheduling/running_systems.rs index fec9ba5eab305..27a22dce1b24b 100644 --- a/benches/benches/bevy_ecs/scheduling/stages.rs +++ b/benches/benches/bevy_ecs/scheduling/running_systems.rs @@ -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); }); }, ); diff --git a/benches/benches/bevy_ecs/scheduling/schedule.rs b/benches/benches/bevy_ecs/scheduling/schedule.rs index 55887fb643c53..75e0c2601d9b4 100644 --- a/benches/benches/bevy_ecs/scheduling/schedule.rs +++ b/benches/benches/bevy_ecs/scheduling/schedule.rs @@ -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 { + 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); } diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 3ba89d0a6256c..0e8cbb6a49675 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -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::() .register_asset_reflect::() .register_type::() - .add_system_to_stage( - CoreStage::PostUpdate, - animation_player.before(TransformSystem::TransformPropagate), + .add_system( + animation_player + .in_set(CoreSet::PostUpdate) + .before(TransformSystem::TransformPropagate), ); } } diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 7864a87997d8a..38e41e73ed108 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -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, // 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, plugin_registry: Vec>, plugin_name_added: HashSet, @@ -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| { +/// +/// // initialize main schedule +/// sub_app.init_schedule(CoreSchedule::Main); +/// sub_app.add_system(|counter: Res| { /// // 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::().0 = main_world.resource::().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::(); - app.add_default_stages().add_event::(); + app.add_default_schedules(); + + app.add_event::(); #[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`] and [`App::add_defaults_sets`]. 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::(); 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) 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`]. - /// - /// # Examples - /// - /// ``` - /// # 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(&mut self, label: impl StageLabel, stage: S) -> &mut Self { - self.schedule.add_stage(label, stage); - 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( - &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( - &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(&mut self, label: impl StageLabel, stage: S) -> &mut Self { - self.schedule - .stage(StartupSchedule, |schedule: &mut Schedule| { - schedule.add_stage(label, stage) - }); - self - } + /// Adds [`State`] and [`NextState`] resources, [`OnEnter`] and [`OnExit`] schedules + /// for each state variant, an instance of [`apply_state_transition::`] in + /// [`CoreSet::StateTransitions`] so that transitions happen before [`CoreSet::Update`] and + /// a instance of [`run_enter_schedule::`] in [`CoreSet::StateTransitions`] with a + /// with a [`run_once`](`run_once_condition`) condition to run the on enter schedule of the + /// initial state. + /// + /// 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`] resource matches their label. + /// + /// 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(&mut self) -> &mut Self { + self.init_resource::>(); + self.init_resource::>(); + self.add_systems( + ( + run_enter_schedule::.run_if(run_once_condition()), + apply_state_transition::, + ) + .chain() + .in_set(CoreSet::StateTransitions), + ); - /// 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( - &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 - } + 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::), + ); + } - /// 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( - &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 - } + // 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()); + } - /// 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 &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(&mut self, system: impl IntoSystemDescriptor) -> &mut Self { - self.add_system_to_stage(CoreStage::Update, system) + pub fn add_system

(&mut self, system: impl IntoSystemConfig

) -> &mut Self { + let mut schedules = self.world.resource_mut::(); + + 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.") + } + + self } - /// Adds a [`SystemSet`] to the [update stage](Self::add_default_stages). + /// 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

(&mut self, systems: impl IntoSystemConfigs

) -> &mut Self { + let mut schedules = self.world.resource_mut::(); + + 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.") + } + + self } - /// 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( + /// Adds a system to the provided [`Schedule`]. + pub fn add_system_to_schedule

( &mut self, - stage_label: impl StageLabel, - system: impl IntoSystemDescriptor, + schedule_label: impl ScheduleLabel, + system: impl IntoSystemConfig

, ) -> &mut Self { - use std::any::TypeId; - assert!( - stage_label.type_id() != TypeId::of::(), - "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); + let mut schedules = self.world.resource_mut::(); + + 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 [`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 collection of system to the provided [`Schedule`]. + pub fn add_systems_to_schedule

( &mut self, - stage_label: impl StageLabel, - system_set: SystemSet, + schedule_label: impl ScheduleLabel, + systems: impl IntoSystemConfigs

, ) -> &mut Self { - use std::any::TypeId; - assert!( - stage_label.type_id() != TypeId::of::(), - "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::(); + + 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 the [startup stage](Self::add_default_stages) of the app's [`Schedule`]. + /// 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( - &mut self, - system: impl IntoSystemDescriptor, - ) -> &mut Self { - self.add_startup_system_to_stage(StartupStage::Startup, system) + pub fn add_startup_system

(&mut self, system: impl IntoSystemConfig

) -> &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,155 +476,84 @@ 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

(&mut self, systems: impl IntoSystemConfigs

) -> &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( - &mut self, - stage_label: impl StageLabel, - system: impl IntoSystemDescriptor, - ) -> &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::() + .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::() + .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` resource and adds a new "driver" to [`CoreStage::Update`]. - /// Each stage that uses `State` 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(&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` resource and adds a new "driver" to the given stage. - /// Each stage that uses `State` 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(&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::::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 - /// - /// 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: + /// The schedules added are defined in the [`CoreSchedule`] enum, + /// and have a starting configuration defined by: /// - /// - **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::() + /// .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`. @@ -788,7 +580,7 @@ impl App { { if !self.world.contains_resource::>() { self.init_resource::>() - .add_system_to_stage(CoreStage::First, Events::::update_system); + .add_system(Events::::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.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::(); + 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.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::()?; + // 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::(); + + 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) { diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index d908c420ed0cc..1d565f886bf38 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -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. +/// 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 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) { + 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`] 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_sets`]. +/// +/// 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 [`Stage`](bevy_ecs::schedule::Stage) that runs before [`CoreStage::Update`]. + /// The copy of [`apply_system_buffers`] that runs immediately after `First`. + FirstFlush, + /// Runs before [`CoreSet::Update`]. PreUpdate, - /// The [`Stage`](bevy_ecs::schedule::Stage) responsible for doing most app logic. Systems should be registered here by default. + /// 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 [`Stage`](bevy_ecs::schedule::Stage) that runs after [`CoreStage::Update`]. + /// The copy of [`apply_system_buffers`] that runs immediately after `Update`. + UpdateFlush, + /// Runs after [`CoreSet::Update`]. PostUpdate, - /// The [`Stage`](bevy_ecs::schedule::Stage) that runs after all other app stages. + /// 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 label for the startup [`Schedule`](bevy_ecs::schedule::Schedule), -/// which runs once at the beginning of the [`App`]. +/// 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_sets`]. +/// +/// 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. /// -/// 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`]. +/// [`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 [`Stage`](bevy_ecs::schedule::Stage) that runs once when an [`App`] starts up. + /// The copy of [`apply_system_buffers`] that runs immediately after `PreStartup`. + PreStartupFlush, + /// Runs once when an [`App`] starts up. Startup, - /// The [`Stage`](bevy_ecs::schedule::Stage) that runs once after [`StartupStage::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 + } } diff --git a/crates/bevy_app/src/schedule_runner.rs b/crates/bevy_app/src/schedule_runner.rs index 28d39baffd2ff..d03ca788e7799 100644 --- a/crates/bevy_app/src/schedule_runner.rs +++ b/crates/bevy_app/src/schedule_runner.rs @@ -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, }, @@ -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. diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index 41c91fe7c7f4f..7f29d3ee99499 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -644,7 +644,7 @@ pub fn free_unused_assets_system(asset_server: Res) { 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::(); - #[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::.after(FreeUnusedAssets)); + app.add_system( + free_unused_assets_system + .in_set(FreeUnusedAssets) + .in_set(CoreSet::Update), + ); + app.add_system( + update_asset_storage_system:: + .after(FreeUnusedAssets) + .in_set(CoreSet::Update), + ); fn load_asset(path: AssetPath, world: &World) -> HandleUntyped { let asset_server = world.resource::(); diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index 1a3a300553990..a359eff7487a5 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -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::::asset_event_system) - .add_system_to_stage(AssetStage::LoadAssets, update_asset_storage_system::) + .add_system(Assets::::asset_event_system.in_set(AssetSet::AssetEvents)) + .add_system(update_asset_storage_system::.in_set(AssetSet::LoadAssets)) .register_type::>() .add_event::>() } @@ -364,7 +360,9 @@ impl AddAsset for App { { #[cfg(feature = "debug_asset_server")] { - self.add_system(crate::debug_asset_server::sync_debug_assets::); + self.add_system( + crate::debug_asset_server::sync_debug_assets::.in_set(bevy_app::CoreSet::Update), + ); let mut app = self .world .non_send_resource_mut::(); diff --git a/crates/bevy_asset/src/debug_asset_server.rs b/crates/bevy_asset/src/debug_asset_server.rs index e8809d5269c58..47fd13422d8b6 100644 --- a/crates/bevy_asset/src/debug_asset_server.rs +++ b/crates/bevy_asset/src/debug_asset_server.rs @@ -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)); } } diff --git a/crates/bevy_asset/src/diagnostic/asset_count_diagnostics_plugin.rs b/crates/bevy_asset/src/diagnostic/asset_count_diagnostics_plugin.rs index 470f3503e15cf..bbcb712ab1d8b 100644 --- a/crates/bevy_asset/src/diagnostic/asset_count_diagnostics_plugin.rs +++ b/crates/bevy_asset/src/diagnostic/asset_count_diagnostics_plugin.rs @@ -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 { @@ -18,8 +18,8 @@ impl Default for AssetCountDiagnosticsPlugin { impl Plugin for AssetCountDiagnosticsPlugin { 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)); } } diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index d0535fa6d2102..c1fe5bef836e6 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -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::(); + + 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::() - .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)); } } diff --git a/crates/bevy_audio/src/lib.rs b/crates/bevy_audio/src/lib.rs index eb7e3b3222901..03aac688a3c4f 100644 --- a/crates/bevy_audio/src/lib.rs +++ b/crates/bevy_audio/src/lib.rs @@ -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::() .add_asset::() .init_resource::>() - .add_system_to_stage( - CoreStage::PostUpdate, - play_queued_audio_system::, - ); + .add_system(play_queued_audio_system::.in_set(CoreSet::PostUpdate)); #[cfg(any(feature = "mp3", feature = "flac", feature = "wav", feature = "vorbis"))] app.init_asset_loader::(); @@ -73,6 +71,6 @@ impl AddAudioSource for App { self.add_asset::() .init_resource::>() .init_resource::>() - .add_system_to_stage(CoreStage::PostUpdate, play_queued_audio_system::) + .add_system(play_queued_audio_system::.in_set(CoreSet::PostUpdate)) } } diff --git a/crates/bevy_core/src/lib.rs b/crates/bevy_core/src/lib.rs index eb0f7007229aa..caa60d4b9f2c6 100644 --- a/crates/bevy_core/src/lib.rs +++ b/crates/bevy_core/src/lib.rs @@ -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>) { + 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::(); - app.add_system_to_stage(CoreStage::Last, update_frame_count); + app.add_system(update_frame_count.in_set(CoreSet::Last)); } } diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs index 93d7090846f7f..e54c1640ed56b 100644 --- a/crates/bevy_core_pipeline/src/bloom/mod.rs +++ b/crates/bevy_core_pipeline/src/bloom/mod.rs @@ -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::() - .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); diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index f391a49496a04..f02c78cf6d0f6 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -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::>() - .add_system_to_stage(RenderStage::Extract, extract_core_2d_camera_phases) - .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) - .add_system_to_stage( - RenderStage::PhaseSort, - batch_phase_system::.after(sort_phase_system::), + .add_system_to_schedule(ExtractSchedule, extract_core_2d_camera_phases) + .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) + .add_system( + batch_phase_system:: + .after(sort_phase_system::) + .in_set(RenderSet::PhaseSort), ); let pass_node_2d = MainPass2dNode::new(&mut render_app.world); diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 78bf4b758a0f9..d69f6c17b6d78 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -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::>() .init_resource::>() .init_resource::>() - .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::) - .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) - .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::); + .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::.in_set(RenderSet::PhaseSort)) + .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) + .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)); let prepass_node = PrepassNode::new(&mut render_app.world); let pass_node_3d = MainPass3dNode::new(&mut render_app.world); diff --git a/crates/bevy_core_pipeline/src/fxaa/mod.rs b/crates/bevy_core_pipeline/src/fxaa/mod.rs index 58ebf04d511e6..8263de32f66af 100644 --- a/crates/bevy_core_pipeline/src/fxaa/mod.rs +++ b/crates/bevy_core_pipeline/src/fxaa/mod.rs @@ -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::() .init_resource::>() - .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); diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs index 9839e07baf5be..bf136f978c2e3 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/mod.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -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::() .init_resource::>() - .add_system_to_stage(RenderStage::Queue, queue_view_tonemapping_pipelines); + .add_system(queue_view_tonemapping_pipelines.in_set(RenderSet::Queue)); } } } diff --git a/crates/bevy_core_pipeline/src/upscaling/mod.rs b/crates/bevy_core_pipeline/src/upscaling/mod.rs index a25e10632cd90..b16dacab09c60 100644 --- a/crates/bevy_core_pipeline/src/upscaling/mod.rs +++ b/crates/bevy_core_pipeline/src/upscaling/mod.rs @@ -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::() .init_resource::>() - .add_system_to_stage(RenderStage::Queue, queue_view_upscaling_pipelines); + .add_system(queue_view_upscaling_pipelines.in_set(RenderSet::Queue)); } } } diff --git a/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs index 94e10cb37dbfb..d8206fa4ad580 100644 --- a/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs @@ -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)); } } diff --git a/crates/bevy_diagnostic/src/frame_time_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/frame_time_diagnostics_plugin.rs index ef31006cd2b6b..9f82d64727e28 100644 --- a/crates/bevy_diagnostic/src/frame_time_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/frame_time_diagnostics_plugin.rs @@ -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)); } } diff --git a/crates/bevy_diagnostic/src/lib.rs b/crates/bevy_diagnostic/src/lib.rs index b2d127f115600..22a8141f398c7 100644 --- a/crates/bevy_diagnostic/src/lib.rs +++ b/crates/bevy_diagnostic/src/lib.rs @@ -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::() - .add_startup_system(system_information_diagnostics_plugin::internal::log_system_info); + app.init_resource::().add_startup_system( + system_information_diagnostics_plugin::internal::log_system_info + .in_set(StartupSet::Startup), + ); } } diff --git a/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs index c0a06238396ed..dea8cd7676d8b 100644 --- a/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs @@ -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)); } } } diff --git a/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs index b2d104dd7c9ce..9bc56418de845 100644 --- a/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs @@ -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)); } } diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index cff59621cf887..15c8dd2bb3e51 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -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" diff --git a/crates/bevy_ecs/README.md b/crates/bevy_ecs/README.md index 8f9c6374fe4e2..ee9126e1b5fb7 100644 --- a/crates/bevy_ecs/README.md +++ b/crates/bevy_ecs/README.md @@ -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); diff --git a/crates/bevy_ecs/examples/change_detection.rs b/crates/bevy_ecs/examples/change_detection.rs index 0578c601f09e3..16baa6c1a07cb 100644 --- a/crates/bevy_ecs/examples/change_detection.rs +++ b/crates/bevy_ecs/examples/change_detection.rs @@ -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, diff --git a/crates/bevy_ecs/examples/derive_label.rs b/crates/bevy_ecs/examples/derive_label.rs deleted file mode 100644 index 573b42dc8153d..0000000000000 --- a/crates/bevy_ecs/examples/derive_label.rs +++ /dev/null @@ -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::::One.as_label(), - GenericLabel::::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 { - One, - #[system_label(ignore_fields)] - Two(PhantomData), -} - -// 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: (), -}*/ diff --git a/crates/bevy_ecs/examples/events.rs b/crates/bevy_ecs/examples/events.rs index b14a209823d60..b0f96e39a1421 100644 --- a/crates/bevy_ecs/examples/events.rs +++ b/crates/bevy_ecs/examples/events.rs @@ -7,31 +7,20 @@ fn main() { let mut world = World::new(); world.insert_resource(Events::::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. 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::::update_system); - schedule.add_stage(Stages::First, first); + // 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; - // 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)); + schedule.add_system(Events::::update_system.in_set(FlushEvents)); - // 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 { diff --git a/crates/bevy_ecs/examples/resources.rs b/crates/bevy_ecs/examples/resources.rs index d4874e98ff6c8..a3fd26fc290a5 100644 --- a/crates/bevy_ecs/examples/resources.rs +++ b/crates/bevy_ecs/examples/resources.rs @@ -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"); diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index d914f3488c430..86e40a8144a01 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -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 { diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 9973d0ccc1db2..6121de200144e 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -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`] diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 5a202277bf9da..f6b1ddc693e56 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -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, diff --git a/crates/bevy_ecs/src/schedule/ambiguity_detection.rs b/crates/bevy_ecs/src/schedule/ambiguity_detection.rs deleted file mode 100644 index 5e1269309154d..0000000000000 --- a/crates/bevy_ecs/src/schedule/ambiguity_detection.rs +++ /dev/null @@ -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, -} - -/// 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, - 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 { - 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)> { - // 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::::with_capacity(systems.len()); - let mut all_dependants = Vec::::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::>(); - 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) {} - fn resmut_system(_res: ResMut) {} - fn nonsend_system(_ns: NonSend) {} - fn nonsendmut_system(_ns: NonSendMut) {} - 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>) {} - fn without_filtered_component_system(_query: Query<&mut A, Without>) {} - fn event_reader_system(_reader: EventReader) {} - fn event_writer_system(_writer: EventWriter) {} - fn event_resource_system(_events: ResMut>) {} - 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::>(); - - 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::>(); - - 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::>(); - - 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::>(); - - 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::>(); - - 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::>(); - - 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) {} - fn system_b(_res: ResMut) {} - fn system_c(_res: ResMut) {} - fn system_d(_res: ResMut) {} - fn system_e(_res: ResMut) {} - - // 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, - }, - ] - ); - } -} diff --git a/crates/bevy_ecs/src/schedule/executor.rs b/crates/bevy_ecs/src/schedule/executor.rs deleted file mode 100644 index ef0cb45fb229d..0000000000000 --- a/crates/bevy_ecs/src/schedule/executor.rs +++ /dev/null @@ -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); - } - } - } -} diff --git a/crates/bevy_ecs/src/schedule/executor_parallel.rs b/crates/bevy_ecs/src/schedule/executor_parallel.rs deleted file mode 100644 index 9ecde8cca4f91..0000000000000 --- a/crates/bevy_ecs/src/schedule/executor_parallel.rs +++ /dev/null @@ -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, - /// 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, - /// Whether or not this system is send-able - is_send: bool, -} - -pub struct ParallelExecutor { - /// Cached metadata of every system. - system_metadata: Vec, - /// Used by systems to notify the executor that they have finished. - finish_sender: Sender, - /// Receives finish events from systems. - finish_receiver: Receiver, - /// 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, - /// Scratch space to avoid reallocating a vector when updating dependency counters. - dependants_scratch: Vec, - #[cfg(test)] - events_sender: Option>, -} - -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::(); - 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::() - .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, - ) -> 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); -} - -#[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); - #[derive(Resource, Default)] - struct Counter(usize); - - fn receive_events(world: &World) -> Vec { - let mut events = Vec::new(); - while let Ok(event) = world.resource::().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::(); - fn wants_mut(_: ResMut) {} - fn wants_ref(_: Res) {} - 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>) {} - fn wants_ref(_: Query<&W>) {} - 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, &W)>) {} - fn wants_mut_u32(_: Query<(&mut W, &W)>) {} - 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>) {} - 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) { - 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::::default()); - stage.run(&mut world); - } -} diff --git a/crates/bevy_ecs/src/schedule/graph_utils.rs b/crates/bevy_ecs/src/schedule/graph_utils.rs deleted file mode 100644 index 88cda8e2042dd..0000000000000 --- a/crates/bevy_ecs/src/schedule/graph_utils.rs +++ /dev/null @@ -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 { - 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( - nodes: &[Node], -) -> HashMap>> -where - Node: GraphNode, - Node::Label: Debug + Clone + Eq + Hash, -{ - let mut labels = HashMap::::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( - graph: &HashMap>, -) -> Result, DependencyGraphError> { - fn check_if_cycles_and_visit( - node: &usize, - graph: &HashMap>, - sorted: &mut Vec, - unvisited: &mut HashSet, - current: &mut Vec, - ) -> 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) -} diff --git a/crates/bevy_ecs/src/schedule/label.rs b/crates/bevy_ecs/src/schedule/label.rs deleted file mode 100644 index c2f4d1fbfab7e..0000000000000 --- a/crates/bevy_ecs/src/schedule/label.rs +++ /dev/null @@ -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, -); diff --git a/crates/bevy_ecs/src/schedule/mod.rs b/crates/bevy_ecs/src/schedule/mod.rs deleted file mode 100644 index bc78e8f0798ac..0000000000000 --- a/crates/bevy_ecs/src/schedule/mod.rs +++ /dev/null @@ -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>, - stage_order: Vec, - run_criteria: BoxedRunCriteria, -} - -impl Schedule { - /// Similar to [`add_stage`](Self::add_stage), but it also returns itself. - #[must_use] - pub fn with_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( - 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( - 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, 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( - mut self, - stage_label: impl StageLabel, - system: impl IntoSystemDescriptor, - ) -> Self { - self.add_system_to_stage(stage_label, system); - self - } - - pub fn set_run_criteria, 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(&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( - &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( - &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( - &mut self, - stage_label: impl StageLabel, - system: impl IntoSystemDescriptor, - ) -> &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::(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 &mut T>( - &mut self, - stage_label: impl StageLabel, - func: F, - ) -> &mut Self { - let label = stage_label.as_label(); - let stage = self.get_stage_mut::(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::(MyStage).unwrap(); - /// ``` - pub fn get_stage(&self, stage_label: impl StageLabel) -> Option<&T> { - let label = stage_label.as_label(); - self.stages - .get(&label) - .and_then(|stage| stage.downcast_ref::()) - } - - /// 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::(MyStage).unwrap(); - /// ``` - pub fn get_stage_mut(&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::()) - } - - /// Removes a [`Stage`] from the schedule. - pub fn remove_stage(&mut self, stage_label: impl StageLabel) -> Option> { - 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 { - 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.") - } - } - } - } -} diff --git a/crates/bevy_ecs/src/schedule/run_criteria.rs b/crates/bevy_ecs/src/schedule/run_criteria.rs deleted file mode 100644 index 8bfd84bf04c22..0000000000000 --- a/crates/bevy_ecs/src/schedule/run_criteria.rs +++ /dev/null @@ -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) -> ShouldRun { - if *ran { - ShouldRun::No - } else { - *ran = true; - ShouldRun::Yes - } - } -} - -impl From for ShouldRun { - fn from(value: bool) -> Self { - if value { - ShouldRun::Yes - } else { - ShouldRun::No - } - } -} - -#[derive(Debug, Default)] -pub(crate) struct BoxedRunCriteria { - criteria_system: Option>, - 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, - }, -} - -#[derive(Debug)] -pub(crate) struct RunCriteriaContainer { - pub(crate) should_run: ShouldRun, - pub(crate) inner: RunCriteriaInner, - pub(crate) label: Option, - pub(crate) before: Vec, - pub(crate) after: Vec, -} - -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, - pub(crate) duplicate_label_strategy: DuplicateLabelStrategy, - pub(crate) before: Vec, - pub(crate) after: Vec, -} - -#[derive(Debug)] -pub(crate) enum RunCriteriaSystem { - Single(BoxedSystem<(), ShouldRun>), - Piped(BoxedSystem), -} - -pub trait IntoRunCriteria { - fn into(self) -> RunCriteriaDescriptorOrLabel; -} - -impl IntoRunCriteria for RunCriteriaDescriptorOrLabel { - fn into(self) -> RunCriteriaDescriptorOrLabel { - self - } -} - -impl IntoRunCriteria for RunCriteriaDescriptor { - fn into(self) -> RunCriteriaDescriptorOrLabel { - RunCriteriaDescriptorOrLabel::Descriptor(self) - } -} - -impl IntoRunCriteria> for BoxedSystem<(), ShouldRun> { - fn into(self) -> RunCriteriaDescriptorOrLabel { - RunCriteriaDescriptorOrLabel::Descriptor(new_run_criteria_descriptor(self)) - } -} - -impl 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 IntoRunCriteria for L -where - L: RunCriteriaLabel, -{ - fn into(self) -> RunCriteriaDescriptorOrLabel { - RunCriteriaDescriptorOrLabel::Label(self.as_label()) - } -} - -impl IntoRunCriteria for RunCriteria { - fn into(self) -> RunCriteriaDescriptorOrLabel { - RunCriteriaDescriptorOrLabel::Label(self.label) - } -} - -pub trait RunCriteriaDescriptorCoercion { - /// 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 RunCriteriaDescriptorCoercion 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

( - label: impl RunCriteriaLabel, - system: impl IntoSystem, - ) -> 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 + '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 + '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 { - "" - } - }, - ) - } -} diff --git a/crates/bevy_ecs/src/schedule/stage.rs b/crates/bevy_ecs/src/schedule/stage.rs deleted file mode 100644 index 6efa2feeb7c37..0000000000000 --- a/crates/bevy_ecs/src/schedule/stage.rs +++ /dev/null @@ -1,1614 +0,0 @@ -use crate::{ - self as bevy_ecs, - change_detection::CHECK_TICK_THRESHOLD, - component::ComponentId, - prelude::IntoSystem, - schedule::{ - graph_utils::{self, DependencyGraphError}, - BoxedRunCriteria, DuplicateLabelStrategy, ExclusiveInsertionPoint, GraphNode, - ParallelExecutor, ParallelSystemExecutor, RunCriteriaContainer, RunCriteriaDescriptor, - RunCriteriaDescriptorOrLabel, RunCriteriaInner, RunCriteriaLabelId, ShouldRun, - SingleThreadedExecutor, SystemContainer, SystemDescriptor, SystemLabelId, SystemSet, - }, - world::{World, WorldId}, -}; -use bevy_ecs_macros::Resource; -use bevy_utils::{tracing::warn, HashMap, HashSet}; -use core::fmt::Debug; -use downcast_rs::{impl_downcast, Downcast}; - -use super::{IntoSystemDescriptor, Schedule}; - -/// A type that can run as a step of a [`Schedule`](super::Schedule). -pub trait Stage: Downcast + Send + Sync { - /// Runs the stage; this happens once per update. - /// Implementors must initialize all of their state and systems before running the first time. - fn run(&mut self, world: &mut World); -} - -impl Debug for dyn Stage { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Some(as_systemstage) = self.as_any().downcast_ref::() { - write!(f, "{as_systemstage:?}") - } else if let Some(as_schedule) = self.as_any().downcast_ref::() { - write!(f, "{as_schedule:?}") - } else { - write!(f, "Unknown dyn Stage") - } - } -} - -impl_downcast!(Stage); - -/// When this resource is present in the `App`'s `Resources`, -/// each `SystemStage` will log a report containing -/// pairs of systems with ambiguous execution order. -/// -/// Systems that access the same Component or Resource within the same stage -/// risk an ambiguous order that could result in logic bugs, unless they have an -/// explicit execution ordering constraint between them. -/// -/// This occurs because, in the absence of explicit constraints, systems are executed in -/// an unstable, arbitrary order within each stage that may vary between runs and frames. -/// -/// Some ambiguities reported by the ambiguity checker may be warranted (to allow two systems to run -/// without blocking each other) or spurious, as the exact combination of archetypes used may -/// prevent them from ever conflicting during actual gameplay. You can resolve the warnings produced -/// by the ambiguity checker by adding `.before` or `.after` to one of the conflicting systems -/// referencing the other system to force a specific ordering. -/// -/// The checker may report a system more times than the amount of constraints it would actually need -/// to have unambiguous order with regards to a group of already-constrained systems. -#[derive(Resource, Default)] -pub struct ReportExecutionOrderAmbiguities; - -/// Stores and executes systems. Execution order is not defined unless explicitly specified; -/// see `SystemDescriptor` documentation. -pub struct SystemStage { - /// The WorldId this stage was last run on. - world_id: Option, - /// Instance of a scheduling algorithm for running the systems. - executor: Box, - /// Determines whether the stage should run. - stage_run_criteria: BoxedRunCriteria, - /// Topologically sorted run criteria of systems. - run_criteria: Vec, - /// Topologically sorted exclusive systems that want to be run at the start of the stage. - pub(super) exclusive_at_start: Vec, - /// Topologically sorted exclusive systems that want to be run after parallel systems but - /// before the application of their command buffers. - pub(super) exclusive_before_commands: Vec, - /// Topologically sorted exclusive systems that want to be run at the end of the stage. - pub(super) exclusive_at_end: Vec, - /// Topologically sorted parallel systems. - pub(super) parallel: Vec, - /// Determines if the stage was modified and needs to rebuild its graphs and orders. - pub(super) systems_modified: bool, - /// Determines if the stage's executor was changed. - executor_modified: bool, - /// Newly inserted run criteria that will be initialized at the next opportunity. - uninitialized_run_criteria: Vec<(usize, DuplicateLabelStrategy)>, - /// Newly inserted systems that will be initialized at the next opportunity. - uninitialized_at_start: Vec, - /// Newly inserted systems that will be initialized at the next opportunity. - uninitialized_before_commands: Vec, - /// Newly inserted systems that will be initialized at the next opportunity. - uninitialized_at_end: Vec, - /// Newly inserted systems that will be initialized at the next opportunity. - uninitialized_parallel: Vec, - /// Saves the value of the World change_tick during the last tick check - last_tick_check: u32, - /// If true, buffers will be automatically applied at the end of the stage. If false, buffers must be manually applied. - apply_buffers: bool, - must_read_resource: Option, -} - -impl SystemStage { - pub fn new(executor: Box) -> Self { - SystemStage { - world_id: None, - executor, - stage_run_criteria: Default::default(), - run_criteria: vec![], - uninitialized_run_criteria: vec![], - exclusive_at_start: Default::default(), - exclusive_before_commands: Default::default(), - exclusive_at_end: Default::default(), - parallel: vec![], - systems_modified: true, - executor_modified: true, - uninitialized_parallel: vec![], - uninitialized_at_start: vec![], - uninitialized_before_commands: vec![], - uninitialized_at_end: vec![], - last_tick_check: Default::default(), - apply_buffers: true, - must_read_resource: None, - } - } - - pub fn single(system: impl IntoSystemDescriptor) -> Self { - Self::single_threaded().with_system(system) - } - - pub fn single_threaded() -> Self { - Self::new(Box::::default()) - } - - pub fn parallel() -> Self { - Self::new(Box::::default()) - } - - pub fn get_executor(&self) -> Option<&T> { - self.executor.downcast_ref() - } - - pub fn get_executor_mut(&mut self) -> Option<&mut T> { - self.executor_modified = true; - self.executor.downcast_mut() - } - - pub fn set_executor(&mut self, executor: Box) { - self.executor_modified = true; - self.executor = executor; - } - - pub fn set_must_read_resource(&mut self, resource_id: ComponentId) { - self.must_read_resource = Some(resource_id); - } - - #[must_use] - pub fn with_system(mut self, system: impl IntoSystemDescriptor) -> Self { - self.add_system(system); - self - } - - pub fn add_system(&mut self, system: impl IntoSystemDescriptor) -> &mut Self { - self.add_system_inner(system.into_descriptor(), None); - self - } - - fn add_system_inner( - &mut self, - mut descriptor: SystemDescriptor, - default_run_criteria: Option, - ) { - self.systems_modified = true; - if let Some(insertion_point) = descriptor.exclusive_insertion_point { - let criteria = descriptor.run_criteria.take(); - let mut container = SystemContainer::from_descriptor(descriptor); - match criteria { - Some(RunCriteriaDescriptorOrLabel::Label(label)) => { - container.run_criteria_label = Some(label); - } - Some(RunCriteriaDescriptorOrLabel::Descriptor(criteria_descriptor)) => { - container.run_criteria_label = criteria_descriptor.label; - container.run_criteria_index = - Some(self.add_run_criteria_internal(criteria_descriptor)); - } - None => { - container.run_criteria_index = default_run_criteria; - } - } - match insertion_point { - ExclusiveInsertionPoint::AtStart => { - let index = self.exclusive_at_start.len(); - self.uninitialized_at_start.push(index); - self.exclusive_at_start.push(container); - } - ExclusiveInsertionPoint::BeforeCommands => { - let index = self.exclusive_before_commands.len(); - self.uninitialized_before_commands.push(index); - self.exclusive_before_commands.push(container); - } - ExclusiveInsertionPoint::AtEnd => { - let index = self.exclusive_at_end.len(); - self.uninitialized_at_end.push(index); - self.exclusive_at_end.push(container); - } - } - } else { - let criteria = descriptor.run_criteria.take(); - let mut container = SystemContainer::from_descriptor(descriptor); - match criteria { - Some(RunCriteriaDescriptorOrLabel::Label(label)) => { - container.run_criteria_label = Some(label); - } - Some(RunCriteriaDescriptorOrLabel::Descriptor(criteria_descriptor)) => { - container.run_criteria_label = criteria_descriptor.label; - container.run_criteria_index = - Some(self.add_run_criteria_internal(criteria_descriptor)); - } - None => { - container.run_criteria_index = default_run_criteria; - } - } - self.uninitialized_parallel.push(self.parallel.len()); - self.parallel.push(container); - } - } - - pub fn apply_buffers(&mut self, world: &mut World) { - #[cfg(feature = "trace")] - let _span = bevy_utils::tracing::info_span!("stage::apply_buffers").entered(); - for container in &mut self.parallel { - container.system_mut().apply_buffers(world); - } - } - - pub fn set_apply_buffers(&mut self, apply_buffers: bool) { - self.apply_buffers = apply_buffers; - } - - /// Topologically sorted parallel systems. - /// - /// Note that systems won't be fully-formed until the stage has been run at least once. - pub fn parallel_systems(&self) -> &[SystemContainer] { - &self.parallel - } - - /// Topologically sorted exclusive systems that want to be run at the start of the stage. - /// - /// Note that systems won't be fully-formed until the stage has been run at least once. - pub fn exclusive_at_start_systems(&self) -> &[SystemContainer] { - &self.exclusive_at_start - } - - /// Topologically sorted exclusive systems that want to be run at the end of the stage. - /// - /// Note that systems won't be fully-formed until the stage has been run at least once. - pub fn exclusive_at_end_systems(&self) -> &[SystemContainer] { - &self.exclusive_at_end - } - - /// Topologically sorted exclusive systems that want to be run after parallel systems but - /// before the application of their command buffers. - /// - /// Note that systems won't be fully-formed until the stage has been run at least once. - pub fn exclusive_before_commands_systems(&self) -> &[SystemContainer] { - &self.exclusive_before_commands - } - - #[must_use] - pub fn with_system_set(mut self, system_set: SystemSet) -> Self { - self.add_system_set(system_set); - self - } - - pub fn add_system_set(&mut self, system_set: SystemSet) -> &mut Self { - self.systems_modified = true; - let (run_criteria, mut systems) = system_set.bake(); - let set_run_criteria_index = run_criteria.and_then(|criteria| { - // validate that no systems have criteria - for descriptor in &mut systems { - if let Some(name) = descriptor - .run_criteria - .is_some() - .then(|| descriptor.system.name()) - { - panic!( - "The system {name} has a run criteria, but its `SystemSet` also has a run \ - criteria. This is not supported. Consider moving the system into a \ - different `SystemSet` or calling `add_system()` instead." - ) - } - } - match criteria { - RunCriteriaDescriptorOrLabel::Descriptor(descriptor) => { - Some(self.add_run_criteria_internal(descriptor)) - } - RunCriteriaDescriptorOrLabel::Label(label) => { - for system in &mut systems { - system.run_criteria = Some(RunCriteriaDescriptorOrLabel::Label(label)); - } - - None - } - } - }); - for system in systems { - self.add_system_inner(system, set_run_criteria_index); - } - self - } - - #[must_use] - pub fn with_run_criteria>( - mut self, - system: S, - ) -> Self { - self.set_run_criteria(system); - self - } - - pub fn set_run_criteria>( - &mut self, - system: S, - ) -> &mut Self { - self.stage_run_criteria - .set(Box::new(IntoSystem::into_system(system))); - self - } - - #[must_use] - pub fn with_system_run_criteria(mut self, run_criteria: RunCriteriaDescriptor) -> Self { - self.add_system_run_criteria(run_criteria); - self - } - - pub fn add_system_run_criteria(&mut self, run_criteria: RunCriteriaDescriptor) -> &mut Self { - self.add_run_criteria_internal(run_criteria); - self - } - - pub(crate) fn add_run_criteria_internal(&mut self, descriptor: RunCriteriaDescriptor) -> usize { - let index = self.run_criteria.len(); - self.uninitialized_run_criteria - .push((index, descriptor.duplicate_label_strategy)); - - self.run_criteria - .push(RunCriteriaContainer::from_descriptor(descriptor)); - index - } - - fn initialize_systems(&mut self, world: &mut World) { - let mut criteria_labels = HashMap::default(); - let uninitialized_criteria: HashMap<_, _> = - self.uninitialized_run_criteria.drain(..).collect(); - // track the number of filtered criteria to correct run criteria indices - let mut filtered_criteria = 0; - let mut new_indices = Vec::new(); - self.run_criteria = self - .run_criteria - .drain(..) - .enumerate() - .filter_map(|(index, mut container)| { - let new_index = index - filtered_criteria; - let label = container.label; - if let Some(strategy) = uninitialized_criteria.get(&index) { - if let Some(ref label) = label { - if let Some(duplicate_index) = criteria_labels.get(label) { - match strategy { - DuplicateLabelStrategy::Panic => panic!( - "Run criteria {} is labelled with {:?}, which \ - is already in use. Consider using \ - `RunCriteriaDescriptorCoercion::label_discard_if_duplicate().", - container.name(), - container.label - ), - DuplicateLabelStrategy::Discard => { - new_indices.push(*duplicate_index); - filtered_criteria += 1; - return None; - } - } - } - } - container.initialize(world); - } - if let Some(label) = label { - criteria_labels.insert(label, new_index); - } - new_indices.push(new_index); - Some(container) - }) - .collect(); - - for index in self.uninitialized_at_start.drain(..) { - let container = &mut self.exclusive_at_start[index]; - if let Some(index) = container.run_criteria() { - container.set_run_criteria(new_indices[index]); - } - container.system_mut().initialize(world); - } - for index in self.uninitialized_before_commands.drain(..) { - let container = &mut self.exclusive_before_commands[index]; - if let Some(index) = container.run_criteria() { - container.set_run_criteria(new_indices[index]); - } - container.system_mut().initialize(world); - } - for index in self.uninitialized_at_end.drain(..) { - let container = &mut self.exclusive_at_end[index]; - if let Some(index) = container.run_criteria() { - container.set_run_criteria(new_indices[index]); - } - container.system_mut().initialize(world); - } - for index in self.uninitialized_parallel.drain(..) { - let container = &mut self.parallel[index]; - if let Some(index) = container.run_criteria() { - container.set_run_criteria(new_indices[index]); - } - container.system_mut().initialize(world); - } - } - - /// Rearranges all systems in topological orders. Systems must be initialized. - fn rebuild_orders_and_dependencies(&mut self) { - // This assertion exists to document that the number of systems in a stage is limited - // to guarantee that change detection never yields false positives. However, it's possible - // (but still unlikely) to circumvent this by abusing exclusive or chained systems. - assert!( - self.exclusive_at_start.len() - + self.exclusive_before_commands.len() - + self.exclusive_at_end.len() - + self.parallel.len() - < (CHECK_TICK_THRESHOLD as usize) - ); - debug_assert!( - self.uninitialized_run_criteria.is_empty() - && self.uninitialized_parallel.is_empty() - && self.uninitialized_at_start.is_empty() - && self.uninitialized_before_commands.is_empty() - && self.uninitialized_at_end.is_empty() - ); - fn unwrap_dependency_cycle_error( - result: Result>, - nodes: &[Node], - nodes_description: &'static str, - ) -> Output { - match result { - Ok(output) => output, - Err(DependencyGraphError::GraphCycles(cycle)) => { - use std::fmt::Write; - let mut message = format!("Found a dependency cycle in {nodes_description}:"); - writeln!(message).unwrap(); - for (index, labels) in &cycle { - writeln!(message, " - {}", nodes[*index].name()).unwrap(); - writeln!( - message, - " wants to be after (because of labels: {labels:?})", - ) - .unwrap(); - } - writeln!(message, " - {}", cycle[0].0).unwrap(); - panic!("{}", message); - } - } - } - let run_criteria_labels = unwrap_dependency_cycle_error( - self.process_run_criteria(), - &self.run_criteria, - "run criteria", - ); - unwrap_dependency_cycle_error( - process_systems(&mut self.parallel, &run_criteria_labels), - &self.parallel, - "parallel systems", - ); - unwrap_dependency_cycle_error( - process_systems(&mut self.exclusive_at_start, &run_criteria_labels), - &self.exclusive_at_start, - "exclusive systems at start of stage", - ); - unwrap_dependency_cycle_error( - process_systems(&mut self.exclusive_before_commands, &run_criteria_labels), - &self.exclusive_before_commands, - "exclusive systems before commands of stage", - ); - unwrap_dependency_cycle_error( - process_systems(&mut self.exclusive_at_end, &run_criteria_labels), - &self.exclusive_at_end, - "exclusive systems at end of stage", - ); - } - - fn check_uses_resource(&self, resource_id: ComponentId, world: &World) { - debug_assert!(!self.systems_modified); - for system in &self.parallel { - if !system.component_access().has_read(resource_id) { - let component_name = world.components().get_info(resource_id).unwrap().name(); - warn!( - "System {} doesn't access resource {component_name}, despite being required to", - system.name() - ); - } - } - } - - /// All system and component change ticks are scanned once the world counter has incremented - /// at least [`CHECK_TICK_THRESHOLD`](crate::change_detection::CHECK_TICK_THRESHOLD) - /// times since the previous `check_tick` scan. - /// - /// During each scan, any change ticks older than [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE) - /// are clamped to that age. This prevents false positives from appearing due to overflow. - fn check_change_ticks(&mut self, world: &mut World) { - let change_tick = world.change_tick(); - let ticks_since_last_check = change_tick.wrapping_sub(self.last_tick_check); - - if ticks_since_last_check >= CHECK_TICK_THRESHOLD { - // Check all system change ticks. - for exclusive_system in &mut self.exclusive_at_start { - exclusive_system.system_mut().check_change_tick(change_tick); - } - for exclusive_system in &mut self.exclusive_before_commands { - exclusive_system.system_mut().check_change_tick(change_tick); - } - for exclusive_system in &mut self.exclusive_at_end { - exclusive_system.system_mut().check_change_tick(change_tick); - } - for parallel_system in &mut self.parallel { - parallel_system.system_mut().check_change_tick(change_tick); - } - - // Check all component change ticks. - world.check_change_ticks(); - self.last_tick_check = change_tick; - } - } - - /// Sorts run criteria and populates resolved input-criteria for piping. - /// Returns a map of run criteria labels to their indices. - fn process_run_criteria( - &mut self, - ) -> Result, DependencyGraphError>> - { - let graph = graph_utils::build_dependency_graph(&self.run_criteria); - let order = graph_utils::topological_order(&graph)?; - let mut order_inverted = order.iter().enumerate().collect::>(); - order_inverted.sort_unstable_by_key(|(_, &key)| key); - let labels: HashMap<_, _> = self - .run_criteria - .iter() - .enumerate() - .filter_map(|(index, criteria)| { - criteria - .label - .as_ref() - .map(|&label| (label, order_inverted[index].0)) - }) - .collect(); - for criteria in &mut self.run_criteria { - if let RunCriteriaInner::Piped { input: parent, .. } = &mut criteria.inner { - let label = &criteria.after[0]; - *parent = *labels.get(label).unwrap_or_else(|| { - panic!("Couldn't find run criteria labelled {label:?} to pipe from.",) - }); - } - } - - fn update_run_criteria_indices( - systems: &mut [SystemContainer], - order_inverted: &[(usize, &usize)], - ) { - for system in systems { - if let Some(index) = system.run_criteria() { - system.set_run_criteria(order_inverted[index].0); - } - } - } - - update_run_criteria_indices(&mut self.exclusive_at_end, &order_inverted); - update_run_criteria_indices(&mut self.exclusive_at_start, &order_inverted); - update_run_criteria_indices(&mut self.exclusive_before_commands, &order_inverted); - update_run_criteria_indices(&mut self.parallel, &order_inverted); - - let mut temp = self.run_criteria.drain(..).map(Some).collect::>(); - for index in order { - self.run_criteria.push(temp[index].take().unwrap()); - } - Ok(labels) - } - - pub fn vec_system_container_debug( - &self, - name: &str, - v: &Vec, - f: &mut std::fmt::Formatter<'_>, - ) -> std::fmt::Result { - write!(f, "{name}: ")?; - if v.len() > 1 { - writeln!(f, "[")?; - for sc in v.iter() { - writeln!(f, "{sc:?},")?; - } - write!(f, "], ") - } else { - write!(f, "{v:?}, ") - } - } -} - -impl std::fmt::Debug for SystemStage { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "SystemStage: {{ ")?; - write!( - f, - "world_id: {:?}, executor: {:?}, stage_run_criteria: {:?}, run_criteria: {:?}, ", - self.world_id, self.executor, self.stage_run_criteria, self.run_criteria - )?; - self.vec_system_container_debug("exclusive_at_start", &self.exclusive_at_start, f)?; - self.vec_system_container_debug( - "exclusive_before_commands", - &self.exclusive_before_commands, - f, - )?; - self.vec_system_container_debug("exclusive_at_end", &self.exclusive_at_end, f)?; - self.vec_system_container_debug("parallel", &self.parallel, f)?; - write!( - f, - "systems_modified: {:?}, uninitialized_run_criteria: {:?}, ", - self.systems_modified, self.uninitialized_run_criteria - )?; - write!( - f, - "uninitialized_at_start: {:?}, uninitialized_before_commands: {:?}, ", - self.uninitialized_at_start, self.uninitialized_before_commands - )?; - write!( - f, - "uninitialized_at_end: {:?}, uninitialized_parallel: {:?}, ", - self.uninitialized_at_end, self.uninitialized_parallel - )?; - write!( - f, - "last_tick_check: {:?}, apply_buffers: {:?}, ", - self.last_tick_check, self.apply_buffers - )?; - write!(f, "must_read_resource: {:?}}}", self.must_read_resource) - } -} - -/// Sorts given system containers topologically, populates their resolved dependencies -/// and run criteria. -fn process_systems( - systems: &mut Vec, - run_criteria_labels: &HashMap, -) -> Result<(), DependencyGraphError>> { - let mut graph = graph_utils::build_dependency_graph(systems); - let order = graph_utils::topological_order(&graph)?; - let mut order_inverted = order.iter().enumerate().collect::>(); - order_inverted.sort_unstable_by_key(|(_, &key)| key); - for (index, container) in systems.iter_mut().enumerate() { - if let Some(index) = container.run_criteria_label().map(|label| { - *run_criteria_labels - .get(label) - .unwrap_or_else(|| panic!("No run criteria with label {label:?} found.")) - }) { - container.set_run_criteria(index); - } - container.set_dependencies( - graph - .get_mut(&index) - .unwrap() - .drain() - .map(|(index, _)| order_inverted[index].0), - ); - } - let mut temp = systems.drain(..).map(Some).collect::>(); - for index in order { - systems.push(temp[index].take().unwrap()); - } - Ok(()) -} - -impl Stage for SystemStage { - fn run(&mut self, world: &mut World) { - if let Some(world_id) = self.world_id { - assert!( - world.id() == world_id, - "Cannot run SystemStage on two different Worlds" - ); - } else { - self.world_id = Some(world.id()); - } - - if self.systems_modified { - self.initialize_systems(world); - self.rebuild_orders_and_dependencies(); - self.systems_modified = false; - self.executor.rebuild_cached_data(&self.parallel); - self.executor_modified = false; - if world.contains_resource::() { - self.report_ambiguities(world); - } - if let Some(resource_id) = self.must_read_resource { - self.check_uses_resource(resource_id, world); - } - } else if self.executor_modified { - self.executor.rebuild_cached_data(&self.parallel); - self.executor_modified = false; - } - - let mut run_stage_loop = true; - while run_stage_loop { - let should_run = self.stage_run_criteria.should_run(world); - match should_run { - ShouldRun::No => return, - ShouldRun::NoAndCheckAgain => continue, - ShouldRun::YesAndCheckAgain => (), - ShouldRun::Yes => { - run_stage_loop = false; - } - }; - - // Evaluate system run criteria. - for index in 0..self.run_criteria.len() { - let (run_criteria, tail) = self.run_criteria.split_at_mut(index); - let mut criteria = &mut tail[0]; - - #[cfg(feature = "trace")] - let _span = - bevy_utils::tracing::info_span!("run criteria", name = &*criteria.name()) - .entered(); - - match &mut criteria.inner { - RunCriteriaInner::Single(system) => criteria.should_run = system.run((), world), - RunCriteriaInner::Piped { - input: parent, - system, - .. - } => criteria.should_run = system.run(run_criteria[*parent].should_run, world), - } - } - - let mut run_system_loop = true; - let mut default_should_run = ShouldRun::Yes; - while run_system_loop { - run_system_loop = false; - - fn should_run( - container: &SystemContainer, - run_criteria: &[RunCriteriaContainer], - default: ShouldRun, - ) -> bool { - matches!( - container - .run_criteria() - .map(|index| run_criteria[index].should_run) - .unwrap_or(default), - ShouldRun::Yes | ShouldRun::YesAndCheckAgain - ) - } - - // Run systems that want to be at the start of stage. - for container in &mut self.exclusive_at_start { - if should_run(container, &self.run_criteria, default_should_run) { - { - #[cfg(feature = "trace")] - let _system_span = bevy_utils::tracing::info_span!( - "exclusive_system", - name = &*container.name() - ) - .entered(); - container.system_mut().run((), world); - } - container.system_mut().apply_buffers(world); - } - } - - // Run parallel systems using the executor. - // TODO: hard dependencies, nested sets, whatever... should be evaluated here. - for container in &mut self.parallel { - container.should_run = - should_run(container, &self.run_criteria, default_should_run); - } - self.executor.run_systems(&mut self.parallel, world); - - // Run systems that want to be between parallel systems and their command buffers. - for container in &mut self.exclusive_before_commands { - if should_run(container, &self.run_criteria, default_should_run) { - { - #[cfg(feature = "trace")] - let _system_span = bevy_utils::tracing::info_span!( - "exclusive_system", - name = &*container.name() - ) - .entered(); - container.system_mut().run((), world); - } - container.system_mut().apply_buffers(world); - } - } - - // Apply parallel systems' buffers. - if self.apply_buffers { - for container in &mut self.parallel { - if container.should_run { - container.system_mut().apply_buffers(world); - } - } - } - - // Run systems that want to be at the end of stage. - for container in &mut self.exclusive_at_end { - if should_run(container, &self.run_criteria, default_should_run) { - { - #[cfg(feature = "trace")] - let _system_span = bevy_utils::tracing::info_span!( - "exclusive_system", - name = &*container.name() - ) - .entered(); - container.system_mut().run((), world); - } - container.system_mut().apply_buffers(world); - } - } - - // Check for old component and system change ticks - self.check_change_ticks(world); - - // Evaluate run criteria. - let run_criteria = &mut self.run_criteria; - for index in 0..run_criteria.len() { - let (run_criteria, tail) = run_criteria.split_at_mut(index); - let criteria = &mut tail[0]; - match criteria.should_run { - ShouldRun::No => (), - ShouldRun::Yes => criteria.should_run = ShouldRun::No, - ShouldRun::YesAndCheckAgain | ShouldRun::NoAndCheckAgain => { - match &mut criteria.inner { - RunCriteriaInner::Single(system) => { - criteria.should_run = system.run((), world); - } - RunCriteriaInner::Piped { - input: parent, - system, - .. - } => { - criteria.should_run = - system.run(run_criteria[*parent].should_run, world); - } - } - match criteria.should_run { - ShouldRun::Yes - | ShouldRun::YesAndCheckAgain - | ShouldRun::NoAndCheckAgain => { - run_system_loop = true; - } - ShouldRun::No => (), - } - } - } - } - - // after the first loop, default to not running systems without run criteria - default_should_run = ShouldRun::No; - } - } - } -} - -#[cfg(test)] -mod tests { - use bevy_ecs_macros::RunCriteriaLabel; - - use crate::{ - schedule::{ - IntoSystemDescriptor, RunCriteria, RunCriteriaDescriptorCoercion, ShouldRun, - SingleThreadedExecutor, Stage, SystemLabel, SystemSet, SystemStage, - }, - system::{In, Local, Query, ResMut}, - world::World, - }; - - use crate as bevy_ecs; - use crate::component::Component; - use crate::system::Resource; - - #[derive(Component)] - struct W(T); - #[derive(Resource)] - struct R(usize); - - #[derive(Resource, Default)] - struct EntityCount(Vec); - - fn make_exclusive(tag: usize) -> impl FnMut(&mut World) { - move |world| world.resource_mut::().0.push(tag) - } - - fn make_parallel(tag: usize) -> impl FnMut(ResMut) { - move |mut resource: ResMut| resource.0.push(tag) - } - - fn every_other_time(mut has_ran: Local) -> ShouldRun { - *has_ran = !*has_ran; - if *has_ran { - ShouldRun::Yes - } else { - ShouldRun::No - } - } - - #[test] - fn insertion_points() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_exclusive(0).at_start()) - .with_system(make_parallel(1)) - .with_system(make_exclusive(2).before_commands()) - .with_system(make_exclusive(3).at_end()); - stage.run(&mut world); - assert_eq!(world.resource_mut::().0, vec![0, 1, 2, 3]); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 0, 1, 2, 3] - ); - - world.resource_mut::().0.clear(); - let mut stage = SystemStage::parallel() - .with_system(make_exclusive(2).before_commands()) - .with_system(make_exclusive(3).at_end()) - .with_system(make_parallel(1)) - .with_system(make_exclusive(0).at_start()); - stage.run(&mut world); - assert_eq!(world.resource::().0, vec![0, 1, 2, 3]); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 0, 1, 2, 3] - ); - - world.resource_mut::().0.clear(); - let mut stage = SystemStage::parallel() - .with_system(make_parallel(2).before_commands()) - .with_system(make_parallel(3).at_end()) - .with_system(make_parallel(1)) - .with_system(make_parallel(0).at_start()); - stage.run(&mut world); - assert_eq!(world.resource::().0, vec![0, 1, 2, 3]); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 0, 1, 2, 3] - ); - } - - #[derive(SystemLabel)] - enum TestLabels { - L0, - L1, - L2, - L3, - L4, - First, - L01, - L234, - } - use TestLabels::*; - - #[test] - fn exclusive_after() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_exclusive(1).label(L1).after(L0)) - .with_system(make_exclusive(2).after(L1)) - .with_system(make_exclusive(0).label(L0)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!(world.resource::().0, vec![0, 1, 2, 0, 1, 2]); - } - - #[test] - fn exclusive_before() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_exclusive(1).label(L1).before(L2)) - .with_system(make_exclusive(2).label(L2)) - .with_system(make_exclusive(0).before(L1)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!(world.resource::().0, vec![0, 1, 2, 0, 1, 2]); - } - - #[test] - fn exclusive_mixed() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_exclusive(2).label(L2)) - .with_system(make_exclusive(1).after(L0).before(L2)) - .with_system(make_exclusive(0).label(L0)) - .with_system(make_exclusive(4).label(L4)) - .with_system(make_exclusive(3).after(L2).before(L4)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4] - ); - } - - #[test] - fn exclusive_multiple_labels() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_exclusive(1).label(First).after(L0)) - .with_system(make_exclusive(2).after(First)) - .with_system(make_exclusive(0).label(First).label(L0)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!(world.resource::().0, vec![0, 1, 2, 0, 1, 2]); - - world.resource_mut::().0.clear(); - let mut stage = SystemStage::parallel() - .with_system(make_exclusive(2).after(L01).label(L2)) - .with_system(make_exclusive(1).label(L01).after(L0)) - .with_system(make_exclusive(0).label(L01).label(L0)) - .with_system(make_exclusive(4).label(L4)) - .with_system(make_exclusive(3).after(L2).before(L4)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4] - ); - - world.resource_mut::().0.clear(); - let mut stage = SystemStage::parallel() - .with_system(make_exclusive(2).label(L234).label(L2)) - .with_system(make_exclusive(1).before(L234).after(L0)) - .with_system(make_exclusive(0).label(L0)) - .with_system(make_exclusive(4).label(L234).label(L4)) - .with_system(make_exclusive(3).label(L234).after(L2).before(L4)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4] - ); - } - - #[test] - fn exclusive_redundant_constraints() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_exclusive(2).label(L2).after(L1).before(L3).before(L3)) - .with_system(make_exclusive(1).label(L1).after(L0).after(L0).before(L2)) - .with_system(make_exclusive(0).label(L0).before(L1)) - .with_system(make_exclusive(4).label(L4).after(L3)) - .with_system(make_exclusive(3).label(L3).after(L2).before(L4)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4] - ); - } - - #[test] - fn exclusive_mixed_across_sets() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_exclusive(2).label(L2)) - .with_system_set( - SystemSet::new() - .with_system(make_exclusive(0).label(L0)) - .with_system(make_exclusive(4).label(L4)) - .with_system(make_exclusive(3).after(L2).before(L4)), - ) - .with_system(make_exclusive(1).after(L0).before(L2)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4] - ); - } - - #[test] - fn exclusive_run_criteria() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_exclusive(0).before(L1)) - .with_system_set( - SystemSet::new() - .with_run_criteria(every_other_time) - .with_system(make_exclusive(1).label(L1)), - ) - .with_system(make_exclusive(2).after(L1)); - stage.run(&mut world); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 0, 2, 0, 1, 2, 0, 2] - ); - } - - #[test] - #[should_panic] - fn exclusive_cycle_1() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel().with_system(make_exclusive(0).label(L0).after(L0)); - stage.run(&mut world); - } - - #[test] - #[should_panic] - fn exclusive_cycle_2() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_exclusive(0).label(L0).after(L1)) - .with_system(make_exclusive(1).label(L1).after(L0)); - stage.run(&mut world); - } - - #[test] - #[should_panic] - fn exclusive_cycle_3() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_exclusive(0).label(L0)) - .with_system(make_exclusive(1).after(L0).before(L2)) - .with_system(make_exclusive(2).label(L2).before(L0)); - stage.run(&mut world); - } - - #[test] - fn parallel_after() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_parallel(1).after(L0).label(L1)) - .with_system(make_parallel(2).after(L1)) - .with_system(make_parallel(0).label(L0)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!(world.resource::().0, vec![0, 1, 2, 0, 1, 2]); - } - - #[test] - fn parallel_before() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_parallel(1).label(L1).before(L2)) - .with_system(make_parallel(2).label(L2)) - .with_system(make_parallel(0).before(L1)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!(world.resource::().0, vec![0, 1, 2, 0, 1, 2]); - } - - #[test] - fn parallel_mixed() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_parallel(2).label(L2)) - .with_system(make_parallel(1).after(L0).before(L2)) - .with_system(make_parallel(0).label(L0)) - .with_system(make_parallel(4).label(L4)) - .with_system(make_parallel(3).after(L2).before(L4)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4] - ); - } - - #[test] - fn parallel_multiple_labels() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_parallel(1).label(First).after(L0)) - .with_system(make_parallel(2).after(First)) - .with_system(make_parallel(0).label(First).label(L0)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!(world.resource::().0, vec![0, 1, 2, 0, 1, 2]); - - world.resource_mut::().0.clear(); - let mut stage = SystemStage::parallel() - .with_system(make_parallel(2).after(L01).label(L2)) - .with_system(make_parallel(1).label(L01).after(L0)) - .with_system(make_parallel(0).label(L01).label(L0)) - .with_system(make_parallel(4).label(L4)) - .with_system(make_parallel(3).after(L2).before(L4)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4] - ); - - world.resource_mut::().0.clear(); - let mut stage = SystemStage::parallel() - .with_system(make_parallel(2).label(L234).label(L2)) - .with_system(make_parallel(1).before(L234).after(L0)) - .with_system(make_parallel(0).label(L0)) - .with_system(make_parallel(4).label(L234).label(L4)) - .with_system(make_parallel(3).label(L234).after(L2).before(L4)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4] - ); - } - - #[test] - fn parallel_redundant_constraints() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_parallel(2).label(L2).after(L1).before(L3).before(L3)) - .with_system(make_parallel(1).label(L1).after(L0).after(L0).before(L2)) - .with_system(make_parallel(0).label(L0).before(L1)) - .with_system(make_parallel(4).label(L4).after(L3)) - .with_system(make_parallel(3).label(L3).after(L2).before(L4)); - stage.run(&mut world); - for container in &stage.parallel { - assert!(container.dependencies().len() <= 1); - } - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4] - ); - } - - #[test] - fn parallel_mixed_across_sets() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_parallel(2).label(L2)) - .with_system_set( - SystemSet::new() - .with_system(make_parallel(0).label(L0)) - .with_system(make_parallel(4).label(L4)) - .with_system(make_parallel(3).after(L2).before(L4)), - ) - .with_system(make_parallel(1).after(L0).before(L2)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4] - ); - } - - #[derive(RunCriteriaLabel)] - struct EveryOtherTime; - - #[test] - fn parallel_run_criteria() { - let mut world = World::new(); - - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system( - make_parallel(0) - .label(L0) - .with_run_criteria(every_other_time), - ) - .with_system(make_parallel(1).after(L0)); - stage.run(&mut world); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - stage.run(&mut world); - assert_eq!(world.resource::().0, vec![0, 1, 1, 0, 1, 1]); - - world.resource_mut::().0.clear(); - let mut stage = SystemStage::parallel() - .with_system(make_parallel(0).before(L1)) - .with_system_set( - SystemSet::new() - .with_run_criteria(every_other_time) - .with_system(make_parallel(1).label(L1)), - ) - .with_system(make_parallel(2).after(L1)); - stage.run(&mut world); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 0, 2, 0, 1, 2, 0, 2] - ); - - // Reusing criteria. - world.resource_mut::().0.clear(); - let mut stage = SystemStage::parallel() - .with_system_run_criteria(every_other_time.label(EveryOtherTime)) - .with_system(make_parallel(0).before(L1)) - .with_system(make_parallel(1).label(L1).with_run_criteria(EveryOtherTime)) - .with_system( - make_parallel(2) - .label(L2) - .after(L1) - .with_run_criteria(EveryOtherTime), - ) - .with_system(make_parallel(3).after(L2)); - stage.run(&mut world); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 0, 3, 0, 1, 2, 3, 0, 3] - ); - assert_eq!(stage.run_criteria.len(), 1); - - // Piping criteria. - world.resource_mut::().0.clear(); - fn eot_piped(input: In, has_ran: Local) -> ShouldRun { - if let ShouldRun::Yes | ShouldRun::YesAndCheckAgain = input.0 { - every_other_time(has_ran) - } else { - ShouldRun::No - } - } - - #[derive(RunCriteriaLabel)] - struct Piped; - - let mut stage = SystemStage::parallel() - .with_system(make_parallel(0).label(L0)) - .with_system( - make_parallel(1) - .label(L1) - .after(L0) - .with_run_criteria(every_other_time.label(EveryOtherTime)), - ) - .with_system( - make_parallel(2) - .label(L2) - .after(L1) - .with_run_criteria(RunCriteria::pipe(EveryOtherTime, eot_piped)), - ) - .with_system( - make_parallel(3) - .label(L3) - .after(L2) - .with_run_criteria(RunCriteria::pipe(EveryOtherTime, eot_piped).label(Piped)), - ) - .with_system(make_parallel(4).after(L3).with_run_criteria(Piped)); - for _ in 0..4 { - stage.run(&mut world); - } - stage.set_executor(Box::::default()); - for _ in 0..5 { - stage.run(&mut world); - } - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 4, 0, 0, 1, 0, 0, 1, 2, 3, 4, 0, 0, 1, 0, 0, 1, 2, 3, 4] - ); - assert_eq!(stage.run_criteria.len(), 3); - - // Discarding extra criteria with matching labels. - world.resource_mut::().0.clear(); - let mut stage = SystemStage::parallel() - .with_system(make_parallel(0).before(L1)) - .with_system( - make_parallel(1) - .label(L1) - .with_run_criteria(every_other_time.label_discard_if_duplicate(EveryOtherTime)), - ) - .with_system( - make_parallel(2) - .label(L2) - .after(L1) - .with_run_criteria(every_other_time.label_discard_if_duplicate(EveryOtherTime)), - ) - .with_system(make_parallel(3).after(L2)); - stage.run(&mut world); - stage.run(&mut world); - // false positive, `Box::default` cannot coerce `SingleThreadedExecutor` to `dyn ParallelSystemExectutor` - stage.set_executor(Box::::default()); - stage.run(&mut world); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 0, 3, 0, 1, 2, 3, 0, 3] - ); - assert_eq!(stage.run_criteria.len(), 1); - } - - #[test] - #[should_panic] - fn duplicate_run_criteria_label_panic() { - let mut world = World::new(); - let mut stage = SystemStage::parallel() - .with_system_run_criteria(every_other_time.label(EveryOtherTime)) - .with_system_run_criteria(every_other_time.label(EveryOtherTime)); - stage.run(&mut world); - } - - #[test] - #[should_panic] - fn parallel_cycle_1() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel().with_system(make_parallel(0).label(L0).after(L0)); - stage.run(&mut world); - } - - #[test] - #[should_panic] - fn parallel_cycle_2() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_parallel(0).label(L0).after(L1)) - .with_system(make_parallel(1).label(L1).after(L0)); - stage.run(&mut world); - } - - #[test] - #[should_panic] - fn parallel_cycle_3() { - let mut world = World::new(); - - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_parallel(0).label(L0)) - .with_system(make_parallel(1).after(L0).before(L2)) - .with_system(make_parallel(2).label(L2).before(L0)); - stage.run(&mut world); - } - - #[test] - #[should_panic] - fn multiple_worlds_same_stage() { - let mut world_a = World::default(); - let mut world_b = World::default(); - let mut stage = SystemStage::parallel(); - stage.run(&mut world_a); - stage.run(&mut world_b); - } - - #[test] - fn archetype_update_single_executor() { - fn query_count_system(mut entity_count: ResMut, query: Query) { - *entity_count = R(query.iter().count()); - } - - let mut world = World::new(); - world.insert_resource(R(0)); - let mut stage = SystemStage::single(query_count_system); - - let entity = world.spawn_empty().id(); - stage.run(&mut world); - assert_eq!(world.resource::().0, 1); - - world.get_entity_mut(entity).unwrap().insert(W(1)); - stage.run(&mut world); - assert_eq!(world.resource::().0, 1); - } - - #[test] - fn archetype_update_parallel_executor() { - fn query_count_system(mut entity_count: ResMut, query: Query) { - *entity_count = R(query.iter().count()); - } - - let mut world = World::new(); - world.insert_resource(R(0)); - let mut stage = SystemStage::parallel(); - stage.add_system(query_count_system); - - let entity = world.spawn_empty().id(); - stage.run(&mut world); - assert_eq!(world.resource::().0, 1); - - world.get_entity_mut(entity).unwrap().insert(W(1)); - stage.run(&mut world); - assert_eq!(world.resource::().0, 1); - } - - #[test] - fn run_criteria_with_query() { - use crate::{self as bevy_ecs, component::Component}; - - #[derive(Component)] - struct Foo; - - fn even_number_of_entities_criteria(query: Query<&Foo>) -> ShouldRun { - if query.iter().len() % 2 == 0 { - ShouldRun::Yes - } else { - ShouldRun::No - } - } - - fn spawn_entity(mut commands: crate::prelude::Commands) { - commands.spawn(Foo); - } - - fn count_entities(query: Query<&Foo>, mut res: ResMut) { - res.0.push(query.iter().len()); - } - - #[derive(SystemLabel)] - struct Spawn; - - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(spawn_entity.label(Spawn)) - .with_system_set( - SystemSet::new() - .with_run_criteria(even_number_of_entities_criteria) - .with_system(count_entities.before(Spawn)), - ); - stage.run(&mut world); - stage.run(&mut world); - stage.run(&mut world); - stage.run(&mut world); - assert_eq!(world.resource::().0, vec![0, 2]); - } - - #[test] - fn stage_run_criteria_with_query() { - use crate::{self as bevy_ecs, component::Component}; - - #[derive(Component)] - struct Foo; - - fn even_number_of_entities_criteria(query: Query<&Foo>) -> ShouldRun { - if query.iter().len() % 2 == 0 { - ShouldRun::Yes - } else { - ShouldRun::No - } - } - - fn spawn_entity(mut commands: crate::prelude::Commands) { - commands.spawn(Foo); - } - - fn count_entities(query: Query<&Foo>, mut res: ResMut) { - res.0.push(query.iter().len()); - } - - let mut world = World::new(); - world.init_resource::(); - let mut stage_spawn = SystemStage::parallel().with_system(spawn_entity); - let mut stage_count = SystemStage::parallel() - .with_run_criteria(even_number_of_entities_criteria) - .with_system(count_entities); - stage_count.run(&mut world); - stage_spawn.run(&mut world); - stage_count.run(&mut world); - stage_spawn.run(&mut world); - stage_count.run(&mut world); - stage_spawn.run(&mut world); - stage_count.run(&mut world); - stage_spawn.run(&mut world); - assert_eq!(world.resource::().0, vec![0, 2]); - } -} diff --git a/crates/bevy_ecs/src/schedule/state.rs b/crates/bevy_ecs/src/schedule/state.rs deleted file mode 100644 index 3ad5dbef5f059..0000000000000 --- a/crates/bevy_ecs/src/schedule/state.rs +++ /dev/null @@ -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 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 { - transition: Option>, - /// The current states in the stack. - /// - /// There is always guaranteed to be at least one. - stack: Vec, - scheduled: Option>, - end_next_loop: bool, -} - -#[derive(Debug)] -enum StateTransition { - 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 { - 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() -> Self { - Self(TypeId::of::(), std::any::type_name::()) - } -} - -impl State -where - T: StateData, -{ - pub fn on_update(pred: T) -> RunCriteriaDescriptor { - (move |state: Res>| { - state.stack.last().unwrap() == &pred && state.transition.is_none() - }) - .pipe(should_run_adapter::) - .after(DriverLabel::of::()) - } - - pub fn on_inactive_update(pred: T) -> RunCriteriaDescriptor { - (move |state: Res>, mut is_inactive: Local| 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::) - .after(DriverLabel::of::()) - } - - pub fn on_in_stack_update(pred: T) -> RunCriteriaDescriptor { - (move |state: Res>, mut is_in_stack: Local| 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::) - .after(DriverLabel::of::()) - } - - pub fn on_enter(pred: T) -> RunCriteriaDescriptor { - (move |state: Res>| { - 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::) - .after(DriverLabel::of::()) - } - - pub fn on_exit(pred: T) -> RunCriteriaDescriptor { - (move |state: Res>| { - state - .transition - .as_ref() - .map_or(false, |transition| match transition { - StateTransition::ExitingToResume(exiting, _) - | StateTransition::ExitingFull(exiting, _) => exiting == &pred, - _ => false, - }) - }) - .pipe(should_run_adapter::) - .after(DriverLabel::of::()) - } - - pub fn on_pause(pred: T) -> RunCriteriaDescriptor { - (move |state: Res>| { - state - .transition - .as_ref() - .map_or(false, |transition| match transition { - StateTransition::Pausing(pausing, _) => pausing == &pred, - _ => false, - }) - }) - .pipe(should_run_adapter::) - .after(DriverLabel::of::()) - } - - pub fn on_resume(pred: T) -> RunCriteriaDescriptor { - (move |state: Res>| { - state - .transition - .as_ref() - .map_or(false, |transition| match transition { - StateTransition::Resuming(_, resuming) => resuming == &pred, - _ => false, - }) - }) - .pipe(should_run_adapter::) - .after(DriverLabel::of::()) - } - - 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::.label(DriverLabel::of::())) - } - - 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(In(cmp_result): In, state: Res>) -> ShouldRun { - if state.end_next_loop { - return ShouldRun::No; - } - if cmp_result { - ShouldRun::YesAndCheckAgain - } else { - ShouldRun::NoAndCheckAgain - } -} - -fn state_cleaner( - mut state: ResMut>, - mut prep_exit: Local, -) -> 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::(); - world.insert_resource(State::new(MyState::S1)); - - let mut stage = SystemStage::parallel(); - - #[derive(SystemLabel)] - enum Inactive { - S4, - S5, - } - - stage.add_system_set(State::::get_driver()); - stage - .add_system_set( - State::on_enter_set(MyState::S1) - .with_system(|mut r: ResMut| r.0.push("startup")), - ) - .add_system_set(State::on_update_set(MyState::S1).with_system( - |mut r: ResMut, mut s: ResMut>| { - 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| r.0.push("enter S2")), - ) - .add_system_set(State::on_update_set(MyState::S2).with_system( - |mut r: ResMut, mut s: ResMut>| { - 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| r.0.push("exit S2")), - ) - .add_system_set( - State::on_enter_set(MyState::S3) - .with_system(|mut r: ResMut| r.0.push("enter S3")), - ) - .add_system_set(State::on_update_set(MyState::S3).with_system( - |mut r: ResMut, mut s: ResMut>| { - 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| r.0.push("pause S3")), - ) - .add_system_set(State::on_update_set(MyState::S4).with_system( - |mut r: ResMut, mut s: ResMut>| { - 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| r.0.push("inactive S4")).label(Inactive::S4), - )) - .add_system_set( - State::on_update_set(MyState::S5).with_system( - (|mut r: ResMut, mut s: ResMut>| { - 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| 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, mut s: ResMut>| { - 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| r.0.push("resume S4")), - ) - .add_system_set( - State::on_exit_set(MyState::S5) - .with_system(|mut r: ResMut| 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::(); - 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::>().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, test_name: Res) { - 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::().0, "after control"); - - world.insert_resource(Flag(false)); - world.insert_resource(Name("test")); - let mut stage = SystemStage::parallel() - .with_system_set(State::::get_driver()) - .with_system(should_run_once); - stage.run(&mut world); - assert!(world.resource::().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); - - let mut world = World::new(); - world.init_resource::(); - world.insert_resource(State::new(LoadState::Load)); - - let mut stage = SystemStage::parallel(); - stage.add_system_set(State::::get_driver()); - - // Systems to track loading status - stage - .add_system_set( - State::on_enter_set(LoadState::Load) - .with_system(|mut r: ResMut| r.0.push(LoadStatus::EnterLoad)), - ) - .add_system_set( - State::on_exit_set(LoadState::Load) - .with_system(|mut r: ResMut| r.0.push(LoadStatus::ExitLoad)), - ) - .add_system_set( - State::on_enter_set(LoadState::Finish).with_system( - |mut r: ResMut| r.0.push(LoadStatus::EnterFinish), - ), - ); - - stage.run(&mut world); - - // A. Restart state - let mut state = world.resource_mut::>(); - let result = state.restart(); - assert!(matches!(result, Ok(()))); - stage.run(&mut world); - - // B. Restart state (overwrite schedule) - let mut state = world.resource_mut::>(); - 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.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::(); - 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::>().current(), - &LoadState::Finish - ); - } -} diff --git a/crates/bevy_ecs/src/schedule/system_container.rs b/crates/bevy_ecs/src/schedule/system_container.rs deleted file mode 100644 index 4178d14c24c38..0000000000000 --- a/crates/bevy_ecs/src/schedule/system_container.rs +++ /dev/null @@ -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>, - pub(crate) run_criteria_index: Option, - pub(crate) run_criteria_label: Option, - pub(crate) should_run: bool, - is_exclusive: bool, - dependencies: Vec, - labels: Vec, - before: Vec, - after: Vec, - 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 { - &*self.system - } - - pub fn system_mut(&mut self) -> &mut dyn System { - &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) { - self.dependencies.clear(); - self.dependencies.extend(dependencies); - } - - pub fn run_criteria(&self) -> Option { - 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 { - 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 - } -} diff --git a/crates/bevy_ecs/src/schedule/system_descriptor.rs b/crates/bevy_ecs/src/schedule/system_descriptor.rs deleted file mode 100644 index 8628069e9042a..0000000000000 --- a/crates/bevy_ecs/src/schedule/system_descriptor.rs +++ /dev/null @@ -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), -} - -/// 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, - pub(crate) run_criteria: Option, - pub(crate) labels: Vec, - pub(crate) before: Vec, - pub(crate) after: Vec, - 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 { - 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( - self, - run_criteria: impl IntoRunCriteria, - ) -> 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(self, label: impl AsSystemLabel) -> SystemDescriptor; - - /// Specifies that the system should run after systems with the given label. - fn after(self, label: impl AsSystemLabel) -> 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(self, label: impl AsSystemLabel) -> 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( - mut self, - run_criteria: impl IntoRunCriteria, - ) -> 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(mut self, label: impl AsSystemLabel) -> SystemDescriptor { - self.before.push(label.as_system_label().as_label()); - self - } - - fn after(mut self, label: impl AsSystemLabel) -> SystemDescriptor { - self.after.push(label.as_system_label().as_label()); - self - } - - fn ambiguous_with(mut self, label: impl AsSystemLabel) -> 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 IntoSystemDescriptor for S -where - S: IntoSystem<(), (), Params>, -{ - fn with_run_criteria( - self, - run_criteria: impl IntoRunCriteria, - ) -> 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(self, label: impl AsSystemLabel) -> SystemDescriptor { - SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).before(label) - } - - fn after(self, label: impl AsSystemLabel) -> SystemDescriptor { - SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).after(label) - } - - fn ambiguous_with(self, label: impl AsSystemLabel) -> 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( - self, - run_criteria: impl IntoRunCriteria, - ) -> SystemDescriptor { - SystemDescriptor::new(self).with_run_criteria(run_criteria) - } - - fn label(self, label: impl SystemLabel) -> SystemDescriptor { - SystemDescriptor::new(self).label(label) - } - - fn before(self, label: impl AsSystemLabel) -> SystemDescriptor { - SystemDescriptor::new(self).before(label) - } - - fn after(self, label: impl AsSystemLabel) -> SystemDescriptor { - SystemDescriptor::new(self).after(label) - } - - fn ambiguous_with(self, label: impl AsSystemLabel) -> 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, -} diff --git a/crates/bevy_ecs/src/schedule/system_set.rs b/crates/bevy_ecs/src/schedule/system_set.rs deleted file mode 100644 index 95eded570fe97..0000000000000 --- a/crates/bevy_ecs/src/schedule/system_set.rs +++ /dev/null @@ -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, - pub(crate) run_criteria: Option, - pub(crate) labels: Vec, - pub(crate) before: Vec, - pub(crate) after: Vec, -} - -impl SystemSet { - pub fn new() -> Self { - Default::default() - } - - pub fn on_update(s: T) -> SystemSet - where - T: StateData, - { - Self::new().with_run_criteria(State::::on_update(s)) - } - - pub fn on_inactive_update(s: T) -> SystemSet - where - T: StateData, - { - Self::new().with_run_criteria(State::::on_inactive_update(s)) - } - - pub fn on_in_stack_update(s: T) -> SystemSet - where - T: StateData, - { - Self::new().with_run_criteria(State::::on_in_stack_update(s)) - } - - pub fn on_enter(s: T) -> SystemSet - where - T: StateData, - { - Self::new().with_run_criteria(State::::on_enter(s)) - } - - pub fn on_exit(s: T) -> SystemSet - where - T: StateData, - { - Self::new().with_run_criteria(State::::on_exit(s)) - } - - pub fn on_pause(s: T) -> SystemSet - where - T: StateData, - { - Self::new().with_run_criteria(State::::on_pause(s)) - } - - pub fn on_resume(s: T) -> SystemSet - where - T: StateData, - { - Self::new().with_run_criteria(State::::on_resume(s)) - } - - #[must_use] - pub fn with_system(mut self, system: impl IntoSystemDescriptor) -> Self { - self.systems.push(system.into_descriptor()); - self - } - - #[must_use] - pub fn with_run_criteria(mut self, run_criteria: impl IntoRunCriteria) -> 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(mut self, label: impl AsSystemLabel) -> Self { - self.before.push(label.as_system_label().as_label()); - self - } - - #[must_use] - pub fn after(mut self, label: impl AsSystemLabel) -> Self { - self.after.push(label.as_system_label().as_label()); - self - } - - pub(crate) fn bake(self) -> (Option, Vec) { - 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) - } -} diff --git a/crates/bevy_ecs/src/schedule_v3/condition.rs b/crates/bevy_ecs/src/schedule_v3/condition.rs index e617375f763a0..8ddf8dee7abe1 100644 --- a/crates/bevy_ecs/src/schedule_v3/condition.rs +++ b/crates/bevy_ecs/src/schedule_v3/condition.rs @@ -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() -> impl FnMut(Option>) -> bool diff --git a/crates/bevy_ecs/src/schedule_v3/config.rs b/crates/bevy_ecs/src/schedule_v3/config.rs index d7a87c374c047..c72d0983f5866 100644 --- a/crates/bevy_ecs/src/schedule_v3/config.rs +++ b/crates/bevy_ecs/src/schedule_v3/config.rs @@ -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(self, set: impl IntoSystemSet) -> 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

(self, condition: impl Condition

) -> 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(self, set: impl IntoSystemSet) -> 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(self, set: impl IntoSystemSet) -> SystemSetConfig { - SystemSetConfig::new(Box::new(self)).before(set) + self.into_config().before(set) } fn after(self, set: impl IntoSystemSet) -> SystemSetConfig { - SystemSetConfig::new(Box::new(self)).after(set) + self.into_config().after(set) } fn run_if

(self, condition: impl Condition

) -> 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(self, set: impl IntoSystemSet) -> 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(self, set: impl IntoSystemSet) -> SystemSetConfig { - SystemSetConfig::new(self).before(set) + self.into_config().before(set) } fn after(self, set: impl IntoSystemSet) -> SystemSetConfig { - SystemSetConfig::new(self).after(set) + self.into_config().after(set) } fn run_if

(self, condition: impl Condition

) -> 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(self, set: impl IntoSystemSet) -> 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(mut self, set: impl IntoSystemSet) -> 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(mut self, set: impl IntoSystemSet) -> Self { ambiguous_with(&mut self.graph_info, Box::new(set.into_system_set())); self @@ -229,6 +253,8 @@ pub trait IntoSystemConfig: sealed::IntoSystemConfig { 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(self, set: impl IntoSystemSet) -> SystemConfig; /// Run after all systems in `set`. @@ -238,6 +264,8 @@ pub trait IntoSystemConfig: sealed::IntoSystemConfig { /// The `Condition` will be evaluated at most once (per schedule run), /// when the system prepares to run. fn run_if

(self, condition: impl Condition

) -> 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(self, set: impl IntoSystemSet) -> 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(self, set: impl IntoSystemSet) -> SystemConfig { - SystemConfig::new(Box::new(IntoSystem::into_system(self))).before(set) + self.into_config().before(set) } fn after(self, set: impl IntoSystemSet) -> SystemConfig { - SystemConfig::new(Box::new(IntoSystem::into_system(self))).after(set) + self.into_config().after(set) } fn run_if

(self, condition: impl Condition

) -> 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(self, set: impl IntoSystemSet) -> 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(self, set: impl IntoSystemSet) -> SystemConfig { - SystemConfig::new(self).before(set) + self.into_config().before(set) } fn after(self, set: impl IntoSystemSet) -> SystemConfig { - SystemConfig::new(self).after(set) + self.into_config().after(set) } fn run_if

(self, condition: impl Condition

) -> 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(self, set: impl IntoSystemSet) -> 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(mut self, set: impl IntoSystemSet) -> 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(mut self, set: impl IntoSystemSet) -> 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(self, set: impl IntoSystemSet) -> 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 diff --git a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs index 8fc1788cb72b5..b26dd181562e9 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs @@ -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. diff --git a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs index 33789b2a15474..dadd75a5dff35 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs @@ -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); }, ); - // 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() }; - apply_system_buffers(&self.unapplied_systems, systems, world); - self.unapplied_systems.clear(); - debug_assert!(self.unapplied_systems.is_clear()); + 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); } } diff --git a/crates/bevy_ecs/src/schedule_v3/executor/simple.rs b/crates/bevy_ecs/src/schedule_v3/executor/simple.rs index 1d45aa29129b3..c4ad1e2282f72 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/simple.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/simple.rs @@ -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); } diff --git a/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs index 289b05b8c1e78..14dfb984d1584 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs @@ -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 { } } - self.apply_system_buffers(schedule, world); + 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); } diff --git a/crates/bevy_ecs/src/schedule_v3/graph_utils.rs b/crates/bevy_ecs/src/schedule_v3/graph_utils.rs index b58bad317a959..27092acda9333 100644 --- a/crates/bevy_ecs/src/schedule_v3/graph_utils.rs +++ b/crates/bevy_ecs/src/schedule_v3/graph_utils.rs @@ -72,6 +72,18 @@ pub(crate) struct GraphInfo { pub(crate) sets: Vec, pub(crate) dependencies: Vec, 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. diff --git a/crates/bevy_ecs/src/schedule_v3/migration.rs b/crates/bevy_ecs/src/schedule_v3/migration.rs deleted file mode 100644 index c4932c2fbca7c..0000000000000 --- a/crates/bevy_ecs/src/schedule_v3/migration.rs +++ /dev/null @@ -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`] and [`NextState`] resources, [`OnEnter`] and [`OnExit`] schedules - /// for each state variant, and an instance of [`apply_state_transition::`] in - /// \ so that transitions happen before `Update`. - fn add_state(&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::().remove(&label) { - schedule.run(self); - self.resource_mut::().insert(label, schedule); - } - } -} diff --git a/crates/bevy_ecs/src/schedule_v3/mod.rs b/crates/bevy_ecs/src/schedule_v3/mod.rs index 11828cbbe5fc0..185dae4d753bd 100644 --- a/crates/bevy_ecs/src/schedule_v3/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/mod.rs @@ -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::(); - 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::().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)] diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index 360ca5249e47c..6488d357a61fb 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -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 { - let label: Box = 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, 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, ambiguous_with_flattened: UnGraphMap, ambiguous_with_all: HashSet, - default_set: Option, 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

(&mut self, systems: impl IntoSystemConfigs

) { let SystemConfigs { systems, chained } = systems.into_configs(); let mut system_iter = systems.into_iter(); @@ -335,20 +350,12 @@ impl ScheduleGraph { ) -> Result { 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 { 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.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 { + fn build_schedule( + &mut self, + components: &Components, + ) -> Result { // 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::>(); 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::>(); - // 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> = + 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); } } } + + set_systems.insert(id, systems); + set_system_bitsets.insert(id, 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 + // 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); - } - } - - 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, ()); + // 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)); } } - (NodeId::System(_), NodeId::Set(_)) => { - for &rhs_ in &systems_in_sets[&rhs] { - dependency_flattened.add_edge(lhs, rhs_, ()); + } else { + for a in dependency_flattened.neighbors_directed(set, Direction::Incoming) { + for &sys in systems { + temp.push((a, sys)); } } - (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)); } } } + + dependency_flattened.remove_node(set); + for (a, b) in temp.drain(..) { + dependency_flattened.add_edge(a, b, ()); + } } - // check flattened dependencies for cycles + // 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)]) { - let mut string = String::from( - "Some systems with conflicting access have indeterminate execution order. \ + fn report_conflicts( + &self, + ambiguities: &[(NodeId, NodeId, Vec)], + 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::(); @@ -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, } } diff --git a/crates/bevy_ecs/src/schedule_v3/set.rs b/crates/bevy_ecs/src/schedule_v3/set.rs index 5b29f31e6a3ae..096b66e25a662 100644 --- a/crates/bevy_ecs/src/schedule_v3/set.rs +++ b/crates/bevy_ecs/src/schedule_v3/set.rs @@ -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; } diff --git a/crates/bevy_ecs/src/schedule_v3/state.rs b/crates/bevy_ecs/src/schedule_v3/state.rs index 85f357c670f7f..cac1a4ed5d492 100644 --- a/crates/bevy_ecs/src/schedule_v3/state.rs +++ b/crates/bevy_ecs/src/schedule_v3/state.rs @@ -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`] resource, +/// and the queued state with the [`NextState`] resource. +/// +/// State transitions typically occur in the [`OnEnter`] and [`OnExit`] schedules, +/// which can be run via the [`apply_state_transition::`] 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; +/// +/// 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; /// 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`] @@ -25,9 +59,9 @@ pub struct OnEnter(pub S); #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] pub struct OnExit(pub S); -/// A [`SystemSet`] that will run within \ 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(pub S); @@ -38,15 +72,31 @@ pub struct OnUpdate(pub S); /// The current state value can be accessed through this resource. To *change* the state, /// queue a transition in the [`NextState`] resource, and it will be applied by the next /// [`apply_state_transition::`] system. -#[derive(Resource)] +/// +/// The starting state is defined via the [`Default`] implementation for `S`. +#[derive(Resource, Default)] pub struct State(pub S); /// The next state of [`State`]. /// /// 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(pub Option); +impl NextState { + /// 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(world: &mut World) { + world.run_schedule(OnEnter(world.resource::>().0.clone())); +} + /// If a new state is queued in [`NextState`], this system: /// - Takes the new state value from [`NextState`] and updates [`State`]. /// - Runs the [`OnExit(exited_state)`] schedule. diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index ed2d469b9b2f5..06acb572d9e6a 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -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::(); /// # -/// # 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); diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index c224dd2e46c15..78b7b27379b2e 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -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 { - vec![self.func.as_system_label().as_label()] - } - fn default_system_sets(&self) -> Vec> { let set = crate::schedule_v3::SystemTypeSet::::new(); vec![Box::new(set)] } } -impl AsSystemLabel<(In, Out, Param, Marker, IsExclusiveFunctionSystem)> - for T -where - Param: ExclusiveSystemParam, - T: ExclusiveSystemParamFunction, -{ - #[inline] - fn as_system_label(&self) -> SystemLabelId { - SystemTypeIdLabel::(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, diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index 7493f6d3a1584..d9a9cd1ebf58e 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -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 { - vec![self.func.as_system_label().as_label()] - } - fn default_system_sets(&self) -> Vec> { let set = crate::schedule_v3::SystemTypeSet::::new(); vec![Box::new(set)] } } -/// A [`SystemLabel`] that was automatically generated for a system on the basis of its `TypeId`. -pub struct SystemTypeIdLabel(pub(crate) PhantomData T>); - -impl SystemLabel for SystemTypeIdLabel { - #[inline] - fn as_str(&self) -> &'static str { - std::any::type_name::() - } -} - -impl Debug for SystemTypeIdLabel { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("SystemTypeIdLabel") - .field(&std::any::type_name::()) - .finish() - } -} - -impl Clone for SystemTypeIdLabel { - fn clone(&self) -> Self { - *self - } -} -impl Copy for SystemTypeIdLabel {} - /// 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 { - fn as_system_label(&self) -> SystemLabelId; -} - -impl> - AsSystemLabel<(In, Out, Param, Marker)> for T -{ - #[inline] - fn as_system_label(&self) -> SystemLabelId { - SystemTypeIdLabel::(PhantomData).as_label() - } -} - -impl AsSystemLabel<()> for T { - #[inline] - fn as_system_label(&self) -> SystemLabelId { - self.as_label() - } -} diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 01871e6204fa3..1b3b735afeb46 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -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); - #[derive(StageLabel)] - struct UpdateStage; - #[test] fn simple_system() { fn sys(query: Query<&A>) { @@ -191,9 +192,7 @@ mod tests { fn run_system>(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::().0, 1); diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index a6034f0d53829..d8256765210d4 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -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 { - Vec::new() - } /// Returns the system's default [system sets](crate::schedule_v3::SystemSet). fn default_system_sets(&self) -> Vec> { Vec::new() diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 64fba4ea8b095..76ad5d447c86c 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -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 {} diff --git a/crates/bevy_ecs/src/system/system_piping.rs b/crates/bevy_ecs/src/system/system_piping.rs index efd545a46a30f..93798b730441f 100644 --- a/crates/bevy_ecs/src/system/system_piping.rs +++ b/crates/bevy_ecs/src/system/system_piping.rs @@ -146,12 +146,6 @@ impl> System for PipeSystem< self.system_b.set_last_change_tick(last_change_tick); } - fn default_labels(&self) -> Vec { - let mut labels = self.system_a.default_labels(); - labels.extend(&self.system_b.default_labels()); - labels - } - fn default_system_sets(&self) -> Vec> { 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) { /// 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) /// ) diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index ced43f4002094..fab437338e71e 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -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.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::() + .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::() + .insert(extracted_label, schedule); + } +} + impl fmt::Debug for World { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("World") diff --git a/crates/bevy_gilrs/src/lib.rs b/crates/bevy_gilrs/src/lib.rs index 9002f10b476c0..958dec3809fd4 100644 --- a/crates/bevy_gilrs/src/lib.rs +++ b/crates/bevy_gilrs/src/lib.rs @@ -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), diff --git a/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs b/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs index 22a2bd088239f..ddd07f19fbec1 100644 --- a/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs +++ b/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs @@ -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 { pub enabled: bool, _comp: PhantomData, } + +impl ReportHierarchyIssue { + /// Constructs a new object + pub fn new(enabled: bool) -> Self { + ReportHierarchyIssue { + enabled, + _comp: Default::default(), + } + } +} + +impl PartialEq for ReportHierarchyIssue { + fn eq(&self, other: &Self) -> bool { + self.enabled == other.enabled + } +} + impl Default for ReportHierarchyIssue { fn default() -> Self { Self { @@ -59,11 +76,11 @@ pub fn check_hierarchy_component_has_valid_parent( } /// Run criteria that only allows running when [`ReportHierarchyIssue`] is enabled. -pub fn on_hierarchy_reports_enabled(report: Res>) -> ShouldRun +pub fn on_hierarchy_reports_enabled(report: Res>) -> bool where T: Component, { - report.enabled.into() + report.enabled } /// Print a warning for each `Entity` with a `T` component @@ -79,11 +96,10 @@ impl Default for ValidParentCheckPlugin { impl Plugin for ValidParentCheckPlugin { fn build(&self, app: &mut App) { - app.init_resource::>() - .add_system_to_stage( - CoreStage::Last, - check_hierarchy_component_has_valid_parent:: - .with_run_criteria(on_hierarchy_reports_enabled::), - ); + app.init_resource::>().add_system( + check_hierarchy_component_has_valid_parent:: + .run_if(resource_equals(ReportHierarchyIssue::::new(true))) + .in_set(CoreSet::Last), + ); } } diff --git a/crates/bevy_input/src/input.rs b/crates/bevy_input/src/input.rs index a22145ea64f5d..45d6a7a3ef6f4 100644 --- a/crates/bevy_input/src/input.rs +++ b/crates/bevy_input/src/input.rs @@ -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. diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index 46c7e51127adb..6cbb4f262ea6b 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -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::() .init_resource::>() .init_resource::>() - .add_system_to_stage( - CoreStage::PreUpdate, - keyboard_input_system.label(InputSystem), - ) + .add_system(keyboard_input_system.in_set(InputSystem)) // mouse .add_event::() .add_event::() .add_event::() .init_resource::>() - .add_system_to_stage( - CoreStage::PreUpdate, - mouse_button_input_system.label(InputSystem), - ) + .add_system(mouse_button_input_system.in_set(InputSystem)) // gamepad .add_event::() .add_event::() @@ -79,30 +73,23 @@ impl Plugin for InputPlugin { .init_resource::>() .init_resource::>() .init_resource::>() - .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( - 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), + .add_systems( + ( + gamepad_event_system, + gamepad_connection_system.after(gamepad_event_system), + gamepad_button_event_system + .after(gamepad_event_system) + .after(gamepad_connection_system), + gamepad_axis_event_system + .after(gamepad_event_system) + .after(gamepad_connection_system), + ) + .in_set(InputSystem), ) // touch .add_event::() .init_resource::() - .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::(); diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 245f683ef62b6..8f22b7b2dbac4 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -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:: { prepass_enabled: self.prepass_enabled, - ..default() + ..Default::default() }) .init_resource::() .init_resource::() .init_resource::() .init_resource::() .add_plugin(ExtractResourcePlugin::::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), + ) + // 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::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), + .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_shadows.label(RenderLightSystems::QueueShadows), + .add_system( + render::queue_shadows + .in_set(RenderLightSystems::QueueShadows) + .in_set(RenderSet::Queue), ) - .add_system_to_stage(RenderStage::Queue, render::queue_shadow_view_bind_group) - .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) + .add_system(render::queue_shadow_view_bind_group.in_set(RenderSet::Queue)) + .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) .init_resource::() .init_resource::>() .init_resource::() diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index c71445b0ee9e4..8edabd93fe67e 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -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, diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index a3039f08cd151..596b7c9b54b41 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -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::>() .init_resource::>() .init_resource::>>() - .add_system_to_stage(RenderStage::Extract, extract_materials::) - .add_system_to_stage( - RenderStage::Prepare, - prepare_materials::.after(PrepareAssetLabel::PreAssetPrepare), + .add_system_to_schedule(ExtractSchedule, extract_materials::) + .add_system( + prepare_materials:: + .after(PrepareAssetLabel::PreAssetPrepare) + .in_set(RenderSet::Prepare), ) - .add_system_to_stage(RenderStage::Queue, queue_material_meshes::); + .add_system(queue_material_meshes::.in_set(RenderSet::Queue)); } if self.prepass_enabled { diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index cea5c77abd239..222266cda5ec9 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -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::) - .add_system_to_stage(RenderStage::Queue, queue_prepass_material_meshes::) - .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) - .add_system_to_stage( - RenderStage::PhaseSort, - sort_phase_system::, - ) + .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::.in_set(RenderSet::Queue)) + .add_system(queue_prepass_material_meshes::.in_set(RenderSet::Queue)) + .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) + .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) .init_resource::>() .init_resource::>() .init_resource::>() diff --git a/crates/bevy_pbr/src/render/fog.rs b/crates/bevy_pbr/src/render/fog.rs index d3125e2e8975a..2c658bb7a16e4 100644 --- a/crates/bevy_pbr/src/render/fog.rs +++ b/crates/bevy_pbr/src/render/fog.rs @@ -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::::default()); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.init_resource::().add_system_to_stage( - RenderStage::Prepare, - prepare_fog.label(RenderFogSystems::PrepareFog), - ); + render_app + .init_resource::() + .add_system(prepare_fog.in_set(RenderFogSystems::PrepareFog)) + .configure_set(RenderFogSystems::PrepareFog.in_set(RenderSet::Prepare)); } } } diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 05f8d30bd3ec8..545e09e6014ff 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -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, diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index c29429a031995..a765fbc59cabe 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -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::() .init_resource::() - .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)); } } } diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 471f3b94f8516..34d93c91853c8 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -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::() .init_resource::() .init_resource::>() - .add_system_to_stage(RenderStage::Queue, queue_wireframes); + .add_system(queue_wireframes.in_set(RenderSet::Queue)); } } } diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 27715d749236c..9434e5e80aaae 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -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( mut window_resized_events: EventReader, mut window_created_events: EventReader, diff --git a/crates/bevy_render/src/camera/mod.rs b/crates/bevy_render/src/camera/mod.rs index df81b87b261c8..a3c46471955cb 100644 --- a/crates/bevy_render/src/camera/mod.rs +++ b/crates/bevy_render/src/camera/mod.rs @@ -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::::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::(); render_graph.add_node(crate::main_graph::node::CAMERA_DRIVER, camera_driver_node); diff --git a/crates/bevy_render/src/camera/projection.rs b/crates/bevy_render/src/camera/projection.rs index c3d6591e17b2d..00df1c0b9a190 100644 --- a/crates/bevy_render/src/camera/projection.rs +++ b/crates/bevy_render/src/camera/projection.rs @@ -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 Default for CameraProjectionPlugin { /// Label for [`camera_system`], shared across all `T`. /// /// [`camera_system`]: crate::camera::camera_system -#[derive(SystemLabel, Clone, Eq, PartialEq, Hash, Debug)] +#[derive(SystemSet, Clone, Eq, PartialEq, Hash, Debug)] pub struct CameraUpdateSystem; impl Plugin for CameraProjectionPlugin { fn build(&self, app: &mut App) { app.register_type::() - .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:: - .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:: - .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. diff --git a/crates/bevy_render/src/extract_component.rs b/crates/bevy_render/src/extract_component.rs index f29905c1a7bc9..6f6241a59ebcb 100644 --- a/crates/bevy_render/src/extract_component.rs +++ b/crates/bevy_render/src/extract_component.rs @@ -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 DynamicUniformIndex { /// 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(PhantomData C>); @@ -83,7 +83,7 @@ impl Plugin for UniformComponentP if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .insert_resource(ComponentUniforms::::default()) - .add_system_to_stage(RenderStage::Prepare, prepare_uniform_components::); + .add_system(prepare_uniform_components::.in_set(RenderSet::Prepare)); } } } @@ -151,7 +151,7 @@ fn prepare_uniform_components( /// 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 { only_extract_visible: bool, @@ -180,10 +180,9 @@ impl Plugin for ExtractComponentPlugin { 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::); + render_app.add_system_to_schedule(ExtractSchedule, extract_visible_components::); } else { - render_app.add_system_to_stage(RenderStage::Extract, extract_components::); + render_app.add_system_to_schedule(ExtractSchedule, extract_components::); } } } diff --git a/crates/bevy_render/src/extract_param.rs b/crates/bevy_render/src/extract_param.rs index 24ad4d70ebc6e..92290407f5b25 100644 --- a/crates/bevy_render/src/extract_param.rs +++ b/crates/bevy_render/src/extract_param.rs @@ -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 diff --git a/crates/bevy_render/src/extract_resource.rs b/crates/bevy_render/src/extract_resource.rs index af726c5c8a38d..65b3d954fbb15 100644 --- a/crates/bevy_render/src/extract_resource.rs +++ b/crates/bevy_render/src/extract_resource.rs @@ -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(PhantomData); @@ -35,7 +32,7 @@ impl Default for ExtractResourcePlugin { impl Plugin for ExtractResourcePlugin { 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::); + render_app.add_system_to_schedule(ExtractSchedule, extract_resource::); } } } diff --git a/crates/bevy_render/src/globals.rs b/crates/bevy_render/src/globals.rs index a0b160dd4b27d..f0a4989467f66 100644 --- a/crates/bevy_render/src/globals.rs +++ b/crates/bevy_render/src/globals.rs @@ -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::(); + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() .init_resource::