From d83d490700d41675135e8ef94870781195b5bc73 Mon Sep 17 00:00:00 2001 From: Alexander Sepity Date: Tue, 9 Feb 2021 23:14:10 +0300 Subject: [PATCH] System sets and parallel executor v2 (#1144) System sets and parallel executor v2 --- crates/bevy_app/src/app.rs | 5 +- crates/bevy_app/src/app_builder.rs | 84 +- crates/bevy_audio/src/audio_output.rs | 2 +- crates/bevy_audio/src/lib.rs | 6 +- crates/bevy_core/src/label.rs | 31 +- crates/bevy_core/src/time/fixed_timestep.rs | 22 +- .../src/entity_count_diagnostics_plugin.rs | 4 +- crates/bevy_ecs/Cargo.toml | 6 + crates/bevy_ecs/benches/system_stage.rs | 172 ++ crates/bevy_ecs/src/core/access.rs | 161 +- crates/bevy_ecs/src/core/mod.rs | 2 +- crates/bevy_ecs/src/lib.rs | 12 +- .../bevy_ecs/src/resource/resource_query.rs | 31 + crates/bevy_ecs/src/resource/resources.rs | 76 +- crates/bevy_ecs/src/schedule/executor.rs | 37 + .../src/schedule/executor_parallel.rs | 508 ++++++ crates/bevy_ecs/src/schedule/mod.rs | 454 ++---- crates/bevy_ecs/src/schedule/stage.rs | 1400 +++++++++++++++-- .../bevy_ecs/src/schedule/stage_executor.rs | 528 ------- crates/bevy_ecs/src/schedule/state.rs | 28 +- .../bevy_ecs/src/schedule/system_container.rs | 178 +++ .../src/schedule/system_descriptor.rs | 254 +++ crates/bevy_ecs/src/schedule/system_set.rs | 45 + .../bevy_ecs/src/system/exclusive_system.rs | 125 ++ crates/bevy_ecs/src/system/into_system.rs | 105 +- .../bevy_ecs/src/system/into_thread_local.rs | 72 - crates/bevy_ecs/src/system/mod.rs | 4 +- crates/bevy_ecs/src/system/system.rs | 16 +- crates/bevy_ecs/src/system/system_chaining.rs | 42 +- crates/bevy_ecs/src/system/system_param.rs | 38 +- crates/bevy_gilrs/src/gilrs_system.rs | 4 +- crates/bevy_gilrs/src/lib.rs | 11 +- crates/bevy_log/src/lib.rs | 2 +- crates/bevy_render/src/lib.rs | 4 +- crates/bevy_render/src/render_graph/graph.rs | 2 +- crates/bevy_render/src/render_graph/system.rs | 4 +- crates/bevy_scene/src/lib.rs | 4 +- crates/bevy_text/src/draw.rs | 3 +- .../hierarchy/hierarchy_maintenance_system.rs | 8 +- .../src/transform_propagate_system.rs | 6 +- crates/bevy_ui/src/lib.rs | 15 +- crates/bevy_ui/src/update.rs | 4 +- crates/bevy_wgpu/src/lib.rs | 4 +- crates/bevy_winit/src/lib.rs | 6 +- examples/scene/scene.rs | 2 +- 45 files changed, 3169 insertions(+), 1358 deletions(-) create mode 100644 crates/bevy_ecs/benches/system_stage.rs create mode 100644 crates/bevy_ecs/src/schedule/executor.rs create mode 100644 crates/bevy_ecs/src/schedule/executor_parallel.rs delete mode 100644 crates/bevy_ecs/src/schedule/stage_executor.rs create mode 100644 crates/bevy_ecs/src/schedule/system_container.rs create mode 100644 crates/bevy_ecs/src/schedule/system_descriptor.rs create mode 100644 crates/bevy_ecs/src/schedule/system_set.rs create mode 100644 crates/bevy_ecs/src/system/exclusive_system.rs delete mode 100644 crates/bevy_ecs/src/system/into_thread_local.rs diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 8454df741fae81..2b2e1a2a0ce448 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -1,5 +1,5 @@ use crate::app_builder::AppBuilder; -use bevy_ecs::{Resources, Schedule, World}; +use bevy_ecs::{Resources, Schedule, Stage, World}; #[cfg(feature = "trace")] use bevy_utils::tracing::info_span; @@ -53,8 +53,7 @@ impl App { } pub fn update(&mut self) { - self.schedule - .initialize_and_run(&mut self.world, &mut self.resources); + self.schedule.run(&mut self.world, &mut self.resources); } pub fn run(mut self) { diff --git a/crates/bevy_app/src/app_builder.rs b/crates/bevy_app/src/app_builder.rs index 6f6e9dd789931c..1d019696b673e3 100644 --- a/crates/bevy_app/src/app_builder.rs +++ b/crates/bevy_app/src/app_builder.rs @@ -5,8 +5,8 @@ use crate::{ stage, startup_stage, PluginGroup, PluginGroupBuilder, }; use bevy_ecs::{ - clear_trackers_system, FromResources, IntoSystem, Resource, Resources, RunOnce, Schedule, - Stage, StateStage, System, SystemStage, World, + clear_trackers_system, FromResources, IntoExclusiveSystem, IntoSystem, Resource, Resources, + RunOnce, Schedule, Stage, StateStage, SystemDescriptor, SystemStage, World, }; use bevy_utils::tracing::debug; @@ -24,7 +24,7 @@ impl Default for AppBuilder { app_builder .add_default_stages() .add_event::() - .add_system_to_stage(stage::LAST, clear_trackers_system.system()); + .add_system_to_stage(stage::LAST, clear_trackers_system.exclusive_system()); app_builder } } @@ -125,60 +125,69 @@ impl AppBuilder { self } - pub fn add_system>(&mut self, system: S) -> &mut Self { + pub fn add_system(&mut self, system: impl Into) -> &mut Self { self.add_system_to_stage(stage::UPDATE, system) } - pub fn on_state_enter>( + pub fn add_system_to_stage( + &mut self, + stage_name: &'static str, + system: impl Into, + ) -> &mut Self { + self.app.schedule.add_system_to_stage(stage_name, system); + self + } + + pub fn add_startup_system(&mut self, system: impl Into) -> &mut Self { + self.add_startup_system_to_stage(startup_stage::STARTUP, system) + } + + pub fn add_startup_system_to_stage( + &mut self, + stage_name: &'static str, + system: impl Into, + ) -> &mut Self { + self.app + .schedule + .stage(stage::STARTUP, |schedule: &mut Schedule| { + schedule.add_system_to_stage(stage_name, system) + }); + self + } + + pub fn on_state_enter( &mut self, stage: &str, state: T, - system: S, + system: impl Into, ) -> &mut Self { self.stage(stage, |stage: &mut StateStage| { stage.on_state_enter(state, system) }) } - pub fn on_state_update>( + pub fn on_state_update( &mut self, stage: &str, state: T, - system: S, + system: impl Into, ) -> &mut Self { self.stage(stage, |stage: &mut StateStage| { stage.on_state_update(state, system) }) } - pub fn on_state_exit>( + pub fn on_state_exit( &mut self, stage: &str, state: T, - system: S, + system: impl Into, ) -> &mut Self { self.stage(stage, |stage: &mut StateStage| { stage.on_state_exit(state, system) }) } - pub fn add_startup_system_to_stage>( - &mut self, - stage_name: &'static str, - system: S, - ) -> &mut Self { - self.app - .schedule - .stage(stage::STARTUP, |schedule: &mut Schedule| { - schedule.add_system_to_stage(stage_name, system) - }); - self - } - - pub fn add_startup_system>(&mut self, system: S) -> &mut Self { - self.add_startup_system_to_stage(startup_stage::STARTUP, system) - } - pub fn add_default_stages(&mut self) -> &mut Self { self.add_stage( stage::STARTUP, @@ -197,15 +206,6 @@ impl AppBuilder { .add_stage(stage::LAST, SystemStage::parallel()) } - pub fn add_system_to_stage>( - &mut self, - stage_name: &'static str, - system: S, - ) -> &mut Self { - self.app.schedule.add_system_to_stage(stage_name, system); - self - } - pub fn add_event(&mut self) -> &mut Self where T: Send + Sync + 'static, @@ -223,11 +223,11 @@ impl AppBuilder { self } - pub fn insert_thread_local_resource(&mut self, resource: T) -> &mut Self + pub fn insert_non_send_resource(&mut self, resource: T) -> &mut Self where T: 'static, { - self.app.resources.insert_thread_local(resource); + self.app.resources.insert_non_send(resource); self } @@ -242,20 +242,18 @@ impl AppBuilder { let resource = R::from_resources(&self.resources()); self.insert_resource(resource); } - self } - pub fn init_thread_local_resource(&mut self) -> &mut Self + pub fn init_non_send_resource(&mut self) -> &mut Self where R: FromResources + 'static, { // See perf comment in init_resource - if self.app.resources.get_thread_local::().is_none() { + if self.app.resources.get_non_send::().is_none() { let resource = R::from_resources(&self.app.resources); - self.app.resources.insert_thread_local(resource); + self.app.resources.insert_non_send(resource); } - self } diff --git a/crates/bevy_audio/src/audio_output.rs b/crates/bevy_audio/src/audio_output.rs index 2571b0c72b9a4f..2268851bfff1c6 100644 --- a/crates/bevy_audio/src/audio_output.rs +++ b/crates/bevy_audio/src/audio_output.rs @@ -65,7 +65,7 @@ where

::Decoder: rodio::Source + Send + Sync, <

::Decoder as Iterator>::Item: rodio::Sample + Send + Sync, { - let audio_output = resources.get_thread_local::>().unwrap(); + let audio_output = resources.get_non_send::>().unwrap(); let mut audio = resources.get_mut::>().unwrap(); if let Some(audio_sources) = resources.get::>() { diff --git a/crates/bevy_audio/src/lib.rs b/crates/bevy_audio/src/lib.rs index 52b7a56dea06e8..594c2c78b65ab7 100644 --- a/crates/bevy_audio/src/lib.rs +++ b/crates/bevy_audio/src/lib.rs @@ -12,7 +12,7 @@ pub mod prelude { use bevy_app::prelude::*; use bevy_asset::AddAsset; -use bevy_ecs::IntoSystem; +use bevy_ecs::IntoExclusiveSystem; /// Adds support for audio playback to an App #[derive(Default)] @@ -20,13 +20,13 @@ pub struct AudioPlugin; impl Plugin for AudioPlugin { fn build(&self, app: &mut AppBuilder) { - app.init_thread_local_resource::>() + app.init_non_send_resource::>() .add_asset::() .init_asset_loader::() .init_resource::>() .add_system_to_stage( stage::POST_UPDATE, - play_queued_audio_system::.system(), + play_queued_audio_system::.exclusive_system(), ); } } diff --git a/crates/bevy_core/src/label.rs b/crates/bevy_core/src/label.rs index 93f7f8ec8e5339..1a70d3220bd3d9 100644 --- a/crates/bevy_core/src/label.rs +++ b/crates/bevy_core/src/label.rs @@ -115,13 +115,14 @@ pub(crate) fn entity_labels_system( #[cfg(test)] mod tests { use super::*; + use bevy_ecs::Stage; fn setup() -> (World, Resources, bevy_ecs::Schedule) { let world = World::new(); let mut resources = Resources::default(); resources.insert(EntityLabels::default()); let mut schedule = bevy_ecs::Schedule::default(); - schedule.add_stage("test", SystemStage::serial()); + schedule.add_stage("test", SystemStage::single_threaded()); schedule.add_system_to_stage("test", entity_labels_system.system()); (world, resources, schedule) } @@ -139,7 +140,7 @@ mod tests { let (mut world, mut resources, mut schedule) = setup(); let e1 = world.spawn((holy_cow(),)); - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); let entity_labels = resources.get::().unwrap(); assert_eq!(entity_labels.get("holy"), &[e1], "holy"); @@ -151,10 +152,10 @@ mod tests { fn add_labels() { let (mut world, mut resources, mut schedule) = setup(); let e1 = world.spawn((holy_cow(),)); - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); world.get_mut::(e1).unwrap().insert("shalau"); - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); let entity_labels = resources.get::().unwrap(); assert_eq!(entity_labels.get("holy"), &[e1], "holy"); @@ -166,10 +167,10 @@ mod tests { fn remove_labels() { let (mut world, mut resources, mut schedule) = setup(); let e1 = world.spawn((holy_cow(),)); - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); world.get_mut::(e1).unwrap().remove("holy"); - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); let entity_labels = resources.get::().unwrap(); assert_eq!(entity_labels.get("holy"), &[], "holy"); @@ -181,10 +182,10 @@ mod tests { fn removes_despawned_entity() { let (mut world, mut resources, mut schedule) = setup(); let e1 = world.spawn((holy_cow(),)); - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); world.despawn(e1).unwrap(); - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); let entity_labels = resources.get::().unwrap(); assert_eq!(entity_labels.get("holy"), &[], "holy"); @@ -196,10 +197,10 @@ mod tests { fn removes_labels_when_component_removed() { let (mut world, mut resources, mut schedule) = setup(); let e1 = world.spawn((holy_cow(),)); - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); world.remove_one::(e1).unwrap(); - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); let entity_labels = resources.get::().unwrap(); assert_eq!(entity_labels.get("holy"), &[], "holy"); @@ -211,10 +212,10 @@ mod tests { fn adds_another_spawned_entity() { let (mut world, mut resources, mut schedule) = setup(); let e1 = world.spawn((holy_cow(),)); - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); let e2 = world.spawn((holy_shamoni(),)); - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); let entity_labels = resources.get::().unwrap(); assert_eq!(entity_labels.get("holy"), &[e1, e2], "holy"); @@ -227,13 +228,13 @@ mod tests { fn removes_despawned_entity_but_leaves_other() { let (mut world, mut resources, mut schedule) = setup(); let e1 = world.spawn((holy_cow(),)); - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); let e2 = world.spawn((holy_shamoni(),)); - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); world.despawn(e1).unwrap(); - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); let entity_labels = resources.get::().unwrap(); assert_eq!(entity_labels.get("holy"), &[e2], "holy"); diff --git a/crates/bevy_core/src/time/fixed_timestep.rs b/crates/bevy_core/src/time/fixed_timestep.rs index e8ddea53037264..80f99ef636e616 100644 --- a/crates/bevy_core/src/time/fixed_timestep.rs +++ b/crates/bevy_core/src/time/fixed_timestep.rs @@ -1,5 +1,5 @@ use crate::Time; -use bevy_ecs::{ArchetypeComponent, ShouldRun, System, SystemId, ThreadLocalExecution, TypeAccess}; +use bevy_ecs::{ArchetypeComponent, ShouldRun, System, SystemId, TypeAccess}; use bevy_utils::HashMap; use std::{any::TypeId, borrow::Cow}; @@ -47,8 +47,9 @@ pub struct FixedTimestep { looping: bool, system_id: SystemId, label: Option, // TODO: consider making this a TypedLabel - resource_access: TypeAccess, archetype_access: TypeAccess, + component_access: TypeAccess, + resource_access: TypeAccess, } impl Default for FixedTimestep { @@ -59,8 +60,9 @@ impl Default for FixedTimestep { accumulator: 0.0, looping: false, label: None, - resource_access: Default::default(), + component_access: Default::default(), archetype_access: Default::default(), + resource_access: Default::default(), } } } @@ -93,7 +95,7 @@ impl FixedTimestep { if self.accumulator >= self.step { self.accumulator -= self.step; self.looping = true; - ShouldRun::YesAndLoop + ShouldRun::YesAndCheckAgain } else { self.looping = false; ShouldRun::No @@ -113,18 +115,22 @@ impl System for FixedTimestep { self.system_id } - fn update(&mut self, _world: &bevy_ecs::World) {} + fn update_access(&mut self, _world: &bevy_ecs::World) {} fn archetype_component_access(&self) -> &TypeAccess { &self.archetype_access } + fn component_access(&self) -> &TypeAccess { + &self.component_access + } + fn resource_access(&self) -> &TypeAccess { &self.resource_access } - fn thread_local_execution(&self) -> ThreadLocalExecution { - ThreadLocalExecution::Immediate + fn is_non_send(&self) -> bool { + false } unsafe fn run_unsafe( @@ -145,7 +151,7 @@ impl System for FixedTimestep { Some(result) } - fn run_thread_local( + fn apply_buffers( &mut self, _world: &mut bevy_ecs::World, _resources: &mut bevy_ecs::Resources, diff --git a/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs index 36a709f453795b..05a309a206c126 100644 --- a/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs @@ -1,6 +1,6 @@ use crate::{Diagnostic, DiagnosticId, Diagnostics}; use bevy_app::prelude::*; -use bevy_ecs::{IntoSystem, ResMut, Resources, World}; +use bevy_ecs::{IntoExclusiveSystem, IntoSystem, ResMut, Resources, World}; /// Adds "entity count" diagnostic to an App #[derive(Default)] @@ -9,7 +9,7 @@ pub struct EntityCountDiagnosticsPlugin; impl Plugin for EntityCountDiagnosticsPlugin { fn build(&self, app: &mut AppBuilder) { app.add_startup_system(Self::setup_system.system()) - .add_system(Self::diagnostic_system.system()); + .add_system(Self::diagnostic_system.exclusive_system()); } } diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index a29b480b412bff..6203ab1a2383cf 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -21,6 +21,7 @@ bevy_tasks = { path = "../bevy_tasks", version = "0.4.0" } bevy_utils = { path = "../bevy_utils", version = "0.4.0" } bevy_ecs_macros = { path = "macros", version = "0.4.0" } fxhash = "0.2" +async-channel = "1.4.2" rand = "0.8.0" serde = "1.0" thiserror = "1.0" @@ -32,8 +33,13 @@ lazy_static = { version = "1.4.0" } [dev-dependencies] bencher = "0.1.5" +criterion = "0.3" [[bench]] name = "bench" harness = false required-features = ["macros"] + +[[bench]] +name = "system_stage" +harness = false diff --git a/crates/bevy_ecs/benches/system_stage.rs b/crates/bevy_ecs/benches/system_stage.rs new file mode 100644 index 00000000000000..67c63d1894155d --- /dev/null +++ b/crates/bevy_ecs/benches/system_stage.rs @@ -0,0 +1,172 @@ +use bevy_ecs::{prelude::*, Stage}; +use criterion::{criterion_group, criterion_main, Criterion}; + +criterion_group!(benches, empty_systems, busy_systems, contrived); +criterion_main!(benches); + +fn run_stage(stage: &mut SystemStage, world: &mut World, resources: &mut Resources) { + // !!NB!! Uncomment next line when running with old executor. + //stage.initialize(world, resources); + stage.run(world, resources); +} + +struct A(f32); +struct B(f32); +struct C(f32); +struct D(f32); +struct E(f32); + +const ENTITY_BUNCH: usize = 5000; + +fn empty_systems(criterion: &mut Criterion) { + let mut world = World::new(); + let mut resources = Resources::default(); + let mut group = criterion.benchmark_group("empty_systems"); + group.warm_up_time(std::time::Duration::from_millis(500)); + group.measurement_time(std::time::Duration::from_secs(3)); + fn empty() {} + for amount in 0..5 { + let mut stage = SystemStage::parallel(); + for _ in 0..amount { + stage.add_system(empty.system()); + } + run_stage(&mut stage, &mut world, &mut resources); + group.bench_function(&format!("{:03}_systems", amount), |bencher| { + bencher.iter(|| { + run_stage(&mut stage, &mut world, &mut resources); + }); + }); + } + for amount in 1..21 { + let mut stage = SystemStage::parallel(); + for _ in 0..amount { + stage + .add_system(empty.system()) + .add_system(empty.system()) + .add_system(empty.system()) + .add_system(empty.system()) + .add_system(empty.system()); + } + run_stage(&mut stage, &mut world, &mut resources); + group.bench_function(&format!("{:03}_systems", 5 * amount), |bencher| { + bencher.iter(|| { + run_stage(&mut stage, &mut world, &mut resources); + }); + }); + } + group.finish() +} + +fn busy_systems(criterion: &mut Criterion) { + fn ab(mut q: Query<(&mut A, &mut B)>) { + for (mut a, mut b) in q.iter_mut() { + std::mem::swap(&mut a.0, &mut b.0); + } + } + fn cd(mut q: Query<(&mut C, &mut D)>) { + for (mut c, mut d) in q.iter_mut() { + std::mem::swap(&mut c.0, &mut d.0); + } + } + fn ce(mut q: Query<(&mut C, &mut E)>) { + for (mut c, mut e) in q.iter_mut() { + std::mem::swap(&mut c.0, &mut e.0); + } + } + let mut world = World::new(); + let mut resources = Resources::default(); + let mut group = criterion.benchmark_group("busy_systems"); + group.warm_up_time(std::time::Duration::from_millis(500)); + group.measurement_time(std::time::Duration::from_secs(3)); + for entity_bunches in 1..6 { + world.spawn_batch((0..4 * ENTITY_BUNCH).map(|_| (A(0.0), B(0.0)))); + world.spawn_batch((0..4 * ENTITY_BUNCH).map(|_| (A(0.0), B(0.0), C(0.0)))); + 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.system()) + .add_system(cd.system()) + .add_system(ce.system()); + for _ in 0..system_amount { + stage + .add_system(ab.system()) + .add_system(cd.system()) + .add_system(ce.system()); + } + run_stage(&mut stage, &mut world, &mut resources); + group.bench_function( + &format!( + "{:02}x_entities_{:02}_systems", + entity_bunches, + 3 * system_amount + 3 + ), + |bencher| { + bencher.iter(|| { + run_stage(&mut stage, &mut world, &mut resources); + }); + }, + ); + } + } + group.finish() +} + +fn contrived(criterion: &mut Criterion) { + fn s_0(mut q_0: Query<(&mut A, &mut B)>) { + for (mut c_0, mut c_1) in q_0.iter_mut() { + std::mem::swap(&mut c_0.0, &mut c_1.0); + } + } + fn s_1(mut q_0: Query<(&mut A, &mut C)>, mut q_1: Query<(&mut B, &mut D)>) { + for (mut c_0, mut c_1) in q_0.iter_mut() { + std::mem::swap(&mut c_0.0, &mut c_1.0); + } + for (mut c_0, mut c_1) in q_1.iter_mut() { + std::mem::swap(&mut c_0.0, &mut c_1.0); + } + } + fn s_2(mut q_0: Query<(&mut C, &mut D)>) { + for (mut c_0, mut c_1) in q_0.iter_mut() { + std::mem::swap(&mut c_0.0, &mut c_1.0); + } + } + let mut world = World::new(); + let mut resources = Resources::default(); + let mut group = criterion.benchmark_group("contrived"); + group.warm_up_time(std::time::Duration::from_millis(500)); + group.measurement_time(std::time::Duration::from_secs(3)); + for entity_bunches in 1..6 { + 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)))); + 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.system()) + .add_system(s_1.system()) + .add_system(s_2.system()); + for _ in 0..system_amount { + stage + .add_system(s_0.system()) + .add_system(s_1.system()) + .add_system(s_2.system()); + } + run_stage(&mut stage, &mut world, &mut resources); + group.bench_function( + &format!( + "{:02}x_entities_{:02}_systems", + entity_bunches, + 3 * system_amount + 3 + ), + |bencher| { + bencher.iter(|| { + run_stage(&mut stage, &mut world, &mut resources); + }); + }, + ); + } + } + group.finish() +} diff --git a/crates/bevy_ecs/src/core/access.rs b/crates/bevy_ecs/src/core/access.rs index 938a159500837d..6eefb4258170b3 100644 --- a/crates/bevy_ecs/src/core/access.rs +++ b/crates/bevy_ecs/src/core/access.rs @@ -1,10 +1,11 @@ use bevy_utils::HashSet; +use fixedbitset::FixedBitSet; use std::{any::TypeId, boxed::Box, hash::Hash, vec::Vec}; use super::{Archetype, World}; #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] -pub enum Access { +enum ArchetypeAccess { None, Read, Write, @@ -81,6 +82,22 @@ impl QueryAccess { } } + pub fn get_component_access(&self, type_access: &mut TypeAccess) { + match self { + QueryAccess::None => {} + QueryAccess::Read(ty, _) => type_access.add_read(*ty), + QueryAccess::Write(ty, _) => type_access.add_write(*ty), + QueryAccess::Optional(access) => access.get_component_access(type_access), + QueryAccess::With(_, access) => access.get_component_access(type_access), + QueryAccess::Without(_, access) => access.get_component_access(type_access), + QueryAccess::Union(accesses) => { + for access in accesses { + access.get_component_access(type_access); + } + } + } + } + pub fn get_type_name(&self, type_id: TypeId) -> Option<&'static str> { match self { QueryAccess::None => None, @@ -115,20 +132,20 @@ impl QueryAccess { /// Returns how this [QueryAccess] accesses the given `archetype`. /// If `type_access` is set, it will populate type access with the types this query reads/writes - pub fn get_access( + fn get_access( &self, archetype: &Archetype, archetype_index: u32, type_access: Option<&mut TypeAccess>, - ) -> Option { + ) -> Option { match self { - QueryAccess::None => Some(Access::None), + QueryAccess::None => Some(ArchetypeAccess::None), QueryAccess::Read(ty, _) => { if archetype.has_type(*ty) { if let Some(type_access) = type_access { type_access.add_read(ArchetypeComponent::new_ty(archetype_index, *ty)); } - Some(Access::Read) + Some(ArchetypeAccess::Read) } else { None } @@ -138,7 +155,7 @@ impl QueryAccess { if let Some(type_access) = type_access { type_access.add_write(ArchetypeComponent::new_ty(archetype_index, *ty)); } - Some(Access::Write) + Some(ArchetypeAccess::Write) } else { None } @@ -152,7 +169,7 @@ impl QueryAccess { Some(access) } } else { - Some(Access::Read) + Some(ArchetypeAccess::Read) } } QueryAccess::With(ty, query_access) => { @@ -174,7 +191,7 @@ impl QueryAccess { for query_access in query_accesses { if let Some(access) = query_access.get_access(archetype, archetype_index, None) { - result = Some(result.unwrap_or(Access::Read).max(access)); + result = Some(result.unwrap_or(ArchetypeAccess::Read).max(access)); } else { return None; } @@ -198,17 +215,17 @@ impl QueryAccess { /// Provides information about the types a [System] reads and writes #[derive(Debug, Eq, PartialEq, Clone)] pub struct TypeAccess { + reads_all: bool, reads_and_writes: HashSet, writes: HashSet, - reads: HashSet, } impl Default for TypeAccess { fn default() -> Self { Self { + reads_all: false, reads_and_writes: Default::default(), writes: Default::default(), - reads: Default::default(), } } } @@ -219,36 +236,44 @@ impl TypeAccess { for write in writes { type_access.add_write(write); } - for read in reads { type_access.add_read(read); } - type_access } pub fn is_compatible(&self, other: &TypeAccess) -> bool { - self.writes.is_disjoint(&other.reads_and_writes) - && self.reads_and_writes.is_disjoint(&other.writes) + if self.reads_all { + other.writes.is_empty() + } else if other.reads_all { + self.writes.is_empty() + } else { + self.writes.is_disjoint(&other.reads_and_writes) + && self.reads_and_writes.is_disjoint(&other.writes) + } } pub fn get_conflict<'a>(&'a self, other: &'a TypeAccess) -> Option<&'a T> { - let conflict = self.writes.intersection(&other.reads_and_writes).next(); - if conflict.is_some() { - return conflict; + if self.reads_all { + other.writes.iter().next() + } else if other.reads_all { + self.writes.iter().next() + } else { + match self.writes.intersection(&other.reads_and_writes).next() { + Some(element) => Some(element), + None => other.writes.intersection(&self.reads_and_writes).next(), + } } - self.reads_and_writes.intersection(&other.writes).next() } - pub fn union(&mut self, other: &TypeAccess) { + pub fn extend(&mut self, other: &TypeAccess) { + self.reads_all = self.reads_all || other.reads_all; self.writes.extend(&other.writes); - self.reads.extend(&other.reads); self.reads_and_writes.extend(&other.reads_and_writes); } pub fn add_read(&mut self, ty: T) { self.reads_and_writes.insert(ty); - self.reads.insert(ty); } pub fn add_write(&mut self, ty: T) { @@ -256,26 +281,108 @@ impl TypeAccess { self.writes.insert(ty); } + pub fn read_all(&mut self) { + self.reads_all = true; + } + pub fn clear(&mut self) { + self.reads_all = false; self.reads_and_writes.clear(); - self.reads.clear(); self.writes.clear(); } pub fn is_read_or_write(&self, ty: &T) -> bool { - self.reads_and_writes.contains(ty) + self.reads_all || self.reads_and_writes.contains(ty) } pub fn is_write(&self, ty: &T) -> bool { self.writes.contains(ty) } - pub fn iter_reads(&self) -> impl Iterator { - self.reads.iter() + pub fn reads_all(&self) -> bool { + self.reads_all + } + + /// Returns an iterator of distinct accessed types if only some types are accessed. + pub fn all_distinct_types(&self) -> Option> { + if !self.reads_all { + return Some(self.reads_and_writes.iter()); + } + None } - pub fn iter_writes(&self) -> impl Iterator { - self.writes.iter() + pub fn condense(&self, all_types: &[T]) -> CondensedTypeAccess { + if self.reads_all { + let mut writes = FixedBitSet::with_capacity(all_types.len()); + for (index, access_type) in all_types.iter().enumerate() { + if self.writes.contains(access_type) { + writes.insert(index); + } + } + CondensedTypeAccess { + reads_all: true, + reads_and_writes: Default::default(), + writes, + } + } else { + let mut reads_and_writes = FixedBitSet::with_capacity(all_types.len()); + let mut writes = FixedBitSet::with_capacity(all_types.len()); + for (index, access_type) in all_types.iter().enumerate() { + if self.writes.contains(access_type) { + reads_and_writes.insert(index); + writes.insert(index); + } else if self.reads_and_writes.contains(access_type) { + reads_and_writes.insert(index); + } + } + CondensedTypeAccess { + reads_all: false, + reads_and_writes, + writes, + } + } + } +} + +// TODO: consider making it typed, to enable compiler helping with bug hunting? +#[derive(Default, Debug, Eq, PartialEq, Clone)] +pub struct CondensedTypeAccess { + reads_all: bool, + reads_and_writes: FixedBitSet, + writes: FixedBitSet, +} + +impl CondensedTypeAccess { + pub fn grow(&mut self, bits: usize) { + self.reads_and_writes.grow(bits); + self.writes.grow(bits); + } + + pub fn reads_all(&self) -> bool { + self.reads_all + } + + pub fn clear(&mut self) { + self.reads_all = false; + self.reads_and_writes.clear(); + self.writes.clear(); + } + + pub fn extend(&mut self, other: &CondensedTypeAccess) { + self.reads_all = self.reads_all || other.reads_all; + self.reads_and_writes.union_with(&other.reads_and_writes); + self.writes.union_with(&other.writes); + } + + pub fn is_compatible(&self, other: &CondensedTypeAccess) -> bool { + if self.reads_all { + 0 == other.writes.count_ones(..) + } else if other.reads_all { + 0 == self.writes.count_ones(..) + } else { + self.writes.is_disjoint(&other.reads_and_writes) + && self.reads_and_writes.is_disjoint(&other.writes) + } } } diff --git a/crates/bevy_ecs/src/core/mod.rs b/crates/bevy_ecs/src/core/mod.rs index 512bb10e488196..9cc61470dd5960 100644 --- a/crates/bevy_ecs/src/core/mod.rs +++ b/crates/bevy_ecs/src/core/mod.rs @@ -43,7 +43,7 @@ mod serde; mod world; mod world_builder; -pub use access::{ArchetypeComponent, QueryAccess, TypeAccess}; +pub use access::{ArchetypeComponent, CondensedTypeAccess, QueryAccess, TypeAccess}; pub use archetype::{Archetype, ComponentFlags, TypeState}; pub use borrow::{AtomicBorrow, Ref, RefMut}; pub use bundle::{Bundle, DynamicBundle, MissingComponent}; diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 70be39c6661108..a2aa0aa2d278dd 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -13,10 +13,14 @@ pub use system::{Query, *}; pub mod prelude { pub use crate::{ core::WorldBuilderSource, - resource::{ChangedRes, FromResources, Local, Res, ResMut, Resource, Resources}, - schedule::{Schedule, State, StateStage, SystemStage}, - system::{Commands, IntoSystem, Query, System}, + resource::{ChangedRes, FromResources, Local, NonSend, Res, ResMut, Resource, Resources}, + schedule::{ + ExclusiveSystemDescriptorCoercion, ParallelSystemDescriptorCoercion, + ReportExecutionOrderAmbiguities, RunOnce, Schedule, Stage, State, StateStage, + SystemSet, SystemStage, + }, + system::{Commands, ExclusiveSystem, IntoExclusiveSystem, IntoSystem, Query, System}, Added, Bundle, Changed, Component, Entity, Flags, In, IntoChainSystem, Mut, Mutated, Or, - QuerySet, Ref, RefMut, With, Without, World, + QuerySet, Ref, RefMut, ShouldRun, With, Without, World, }; } diff --git a/crates/bevy_ecs/src/resource/resource_query.rs b/crates/bevy_ecs/src/resource/resource_query.rs index ef4d560dfd0bbf..6cd55a1d4a3387 100644 --- a/crates/bevy_ecs/src/resource/resource_query.rs +++ b/crates/bevy_ecs/src/resource/resource_query.rs @@ -133,6 +133,37 @@ impl<'a, T: Resource + FromResources> DerefMut for Local<'a, T> { } } +/// `NonSend` resources cannot leave the main thread, so any system that wants access to +/// a non-send resource will run on the main thread. See `Resources::insert_non_send()` and friends. +#[derive(Debug)] +pub struct NonSend<'a, T: Resource> { + value: *mut T, + _marker: PhantomData<&'a T>, +} + +impl<'a, T: Resource> NonSend<'a, T> { + pub(crate) unsafe fn new(resources: &Resources) -> Self { + NonSend { + value: resources.get_unsafe_non_send_ref::().as_ptr(), + _marker: Default::default(), + } + } +} + +impl<'a, T: Resource> Deref for NonSend<'a, T> { + type Target = T; + + fn deref(&self) -> &T { + unsafe { &*self.value } + } +} + +impl<'a, T: Resource> DerefMut for NonSend<'a, T> { + fn deref_mut(&mut self) -> &mut T { + unsafe { &mut *self.value } + } +} + // #[cfg(test)] // mod tests { // use super::*; diff --git a/crates/bevy_ecs/src/resource/resources.rs b/crates/bevy_ecs/src/resource/resources.rs index 8550eaa4b04932..38123b09f63db1 100644 --- a/crates/bevy_ecs/src/resource/resources.rs +++ b/crates/bevy_ecs/src/resource/resources.rs @@ -99,7 +99,7 @@ impl ResourceStorage for VecResourceStorage { /// A collection of resource instances identified by their type. pub struct Resources { pub(crate) resource_data: HashMap, - thread_local_data: HashMap>, + non_send_data: HashMap>, main_thread_id: ThreadId, } @@ -107,7 +107,7 @@ impl Default for Resources { fn default() -> Self { Resources { resource_data: Default::default(), - thread_local_data: Default::default(), + non_send_data: Default::default(), main_thread_id: std::thread::current().id(), } } @@ -118,10 +118,10 @@ impl Resources { self.insert_resource(resource, ResourceIndex::Global); } - pub fn insert_thread_local(&mut self, resource: T) { - self.check_thread_local(); + pub fn insert_non_send(&mut self, resource: T) { + self.check_if_main_thread(); let entry = self - .thread_local_data + .non_send_data .entry(TypeId::of::()) .or_insert_with(|| Box::new(VecResourceStorage::::default())); let resources = entry.downcast_mut::>().unwrap(); @@ -132,9 +132,9 @@ impl Resources { } } - fn check_thread_local(&self) { + fn check_if_main_thread(&self) { if std::thread::current().id() != self.main_thread_id { - panic!("Attempted to access a thread local resource off of the main thread.") + panic!("Attempted to access a non-send resource off of the main thread.") } } @@ -150,9 +150,9 @@ impl Resources { self.get_resource_mut(ResourceIndex::Global) } - pub fn get_thread_local(&self) -> Option> { - self.check_thread_local(); - self.thread_local_data + pub fn get_non_send(&self) -> Option> { + self.check_if_main_thread(); + self.non_send_data .get(&TypeId::of::()) .and_then(|storage| { let resources = storage.downcast_ref::>().unwrap(); @@ -160,9 +160,9 @@ impl Resources { }) } - pub fn get_thread_local_mut(&self) -> Option> { - self.check_thread_local(); - self.thread_local_data + pub fn get_non_send_mut(&self) -> Option> { + self.check_if_main_thread(); + self.non_send_data .get(&TypeId::of::()) .and_then(|storage| { let resources = storage.downcast_ref::>().unwrap(); @@ -282,6 +282,24 @@ impl Resources { .unwrap_or_else(|| panic!("Resource does not exist {}.", std::any::type_name::())) } + #[inline] + #[allow(clippy::missing_safety_doc)] + pub unsafe fn get_unsafe_non_send_ref(&self) -> NonNull { + self.check_if_main_thread(); + self.non_send_data + .get(&TypeId::of::()) + .map(|storage| { + let resources = storage.downcast_ref::>().unwrap(); + resources.get_unsafe_ref(0) + }) + .unwrap_or_else(|| { + panic!( + "Non-send resource does not exist {}.", + std::any::type_name::() + ) + }) + } + #[inline] #[allow(clippy::missing_safety_doc)] pub unsafe fn get_unsafe_ref_with_added_and_mutated( @@ -528,40 +546,40 @@ mod tests { } #[test] - fn thread_local_resource() { + fn non_send_resource() { let mut resources = Resources::default(); - resources.insert_thread_local(123i32); - resources.insert_thread_local(456i64); - assert_eq!(*resources.get_thread_local::().unwrap(), 123); - assert_eq!(*resources.get_thread_local_mut::().unwrap(), 456); + resources.insert_non_send(123i32); + resources.insert_non_send(456i64); + assert_eq!(*resources.get_non_send::().unwrap(), 123); + assert_eq!(*resources.get_non_send_mut::().unwrap(), 456); } #[test] - fn thread_local_resource_ref_aliasing() { + fn non_send_resource_ref_aliasing() { let mut resources = Resources::default(); - resources.insert_thread_local(123i32); - let a = resources.get_thread_local::().unwrap(); - let b = resources.get_thread_local::().unwrap(); + resources.insert_non_send(123i32); + let a = resources.get_non_send::().unwrap(); + let b = resources.get_non_send::().unwrap(); assert_eq!(*a, 123); assert_eq!(*b, 123); } #[test] #[should_panic] - fn thread_local_resource_mut_ref_aliasing() { + fn non_send_resource_mut_ref_aliasing() { let mut resources = Resources::default(); - resources.insert_thread_local(123i32); - let _a = resources.get_thread_local::().unwrap(); - let _b = resources.get_thread_local_mut::().unwrap(); + resources.insert_non_send(123i32); + let _a = resources.get_non_send::().unwrap(); + let _b = resources.get_non_send_mut::().unwrap(); } #[test] #[should_panic] - fn thread_local_resource_panic() { + fn non_send_resource_panic() { let mut resources = Resources::default(); - resources.insert_thread_local(0i32); + resources.insert_non_send(0i32); std::thread::spawn(move || { - let _ = resources.get_thread_local_mut::(); + let _ = resources.get_non_send_mut::(); }) .join() .unwrap(); diff --git a/crates/bevy_ecs/src/schedule/executor.rs b/crates/bevy_ecs/src/schedule/executor.rs new file mode 100644 index 00000000000000..d220e9ff4fd715 --- /dev/null +++ b/crates/bevy_ecs/src/schedule/executor.rs @@ -0,0 +1,37 @@ +use downcast_rs::{impl_downcast, Downcast}; + +use crate::{ParallelSystemContainer, Resources, World}; + +pub trait ParallelSystemExecutor: Downcast + Send + Sync { + /// Called by `SystemStage` whenever `systems` have been changed. + fn rebuild_cached_data(&mut self, systems: &mut [ParallelSystemContainer], world: &World); + + fn run_systems( + &mut self, + systems: &mut [ParallelSystemContainer], + world: &mut World, + resources: &mut Resources, + ); +} + +impl_downcast!(ParallelSystemExecutor); + +#[derive(Default)] +pub struct SingleThreadedExecutor; + +impl ParallelSystemExecutor for SingleThreadedExecutor { + fn rebuild_cached_data(&mut self, _: &mut [ParallelSystemContainer], _: &World) {} + + fn run_systems( + &mut self, + systems: &mut [ParallelSystemContainer], + world: &mut World, + resources: &mut Resources, + ) { + for system in systems { + if system.should_run() { + system.system_mut().run((), world, resources); + } + } + } +} diff --git a/crates/bevy_ecs/src/schedule/executor_parallel.rs b/crates/bevy_ecs/src/schedule/executor_parallel.rs new file mode 100644 index 00000000000000..8e9a71dfdb0d6a --- /dev/null +++ b/crates/bevy_ecs/src/schedule/executor_parallel.rs @@ -0,0 +1,508 @@ +use async_channel::{Receiver, Sender}; +use bevy_tasks::{ComputeTaskPool, Scope, TaskPool}; +use bevy_utils::HashSet; +use fixedbitset::FixedBitSet; + +use crate::{ + ArchetypesGeneration, CondensedTypeAccess, ParallelSystemContainer, ParallelSystemExecutor, + Resources, System, World, +}; + +#[cfg(test)] +use SchedulingEvent::*; + +struct SystemSchedulingMetadata { + /// Used to signal the system's task to start the system. + start_sender: Sender<()>, + /// Receives the signal to start the system. + start_receiver: Receiver<()>, + /// 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 condensed into executor-specific bitsets. + archetype_component_access: CondensedTypeAccess, + /// Resource access information condensed into executor-specific bitsets. + resource_access: CondensedTypeAccess, +} + +pub struct ParallelExecutor { + /// Last archetypes generation observed by parallel systems. + last_archetypes_generation: ArchetypesGeneration, + /// 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 must run on the main thread. + non_send: FixedBitSet, + /// 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: CondensedTypeAccess, + /// Compound resource access information of currently running systems. + active_resource_access: CondensedTypeAccess, + /// 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 { + let (finish_sender, finish_receiver) = async_channel::unbounded(); + Self { + // MAX ensures access information will be initialized on first run. + last_archetypes_generation: ArchetypesGeneration(u64::MAX), + system_metadata: Default::default(), + finish_sender, + finish_receiver, + non_send: Default::default(), + queued: Default::default(), + running: Default::default(), + non_send_running: false, + should_run: Default::default(), + active_archetype_component_access: Default::default(), + active_resource_access: Default::default(), + dependants_scratch: Default::default(), + #[cfg(test)] + events_sender: None, + } + } +} + +impl ParallelSystemExecutor for ParallelExecutor { + fn rebuild_cached_data(&mut self, systems: &mut [ParallelSystemContainer], world: &World) { + self.system_metadata.clear(); + self.non_send.clear(); + self.non_send.grow(systems.len()); + self.queued.grow(systems.len()); + self.running.grow(systems.len()); + self.should_run.grow(systems.len()); + // Collect all distinct types accessed by systems in order to condense their + // access sets into bitsets. + let mut all_archetype_components = HashSet::default(); + let mut all_resource_types = HashSet::default(); + let mut gather_distinct_access_types = |system: &dyn System| { + if let Some(archetype_components) = + system.archetype_component_access().all_distinct_types() + { + all_archetype_components.extend(archetype_components); + } + if let Some(resources) = system.resource_access().all_distinct_types() { + all_resource_types.extend(resources); + } + }; + // If the archetypes were changed too, system access should be updated + // before gathering the types. + if self.last_archetypes_generation != world.archetypes_generation() { + for container in systems.iter_mut() { + let system = container.system_mut(); + system.update_access(world); + gather_distinct_access_types(system); + } + self.last_archetypes_generation = world.archetypes_generation(); + } else { + for container in systems.iter() { + gather_distinct_access_types(container.system()); + } + } + let all_archetype_components = all_archetype_components.drain().collect::>(); + let all_resource_types = all_resource_types.drain().collect::>(); + // Construct scheduling data for systems. + for container in systems.iter() { + let dependencies_total = container.dependencies().len(); + let system = container.system(); + if system.is_non_send() { + self.non_send.insert(self.system_metadata.len()); + } + let (start_sender, start_receiver) = async_channel::bounded(1); + self.system_metadata.push(SystemSchedulingMetadata { + start_sender, + start_receiver, + dependants: vec![], + dependencies_total, + dependencies_now: 0, + archetype_component_access: system + .archetype_component_access() + .condense(&all_archetype_components), + resource_access: system.resource_access().condense(&all_resource_types), + }); + } + // 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 [ParallelSystemContainer], + world: &mut World, + resources: &mut Resources, + ) { + #[cfg(test)] + if self.events_sender.is_none() { + let (sender, receiver) = async_channel::unbounded::(); + resources.insert(receiver); + self.events_sender = Some(sender); + } + if self.last_archetypes_generation != world.archetypes_generation() { + self.update_access(systems, world); + self.last_archetypes_generation = world.archetypes_generation(); + } + let compute_pool = resources + .get_or_insert_with(|| ComputeTaskPool(TaskPool::default())) + .clone(); + compute_pool.scope(|scope| { + self.prepare_systems(scope, systems, world, resources); + scope.spawn(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().await; + // 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(); + } + }); + }); + } +} + +impl ParallelExecutor { + /// Updates access and recondenses the archetype component bitsets of systems. + fn update_access(&mut self, systems: &mut [ParallelSystemContainer], world: &mut World) { + let mut all_archetype_components = HashSet::default(); + for container in systems.iter_mut() { + let system = container.system_mut(); + system.update_access(world); + if let Some(archetype_components) = + system.archetype_component_access().all_distinct_types() + { + all_archetype_components.extend(archetype_components); + } + } + let all_archetype_components = all_archetype_components.drain().collect::>(); + for (index, container) in systems.iter().enumerate() { + let system = container.system(); + if !system.archetype_component_access().reads_all() { + self.system_metadata[index].archetype_component_access = system + .archetype_component_access() + .condense(&all_archetype_components); + } + } + } + + /// 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: &mut Scope<'scope, ()>, + systems: &'scope [ParallelSystemContainer], + world: &'scope World, + resources: &'scope Resources, + ) { + self.should_run.clear(); + for (index, system_data) in self.system_metadata.iter_mut().enumerate() { + // Spawn the system task. + if systems[index].should_run() { + self.should_run.set(index, true); + let start_receiver = system_data.start_receiver.clone(); + let finish_sender = self.finish_sender.clone(); + let system = unsafe { systems[index].system_mut_unsafe() }; + let task = async move { + start_receiver + .recv() + .await + .unwrap_or_else(|error| unreachable!(error)); + unsafe { system.run_unsafe((), world, resources) }; + finish_sender + .send(index) + .await + .unwrap_or_else(|error| unreachable!(error)); + }; + if self.non_send[index] { + scope.spawn_local(task); + } else { + scope.spawn(task); + } + } + // Queue the system if it has no dependencies, otherwise reset its dependency counter. + if system_data.dependencies_total == 0 { + self.queued.insert(index); + } else { + system_data.dependencies_now = system_data.dependencies_total; + } + } + } + + /// Determines if the system with given index has no conflicts with already running systems. + fn can_start_now(&self, index: usize) -> bool { + let system_data = &self.system_metadata[index]; + // Non-send systems are considered conflicting with each other. + !(self.non_send[index] && self.non_send_running) + && system_data + .resource_access + .is_compatible(&self.active_resource_access) + && system_data + .archetype_component_access + .is_compatible(&self.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. + async fn process_queued_systems(&mut self) { + #[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. + if !self.should_run[index] { + self.dependants_scratch + .extend(&self.system_metadata[index].dependants); + } else if self.can_start_now(index) { + #[cfg(test)] + { + started_systems += 1; + } + let system_data = &self.system_metadata[index]; + system_data + .start_sender + .send(()) + .await + .unwrap_or_else(|error| unreachable!(error)); + self.running.set(index, true); + if self.non_send[index] { + 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); + self.active_resource_access + .extend(&system_data.resource_access); + } + } + #[cfg(test)] + if started_systems != 0 { + self.emit_event(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) { + if self.non_send[index] { + self.non_send_running = false; + } + self.running.set(index, false); + self.dependants_scratch + .extend(&self.system_metadata[index].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(); + self.active_resource_access.clear(); + for index in self.running.ones() { + self.active_archetype_component_access + .extend(&self.system_metadata[index].archetype_component_access); + self.active_resource_access + .extend(&self.system_metadata[index].resource_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) { + self.events_sender + .as_ref() + .unwrap() + .try_send(event) + .unwrap(); + } +} + +#[cfg(test)] +#[derive(Debug, PartialEq, Eq)] +enum SchedulingEvent { + StartedSystems(usize), +} + +#[cfg(test)] +mod tests { + use super::SchedulingEvent::{self, *}; + use crate::{prelude::*, SingleThreadedExecutor}; + use async_channel::Receiver; + use std::thread::{self, ThreadId}; + + fn receive_events(resources: &Resources) -> Vec { + let mut events = Vec::new(); + while let Ok(event) = resources + .get::>() + .unwrap() + .try_recv() + { + events.push(event); + } + events + } + + #[test] + fn trivial() { + let mut world = World::new(); + let mut resources = Resources::default(); + fn wants_for_nothing() {} + let mut stage = SystemStage::parallel() + .with_system(wants_for_nothing.system()) + .with_system(wants_for_nothing.system()) + .with_system(wants_for_nothing.system()); + stage.run(&mut world, &mut resources); + stage.run(&mut world, &mut resources); + assert_eq!( + receive_events(&resources), + vec![StartedSystems(3), StartedSystems(3),] + ) + } + + #[test] + fn resources() { + let mut world = World::new(); + let mut resources = Resources::default(); + resources.insert(0usize); + fn wants_mut(_: ResMut) {} + fn wants_ref(_: Res) {} + let mut stage = SystemStage::parallel() + .with_system(wants_mut.system()) + .with_system(wants_mut.system()); + stage.run(&mut world, &mut resources); + assert_eq!( + receive_events(&resources), + vec![StartedSystems(1), StartedSystems(1),] + ); + let mut stage = SystemStage::parallel() + .with_system(wants_mut.system()) + .with_system(wants_ref.system()); + stage.run(&mut world, &mut resources); + assert_eq!( + receive_events(&resources), + vec![StartedSystems(1), StartedSystems(1),] + ); + let mut stage = SystemStage::parallel() + .with_system(wants_ref.system()) + .with_system(wants_ref.system()); + stage.run(&mut world, &mut resources); + assert_eq!(receive_events(&resources), vec![StartedSystems(2),]); + } + + #[test] + fn queries() { + let mut world = World::new(); + let mut resources = Resources::default(); + world.spawn((0usize,)); + fn wants_mut(_: Query<&mut usize>) {} + fn wants_ref(_: Query<&usize>) {} + let mut stage = SystemStage::parallel() + .with_system(wants_mut.system()) + .with_system(wants_mut.system()); + stage.run(&mut world, &mut resources); + assert_eq!( + receive_events(&resources), + vec![StartedSystems(1), StartedSystems(1),] + ); + let mut stage = SystemStage::parallel() + .with_system(wants_mut.system()) + .with_system(wants_ref.system()); + stage.run(&mut world, &mut resources); + assert_eq!( + receive_events(&resources), + vec![StartedSystems(1), StartedSystems(1),] + ); + let mut stage = SystemStage::parallel() + .with_system(wants_ref.system()) + .with_system(wants_ref.system()); + stage.run(&mut world, &mut resources); + assert_eq!(receive_events(&resources), vec![StartedSystems(2),]); + let mut world = World::new(); + world.spawn((0usize, 0u32, 0f32)); + fn wants_mut_usize(_: Query<(&mut usize, &f32)>) {} + fn wants_mut_u32(_: Query<(&mut u32, &f32)>) {} + let mut stage = SystemStage::parallel() + .with_system(wants_mut_usize.system()) + .with_system(wants_mut_u32.system()); + stage.run(&mut world, &mut resources); + assert_eq!(receive_events(&resources), vec![StartedSystems(2),]); + } + + #[test] + fn non_send_resource() { + let mut world = World::new(); + let mut resources = Resources::default(); + resources.insert_non_send(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.system()) + .with_system(non_send.system()) + .with_system(empty.system()) + .with_system(empty.system()) + .with_system(non_send.system()) + .with_system(non_send.system()); + stage.run(&mut world, &mut resources); + assert_eq!( + receive_events(&resources), + vec![ + StartedSystems(3), + StartedSystems(1), + StartedSystems(1), + StartedSystems(1), + ] + ); + stage.set_executor(Box::new(SingleThreadedExecutor::default())); + stage.run(&mut world, &mut resources); + } +} diff --git a/crates/bevy_ecs/src/schedule/mod.rs b/crates/bevy_ecs/src/schedule/mod.rs index 9c9ada93affa75..5532c0f4d5c143 100644 --- a/crates/bevy_ecs/src/schedule/mod.rs +++ b/crates/bevy_ecs/src/schedule/mod.rs @@ -1,20 +1,30 @@ +mod executor; +mod executor_parallel; mod stage; -mod stage_executor; mod state; +mod system_container; +mod system_descriptor; +mod system_set; +pub use executor::*; +pub use executor_parallel::*; pub use stage::*; -pub use stage_executor::*; pub use state::*; +pub use system_container::*; +pub use system_descriptor::*; +pub use system_set::*; -use crate::{BoxedSystem, IntoSystem, Resources, System, World}; +use crate::{ + ArchetypeComponent, BoxedSystem, IntoSystem, Resources, System, SystemId, TypeAccess, World, +}; use bevy_utils::HashMap; +use std::{any::TypeId, borrow::Cow}; #[derive(Default)] pub struct Schedule { stages: HashMap>, stage_order: Vec, - run_criteria: Option>, - run_criteria_initialized: bool, + run_criteria: RunCriteria, } impl Schedule { @@ -38,10 +48,10 @@ impl Schedule { self } - pub fn with_system_in_stage>( + pub fn with_system_in_stage( mut self, stage_name: &'static str, - system: S, + system: impl Into, ) -> Self { self.add_system_to_stage(stage_name, system); self @@ -51,8 +61,7 @@ impl Schedule { &mut self, system: S, ) -> &mut Self { - self.run_criteria = Some(Box::new(system.system())); - self.run_criteria_initialized = false; + self.run_criteria.set(Box::new(system.system())); self } @@ -99,10 +108,10 @@ impl Schedule { self } - pub fn add_system_to_stage>( + pub fn add_system_to_stage( &mut self, stage_name: &'static str, - system: S, + system: impl Into, ) -> &mut Self { let stage = self .get_stage_mut::(stage_name) @@ -112,7 +121,7 @@ impl Schedule { stage_name ) }); - stage.add_system(system.system()); + stage.add_system(system); self } @@ -150,49 +159,23 @@ impl Schedule { stage.run(world, resources); } } - - /// Shorthand for [Schedule::initialize] and [Schedule::run] - pub fn initialize_and_run(&mut self, world: &mut World, resources: &mut Resources) { - self.initialize(world, resources); - self.run(world, resources); - } } impl Stage for Schedule { - fn initialize(&mut self, world: &mut World, resources: &mut Resources) { - if let Some(ref mut run_criteria) = self.run_criteria { - if !self.run_criteria_initialized { - run_criteria.initialize(world, resources); - self.run_criteria_initialized = true; - } - } - - for name in self.stage_order.iter() { - let stage = self.stages.get_mut(name).unwrap(); - stage.initialize(world, resources); - } - } - fn run(&mut self, world: &mut World, resources: &mut Resources) { loop { - let should_run = if let Some(ref mut run_criteria) = self.run_criteria { - let should_run = run_criteria.run((), world, resources); - run_criteria.run_thread_local(world, resources); - // don't run when no result is returned or false is returned - should_run.unwrap_or(ShouldRun::No) - } else { - ShouldRun::Yes - }; - - match should_run { + match self.run_criteria.should_run(world, resources) { ShouldRun::No => return, ShouldRun::Yes => { self.run_once(world, resources); return; } - ShouldRun::YesAndLoop => { + ShouldRun::YesAndCheckAgain => { self.run_once(world, resources); } + ShouldRun::NoAndCheckAgain => { + panic!("`NoAndCheckAgain` would loop infinitely in this situation.") + } } } } @@ -203,320 +186,119 @@ pub fn clear_trackers_system(world: &mut World, resources: &mut Resources) { resources.clear_trackers(); } -#[cfg(test)] -mod tests { - use crate::{ - resource::{Res, ResMut, Resources}, - schedule::{ParallelSystemStageExecutor, Schedule, SystemStage}, - system::Query, - Commands, Entity, IntoSystem, World, - }; - use bevy_tasks::{ComputeTaskPool, TaskPool}; - use fixedbitset::FixedBitSet; - use parking_lot::Mutex; - use std::{collections::HashSet, sync::Arc}; - - #[derive(Default)] - struct CompletedSystems { - completed_systems: Arc>>, - } - - #[test] - fn cross_stage_archetype_change_prepare() { - let mut world = World::new(); - let mut resources = Resources::default(); - resources.insert(ComputeTaskPool(TaskPool::default())); - - fn insert(commands: &mut Commands) { - commands.spawn((1u32,)); - } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ShouldRun { + /// Yes, the system should run. + Yes, + /// No, the system should not run. + No, + /// Yes, the system should run, and afterwards the criteria should be checked again. + YesAndCheckAgain, + /// No, the system should not run right now, but the criteria should be checked again later. + NoAndCheckAgain, +} - fn read(query: Query<&u32>, entities: Query) { - for entity in &mut entities.iter() { - // query.get() does a "system permission check" that will fail if the entity is from a - // new archetype which hasnt been "prepared yet" - query.get_component::(entity).unwrap(); - } +pub(crate) struct RunCriteria { + criteria_system: Option>, + initialized: bool, +} - assert_eq!(1, entities.iter().count()); +impl Default for RunCriteria { + fn default() -> Self { + Self { + criteria_system: None, + initialized: false, } - - let mut schedule = Schedule::default(); - let mut pre_archetype_change = SystemStage::parallel(); - pre_archetype_change.add_system(insert.system()); - schedule.add_stage("PreArchetypeChange", pre_archetype_change); - let mut post_archetype_change = SystemStage::parallel(); - post_archetype_change.add_system(read.system()); - schedule.add_stage("PostArchetypeChange", post_archetype_change); - - schedule.initialize_and_run(&mut world, &mut resources); } +} - #[test] - fn intra_stage_archetype_change_prepare() { - let mut world = World::new(); - let mut resources = Resources::default(); - resources.insert(ComputeTaskPool(TaskPool::default())); - - fn insert(world: &mut World, _resources: &mut Resources) { - world.spawn((1u32,)); - } +impl RunCriteria { + pub fn set(&mut self, criteria_system: BoxedSystem<(), ShouldRun>) { + self.criteria_system = Some(criteria_system); + self.initialized = false; + } - fn read(query: Query<&u32>, entities: Query) { - for entity in &mut entities.iter() { - // query.get() does a "system permission check" that will fail if the entity is from a - // new archetype which hasnt been "prepared yet" - query.get_component::(entity).unwrap(); + pub fn should_run(&mut self, world: &mut World, resources: &mut Resources) -> ShouldRun { + if let Some(ref mut run_criteria) = self.criteria_system { + if !self.initialized { + run_criteria.initialize(world, resources); + self.initialized = true; } - - assert_eq!(1, entities.iter().count()); + let should_run = run_criteria.run((), world, resources); + run_criteria.apply_buffers(world, resources); + // don't run when no result is returned or false is returned + should_run.unwrap_or(ShouldRun::No) + } else { + ShouldRun::Yes } - - let mut update = SystemStage::parallel(); - update.add_system(insert.system()); - update.add_system(read.system()); - - let mut schedule = Schedule::default(); - schedule.add_stage("update", update); - - schedule.initialize_and_run(&mut world, &mut resources); } +} - #[test] - fn schedule() { - let mut world = World::new(); - let mut resources = Resources::default(); - resources.insert(ComputeTaskPool(TaskPool::default())); - resources.insert(CompletedSystems::default()); - resources.insert(1.0f64); - resources.insert(2isize); - - world.spawn((1.0f32,)); - world.spawn((1u32, 1u64)); - world.spawn((2u32,)); - - let mut stage_a = SystemStage::parallel(); // component queries - let mut stage_b = SystemStage::parallel(); // thread local - let mut stage_c = SystemStage::parallel(); // resources - - // A system names - const READ_U32_SYSTEM_NAME: &str = "read_u32"; - const WRITE_FLOAT_SYSTEM_NAME: &str = "write_float"; - const READ_U32_WRITE_U64_SYSTEM_NAME: &str = "read_u32_write_u64"; - const READ_U64_SYSTEM_NAME: &str = "read_u64"; - - // B system names - const WRITE_U64_SYSTEM_NAME: &str = "write_u64"; - const THREAD_LOCAL_SYSTEM_SYSTEM_NAME: &str = "thread_local_system"; - const WRITE_F32_SYSTEM_NAME: &str = "write_f32"; - - // C system names - const READ_F64_RES_SYSTEM_NAME: &str = "read_f64_res"; - const READ_ISIZE_RES_SYSTEM_NAME: &str = "read_isize_res"; - const READ_ISIZE_WRITE_F64_RES_SYSTEM_NAME: &str = "read_isize_write_f64_res"; - const WRITE_F64_RES_SYSTEM_NAME: &str = "write_f64_res"; - - // A systems - - fn read_u32(completed_systems: Res, _query: Query<&u32>) { - let mut completed_systems = completed_systems.completed_systems.lock(); - completed_systems.insert(READ_U32_SYSTEM_NAME); - } - - fn write_float(completed_systems: Res, _query: Query<&f32>) { - let mut completed_systems = completed_systems.completed_systems.lock(); - completed_systems.insert(WRITE_FLOAT_SYSTEM_NAME); - } - - fn read_u32_write_u64( - completed_systems: Res, - _query: Query<(&u32, &mut u64)>, - ) { - let mut completed_systems = completed_systems.completed_systems.lock(); - assert!(!completed_systems.contains(READ_U64_SYSTEM_NAME)); - completed_systems.insert(READ_U32_WRITE_U64_SYSTEM_NAME); - } +pub struct RunOnce { + ran: bool, + system_id: SystemId, + archetype_component_access: TypeAccess, + component_access: TypeAccess, + resource_access: TypeAccess, +} - fn read_u64(completed_systems: Res, _query: Query<&u64>) { - let mut completed_systems = completed_systems.completed_systems.lock(); - assert!(completed_systems.contains(READ_U32_WRITE_U64_SYSTEM_NAME)); - assert!(!completed_systems.contains(WRITE_U64_SYSTEM_NAME)); - completed_systems.insert(READ_U64_SYSTEM_NAME); +impl Default for RunOnce { + fn default() -> Self { + Self { + ran: false, + system_id: SystemId::new(), + archetype_component_access: Default::default(), + component_access: Default::default(), + resource_access: Default::default(), } + } +} - stage_a.add_system(read_u32.system()); - stage_a.add_system(write_float.system()); - stage_a.add_system(read_u32_write_u64.system()); - stage_a.add_system(read_u64.system()); - - // B systems +impl System for RunOnce { + type In = (); + type Out = ShouldRun; - fn write_u64(completed_systems: Res, _query: Query<&mut u64>) { - let mut completed_systems = completed_systems.completed_systems.lock(); - assert!(completed_systems.contains(READ_U64_SYSTEM_NAME)); - assert!(!completed_systems.contains(THREAD_LOCAL_SYSTEM_SYSTEM_NAME)); - assert!(!completed_systems.contains(WRITE_F32_SYSTEM_NAME)); - completed_systems.insert(WRITE_U64_SYSTEM_NAME); - } - - fn thread_local_system(_world: &mut World, resources: &mut Resources) { - let completed_systems = resources.get::().unwrap(); - let mut completed_systems = completed_systems.completed_systems.lock(); - assert!(completed_systems.contains(WRITE_U64_SYSTEM_NAME)); - assert!(!completed_systems.contains(WRITE_F32_SYSTEM_NAME)); - completed_systems.insert(THREAD_LOCAL_SYSTEM_SYSTEM_NAME); - } + fn name(&self) -> Cow<'static, str> { + Cow::Borrowed(std::any::type_name::()) + } - fn write_f32(completed_systems: Res, _query: Query<&mut f32>) { - let mut completed_systems = completed_systems.completed_systems.lock(); - assert!(completed_systems.contains(WRITE_U64_SYSTEM_NAME)); - assert!(completed_systems.contains(THREAD_LOCAL_SYSTEM_SYSTEM_NAME)); - assert!(!completed_systems.contains(READ_F64_RES_SYSTEM_NAME)); - completed_systems.insert(WRITE_F32_SYSTEM_NAME); - } + fn id(&self) -> SystemId { + self.system_id + } - stage_b.add_system(write_u64.system()); - stage_b.add_system(thread_local_system.system()); - stage_b.add_system(write_f32.system()); + fn update_access(&mut self, _world: &World) {} - // C systems + fn archetype_component_access(&self) -> &TypeAccess { + &self.archetype_component_access + } - fn read_f64_res(completed_systems: Res, _f64_res: Res) { - let mut completed_systems = completed_systems.completed_systems.lock(); - assert!(completed_systems.contains(WRITE_F32_SYSTEM_NAME)); - assert!(!completed_systems.contains(READ_ISIZE_WRITE_F64_RES_SYSTEM_NAME)); - assert!(!completed_systems.contains(WRITE_F64_RES_SYSTEM_NAME)); - completed_systems.insert(READ_F64_RES_SYSTEM_NAME); - } + fn component_access(&self) -> &TypeAccess { + &self.component_access + } - fn read_isize_res(completed_systems: Res, _isize_res: Res) { - let mut completed_systems = completed_systems.completed_systems.lock(); - completed_systems.insert(READ_ISIZE_RES_SYSTEM_NAME); - } + fn resource_access(&self) -> &TypeAccess { + &self.resource_access + } - fn read_isize_write_f64_res( - completed_systems: Res, - _isize_res: Res, - _f64_res: ResMut, - ) { - let mut completed_systems = completed_systems.completed_systems.lock(); - assert!(completed_systems.contains(READ_F64_RES_SYSTEM_NAME)); - assert!(!completed_systems.contains(WRITE_F64_RES_SYSTEM_NAME)); - completed_systems.insert(READ_ISIZE_WRITE_F64_RES_SYSTEM_NAME); - } + fn is_non_send(&self) -> bool { + false + } - fn write_f64_res(completed_systems: Res, _f64_res: ResMut) { - let mut completed_systems = completed_systems.completed_systems.lock(); - assert!(completed_systems.contains(READ_F64_RES_SYSTEM_NAME)); - assert!(completed_systems.contains(READ_ISIZE_WRITE_F64_RES_SYSTEM_NAME)); - completed_systems.insert(WRITE_F64_RES_SYSTEM_NAME); - } + unsafe fn run_unsafe( + &mut self, + _input: Self::In, + _world: &World, + _resources: &Resources, + ) -> Option { + Some(if self.ran { + ShouldRun::No + } else { + self.ran = true; + ShouldRun::Yes + }) + } - stage_c.add_system(read_f64_res.system()); - stage_c.add_system(read_isize_res.system()); - stage_c.add_system(read_isize_write_f64_res.system()); - stage_c.add_system(write_f64_res.system()); - - fn run_and_validate(schedule: &mut Schedule, world: &mut World, resources: &mut Resources) { - schedule.initialize_and_run(world, resources); - - let stage_a = schedule.get_stage::("a").unwrap(); - let stage_b = schedule.get_stage::("b").unwrap(); - let stage_c = schedule.get_stage::("c").unwrap(); - - let a_executor = stage_a - .get_executor::() - .unwrap(); - let b_executor = stage_b - .get_executor::() - .unwrap(); - let c_executor = stage_c - .get_executor::() - .unwrap(); - - assert_eq!( - a_executor.system_dependents(), - vec![vec![], vec![], vec![3], vec![]] - ); - assert_eq!( - b_executor.system_dependents(), - vec![vec![1], vec![2], vec![]] - ); - assert_eq!( - c_executor.system_dependents(), - vec![vec![2, 3], vec![], vec![3], vec![]] - ); - - let stage_a_len = a_executor.system_dependencies().len(); - let mut read_u64_deps = FixedBitSet::with_capacity(stage_a_len); - read_u64_deps.insert(2); - - assert_eq!( - a_executor.system_dependencies(), - vec![ - FixedBitSet::with_capacity(stage_a_len), - FixedBitSet::with_capacity(stage_a_len), - FixedBitSet::with_capacity(stage_a_len), - read_u64_deps, - ] - ); - - let stage_b_len = b_executor.system_dependencies().len(); - let mut thread_local_deps = FixedBitSet::with_capacity(stage_b_len); - thread_local_deps.insert(0); - let mut write_f64_deps = FixedBitSet::with_capacity(stage_b_len); - write_f64_deps.insert(1); - assert_eq!( - b_executor.system_dependencies(), - vec![ - FixedBitSet::with_capacity(stage_b_len), - thread_local_deps, - write_f64_deps - ] - ); - - let stage_c_len = c_executor.system_dependencies().len(); - let mut read_isize_write_f64_res_deps = FixedBitSet::with_capacity(stage_c_len); - read_isize_write_f64_res_deps.insert(0); - let mut write_f64_res_deps = FixedBitSet::with_capacity(stage_c_len); - write_f64_res_deps.insert(0); - write_f64_res_deps.insert(2); - assert_eq!( - c_executor.system_dependencies(), - vec![ - FixedBitSet::with_capacity(stage_c_len), - FixedBitSet::with_capacity(stage_c_len), - read_isize_write_f64_res_deps, - write_f64_res_deps - ] - ); - - let completed_systems = resources.get::().unwrap(); - assert_eq!( - completed_systems.completed_systems.lock().len(), - 11, - "completed_systems should have been incremented once for each system" - ); - } + fn apply_buffers(&mut self, _world: &mut World, _resources: &mut Resources) {} - let mut schedule = Schedule::default(); - schedule.add_stage("a", stage_a); - schedule.add_stage("b", stage_b); - schedule.add_stage("c", stage_c); - - // Test the "clean start" case - run_and_validate(&mut schedule, &mut world, &mut resources); - - // Stress test the "continue running" case - for _ in 0..1000 { - // run again (with completed_systems reset) to ensure executor works correctly across runs - resources - .get::() - .unwrap() - .completed_systems - .lock() - .clear(); - run_and_validate(&mut schedule, &mut world, &mut resources); - } - } + fn initialize(&mut self, _world: &mut World, _resources: &mut Resources) {} } diff --git a/crates/bevy_ecs/src/schedule/stage.rs b/crates/bevy_ecs/src/schedule/stage.rs index 62a6761d94a4f3..1bcee3a201b759 100644 --- a/crates/bevy_ecs/src/schedule/stage.rs +++ b/crates/bevy_ecs/src/schedule/stage.rs @@ -1,232 +1,1340 @@ -use std::{any::TypeId, borrow::Cow}; +use bevy_utils::{tracing::info, HashMap, HashSet}; +use downcast_rs::{impl_downcast, Downcast}; +use fixedbitset::FixedBitSet; +use std::borrow::Cow; +use super::{ + ExclusiveSystemContainer, ParallelExecutor, ParallelSystemContainer, ParallelSystemExecutor, + SingleThreadedExecutor, SystemContainer, +}; use crate::{ - ArchetypeComponent, BoxedSystem, Resources, System, SystemId, ThreadLocalExecution, TypeAccess, - World, + InsertionPoint, Resources, RunCriteria, + ShouldRun::{self, *}, + System, SystemDescriptor, SystemSet, World, }; -use bevy_utils::HashSet; -use downcast_rs::{impl_downcast, Downcast}; - -use super::{ParallelSystemStageExecutor, SerialSystemStageExecutor, SystemStageExecutor}; - -pub enum StageError { - SystemAlreadyExists(SystemId), -} pub trait Stage: Downcast + Send + Sync { - /// Stages can perform setup here. Initialize should be called for every stage before calling [Stage::run]. Initialize will - /// be called once per update, so internally this should avoid re-doing work where possible. - fn initialize(&mut self, world: &mut World, resources: &mut Resources); - - /// Runs the stage. This happens once per update (after [Stage::initialize] is called). + /// 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, resources: &mut Resources); } impl_downcast!(Stage); +/// When this resource is present in `Resources`, `SystemStage` will log a report containing +/// pairs of systems with ambiguous execution order - i.e., those systems might induce different +/// results depending on the order they're executed in, yet don't have an explicit execution order +/// constraint between them. +/// This is not necessarily a bad thing - you have to make that judgement yourself. +pub struct ReportExecutionOrderAmbiguities; + +struct VirtualSystemSet { + run_criteria: RunCriteria, + should_run: ShouldRun, +} + +/// Stores and executes systems. Execution order is not defined unless explicitly specified; +/// see `SystemDescriptor` documentation. pub struct SystemStage { - systems: Vec, - system_ids: HashSet, - executor: Box, - run_criteria: Option>, - run_criteria_initialized: bool, - uninitialized_systems: Vec, - unexecuted_systems: Vec, + /// Instance of a scheduling algorithm for running the systems. + executor: Box, + /// Groups of systems; each set has its own run criterion. + system_sets: Vec, + /// Topologically sorted exclusive systems that want to be ran at the start of the stage. + exclusive_at_start: Vec, + /// Topologically sorted exclusive systems that want to be ran after parallel systems but + /// before the application of their command buffers. + exclusive_before_commands: Vec, + /// Topologically sorted exclusive systems that want to be ran at the end of the stage. + exclusive_at_end: Vec, + /// Topologically sorted parallel systems. + parallel: Vec, + /// Determines if the stage was modified and needs to rebuild its graphs and orders. + systems_modified: bool, + /// Determines if the stage's executor was changed. + executor_modified: bool, + /// 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, } impl SystemStage { - pub fn new(executor: Box) -> Self { + pub fn new(executor: Box) -> Self { + let set = VirtualSystemSet { + run_criteria: Default::default(), + should_run: ShouldRun::Yes, + }; SystemStage { executor, - run_criteria: None, - run_criteria_initialized: false, - systems: Default::default(), - system_ids: Default::default(), - uninitialized_systems: Default::default(), - unexecuted_systems: Default::default(), + system_sets: vec![set], + 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![], } } - pub fn single>(system: S) -> Self { - Self::serial().with_system(system) + pub fn single(system: impl Into) -> Self { + Self::single_threaded().with_system(system) } - pub fn serial() -> Self { - Self::new(Box::new(SerialSystemStageExecutor::default())) + pub fn single_threaded() -> Self { + Self::new(Box::new(SingleThreadedExecutor::default())) } pub fn parallel() -> Self { - Self::new(Box::new(ParallelSystemStageExecutor::default())) + Self::new(Box::new(ParallelExecutor::default())) } - pub fn with_system>(mut self, system: S) -> Self { - self.add_system_boxed(Box::new(system)); - self + pub fn get_executor(&self) -> Option<&T> { + self.executor.downcast_ref() } - pub fn with_run_criteria>(mut self, system: S) -> Self { - self.add_run_criteria(system); + 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 with_system(mut self, system: impl Into) -> Self { + self.add_system(system); self } - pub fn add_run_criteria>( - &mut self, - system: S, - ) -> &mut Self { - self.run_criteria = Some(Box::new(system)); - self.run_criteria_initialized = false; + pub fn with_system_set(mut self, system_set: SystemSet) -> Self { + self.add_system_set(system_set); self } - pub fn add_system>(&mut self, system: S) -> &mut Self { - self.add_system_boxed(Box::new(system)); + pub fn with_run_criteria>(mut self, system: S) -> Self { + self.system_sets[0].run_criteria.set(Box::new(system)); self } - pub fn add_system_boxed(&mut self, system: BoxedSystem) -> &mut Self { - if self.system_ids.contains(&system.id()) { - panic!( - "System with id {:?} ({}) already exists", - system.id(), - system.name() - ); + pub fn add_system_set(&mut self, system_set: SystemSet) -> &mut Self { + self.systems_modified = true; + let SystemSet { + run_criteria, + mut descriptors, + } = system_set; + let set = self.system_sets.len(); + self.system_sets.push(VirtualSystemSet { + run_criteria, + should_run: ShouldRun::No, + }); + for system in descriptors.drain(..) { + self.add_system_to_set(system, set); } - self.system_ids.insert(system.id()); - self.unexecuted_systems.push(self.systems.len()); - self.uninitialized_systems.push(self.systems.len()); - self.systems.push(system); self } - pub fn get_executor(&self) -> Option<&T> { - self.executor.downcast_ref() + pub fn add_system(&mut self, system: impl Into) -> &mut Self { + self.add_system_to_set(system, 0) } - pub fn get_executor_mut(&mut self) -> Option<&mut T> { - self.executor.downcast_mut() + // TODO: consider exposing + fn add_system_to_set(&mut self, system: impl Into, set: usize) -> &mut Self { + self.systems_modified = true; + match system.into() { + SystemDescriptor::Exclusive(descriptor) => { + let insertion_point = descriptor.insertion_point; + let container = ExclusiveSystemContainer::from_descriptor(descriptor, set); + match insertion_point { + InsertionPoint::AtStart => { + let index = self.exclusive_at_start.len(); + self.uninitialized_at_start.push(index); + self.exclusive_at_start.push(container); + } + InsertionPoint::BeforeCommands => { + let index = self.exclusive_before_commands.len(); + self.uninitialized_before_commands.push(index); + self.exclusive_before_commands.push(container); + } + InsertionPoint::AtEnd => { + let index = self.exclusive_at_end.len(); + self.uninitialized_at_end.push(index); + self.exclusive_at_end.push(container); + } + } + } + SystemDescriptor::Parallel(descriptor) => { + self.uninitialized_parallel.push(self.parallel.len()); + self.parallel + .push(ParallelSystemContainer::from_descriptor(descriptor, set)); + } + } + self } - pub fn run_once(&mut self, world: &mut World, resources: &mut Resources) { - let unexecuted_systems = std::mem::take(&mut self.unexecuted_systems); - self.executor - .execute_stage(&mut self.systems, &unexecuted_systems, world, resources); + fn initialize_systems(&mut self, world: &mut World, resources: &mut Resources) { + for index in self.uninitialized_at_start.drain(..) { + self.exclusive_at_start[index] + .system_mut() + .initialize(world, resources); + } + for index in self.uninitialized_before_commands.drain(..) { + self.exclusive_before_commands[index] + .system_mut() + .initialize(world, resources); + } + for index in self.uninitialized_at_end.drain(..) { + self.exclusive_at_end[index] + .system_mut() + .initialize(world, resources); + } + for index in self.uninitialized_parallel.drain(..) { + self.parallel[index] + .system_mut() + .initialize(world, resources); + } } -} -impl Stage for SystemStage { - fn initialize(&mut self, world: &mut World, resources: &mut Resources) { - if let Some(ref mut run_criteria) = self.run_criteria { - if !self.run_criteria_initialized { - run_criteria.initialize(world, resources); - self.run_criteria_initialized = true; + /// Rearranges all systems in topological orders. Systems must be initialized. + fn rebuild_orders_and_dependencies(&mut self) { + debug_assert!( + self.uninitialized_parallel.is_empty() + && self.uninitialized_at_start.is_empty() + && self.uninitialized_before_commands.is_empty() + && self.uninitialized_at_end.is_empty() + ); + use DependencyGraphError::*; + match sort_systems(&mut self.parallel) { + Ok(()) => (), + Err(LabelNotFound(label)) => { + panic!("No parallel system with label {:?} in stage.", label) + } + Err(DuplicateLabel(label)) => { + panic!("Label {:?} already used by a parallel system.", label) + } + Err(GraphCycles(labels)) => { + panic!( + "Found a dependency cycle in parallel systems: {:?}.", + labels + ) + } + } + match sort_systems(&mut self.exclusive_at_start) { + Ok(()) => (), + Err(LabelNotFound(label)) => { + panic!( + "No exclusive system with label {:?} at start of stage.", + label + ) + } + Err(DuplicateLabel(label)) => { + panic!( + "Label {:?} already used by an exclusive system at start of stage.", + label + ) + } + Err(GraphCycles(labels)) => { + panic!( + "Found a dependency cycle in exclusive systems at start of stage: {:?}.", + labels + ) + } + } + match sort_systems(&mut self.exclusive_before_commands) { + Ok(()) => (), + Err(LabelNotFound(label)) => { + panic!( + "No exclusive system with label {:?} before commands of stage.", + label + ) + } + Err(DuplicateLabel(label)) => { + panic!( + "Label {:?} already used by an exclusive system before commands of stage.", + label + ) + } + Err(GraphCycles(labels)) => { + panic!( + "Found a dependency cycle in exclusive systems before commands of stage: {:?}.", + labels + ) + } + } + match sort_systems(&mut self.exclusive_at_end) { + Ok(()) => (), + Err(LabelNotFound(label)) => { + panic!( + "No exclusive system with label {:?} at end of stage.", + label + ) + } + Err(DuplicateLabel(label)) => { + panic!( + "Label {:?} already used by an exclusive system at end of stage.", + label + ) + } + Err(GraphCycles(labels)) => { + panic!( + "Found a dependency cycle in exclusive systems at end of stage: {:?}.", + labels + ) } } + } - let uninitialized_systems = std::mem::take(&mut self.uninitialized_systems); - for system_index in uninitialized_systems.iter() { - self.systems[*system_index].initialize(world, resources); + /// Logs execution order ambiguities between systems. System orders must be fresh. + fn report_ambiguities(&self) { + debug_assert!(!self.systems_modified); + use std::fmt::Write; + fn write_display_names_of_pairs( + string: &mut String, + systems: &[impl SystemContainer], + mut ambiguities: Vec<(usize, usize)>, + ) { + for (index_a, index_b) in ambiguities.drain(..) { + writeln!( + string, + " -- {:?} and {:?}", + systems[index_a].display_name(), + systems[index_b].display_name() + ) + .unwrap(); + } + } + let parallel = find_ambiguities(&self.parallel); + let at_start = find_ambiguities(&self.exclusive_at_start); + let before_commands = find_ambiguities(&self.exclusive_before_commands); + let at_end = find_ambiguities(&self.exclusive_at_end); + if !(parallel.is_empty() + && at_start.is_empty() + && before_commands.is_empty() + && at_end.is_empty()) + { + let mut string = "Execution order ambiguities detected, you might want to \ + add an explicit dependency relation between some these systems:\n" + .to_owned(); + if !parallel.is_empty() { + writeln!(string, " * Parallel systems:").unwrap(); + write_display_names_of_pairs(&mut string, &self.parallel, parallel); + } + if !at_start.is_empty() { + writeln!(string, " * Exclusive systems at start of stage:").unwrap(); + write_display_names_of_pairs(&mut string, &self.exclusive_at_start, at_start); + } + if !before_commands.is_empty() { + writeln!(string, " * Exclusive systems before commands of stage:").unwrap(); + write_display_names_of_pairs( + &mut string, + &self.exclusive_before_commands, + before_commands, + ); + } + if !at_end.is_empty() { + writeln!(string, " * Exclusive systems at end of stage:").unwrap(); + write_display_names_of_pairs(&mut string, &self.exclusive_at_end, at_end); + } + info!("{}", string); } } +} - fn run(&mut self, world: &mut World, resources: &mut Resources) { - loop { - let should_run = if let Some(ref mut run_criteria) = self.run_criteria { - let should_run = run_criteria.run((), world, resources); - run_criteria.run_thread_local(world, resources); - // don't run when no result is returned or false is returned - should_run.unwrap_or(ShouldRun::No) - } else { - ShouldRun::Yes - }; - - match should_run { - ShouldRun::No => return, - ShouldRun::Yes => { - self.run_once(world, resources); - return; +enum DependencyGraphError { + LabelNotFound(Cow<'static, str>), + DuplicateLabel(Cow<'static, str>), + GraphCycles(Vec>), +} + +/// Sorts given system containers topologically and populates their resolved dependencies. +fn sort_systems(systems: &mut Vec) -> Result<(), DependencyGraphError> { + let mut graph = build_dependency_graph(systems)?; + let order = topological_order(systems, &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() { + 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(()) +} + +/// Constructs a dependency graph of given system containers. +fn build_dependency_graph( + systems: &[impl SystemContainer], +) -> Result>, DependencyGraphError> { + let mut labels = HashMap::, usize>::default(); + for (label, index) in systems.iter().enumerate().filter_map(|(index, container)| { + container + .label() + .as_ref() + .cloned() + .map(|label| (label, index)) + }) { + if labels.contains_key(&label) { + return Err(DependencyGraphError::DuplicateLabel(label)); + } + labels.insert(label, index); + } + let mut graph = HashMap::default(); + for (system_index, container) in systems.iter().enumerate() { + let dependencies = graph.entry(system_index).or_insert_with(Vec::new); + for label in container.after() { + match labels.get(label) { + Some(dependency) => { + if !dependencies.contains(dependency) { + dependencies.push(*dependency); + } } - ShouldRun::YesAndLoop => { - self.run_once(world, resources); + None => return Err(DependencyGraphError::LabelNotFound(label.clone())), + } + } + for label in container.before() { + match labels.get(label) { + Some(dependant) => { + let dependencies = graph.entry(*dependant).or_insert_with(Vec::new); + if !dependencies.contains(&system_index) { + dependencies.push(system_index); + } } + None => return Err(DependencyGraphError::LabelNotFound(label.clone())), } } } + Ok(graph) } -pub enum ShouldRun { - /// No, the system should not run - No, - /// Yes, the system should run - Yes, - /// Yes, the system should run and after running, the criteria should be checked again. - YesAndLoop, +/// Generates a topological order for the given graph. +fn topological_order( + systems: &[impl SystemContainer], + graph: &HashMap>, +) -> Result, DependencyGraphError> { + fn check_if_cycles_and_visit( + node: &usize, + graph: &HashMap>, + sorted: &mut Vec, + unvisited: &mut HashSet, + current: &mut HashSet, + ) -> bool { + if current.contains(node) { + return true; + } else if !unvisited.remove(node) { + return false; + } + current.insert(*node); + for dependency in graph.get(node).unwrap() { + if check_if_cycles_and_visit(dependency, &graph, sorted, unvisited, current) { + return true; + } + } + sorted.push(*node); + current.remove(node); + false + } + let mut sorted = Vec::with_capacity(graph.len()); + let mut current = HashSet::with_capacity_and_hasher(graph.len(), Default::default()); + 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) { + return Err(DependencyGraphError::GraphCycles( + current + .iter() + .map(|index| systems[*index].display_name()) + .collect::>(), + )); + } + } + Ok(sorted) } -impl> From for SystemStage { - fn from(system: S) -> Self { - SystemStage::single(system) +/// Returns vector containing all pairs of indices of systems with ambiguous execution order. +/// Systems must be topologically sorted beforehand. +fn find_ambiguities(systems: &[impl SystemContainer]) -> Vec<(usize, usize)> { + 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 mut all_relations = all_dependencies + .drain(..) + .zip(all_dependants.drain(..)) + .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.drain(..).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) && !systems[index_a].is_compatible(&systems[index_b]) { + ambiguities.push((index_a, index_b)); + } + } + processed.insert(index_a); + } + ambiguities } -pub struct RunOnce { - ran: bool, - system_id: SystemId, - resource_access: TypeAccess, - archetype_access: TypeAccess, -} +impl Stage for SystemStage { + fn run(&mut self, world: &mut World, resources: &mut Resources) { + // Evaluate sets' run criteria, initialize sets as needed, detect if any sets were changed. + let mut has_work = false; + for system_set in self.system_sets.iter_mut() { + let result = system_set.run_criteria.should_run(world, resources); + match result { + Yes | YesAndCheckAgain => has_work = true, + No | NoAndCheckAgain => (), + } + system_set.should_run = result; + } -impl Default for RunOnce { - fn default() -> Self { - Self { - ran: false, - system_id: SystemId::new(), - resource_access: Default::default(), - archetype_access: Default::default(), + if self.systems_modified { + self.initialize_systems(world, resources); + self.rebuild_orders_and_dependencies(); + self.systems_modified = false; + self.executor.rebuild_cached_data(&mut self.parallel, world); + self.executor_modified = false; + if resources.contains::() { + self.report_ambiguities(); + } + } else if self.executor_modified { + self.executor.rebuild_cached_data(&mut self.parallel, world); + self.executor_modified = false; + } + + while has_work { + // Run systems that want to be at the start of stage. + for container in &mut self.exclusive_at_start { + if let Yes | YesAndCheckAgain = self.system_sets[container.system_set()].should_run + { + container.system_mut().run(world, resources); + } + } + + // Run parallel systems using the executor. + // TODO: hard dependencies, nested sets, whatever... should be evaluated here. + for container in &mut self.parallel { + match self.system_sets[container.system_set()].should_run { + Yes | YesAndCheckAgain => container.should_run = true, + No | NoAndCheckAgain => container.should_run = false, + } + } + self.executor + .run_systems(&mut self.parallel, world, resources); + + // Run systems that want to be between parallel systems and their command buffers. + for container in &mut self.exclusive_before_commands { + if let Yes | YesAndCheckAgain = self.system_sets[container.system_set()].should_run + { + container.system_mut().run(world, resources); + } + } + + // Apply parallel systems' buffers. + for container in &mut self.parallel { + if container.should_run { + container.system_mut().apply_buffers(world, resources); + } + } + + // Run systems that want to be at the end of stage. + for container in &mut self.exclusive_at_end { + if let Yes | YesAndCheckAgain = self.system_sets[container.system_set()].should_run + { + container.system_mut().run(world, resources); + } + } + + // Reevaluate system sets' run criteria. + has_work = false; + for system_set in self.system_sets.iter_mut() { + match system_set.should_run { + No => (), + Yes => system_set.should_run = No, + YesAndCheckAgain | NoAndCheckAgain => { + let new_result = system_set.run_criteria.should_run(world, resources); + match new_result { + Yes | YesAndCheckAgain => has_work = true, + No | NoAndCheckAgain => (), + } + system_set.should_run = new_result; + } + } + } } } } -impl System for RunOnce { - type In = (); - type Out = ShouldRun; +#[cfg(test)] +mod tests { + use crate::{prelude::*, SingleThreadedExecutor}; + + fn make_exclusive(tag: usize) -> impl FnMut(&mut Resources) { + move |resources| resources.get_mut::>().unwrap().push(tag) + } - fn name(&self) -> Cow<'static, str> { - Cow::Borrowed(std::any::type_name::()) + // This is silly. https://github.com/bevyengine/bevy/issues/1029 + macro_rules! make_parallel { + ($tag:expr) => {{ + fn parallel(mut resource: ResMut>) { + resource.push($tag) + } + parallel + }}; } - fn id(&self) -> SystemId { - self.system_id + fn empty() {} + + fn resettable_run_once(mut has_ran: ResMut) -> ShouldRun { + if !*has_ran { + *has_ran = true; + return ShouldRun::Yes; + } + ShouldRun::No } - fn update(&mut self, _world: &World) {} + #[test] + fn insertion_points() { + let mut world = World::new(); + let mut resources = Resources::default(); + + resources.insert(Vec::::new()); + let mut stage = SystemStage::parallel() + .with_system(make_exclusive(0).exclusive_system().at_start()) + .with_system(make_parallel!(1).system()) + .with_system(make_exclusive(2).exclusive_system().before_commands()) + .with_system(make_exclusive(3).exclusive_system().at_end()); + stage.run(&mut world, &mut resources); + assert_eq!(*resources.get::>().unwrap(), vec![0, 1, 2, 3]); + stage.set_executor(Box::new(SingleThreadedExecutor::default())); + stage.run(&mut world, &mut resources); + assert_eq!( + *resources.get::>().unwrap(), + vec![0, 1, 2, 3, 0, 1, 2, 3] + ); + + resources.get_mut::>().unwrap().clear(); + let mut stage = SystemStage::parallel() + .with_system(make_exclusive(2).exclusive_system().before_commands()) + .with_system(make_exclusive(3).exclusive_system().at_end()) + .with_system(make_parallel!(1).system()) + .with_system(make_exclusive(0).exclusive_system().at_start()); + stage.run(&mut world, &mut resources); + assert_eq!(*resources.get::>().unwrap(), vec![0, 1, 2, 3]); + stage.set_executor(Box::new(SingleThreadedExecutor::default())); + stage.run(&mut world, &mut resources); + assert_eq!( + *resources.get::>().unwrap(), + vec![0, 1, 2, 3, 0, 1, 2, 3] + ); - fn archetype_component_access(&self) -> &TypeAccess { - &self.archetype_access + resources.get_mut::>().unwrap().clear(); + let mut stage = SystemStage::parallel() + .with_system(make_parallel!(2).exclusive_system().before_commands()) + .with_system(make_parallel!(3).exclusive_system().at_end()) + .with_system(make_parallel!(1).system()) + .with_system(make_parallel!(0).exclusive_system().at_start()); + stage.run(&mut world, &mut resources); + assert_eq!(*resources.get::>().unwrap(), vec![0, 1, 2, 3]); + stage.set_executor(Box::new(SingleThreadedExecutor::default())); + stage.run(&mut world, &mut resources); + assert_eq!( + *resources.get::>().unwrap(), + vec![0, 1, 2, 3, 0, 1, 2, 3] + ); } - fn resource_access(&self) -> &TypeAccess { - &self.resource_access + #[test] + #[should_panic(expected = "No exclusive system with label \"empty\" at start of stage.")] + fn exclusive_unknown_label() { + let mut world = World::new(); + let mut resources = Resources::default(); + resources.insert(Vec::::new()); + let mut stage = SystemStage::parallel() + .with_system(empty.exclusive_system().at_end().label("empty")) + .with_system(empty.exclusive_system().after("empty")); + stage.run(&mut world, &mut resources); } - fn thread_local_execution(&self) -> ThreadLocalExecution { - ThreadLocalExecution::Immediate + #[test] + #[should_panic( + expected = "Label \"empty\" already used by an exclusive system at start of stage." + )] + fn exclusive_duplicate_label() { + let mut world = World::new(); + let mut resources = Resources::default(); + resources.insert(Vec::::new()); + let mut stage = SystemStage::parallel() + .with_system(empty.exclusive_system().at_end().label("empty")) + .with_system(empty.exclusive_system().before_commands().label("empty")); + stage.run(&mut world, &mut resources); + let mut stage = SystemStage::parallel() + .with_system(empty.exclusive_system().label("empty")) + .with_system(empty.exclusive_system().label("empty")); + stage.run(&mut world, &mut resources); } - unsafe fn run_unsafe( - &mut self, - _input: Self::In, - _world: &World, - _resources: &Resources, - ) -> Option { - Some(if self.ran { - ShouldRun::No - } else { - self.ran = true; - ShouldRun::Yes - }) + #[test] + fn exclusive_after() { + let mut world = World::new(); + let mut resources = Resources::default(); + resources.insert(Vec::::new()); + let mut stage = SystemStage::parallel() + .with_system(make_exclusive(1).exclusive_system().label("1").after("0")) + .with_system(make_exclusive(2).exclusive_system().after("1")) + .with_system(make_exclusive(0).exclusive_system().label("0")); + stage.run(&mut world, &mut resources); + stage.set_executor(Box::new(SingleThreadedExecutor::default())); + stage.run(&mut world, &mut resources); + assert_eq!( + *resources.get::>().unwrap(), + vec![0, 1, 2, 0, 1, 2] + ); + } + + #[test] + fn exclusive_before() { + let mut world = World::new(); + let mut resources = Resources::default(); + resources.insert(Vec::::new()); + let mut stage = SystemStage::parallel() + .with_system(make_exclusive(1).exclusive_system().label("1").before("2")) + .with_system(make_exclusive(2).exclusive_system().label("2")) + .with_system(make_exclusive(0).exclusive_system().before("1")); + stage.run(&mut world, &mut resources); + stage.set_executor(Box::new(SingleThreadedExecutor::default())); + stage.run(&mut world, &mut resources); + assert_eq!( + *resources.get::>().unwrap(), + vec![0, 1, 2, 0, 1, 2] + ); + } + + #[test] + fn exclusive_mixed() { + let mut world = World::new(); + let mut resources = Resources::default(); + resources.insert(Vec::::new()); + let mut stage = SystemStage::parallel() + .with_system(make_exclusive(2).exclusive_system().label("2")) + .with_system(make_exclusive(1).exclusive_system().after("0").before("2")) + .with_system(make_exclusive(0).exclusive_system().label("0")) + .with_system(make_exclusive(4).exclusive_system().label("4")) + .with_system(make_exclusive(3).exclusive_system().after("2").before("4")); + stage.run(&mut world, &mut resources); + stage.set_executor(Box::new(SingleThreadedExecutor::default())); + stage.run(&mut world, &mut resources); + assert_eq!( + *resources.get::>().unwrap(), + vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4] + ); + } + + #[test] + fn exclusive_redundant_constraints() { + let mut world = World::new(); + let mut resources = Resources::default(); + resources.insert(Vec::::new()); + let mut stage = SystemStage::parallel() + .with_system( + make_exclusive(2) + .exclusive_system() + .label("2") + .after("1") + .before("3") + .before("3"), + ) + .with_system( + make_exclusive(1) + .exclusive_system() + .label("1") + .after("0") + .after("0") + .before("2"), + ) + .with_system(make_exclusive(0).exclusive_system().label("0").before("1")) + .with_system(make_exclusive(4).exclusive_system().label("4").after("3")) + .with_system( + make_exclusive(3) + .exclusive_system() + .label("3") + .after("2") + .before("4"), + ); + stage.run(&mut world, &mut resources); + stage.set_executor(Box::new(SingleThreadedExecutor::default())); + stage.run(&mut world, &mut resources); + assert_eq!( + *resources.get::>().unwrap(), + vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4] + ); + } + + #[test] + fn exclusive_mixed_across_sets() { + let mut world = World::new(); + let mut resources = Resources::default(); + resources.insert(Vec::::new()); + let mut stage = SystemStage::parallel() + .with_system(make_exclusive(2).exclusive_system().label("2")) + .with_system_set( + SystemSet::new() + .with_system(make_exclusive(0).exclusive_system().label("0")) + .with_system(make_exclusive(4).exclusive_system().label("4")) + .with_system(make_exclusive(3).exclusive_system().after("2").before("4")), + ) + .with_system(make_exclusive(1).exclusive_system().after("0").before("2")); + stage.run(&mut world, &mut resources); + stage.set_executor(Box::new(SingleThreadedExecutor::default())); + stage.run(&mut world, &mut resources); + assert_eq!( + *resources.get::>().unwrap(), + vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4] + ); + } + + #[test] + fn exclusive_run_criteria() { + let mut world = World::new(); + let mut resources = Resources::default(); + resources.insert(Vec::::new()); + resources.insert(false); + let mut stage = SystemStage::parallel() + .with_system(make_exclusive(0).exclusive_system().before("1")) + .with_system_set( + SystemSet::new() + .with_run_criteria(resettable_run_once.system()) + .with_system(make_exclusive(1).exclusive_system().label("1")), + ) + .with_system(make_exclusive(2).exclusive_system().after("1")); + stage.run(&mut world, &mut resources); + stage.run(&mut world, &mut resources); + *resources.get_mut::().unwrap() = false; + stage.set_executor(Box::new(SingleThreadedExecutor::default())); + stage.run(&mut world, &mut resources); + stage.run(&mut world, &mut resources); + assert_eq!( + *resources.get::>().unwrap(), + vec![0, 1, 2, 0, 2, 0, 1, 2, 0, 2] + ); + } + + #[test] + #[should_panic] + fn exclusive_cycle_1() { + let mut world = World::new(); + let mut resources = Resources::default(); + resources.insert(Vec::::new()); + let mut stage = SystemStage::parallel() + .with_system(make_exclusive(0).exclusive_system().label("0").after("0")); + stage.run(&mut world, &mut resources); + } + + #[test] + #[should_panic] + fn exclusive_cycle_2() { + let mut world = World::new(); + let mut resources = Resources::default(); + resources.insert(Vec::::new()); + let mut stage = SystemStage::parallel() + .with_system(make_exclusive(0).exclusive_system().label("0").after("1")) + .with_system(make_exclusive(1).exclusive_system().label("1").after("0")); + stage.run(&mut world, &mut resources); + } + + #[test] + #[should_panic] + fn exclusive_cycle_3() { + let mut world = World::new(); + let mut resources = Resources::default(); + resources.insert(Vec::::new()); + let mut stage = SystemStage::parallel() + .with_system(make_exclusive(0).exclusive_system().label("0")) + .with_system(make_exclusive(1).exclusive_system().after("0").before("2")) + .with_system(make_exclusive(2).exclusive_system().label("2").before("0")); + stage.run(&mut world, &mut resources); + } + + #[test] + #[should_panic(expected = "No parallel system with label \"empty\" in stage.")] + fn parallel_unknown_label() { + let mut world = World::new(); + let mut resources = Resources::default(); + resources.insert(Vec::::new()); + let mut stage = SystemStage::parallel() + .with_system(empty.system()) + .with_system(empty.system().after("empty")); + stage.run(&mut world, &mut resources); + } + + #[test] + #[should_panic(expected = "Label \"empty\" already used by a parallel system.")] + fn parallel_duplicate_label() { + let mut world = World::new(); + let mut resources = Resources::default(); + resources.insert(Vec::::new()); + let mut stage = SystemStage::parallel() + .with_system(empty.system().label("empty")) + .with_system(empty.system().label("empty")); + stage.run(&mut world, &mut resources); + } + + #[test] + fn parallel_after() { + let mut world = World::new(); + let mut resources = Resources::default(); + resources.insert(Vec::::new()); + let mut stage = SystemStage::parallel() + .with_system(make_parallel!(1).system().after("0").label("1")) + .with_system(make_parallel!(2).system().after("1")) + .with_system(make_parallel!(0).system().label("0")); + stage.run(&mut world, &mut resources); + stage.set_executor(Box::new(SingleThreadedExecutor::default())); + stage.run(&mut world, &mut resources); + assert_eq!( + *resources.get::>().unwrap(), + vec![0, 1, 2, 0, 1, 2] + ); + } + + #[test] + fn parallel_before() { + let mut world = World::new(); + let mut resources = Resources::default(); + resources.insert(Vec::::new()); + let mut stage = SystemStage::parallel() + .with_system(make_parallel!(1).system().label("1").before("2")) + .with_system(make_parallel!(2).system().label("2")) + .with_system(make_parallel!(0).system().before("1")); + stage.run(&mut world, &mut resources); + stage.set_executor(Box::new(SingleThreadedExecutor::default())); + stage.run(&mut world, &mut resources); + assert_eq!( + *resources.get::>().unwrap(), + vec![0, 1, 2, 0, 1, 2] + ); + } + + #[test] + fn parallel_mixed() { + let mut world = World::new(); + let mut resources = Resources::default(); + resources.insert(Vec::::new()); + let mut stage = SystemStage::parallel() + .with_system(make_parallel!(2).system().label("2")) + .with_system(make_parallel!(1).system().after("0").before("2")) + .with_system(make_parallel!(0).system().label("0")) + .with_system(make_parallel!(4).system().label("4")) + .with_system(make_parallel!(3).system().after("2").before("4")); + stage.run(&mut world, &mut resources); + stage.set_executor(Box::new(SingleThreadedExecutor::default())); + stage.run(&mut world, &mut resources); + assert_eq!( + *resources.get::>().unwrap(), + vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4] + ); + } + + #[test] + fn parallel_redundant_constraints() { + let mut world = World::new(); + let mut resources = Resources::default(); + resources.insert(Vec::::new()); + let mut stage = SystemStage::parallel() + .with_system( + make_parallel!(2) + .system() + .label("2") + .after("1") + .before("3") + .before("3"), + ) + .with_system( + make_parallel!(1) + .system() + .label("1") + .after("0") + .after("0") + .before("2"), + ) + .with_system(make_parallel!(0).system().label("0").before("1")) + .with_system(make_parallel!(4).system().label("4").after("3")) + .with_system(make_parallel!(3).system().label("3").after("2").before("4")); + stage.run(&mut world, &mut resources); + for container in stage.parallel.iter() { + assert!(container.dependencies().len() <= 1); + } + stage.set_executor(Box::new(SingleThreadedExecutor::default())); + stage.run(&mut world, &mut resources); + assert_eq!( + *resources.get::>().unwrap(), + vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4] + ); + } + + #[test] + fn parallel_mixed_across_sets() { + let mut world = World::new(); + let mut resources = Resources::default(); + resources.insert(Vec::::new()); + let mut stage = SystemStage::parallel() + .with_system(make_parallel!(2).system().label("2")) + .with_system_set( + SystemSet::new() + .with_system(make_parallel!(0).system().label("0")) + .with_system(make_parallel!(4).system().label("4")) + .with_system(make_parallel!(3).system().after("2").before("4")), + ) + .with_system(make_parallel!(1).system().after("0").before("2")); + stage.run(&mut world, &mut resources); + stage.set_executor(Box::new(SingleThreadedExecutor::default())); + stage.run(&mut world, &mut resources); + assert_eq!( + *resources.get::>().unwrap(), + vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4] + ); + } + + #[test] + fn parallel_run_criteria() { + let mut world = World::new(); + let mut resources = Resources::default(); + resources.insert(Vec::::new()); + resources.insert(false); + let mut stage = SystemStage::parallel() + .with_system(make_parallel!(0).system().before("1")) + .with_system_set( + SystemSet::new() + .with_run_criteria(resettable_run_once.system()) + .with_system(make_parallel!(1).system().label("1")), + ) + .with_system(make_parallel!(2).system().after("1")); + stage.run(&mut world, &mut resources); + stage.run(&mut world, &mut resources); + *resources.get_mut::().unwrap() = false; + stage.set_executor(Box::new(SingleThreadedExecutor::default())); + stage.run(&mut world, &mut resources); + stage.run(&mut world, &mut resources); + assert_eq!( + *resources.get::>().unwrap(), + vec![0, 1, 2, 0, 2, 0, 1, 2, 0, 2] + ); + } + + #[test] + #[should_panic] + fn parallel_cycle_1() { + let mut world = World::new(); + let mut resources = Resources::default(); + resources.insert(Vec::::new()); + let mut stage = + SystemStage::parallel().with_system(make_parallel!(0).system().label("0").after("0")); + stage.run(&mut world, &mut resources); + } + + #[test] + #[should_panic] + fn parallel_cycle_2() { + let mut world = World::new(); + let mut resources = Resources::default(); + resources.insert(Vec::::new()); + let mut stage = SystemStage::parallel() + .with_system(make_parallel!(0).system().label("0").after("1")) + .with_system(make_parallel!(1).system().label("1").after("0")); + stage.run(&mut world, &mut resources); } - fn run_thread_local(&mut self, _world: &mut World, _resources: &mut Resources) {} + #[test] + #[should_panic] + fn parallel_cycle_3() { + let mut world = World::new(); + let mut resources = Resources::default(); + resources.insert(Vec::::new()); + let mut stage = SystemStage::parallel() + .with_system(make_parallel!(0).system().label("0")) + .with_system(make_parallel!(1).system().after("0").before("2")) + .with_system(make_parallel!(2).system().label("2").before("0")); + stage.run(&mut world, &mut resources); + } + + #[test] + fn ambiguity_detection() { + use super::{find_ambiguities, SystemContainer}; + use std::borrow::Cow; + + fn find_ambiguities_labels( + systems: &[impl SystemContainer], + ) -> Vec<(Cow<'static, str>, Cow<'static, str>)> { + find_ambiguities(systems) + .drain(..) + .map(|(index_a, index_b)| { + ( + systems[index_a].display_name(), + systems[index_b].display_name(), + ) + }) + .collect() + } + + fn empty() {} + fn resource(_: ResMut) {} + fn component(_: Query<&mut f32>) {} + + let mut world = World::new(); + let mut resources = Resources::default(); + + let mut stage = SystemStage::parallel() + .with_system(empty.system().label("0")) + .with_system(empty.system().label("1").after("0")) + .with_system(empty.system().label("2")) + .with_system(empty.system().label("3").after("2").before("4")) + .with_system(empty.system().label("4")); + stage.initialize_systems(&mut world, &mut resources); + stage.rebuild_orders_and_dependencies(); + assert_eq!(find_ambiguities(&stage.parallel).len(), 0); + + let mut stage = SystemStage::parallel() + .with_system(empty.system().label("0")) + .with_system(component.system().label("1").after("0")) + .with_system(empty.system().label("2")) + .with_system(empty.system().label("3").after("2").before("4")) + .with_system(component.system().label("4")); + stage.initialize_systems(&mut world, &mut resources); + stage.rebuild_orders_and_dependencies(); + let ambiguities = find_ambiguities_labels(&stage.parallel); + assert!( + ambiguities.contains(&("1".into(), "4".into())) + || ambiguities.contains(&("4".into(), "1".into())) + ); + assert_eq!(ambiguities.len(), 1); - fn initialize(&mut self, _world: &mut World, _resources: &mut Resources) {} + let mut stage = SystemStage::parallel() + .with_system(empty.system().label("0")) + .with_system(resource.system().label("1").after("0")) + .with_system(empty.system().label("2")) + .with_system(empty.system().label("3").after("2").before("4")) + .with_system(resource.system().label("4")); + stage.initialize_systems(&mut world, &mut resources); + stage.rebuild_orders_and_dependencies(); + let ambiguities = find_ambiguities_labels(&stage.parallel); + assert!( + ambiguities.contains(&("1".into(), "4".into())) + || ambiguities.contains(&("4".into(), "1".into())) + ); + assert_eq!(ambiguities.len(), 1); + + let mut stage = SystemStage::parallel() + .with_system(empty.system().label("0")) + .with_system(resource.system().label("1").after("0")) + .with_system(empty.system().label("2")) + .with_system(empty.system().label("3").after("2").before("4")) + .with_system(component.system().label("4")); + stage.initialize_systems(&mut world, &mut resources); + stage.rebuild_orders_and_dependencies(); + assert_eq!(find_ambiguities(&stage.parallel).len(), 0); + + let mut stage = SystemStage::parallel() + .with_system(component.system().label("0")) + .with_system(resource.system().label("1").after("0")) + .with_system(empty.system().label("2")) + .with_system(component.system().label("3").after("2").before("4")) + .with_system(resource.system().label("4")); + stage.initialize_systems(&mut world, &mut resources); + stage.rebuild_orders_and_dependencies(); + let ambiguities = find_ambiguities_labels(&stage.parallel); + assert!( + ambiguities.contains(&("0".into(), "3".into())) + || ambiguities.contains(&("3".into(), "0".into())) + ); + assert!( + ambiguities.contains(&("1".into(), "4".into())) + || ambiguities.contains(&("4".into(), "1".into())) + ); + assert_eq!(ambiguities.len(), 2); + + let mut stage = SystemStage::parallel() + .with_system(component.system().label("0").before("2")) + .with_system(component.system().label("1").before("2")) + .with_system(component.system().label("2")); + stage.initialize_systems(&mut world, &mut resources); + stage.rebuild_orders_and_dependencies(); + let ambiguities = find_ambiguities_labels(&stage.parallel); + assert!( + ambiguities.contains(&("0".into(), "1".into())) + || ambiguities.contains(&("1".into(), "0".into())) + ); + assert_eq!(ambiguities.len(), 1); + + let mut stage = SystemStage::parallel() + .with_system(component.system().label("0")) + .with_system(component.system().label("1").after("0")) + .with_system(component.system().label("2").after("0")); + stage.initialize_systems(&mut world, &mut resources); + stage.rebuild_orders_and_dependencies(); + let ambiguities = find_ambiguities_labels(&stage.parallel); + assert!( + ambiguities.contains(&("1".into(), "2".into())) + || ambiguities.contains(&("2".into(), "1".into())) + ); + assert_eq!(ambiguities.len(), 1); + + let mut stage = SystemStage::parallel() + .with_system(component.system().label("0").before("1").before("2")) + .with_system(component.system().label("1")) + .with_system(component.system().label("2")) + .with_system(component.system().label("3").after("1").after("2")); + stage.initialize_systems(&mut world, &mut resources); + stage.rebuild_orders_and_dependencies(); + let ambiguities = find_ambiguities_labels(&stage.parallel); + assert!( + ambiguities.contains(&("1".into(), "2".into())) + || ambiguities.contains(&("2".into(), "1".into())) + ); + assert_eq!(ambiguities.len(), 1); + + let mut stage = SystemStage::parallel() + .with_system( + component + .system() + .label("0") + .before("1") + .before("2") + .before("3") + .before("4"), + ) + .with_system(component.system().label("1")) + .with_system(component.system().label("2")) + .with_system(component.system().label("3")) + .with_system(component.system().label("4")) + .with_system( + component + .system() + .label("5") + .after("1") + .after("2") + .after("3") + .after("4"), + ); + stage.initialize_systems(&mut world, &mut resources); + stage.rebuild_orders_and_dependencies(); + let ambiguities = find_ambiguities_labels(&stage.parallel); + assert!( + ambiguities.contains(&("1".into(), "2".into())) + || ambiguities.contains(&("2".into(), "1".into())) + ); + assert!( + ambiguities.contains(&("1".into(), "3".into())) + || ambiguities.contains(&("3".into(), "1".into())) + ); + assert!( + ambiguities.contains(&("1".into(), "4".into())) + || ambiguities.contains(&("4".into(), "1".into())) + ); + assert!( + ambiguities.contains(&("2".into(), "3".into())) + || ambiguities.contains(&("3".into(), "2".into())) + ); + assert!( + ambiguities.contains(&("2".into(), "4".into())) + || ambiguities.contains(&("4".into(), "2".into())) + ); + assert!( + ambiguities.contains(&("3".into(), "4".into())) + || ambiguities.contains(&("4".into(), "3".into())) + ); + assert_eq!(ambiguities.len(), 6); + + let mut stage = SystemStage::parallel() + .with_system(empty.exclusive_system().label("0")) + .with_system(empty.exclusive_system().label("1").after("0")) + .with_system(empty.exclusive_system().label("2").after("1")) + .with_system(empty.exclusive_system().label("3").after("2")) + .with_system(empty.exclusive_system().label("4").after("3")) + .with_system(empty.exclusive_system().label("5").after("4")) + .with_system(empty.exclusive_system().label("6").after("5")) + .with_system(empty.exclusive_system().label("7").after("6")); + stage.initialize_systems(&mut world, &mut resources); + stage.rebuild_orders_and_dependencies(); + assert_eq!(find_ambiguities(&stage.exclusive_at_start).len(), 0); + + let mut stage = SystemStage::parallel() + .with_system(empty.exclusive_system().label("0").before("1").before("3")) + .with_system(empty.exclusive_system().label("1")) + .with_system(empty.exclusive_system().label("2").after("1")) + .with_system(empty.exclusive_system().label("3")) + .with_system(empty.exclusive_system().label("4").after("3").before("5")) + .with_system(empty.exclusive_system().label("5")) + .with_system(empty.exclusive_system().label("6").after("2").after("5")); + stage.initialize_systems(&mut world, &mut resources); + stage.rebuild_orders_and_dependencies(); + let ambiguities = find_ambiguities_labels(&stage.exclusive_at_start); + assert!( + ambiguities.contains(&("1".into(), "3".into())) + || ambiguities.contains(&("3".into(), "1".into())) + ); + assert!( + ambiguities.contains(&("2".into(), "3".into())) + || ambiguities.contains(&("3".into(), "2".into())) + ); + assert!( + ambiguities.contains(&("1".into(), "4".into())) + || ambiguities.contains(&("4".into(), "1".into())) + ); + assert!( + ambiguities.contains(&("2".into(), "4".into())) + || ambiguities.contains(&("4".into(), "2".into())) + ); + assert!( + ambiguities.contains(&("1".into(), "5".into())) + || ambiguities.contains(&("5".into(), "1".into())) + ); + assert!( + ambiguities.contains(&("2".into(), "5".into())) + || ambiguities.contains(&("5".into(), "2".into())) + ); + assert_eq!(ambiguities.len(), 6); + } } diff --git a/crates/bevy_ecs/src/schedule/stage_executor.rs b/crates/bevy_ecs/src/schedule/stage_executor.rs deleted file mode 100644 index 35d0d310e15761..00000000000000 --- a/crates/bevy_ecs/src/schedule/stage_executor.rs +++ /dev/null @@ -1,528 +0,0 @@ -use std::ops::Range; - -use bevy_tasks::{ComputeTaskPool, CountdownEvent, TaskPool}; -use bevy_utils::tracing::trace; -use downcast_rs::{impl_downcast, Downcast}; -use fixedbitset::FixedBitSet; - -use crate::{ - ArchetypesGeneration, BoxedSystem, Resources, ThreadLocalExecution, TypeAccess, World, -}; - -pub trait SystemStageExecutor: Downcast + Send + Sync { - fn execute_stage( - &mut self, - systems: &mut [BoxedSystem], - changed_systems: &[usize], - world: &mut World, - resources: &mut Resources, - ); -} - -impl_downcast!(SystemStageExecutor); - -#[derive(Default)] -pub struct SerialSystemStageExecutor; - -impl SystemStageExecutor for SerialSystemStageExecutor { - fn execute_stage( - &mut self, - systems: &mut [BoxedSystem], - _changed_systems: &[usize], - world: &mut World, - resources: &mut Resources, - ) { - for system in systems.iter_mut() { - system.update(world); - match system.thread_local_execution() { - ThreadLocalExecution::NextFlush => { - system.run((), world, resources); - } - ThreadLocalExecution::Immediate => { - system.run((), world, resources); - system.run_thread_local(world, resources); - } - } - } - - // "flush" - for system in systems.iter_mut() { - match system.thread_local_execution() { - ThreadLocalExecution::NextFlush => system.run_thread_local(world, resources), - ThreadLocalExecution::Immediate => { /* already ran immediate */ } - } - } - } -} - -/// Executes the stage in parallel by analyzing system dependencies. -/// System execution order is undefined except under the following conditions: -/// * systems in earlier stages run before systems in later stages -/// * in a given stage, systems that mutate [archetype+component] X cannot run before systems registered before them that read/write [archetype+component] X -/// * in a given stage, systems the read [archetype+component] X cannot run before systems registered before them that write [archetype+component] X -/// * in a given stage, systems that mutate resource Y cannot run before systems registered before them that read/write resource Y -/// * in a given stage, systems the read resource Y cannot run before systems registered before them that write resource Y -pub struct ParallelSystemStageExecutor { - /// each system's set of dependencies - system_dependencies: Vec, - /// count of each system's dependencies - system_dependency_count: Vec, - /// Countdown of finished dependencies, used to trigger the next system - ready_events: Vec>, - /// When a system finishes, it will decrement the countdown events of all dependents - ready_events_of_dependents: Vec>, - /// each system's dependents (the systems that can't run until this system has run) - system_dependents: Vec>, - /// stores the indices of thread local systems in this stage, which are used during stage.prepare() - thread_local_system_indices: Vec, - /// When archetypes change a counter is bumped - we cache the state of that counter when it was - /// last read here so that we can detect when archetypes are changed - last_archetypes_generation: ArchetypesGeneration, -} - -impl Default for ParallelSystemStageExecutor { - fn default() -> Self { - Self { - system_dependents: Default::default(), - system_dependency_count: Default::default(), - ready_events: Default::default(), - ready_events_of_dependents: Default::default(), - system_dependencies: Default::default(), - thread_local_system_indices: Default::default(), - last_archetypes_generation: ArchetypesGeneration(u64::MAX), // MAX forces prepare to run the first time - } - } -} - -impl ParallelSystemStageExecutor { - pub fn system_dependents(&self) -> &[Vec] { - &self.system_dependents - } - - pub fn system_dependencies(&self) -> &[FixedBitSet] { - &self.system_dependencies - } - - /// Sets up state to run the next "batch" of systems. Each batch contains 0..n systems and - /// optionally a thread local system at the end. After this function runs, a bunch of state - /// in self will be populated for systems in this batch. Returns the range of systems - /// that we prepared, up to but NOT including the thread local system that MIGHT be at the end - /// of the range - pub fn prepare_to_next_thread_local( - &mut self, - world: &World, - systems: &mut [BoxedSystem], - stage_changed: bool, - next_thread_local_index: usize, - ) -> Range { - // Find the first system in this batch and (if there is one) the thread local system that - // ends it. - let (prepare_system_start_index, last_thread_local_index) = if next_thread_local_index == 0 - { - (0, None) - } else { - // start right after the last thread local system - ( - self.thread_local_system_indices[next_thread_local_index - 1] + 1, - Some(self.thread_local_system_indices[next_thread_local_index - 1]), - ) - }; - - let prepare_system_index_range = if let Some(index) = self - .thread_local_system_indices - .get(next_thread_local_index) - { - // if there is an upcoming thread local system, prepare up to (and including) it - prepare_system_start_index..(*index + 1) - } else { - // if there are no upcoming thread local systems, prepare everything right now - prepare_system_start_index..systems.len() - }; - - let archetypes_generation_changed = - self.last_archetypes_generation != world.archetypes_generation(); - - if stage_changed || archetypes_generation_changed { - // update each system's [archetype+component] access to latest world archetypes - for system_index in prepare_system_index_range.clone() { - systems[system_index].update(world); - - // Clear this so that the next block of code that populates it doesn't insert - // duplicates - self.system_dependents[system_index].clear(); - self.system_dependencies[system_index].clear(); - } - - // calculate dependencies between systems and build execution order - let mut current_archetype_access = TypeAccess::default(); - let mut current_resource_access = TypeAccess::default(); - for system_index in prepare_system_index_range.clone() { - let system = &systems[system_index]; - let archetype_access = system.archetype_component_access(); - match system.thread_local_execution() { - ThreadLocalExecution::NextFlush => { - let resource_access = system.resource_access(); - // if any system before this one conflicts, check all systems that came before for compatibility - if !current_archetype_access.is_compatible(archetype_access) - || !current_resource_access.is_compatible(resource_access) - { - #[allow(clippy::needless_range_loop)] - for earlier_system_index in - prepare_system_index_range.start..system_index - { - let earlier_system = &systems[earlier_system_index]; - - // due to how prepare ranges work, previous systems should all be "NextFlush" - debug_assert_eq!( - earlier_system.thread_local_execution(), - ThreadLocalExecution::NextFlush - ); - - // if earlier system is incompatible, make the current system dependent - if !earlier_system - .archetype_component_access() - .is_compatible(archetype_access) - || !earlier_system - .resource_access() - .is_compatible(resource_access) - { - self.system_dependents[earlier_system_index].push(system_index); - self.system_dependencies[system_index] - .insert(earlier_system_index); - } - } - } - - current_archetype_access.union(archetype_access); - current_resource_access.union(resource_access); - - if let Some(last_thread_local_index) = last_thread_local_index { - self.system_dependents[last_thread_local_index].push(system_index); - self.system_dependencies[system_index].insert(last_thread_local_index); - } - } - ThreadLocalExecution::Immediate => { - for earlier_system_index in prepare_system_index_range.start..system_index { - // treat all earlier systems as "incompatible" to ensure we run this thread local system exclusively - self.system_dependents[earlier_system_index].push(system_index); - self.system_dependencies[system_index].insert(earlier_system_index); - } - } - } - } - - // Verify that dependents are not duplicated - #[cfg(debug_assertions)] - for system_index in prepare_system_index_range.clone() { - let mut system_dependents_set = std::collections::HashSet::new(); - for dependent_system in &self.system_dependents[system_index] { - let inserted = system_dependents_set.insert(*dependent_system); - - // This means duplicate values are in the system_dependents list - // This is reproducing when archetypes change. When we fix this, we can remove - // the hack below and make this a debug-only assert or remove it - debug_assert!(inserted); - } - } - - // Clear the ready events lists associated with each system so we can rebuild them - for ready_events_of_dependents in - &mut self.ready_events_of_dependents[prepare_system_index_range.clone()] - { - ready_events_of_dependents.clear(); - } - - // Now that system_dependents and system_dependencies is populated, update - // system_dependency_count and ready_events - for system_index in prepare_system_index_range.clone() { - // Count all dependencies to update system_dependency_count - assert!(!self.system_dependencies[system_index].contains(system_index)); - let dependency_count = self.system_dependencies[system_index].count_ones(..); - self.system_dependency_count[system_index] = dependency_count; - - // If dependency count > 0, allocate a ready_event - self.ready_events[system_index] = match self.system_dependency_count[system_index] { - 0 => None, - dependency_count => Some(CountdownEvent::new(dependency_count as isize)), - } - } - - // Now that ready_events are created, we can build ready_events_of_dependents - for system_index in prepare_system_index_range.clone() { - for dependent_system in &self.system_dependents[system_index] { - self.ready_events_of_dependents[system_index].push( - self.ready_events[*dependent_system] - .as_ref() - .expect("A dependent task should have a non-None ready event.") - .clone(), - ); - } - } - } else { - // Reset the countdown events for this range of systems. Resetting is required even if the - // schedule didn't change - self.reset_system_ready_events(prepare_system_index_range); - } - - if let Some(index) = self - .thread_local_system_indices - .get(next_thread_local_index) - { - // if there is an upcoming thread local system, prepare up to (and NOT including) it - prepare_system_start_index..(*index) - } else { - // if there are no upcoming thread local systems, prepare everything right now - prepare_system_start_index..systems.len() - } - } - - fn reset_system_ready_events(&mut self, prepare_system_index_range: Range) { - for system_index in prepare_system_index_range { - let dependency_count = self.system_dependency_count[system_index]; - if dependency_count > 0 { - self.ready_events[system_index] - .as_ref() - .expect("A system with >0 dependency count should have a non-None ready event.") - .reset(dependency_count as isize) - } - } - } - - /// Runs the non-thread-local systems in the given prepared_system_range range - pub fn run_systems( - &self, - world: &World, - resources: &Resources, - systems: &mut [BoxedSystem], - prepared_system_range: Range, - compute_pool: &TaskPool, - ) { - // Generate tasks for systems in the given range and block until they are complete - trace!("running systems {:?}", prepared_system_range); - compute_pool.scope(|scope| { - let start_system_index = prepared_system_range.start; - let mut system_index = start_system_index; - for system in &mut systems[prepared_system_range] { - trace!( - "prepare {} {} with {} dependents and {} dependencies", - system_index, - system.name(), - self.system_dependents[system_index].len(), - self.system_dependencies[system_index].count_ones(..) - ); - - // This event will be awaited, preventing the task from starting until all - // our dependencies finish running - let ready_event = &self.ready_events[system_index]; - - // Clear any dependencies on systems before this range of systems. We know at this - // point everything before start_system_index is finished, and our ready_event did - // not exist to be decremented until we started processing this range - if start_system_index != 0 { - if let Some(ready_event) = ready_event.as_ref() { - for dependency in self.system_dependencies[system_index].ones() { - if dependency < start_system_index { - ready_event.decrement(); - } - } - } - } - - let world_ref = &*world; - let resources_ref = &*resources; - - let trigger_events = &self.ready_events_of_dependents[system_index]; - - // Verify that any dependent task has a > 0 count. If a dependent task has > 0 - // count, then the current system we are starting now isn't blocking it from running - // as it should be. Failure here implies the sync primitives are not matching the - // intended schedule. This likely compiles out if trace/asserts are disabled but - // make it explicitly debug-only anyways - #[cfg(debug_assertions)] - { - let dependent_systems = &self.system_dependents[system_index]; - debug_assert_eq!(trigger_events.len(), dependent_systems.len()); - for (trigger_event, dependent_system_index) in - trigger_events.iter().zip(dependent_systems) - { - debug_assert!( - *dependent_system_index < start_system_index || trigger_event.get() > 0 - ); - } - } - - // Spawn the task - scope.spawn(async move { - // Wait until our dependencies are done - if let Some(ready_event) = ready_event { - ready_event.listen().await; - } - - // Execute the system - in a scope to ensure the system lock is dropped before - // triggering dependents - { - #[cfg(feature = "trace")] - let system_span = bevy_utils::tracing::info_span!( - "system", - name = system.name().as_ref() - ); - #[cfg(feature = "trace")] - let _system_guard = system_span.enter(); - - // SAFETY: scheduler ensures safe world / resource access - unsafe { - system.run_unsafe((), world_ref, resources_ref); - } - } - - // Notify dependents that this task is done - for trigger_event in trigger_events { - trigger_event.decrement(); - } - }); - system_index += 1; - } - }); - } -} - -impl SystemStageExecutor for ParallelSystemStageExecutor { - fn execute_stage( - &mut self, - systems: &mut [BoxedSystem], - changed_systems: &[usize], - world: &mut World, - resources: &mut Resources, - ) { - let start_archetypes_generation = world.archetypes_generation(); - let compute_pool = resources - .get_or_insert_with(|| ComputeTaskPool(TaskPool::default())) - .clone(); - - let stage_changed = !changed_systems.is_empty(); - - // if the schedule has changed, clear executor state / fill it with new defaults - // This is mostly zeroing out a bunch of arrays parallel to the systems array. They will get - // repopulated by prepare_to_next_thread_local() calls - if stage_changed { - self.system_dependencies.clear(); - self.system_dependencies - .resize_with(systems.len(), || FixedBitSet::with_capacity(systems.len())); - - self.system_dependency_count.clear(); - self.system_dependency_count.resize(systems.len(), 0); - - self.thread_local_system_indices = Vec::new(); - - self.system_dependents.clear(); - self.system_dependents.resize(systems.len(), Vec::new()); - - self.ready_events.resize(systems.len(), None); - self.ready_events_of_dependents - .resize(systems.len(), Vec::new()); - - for (system_index, system) in systems.iter().enumerate() { - if system.thread_local_execution() == ThreadLocalExecution::Immediate { - #[cfg(feature = "trace")] - let system_span = - bevy_utils::tracing::info_span!("system", name = system.name().as_ref()); - #[cfg(feature = "trace")] - let _system_guard = system_span.enter(); - - self.thread_local_system_indices.push(system_index); - } - } - } - - // index of next thread local system in thread_local_system_indices. (always incremented by one - // when prepare_to_next_thread_local is called. (We prepared up to index 0 above) - let mut next_thread_local_index = 0; - - { - // Prepare all system up to and including the first thread local system. This will return - // the range of systems to run, up to but NOT including the next thread local - let prepared_system_range = self.prepare_to_next_thread_local( - world, - systems, - stage_changed, - next_thread_local_index, - ); - - // Run everything up to the thread local system - self.run_systems( - world, - resources, - systems, - prepared_system_range, - &*compute_pool, - ); - } - - loop { - // Bail if we have no more thread local systems - if next_thread_local_index >= self.thread_local_system_indices.len() { - break; - } - - // Run the thread local system at the end of the range of systems we just processed - let thread_local_system_index = - self.thread_local_system_indices[next_thread_local_index]; - { - // if a thread local system is ready to run, run it exclusively on the main thread - let system = systems[thread_local_system_index].as_mut(); - - #[cfg(feature = "trace")] - let system_span = bevy_utils::tracing::info_span!( - "thread_local_system", - name = system.name().as_ref() - ); - #[cfg(feature = "trace")] - let _system_guard = system_span.enter(); - - system.run((), world, resources); - system.run_thread_local(world, resources); - } - - // Now that the previous thread local system has run, time to advance to the next one - next_thread_local_index += 1; - - // Prepare all systems up to and including the next thread local system. This will - // return the range of systems to run, up to but NOT including the next thread local - let run_ready_system_index_range = self.prepare_to_next_thread_local( - world, - systems, - stage_changed, - next_thread_local_index, - ); - - self.run_systems( - world, - resources, - systems, - run_ready_system_index_range, - &*compute_pool, - ); - } - - // "flush" - for system in systems.iter_mut() { - match system.thread_local_execution() { - ThreadLocalExecution::NextFlush => { - #[cfg(feature = "trace")] - let system_span = - bevy_utils::tracing::info_span!("system", name = system.name().as_ref()); - #[cfg(feature = "trace")] - let _system_guard = system_span.enter(); - system.run_thread_local(world, resources); - } - ThreadLocalExecution::Immediate => { /* already ran */ } - } - } - - // If world's archetypes_generation is the same as it was before running any systems then - // we can assume that all systems have correct archetype accesses. - if start_archetypes_generation == world.archetypes_generation() { - self.last_archetypes_generation = world.archetypes_generation(); - } - } -} diff --git a/crates/bevy_ecs/src/schedule/state.rs b/crates/bevy_ecs/src/schedule/state.rs index a798ebbd4d1990..d992894ec09909 100644 --- a/crates/bevy_ecs/src/schedule/state.rs +++ b/crates/bevy_ecs/src/schedule/state.rs @@ -1,4 +1,4 @@ -use crate::{Resource, Resources, Stage, System, SystemStage, World}; +use crate::{Resource, Resources, Stage, SystemDescriptor, SystemStage, World}; use bevy_utils::HashMap; use std::{mem::Discriminant, ops::Deref}; use thiserror::Error; @@ -66,31 +66,19 @@ impl StateStage { self } - pub fn on_state_enter>( - &mut self, - state: T, - system: S, - ) -> &mut Self { + pub fn on_state_enter(&mut self, state: T, system: impl Into) -> &mut Self { self.enter_stage(state, |system_stage: &mut SystemStage| { system_stage.add_system(system) }) } - pub fn on_state_exit>( - &mut self, - state: T, - system: S, - ) -> &mut Self { + pub fn on_state_exit(&mut self, state: T, system: impl Into) -> &mut Self { self.exit_stage(state, |system_stage: &mut SystemStage| { system_stage.add_system(system) }) } - pub fn on_state_update>( - &mut self, - state: T, - system: S, - ) -> &mut Self { + pub fn on_state_update(&mut self, state: T, system: impl Into) -> &mut Self { self.update_stage(state, |system_stage: &mut SystemStage| { system_stage.add_system(system) }) @@ -150,14 +138,6 @@ impl StateStage { #[allow(clippy::mem_discriminant_non_enum)] impl Stage for StateStage { - fn initialize(&mut self, world: &mut World, resources: &mut Resources) { - for state_stages in self.stages.values_mut() { - state_stages.enter.initialize(world, resources); - state_stages.update.initialize(world, resources); - state_stages.exit.initialize(world, resources); - } - } - fn run(&mut self, world: &mut World, resources: &mut Resources) { let current_stage = loop { let (next_stage, current_stage) = { diff --git a/crates/bevy_ecs/src/schedule/system_container.rs b/crates/bevy_ecs/src/schedule/system_container.rs new file mode 100644 index 00000000000000..32618c287f7df7 --- /dev/null +++ b/crates/bevy_ecs/src/schedule/system_container.rs @@ -0,0 +1,178 @@ +use std::{borrow::Cow, ptr::NonNull}; + +use crate::{ExclusiveSystem, ExclusiveSystemDescriptor, ParallelSystemDescriptor, System}; + +pub(super) trait SystemContainer { + fn display_name(&self) -> Cow<'static, str>; + fn dependencies(&self) -> &[usize]; + fn set_dependencies(&mut self, dependencies: impl IntoIterator); + fn system_set(&self) -> usize; + fn label(&self) -> &Option>; + fn before(&self) -> &[Cow<'static, str>]; + fn after(&self) -> &[Cow<'static, str>]; + fn is_compatible(&self, other: &Self) -> bool; +} + +pub(super) struct ExclusiveSystemContainer { + system: Box, + dependencies: Vec, + set: usize, + label: Option>, + before: Vec>, + after: Vec>, +} + +impl ExclusiveSystemContainer { + pub fn from_descriptor(descriptor: ExclusiveSystemDescriptor, set: usize) -> Self { + ExclusiveSystemContainer { + system: descriptor.system, + dependencies: Vec::new(), + set, + label: descriptor.label, + before: descriptor.before, + after: descriptor.after, + } + } + + pub fn system_mut(&mut self) -> &mut Box { + &mut self.system + } +} + +impl SystemContainer for ExclusiveSystemContainer { + fn display_name(&self) -> Cow<'static, str> { + self.label + .as_ref() + .cloned() + .unwrap_or_else(|| self.system.name()) + } + + fn dependencies(&self) -> &[usize] { + &self.dependencies + } + + fn set_dependencies(&mut self, dependencies: impl IntoIterator) { + self.dependencies.clear(); + self.dependencies.extend(dependencies); + } + + fn system_set(&self) -> usize { + self.set + } + + fn label(&self) -> &Option> { + &self.label + } + + fn before(&self) -> &[Cow<'static, str>] { + &self.before + } + + fn after(&self) -> &[Cow<'static, str>] { + &self.after + } + + fn is_compatible(&self, _: &Self) -> bool { + false + } +} + +pub struct ParallelSystemContainer { + system: NonNull>, + pub(crate) should_run: bool, + dependencies: Vec, + set: usize, + label: Option>, + before: Vec>, + after: Vec>, +} + +impl SystemContainer for ParallelSystemContainer { + fn display_name(&self) -> Cow<'static, str> { + self.label + .as_ref() + .cloned() + .unwrap_or_else(|| self.system().name()) + } + + fn dependencies(&self) -> &[usize] { + &self.dependencies + } + + fn set_dependencies(&mut self, dependencies: impl IntoIterator) { + self.dependencies.clear(); + self.dependencies.extend(dependencies); + } + + fn system_set(&self) -> usize { + self.set + } + + fn label(&self) -> &Option> { + &self.label + } + + fn before(&self) -> &[Cow<'static, str>] { + &self.before + } + + fn after(&self) -> &[Cow<'static, str>] { + &self.after + } + + fn is_compatible(&self, other: &Self) -> bool { + self.system() + .component_access() + .is_compatible(other.system().component_access()) + && self + .system() + .resource_access() + .is_compatible(other.system().resource_access()) + } +} + +unsafe impl Send for ParallelSystemContainer {} +unsafe impl Sync for ParallelSystemContainer {} + +impl ParallelSystemContainer { + pub(crate) fn from_descriptor(descriptor: ParallelSystemDescriptor, set: usize) -> Self { + ParallelSystemContainer { + system: unsafe { NonNull::new_unchecked(Box::into_raw(descriptor.system)) }, + should_run: false, + set, + dependencies: Vec::new(), + label: descriptor.label, + before: descriptor.before, + after: descriptor.after, + } + } + + pub fn display_name(&self) -> Cow<'static, str> { + SystemContainer::display_name(self) + } + + pub fn system(&self) -> &dyn System { + // SAFE: statically enforced shared access. + unsafe { self.system.as_ref() } + } + + pub fn system_mut(&mut self) -> &mut dyn System { + // SAFE: statically enforced exclusive access. + unsafe { self.system.as_mut() } + } + + /// # Safety + /// Ensure no other borrows exist along with this one. + #[allow(clippy::mut_from_ref)] + pub unsafe fn system_mut_unsafe(&self) -> &mut dyn System { + &mut *self.system.as_ptr() + } + + pub fn should_run(&self) -> bool { + self.should_run + } + + pub fn dependencies(&self) -> &[usize] { + &self.dependencies + } +} diff --git a/crates/bevy_ecs/src/schedule/system_descriptor.rs b/crates/bevy_ecs/src/schedule/system_descriptor.rs new file mode 100644 index 00000000000000..60d65508e8d783 --- /dev/null +++ b/crates/bevy_ecs/src/schedule/system_descriptor.rs @@ -0,0 +1,254 @@ +use crate::{BoxedSystem, ExclusiveSystem, ExclusiveSystemCoerced, ExclusiveSystemFn, System}; +use std::borrow::Cow; + +/// 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. +/// +/// All systems can have a label attached to them; other systems in the same group can then specify +/// that they have to run before or after the system with that label. +/// +/// # Example +/// ```rust +/// # use bevy_ecs::prelude::*; +/// # fn do_something() {} +/// # fn do_the_other_thing() {} +/// # fn do_something_else() {} +/// SystemStage::parallel() +/// .with_system(do_something.system().label("something")) +/// .with_system(do_the_other_thing.system().after("something")) +/// .with_system(do_something_else.exclusive_system().at_end()); +/// ``` +pub enum SystemDescriptor { + Parallel(ParallelSystemDescriptor), + Exclusive(ExclusiveSystemDescriptor), +} + +impl From for SystemDescriptor { + fn from(descriptor: ParallelSystemDescriptor) -> Self { + SystemDescriptor::Parallel(descriptor) + } +} + +impl From for SystemDescriptor +where + S: System, +{ + fn from(system: S) -> Self { + new_parallel_descriptor(Box::new(system)).into() + } +} + +impl From> for SystemDescriptor { + fn from(system: BoxedSystem<(), ()>) -> Self { + new_parallel_descriptor(system).into() + } +} + +impl From for SystemDescriptor { + fn from(descriptor: ExclusiveSystemDescriptor) -> Self { + SystemDescriptor::Exclusive(descriptor) + } +} + +impl From for SystemDescriptor { + fn from(system: ExclusiveSystemFn) -> Self { + new_exclusive_descriptor(Box::new(system)).into() + } +} + +impl From for SystemDescriptor { + fn from(system: ExclusiveSystemCoerced) -> Self { + new_exclusive_descriptor(Box::new(system)).into() + } +} + +/// Encapsulates a parallel system and information on when it run in a `SystemStage`. +pub struct ParallelSystemDescriptor { + pub(crate) system: BoxedSystem<(), ()>, + pub(crate) label: Option>, + pub(crate) before: Vec>, + pub(crate) after: Vec>, +} + +fn new_parallel_descriptor(system: BoxedSystem<(), ()>) -> ParallelSystemDescriptor { + ParallelSystemDescriptor { + system, + label: None, + before: Vec::new(), + after: Vec::new(), + } +} + +pub trait ParallelSystemDescriptorCoercion { + /// Assigns a label to the system. + fn label(self, label: impl Into>) -> ParallelSystemDescriptor; + + /// Specifies that the system should run before the system with given label. + fn before(self, label: impl Into>) -> ParallelSystemDescriptor; + + /// Specifies that the system should run after the system with given label. + fn after(self, label: impl Into>) -> ParallelSystemDescriptor; +} + +impl ParallelSystemDescriptorCoercion for ParallelSystemDescriptor { + fn label(mut self, label: impl Into>) -> ParallelSystemDescriptor { + self.label = Some(label.into()); + self + } + + fn before(mut self, label: impl Into>) -> ParallelSystemDescriptor { + self.before.push(label.into()); + self + } + + fn after(mut self, label: impl Into>) -> ParallelSystemDescriptor { + self.after.push(label.into()); + self + } +} + +impl ParallelSystemDescriptorCoercion for S +where + S: System, +{ + fn label(self, label: impl Into>) -> ParallelSystemDescriptor { + new_parallel_descriptor(Box::new(self)).label(label) + } + + fn before(self, label: impl Into>) -> ParallelSystemDescriptor { + new_parallel_descriptor(Box::new(self)).before(label) + } + + fn after(self, label: impl Into>) -> ParallelSystemDescriptor { + new_parallel_descriptor(Box::new(self)).after(label) + } +} + +impl ParallelSystemDescriptorCoercion for BoxedSystem<(), ()> { + fn label(self, label: impl Into>) -> ParallelSystemDescriptor { + new_parallel_descriptor(self).label(label) + } + + fn before(self, label: impl Into>) -> ParallelSystemDescriptor { + new_parallel_descriptor(self).before(label) + } + + fn after(self, label: impl Into>) -> ParallelSystemDescriptor { + new_parallel_descriptor(self).after(label) + } +} + +#[derive(Debug, Clone, Copy)] +pub(crate) enum InsertionPoint { + AtStart, + BeforeCommands, + AtEnd, +} + +/// Encapsulates an exclusive system and information on when it run in a `SystemStage`. +pub struct ExclusiveSystemDescriptor { + pub(crate) system: Box, + pub(crate) label: Option>, + pub(crate) before: Vec>, + pub(crate) after: Vec>, + pub(crate) insertion_point: InsertionPoint, +} + +fn new_exclusive_descriptor(system: Box) -> ExclusiveSystemDescriptor { + ExclusiveSystemDescriptor { + system, + label: None, + before: Vec::new(), + after: Vec::new(), + insertion_point: InsertionPoint::AtStart, + } +} + +pub trait ExclusiveSystemDescriptorCoercion { + /// Assigns a label to the system. + fn label(self, label: impl Into>) -> ExclusiveSystemDescriptor; + + /// Specifies that the system should run before the system with given label. + fn before(self, label: impl Into>) -> ExclusiveSystemDescriptor; + + /// Specifies that the system should run after the system with given label. + fn after(self, label: impl Into>) -> ExclusiveSystemDescriptor; + + /// Specifies that the system should run with other exclusive systems at the start of stage. + fn at_start(self) -> ExclusiveSystemDescriptor; + + /// Specifies that the system should run with other exclusive systems after the parallel + /// systems and before command buffer application. + fn before_commands(self) -> ExclusiveSystemDescriptor; + + /// Specifies that the system should run with other exclusive systems at the end of stage. + fn at_end(self) -> ExclusiveSystemDescriptor; +} + +impl ExclusiveSystemDescriptorCoercion for ExclusiveSystemDescriptor { + fn label(mut self, label: impl Into>) -> ExclusiveSystemDescriptor { + self.label = Some(label.into()); + self + } + + fn before(mut self, label: impl Into>) -> ExclusiveSystemDescriptor { + self.before.push(label.into()); + self + } + + fn after(mut self, label: impl Into>) -> ExclusiveSystemDescriptor { + self.after.push(label.into()); + self + } + + fn at_start(mut self) -> ExclusiveSystemDescriptor { + self.insertion_point = InsertionPoint::AtStart; + self + } + + fn before_commands(mut self) -> ExclusiveSystemDescriptor { + self.insertion_point = InsertionPoint::BeforeCommands; + self + } + + fn at_end(mut self) -> ExclusiveSystemDescriptor { + self.insertion_point = InsertionPoint::AtEnd; + self + } +} + +impl ExclusiveSystemDescriptorCoercion for T +where + T: ExclusiveSystem + 'static, +{ + fn label(self, label: impl Into>) -> ExclusiveSystemDescriptor { + new_exclusive_descriptor(Box::new(self)).label(label) + } + + fn before(self, label: impl Into>) -> ExclusiveSystemDescriptor { + new_exclusive_descriptor(Box::new(self)).before(label) + } + + fn after(self, label: impl Into>) -> ExclusiveSystemDescriptor { + new_exclusive_descriptor(Box::new(self)).after(label) + } + + fn at_start(self) -> ExclusiveSystemDescriptor { + new_exclusive_descriptor(Box::new(self)).at_start() + } + + fn before_commands(self) -> ExclusiveSystemDescriptor { + new_exclusive_descriptor(Box::new(self)).before_commands() + } + + fn at_end(self) -> ExclusiveSystemDescriptor { + new_exclusive_descriptor(Box::new(self)).at_end() + } +} diff --git a/crates/bevy_ecs/src/schedule/system_set.rs b/crates/bevy_ecs/src/schedule/system_set.rs new file mode 100644 index 00000000000000..5a4005e58b3b0e --- /dev/null +++ b/crates/bevy_ecs/src/schedule/system_set.rs @@ -0,0 +1,45 @@ +use crate::{RunCriteria, ShouldRun, System, SystemDescriptor}; + +/// Describes a group of systems sharing one run criterion. +pub struct SystemSet { + pub(crate) run_criteria: RunCriteria, + pub(crate) descriptors: Vec, +} + +impl Default for SystemSet { + fn default() -> SystemSet { + SystemSet { + run_criteria: Default::default(), + descriptors: vec![], + } + } +} + +impl SystemSet { + pub fn new() -> Self { + Default::default() + } + + pub fn with_run_criteria>(mut self, system: S) -> Self { + self.add_run_criteria(system); + self + } + + pub fn add_run_criteria>( + &mut self, + system: S, + ) -> &mut Self { + self.run_criteria.set(Box::new(system)); + self + } + + pub fn with_system(mut self, system: impl Into) -> Self { + self.add_system(system); + self + } + + pub fn add_system(&mut self, system: impl Into) -> &mut Self { + self.descriptors.push(system.into()); + self + } +} diff --git a/crates/bevy_ecs/src/system/exclusive_system.rs b/crates/bevy_ecs/src/system/exclusive_system.rs new file mode 100644 index 00000000000000..5dca8a8b7ce23a --- /dev/null +++ b/crates/bevy_ecs/src/system/exclusive_system.rs @@ -0,0 +1,125 @@ +pub use super::Query; +use crate::{resource::Resources, system::SystemId, BoxedSystem, IntoSystem, System, World}; +use std::borrow::Cow; + +pub trait ExclusiveSystem: Send + Sync + 'static { + fn name(&self) -> Cow<'static, str>; + + fn id(&self) -> SystemId; + + fn run(&mut self, world: &mut World, resources: &mut Resources); + + fn initialize(&mut self, world: &mut World, resources: &mut Resources); +} + +pub struct ExclusiveSystemFn { + func: Box, + name: Cow<'static, str>, + id: SystemId, +} + +impl ExclusiveSystem for ExclusiveSystemFn { + fn name(&self) -> Cow<'static, str> { + self.name.clone() + } + + fn id(&self) -> SystemId { + self.id + } + + fn run(&mut self, world: &mut World, resources: &mut Resources) { + (self.func)(world, resources); + } + + fn initialize(&mut self, _: &mut World, _: &mut Resources) {} +} + +pub trait IntoExclusiveSystem { + fn exclusive_system(self) -> SystemType; +} + +impl IntoExclusiveSystem<(&mut World, &mut Resources), ExclusiveSystemFn> for F +where + F: FnMut(&mut World, &mut Resources) + Send + Sync + 'static, +{ + fn exclusive_system(self) -> ExclusiveSystemFn { + ExclusiveSystemFn { + func: Box::new(self), + name: core::any::type_name::().into(), + id: SystemId::new(), + } + } +} + +impl IntoExclusiveSystem<(&mut Resources, &mut World), ExclusiveSystemFn> for F +where + F: FnMut(&mut Resources, &mut World) + Send + Sync + 'static, +{ + fn exclusive_system(mut self) -> ExclusiveSystemFn { + ExclusiveSystemFn { + func: Box::new(move |world, resources| self(resources, world)), + name: core::any::type_name::().into(), + id: SystemId::new(), + } + } +} + +impl IntoExclusiveSystem<&mut World, ExclusiveSystemFn> for F +where + F: FnMut(&mut World) + Send + Sync + 'static, +{ + fn exclusive_system(mut self) -> ExclusiveSystemFn { + ExclusiveSystemFn { + func: Box::new(move |world, _| self(world)), + name: core::any::type_name::().into(), + id: SystemId::new(), + } + } +} + +impl IntoExclusiveSystem<&mut Resources, ExclusiveSystemFn> for F +where + F: FnMut(&mut Resources) + Send + Sync + 'static, +{ + fn exclusive_system(mut self) -> ExclusiveSystemFn { + ExclusiveSystemFn { + func: Box::new(move |_, resources| self(resources)), + name: core::any::type_name::().into(), + id: SystemId::new(), + } + } +} + +pub struct ExclusiveSystemCoerced { + system: BoxedSystem<(), ()>, +} + +impl ExclusiveSystem for ExclusiveSystemCoerced { + fn name(&self) -> Cow<'static, str> { + self.system.name() + } + + fn id(&self) -> SystemId { + self.system.id() + } + + fn run(&mut self, world: &mut World, resources: &mut Resources) { + self.system.run((), world, resources); + } + + fn initialize(&mut self, world: &mut World, resources: &mut Resources) { + self.system.initialize(world, resources); + } +} + +impl IntoExclusiveSystem<(Params, SystemType), ExclusiveSystemCoerced> for S +where + S: IntoSystem, + SystemType: System, +{ + fn exclusive_system(self) -> ExclusiveSystemCoerced { + ExclusiveSystemCoerced { + system: Box::new(self.system()), + } + } +} diff --git a/crates/bevy_ecs/src/system/into_system.rs b/crates/bevy_ecs/src/system/into_system.rs index 5027ca05b6f47c..a97903e8f06aba 100644 --- a/crates/bevy_ecs/src/system/into_system.rs +++ b/crates/bevy_ecs/src/system/into_system.rs @@ -1,7 +1,7 @@ use super::system_param::FetchSystemParam; use crate::{ ArchetypeComponent, Commands, QueryAccess, Resources, System, SystemId, SystemParam, - ThreadLocalExecution, TypeAccess, World, + TypeAccess, World, }; use parking_lot::Mutex; use std::{any::TypeId, borrow::Cow, cell::UnsafeCell, sync::Arc}; @@ -10,7 +10,9 @@ pub struct SystemState { pub(crate) id: SystemId, pub(crate) name: Cow<'static, str>, pub(crate) archetype_component_access: TypeAccess, + pub(crate) component_access: TypeAccess, pub(crate) resource_access: TypeAccess, + pub(crate) is_non_send: bool, pub(crate) local_resource_access: TypeAccess, pub(crate) query_archetype_component_accesses: Vec>, pub(crate) query_accesses: Vec>, @@ -59,7 +61,7 @@ impl SystemState { }); break; } - self.archetype_component_access.union(component_access); + self.archetype_component_access.extend(component_access); } if let Some(conflict_index) = conflict_index { let mut conflicts_with_index = None; @@ -82,8 +84,6 @@ impl SystemState { pub struct FuncSystem { func: Box Option + Send + Sync + 'static>, - thread_local_func: - Box, init_func: Box, state: SystemState, } @@ -100,7 +100,7 @@ impl System for FuncSystem { self.state.id } - fn update(&mut self, world: &World) { + fn update_access(&mut self, world: &World) { self.state.update(world); } @@ -108,12 +108,16 @@ impl System for FuncSystem { &self.state.archetype_component_access } + fn component_access(&self) -> &TypeAccess { + &self.state.component_access + } + fn resource_access(&self) -> &TypeAccess { &self.state.resource_access } - fn thread_local_execution(&self) -> ThreadLocalExecution { - ThreadLocalExecution::NextFlush + fn is_non_send(&self) -> bool { + self.state.is_non_send } unsafe fn run_unsafe( @@ -125,8 +129,15 @@ impl System for FuncSystem { (self.func)(&mut self.state, world, resources) } - fn run_thread_local(&mut self, world: &mut World, resources: &mut Resources) { - (self.thread_local_func)(&mut self.state, world, resources) + fn apply_buffers(&mut self, world: &mut World, resources: &mut Resources) { + // SAFE: this is called with unique access to SystemState + unsafe { + (&mut *self.state.commands.get()).apply(world, resources); + } + if let Some(ref commands) = self.state.arc_commands { + let mut commands = commands.lock(); + commands.apply(world, resources); + } } fn initialize(&mut self, world: &mut World, resources: &mut Resources) { @@ -138,8 +149,6 @@ pub struct InputFuncSystem { func: Box< dyn FnMut(In, &mut SystemState, &World, &Resources) -> Option + Send + Sync + 'static, >, - thread_local_func: - Box, init_func: Box, state: SystemState, } @@ -156,7 +165,7 @@ impl System for InputFuncSystem { self.state.id } - fn update(&mut self, world: &World) { + fn update_access(&mut self, world: &World) { self.state.update(world); } @@ -164,12 +173,16 @@ impl System for InputFuncSystem { &self.state.archetype_component_access } + fn component_access(&self) -> &TypeAccess { + &self.state.component_access + } + fn resource_access(&self) -> &TypeAccess { &self.state.resource_access } - fn thread_local_execution(&self) -> ThreadLocalExecution { - ThreadLocalExecution::NextFlush + fn is_non_send(&self) -> bool { + self.state.is_non_send } unsafe fn run_unsafe( @@ -181,8 +194,15 @@ impl System for InputFuncSystem { (self.func)(input, &mut self.state, world, resources) } - fn run_thread_local(&mut self, world: &mut World, resources: &mut Resources) { - (self.thread_local_func)(&mut self.state, world, resources) + fn apply_buffers(&mut self, world: &mut World, resources: &mut Resources) { + // SAFE: this is called with unique access to SystemState + unsafe { + (&mut *self.state.commands.get()).apply(world, resources); + } + if let Some(ref commands) = self.state.arc_commands { + let mut commands = commands.lock(); + commands.apply(world, resources); + } } fn initialize(&mut self, world: &mut World, resources: &mut Resources) { @@ -190,7 +210,7 @@ impl System for InputFuncSystem { } } -pub trait IntoSystem { +pub trait IntoSystem { fn system(self) -> SystemType; } @@ -219,7 +239,9 @@ macro_rules! impl_into_system { state: SystemState { name: std::any::type_name::().into(), archetype_component_access: TypeAccess::default(), + component_access: TypeAccess::default(), resource_access: TypeAccess::default(), + is_non_send: false, local_resource_access: TypeAccess::default(), id: SystemId::new(), commands: Default::default(), @@ -240,16 +262,6 @@ macro_rules! impl_into_system { } } }), - thread_local_func: Box::new(|state, world, resources| { - // SAFE: this is called with unique access to SystemState - unsafe { - (&mut *state.commands.get()).apply(world, resources); - } - if let Some(ref commands) = state.arc_commands { - let mut commands = commands.lock(); - commands.apply(world, resources); - } - }), init_func: Box::new(|state, world, resources| { <<($($param,)*) as SystemParam>::Fetch as FetchSystemParam>::init(state, world, resources) }), @@ -271,7 +283,9 @@ macro_rules! impl_into_system { state: SystemState { name: std::any::type_name::().into(), archetype_component_access: TypeAccess::default(), + component_access: TypeAccess::default(), resource_access: TypeAccess::default(), + is_non_send: false, local_resource_access: TypeAccess::default(), id: SystemId::new(), commands: Default::default(), @@ -292,16 +306,6 @@ macro_rules! impl_into_system { } } }), - thread_local_func: Box::new(|state, world, resources| { - // SAFE: this is called with unique access to SystemState - unsafe { - (&mut *state.commands.get()).apply(world, resources); - } - if let Some(ref commands) = state.arc_commands { - let mut commands = commands.lock(); - commands.apply(world, resources); - } - }), init_func: Box::new(|state, world, resources| { <<($($param,)*) as SystemParam>::Fetch as FetchSystemParam>::init(state, world, resources) }), @@ -336,7 +340,8 @@ mod tests { clear_trackers_system, resource::{Res, ResMut, Resources}, schedule::Schedule, - ChangedRes, Entity, Local, Or, Query, QuerySet, System, SystemStage, With, World, + ChangedRes, Entity, IntoExclusiveSystem, Local, Or, Query, QuerySet, Stage, System, + SystemStage, With, World, }; #[derive(Debug, Eq, PartialEq, Default)] @@ -456,17 +461,17 @@ mod tests { schedule.add_stage("update", update); schedule.add_stage( "clear_trackers", - SystemStage::single(clear_trackers_system.system()), + SystemStage::single(clear_trackers_system.exclusive_system()), ); - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); assert_eq!(*(world.get::(ent).unwrap()), 1); - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); assert_eq!(*(world.get::(ent).unwrap()), 1); *resources.get_mut::().unwrap() = true; - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); assert_eq!(*(world.get::(ent).unwrap()), 2); } @@ -493,24 +498,24 @@ mod tests { schedule.add_stage("update", update); schedule.add_stage( "clear_trackers", - SystemStage::single(clear_trackers_system.system()), + SystemStage::single(clear_trackers_system.exclusive_system()), ); - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); assert_eq!(*(world.get::(ent).unwrap()), 1); - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); assert_eq!(*(world.get::(ent).unwrap()), 1); *resources.get_mut::().unwrap() = true; - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); assert_eq!(*(world.get::(ent).unwrap()), 2); - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); assert_eq!(*(world.get::(ent).unwrap()), 2); *resources.get_mut::().unwrap() = 20; - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); assert_eq!(*(world.get::(ent).unwrap()), 3); } @@ -581,7 +586,7 @@ mod tests { let mut update = SystemStage::parallel(); update.add_system(system); schedule.add_stage("update", update); - schedule.initialize_and_run(world, resources); + schedule.run(world, resources); } #[derive(Default)] @@ -595,7 +600,7 @@ mod tests { resources.insert(BufferRes::default()); resources.insert(A); resources.insert(B); - run_system(&mut world, &mut resources, sys.system()); + run_system(&mut world, &mut resources, sys); } #[test] diff --git a/crates/bevy_ecs/src/system/into_thread_local.rs b/crates/bevy_ecs/src/system/into_thread_local.rs deleted file mode 100644 index d08f33a200d7af..00000000000000 --- a/crates/bevy_ecs/src/system/into_thread_local.rs +++ /dev/null @@ -1,72 +0,0 @@ -pub use super::Query; -use crate::{ - resource::Resources, - system::{System, SystemId, ThreadLocalExecution}, - ArchetypeComponent, IntoSystem, TypeAccess, World, -}; -use std::{any::TypeId, borrow::Cow}; - -pub struct ThreadLocalSystemFn { - pub func: Box, - pub resource_access: TypeAccess, - pub archetype_component_access: TypeAccess, - pub name: Cow<'static, str>, - pub id: SystemId, -} - -impl System for ThreadLocalSystemFn { - type In = (); - type Out = (); - - fn name(&self) -> Cow<'static, str> { - self.name.clone() - } - - fn update(&mut self, _world: &World) {} - - fn archetype_component_access(&self) -> &TypeAccess { - &self.archetype_component_access - } - - fn resource_access(&self) -> &TypeAccess { - &self.resource_access - } - - fn thread_local_execution(&self) -> ThreadLocalExecution { - ThreadLocalExecution::Immediate - } - - unsafe fn run_unsafe( - &mut self, - _input: (), - _world: &World, - _resources: &Resources, - ) -> Option<()> { - Some(()) - } - - fn run_thread_local(&mut self, world: &mut World, resources: &mut Resources) { - (self.func)(world, resources); - } - - fn initialize(&mut self, _world: &mut World, _resources: &mut Resources) {} - - fn id(&self) -> SystemId { - self.id - } -} - -impl IntoSystem<(&mut World, &mut Resources), ThreadLocalSystemFn> for F -where - F: FnMut(&mut World, &mut Resources) + Send + Sync + 'static, -{ - fn system(mut self) -> ThreadLocalSystemFn { - ThreadLocalSystemFn { - func: Box::new(move |world, resources| (self)(world, resources)), - name: core::any::type_name::().into(), - id: SystemId::new(), - resource_access: TypeAccess::default(), - archetype_component_access: TypeAccess::default(), - } - } -} diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index b0ca3e37288490..6d38135a5b4f5d 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -1,6 +1,6 @@ mod commands; +mod exclusive_system; mod into_system; -mod into_thread_local; mod query; #[allow(clippy::module_inception)] mod system; @@ -8,8 +8,8 @@ mod system_chaining; mod system_param; pub use commands::*; +pub use exclusive_system::*; pub use into_system::*; -pub use into_thread_local::*; pub use query::*; pub use system::*; pub use system_chaining::*; diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index b65debf2cb1458..98ea24a7e5ad21 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -1,13 +1,6 @@ use crate::{ArchetypeComponent, Resources, TypeAccess, World}; use std::{any::TypeId, borrow::Cow}; -/// Determines the strategy used to run the `run_thread_local` function in a [System] -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub enum ThreadLocalExecution { - Immediate, - NextFlush, -} - #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct SystemId(pub usize); @@ -24,10 +17,11 @@ pub trait System: Send + Sync + 'static { type Out; fn name(&self) -> Cow<'static, str>; fn id(&self) -> SystemId; - fn update(&mut self, world: &World); + fn update_access(&mut self, world: &World); fn archetype_component_access(&self) -> &TypeAccess; + fn component_access(&self) -> &TypeAccess; fn resource_access(&self) -> &TypeAccess; - fn thread_local_execution(&self) -> ThreadLocalExecution; + fn is_non_send(&self) -> bool; /// # Safety /// This might access World and Resources in an unsafe manner. This should only be called in one of the following contexts: /// 1. This system is the only system running on the given World and Resources across all threads @@ -47,8 +41,8 @@ pub trait System: Send + Sync + 'static { // SAFE: world and resources are exclusively borrowed unsafe { self.run_unsafe(input, world, resources) } } - fn run_thread_local(&mut self, world: &mut World, resources: &mut Resources); - fn initialize(&mut self, _world: &mut World, _resources: &mut Resources); + fn apply_buffers(&mut self, world: &mut World, resources: &mut Resources); + fn initialize(&mut self, world: &mut World, resources: &mut Resources); } pub type BoxedSystem = Box>; diff --git a/crates/bevy_ecs/src/system/system_chaining.rs b/crates/bevy_ecs/src/system/system_chaining.rs index b0cfd4c055a9bf..bca2ea5ae5c97e 100644 --- a/crates/bevy_ecs/src/system/system_chaining.rs +++ b/crates/bevy_ecs/src/system/system_chaining.rs @@ -1,6 +1,4 @@ -use crate::{ - ArchetypeComponent, Resources, System, SystemId, ThreadLocalExecution, TypeAccess, World, -}; +use crate::{ArchetypeComponent, Resources, System, SystemId, TypeAccess, World}; use std::{any::TypeId, borrow::Cow}; pub struct ChainSystem { @@ -8,8 +6,9 @@ pub struct ChainSystem { system_b: SystemB, name: Cow<'static, str>, id: SystemId, - pub(crate) archetype_component_access: TypeAccess, - pub(crate) resource_access: TypeAccess, + archetype_component_access: TypeAccess, + component_access: TypeAccess, + resource_access: TypeAccess, } impl> System for ChainSystem { @@ -24,27 +23,39 @@ impl> System for ChainSystem self.id } - fn update(&mut self, world: &World) { + fn update_access(&mut self, world: &World) { self.archetype_component_access.clear(); + self.component_access.clear(); self.resource_access.clear(); - self.system_a.update(world); - self.system_b.update(world); + self.system_a.update_access(world); + self.system_b.update_access(world); self.archetype_component_access - .union(self.system_a.archetype_component_access()); - self.resource_access.union(self.system_b.resource_access()); + .extend(self.system_a.archetype_component_access()); + self.archetype_component_access + .extend(self.system_b.archetype_component_access()); + self.component_access + .extend(self.system_a.component_access()); + self.component_access + .extend(self.system_b.component_access()); + self.resource_access.extend(self.system_a.resource_access()); + self.resource_access.extend(self.system_b.resource_access()); } fn archetype_component_access(&self) -> &TypeAccess { &self.archetype_component_access } + fn component_access(&self) -> &TypeAccess { + &self.component_access + } + fn resource_access(&self) -> &TypeAccess { &self.resource_access } - fn thread_local_execution(&self) -> ThreadLocalExecution { - ThreadLocalExecution::NextFlush + fn is_non_send(&self) -> bool { + self.system_a.is_non_send() || self.system_b.is_non_send() } unsafe fn run_unsafe( @@ -57,9 +68,9 @@ impl> System for ChainSystem self.system_b.run_unsafe(out, world, resources) } - fn run_thread_local(&mut self, world: &mut World, resources: &mut Resources) { - self.system_a.run_thread_local(world, resources); - self.system_b.run_thread_local(world, resources); + fn apply_buffers(&mut self, world: &mut World, resources: &mut Resources) { + self.system_a.apply_buffers(world, resources); + self.system_b.apply_buffers(world, resources); } fn initialize(&mut self, world: &mut World, resources: &mut Resources) { @@ -86,6 +97,7 @@ where system_a: self, system_b: system, archetype_component_access: Default::default(), + component_access: Default::default(), resource_access: Default::default(), id: SystemId::new(), } diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 0b29e91371e0cd..603699be1b0fc8 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -1,7 +1,7 @@ use crate::{ - ArchetypeComponent, ChangedRes, Commands, Fetch, FromResources, Local, Or, Query, QueryAccess, - QueryFilter, QuerySet, QueryTuple, Res, ResMut, Resource, ResourceIndex, Resources, - SystemState, TypeAccess, World, WorldQuery, + ArchetypeComponent, ChangedRes, Commands, Fetch, FromResources, Local, NonSend, Or, Query, + QueryAccess, QueryFilter, QuerySet, QueryTuple, Res, ResMut, Resource, ResourceIndex, + Resources, SystemState, TypeAccess, World, WorldQuery, }; use parking_lot::Mutex; use std::{any::TypeId, marker::PhantomData, sync::Arc}; @@ -49,6 +49,7 @@ impl<'a, Q: WorldQuery, F: QueryFilter> FetchSystemParam<'a> for FetchQuery FetchSystemParam<'a> for FetchQuerySet { system_state .query_archetype_component_accesses .push(TypeAccess::default()); - system_state.query_accesses.push(T::get_accesses()); + let accesses = T::get_accesses(); + for access in &accesses { + access.get_component_access(&mut system_state.component_access); + } + system_state.query_accesses.push(accesses); system_state .query_type_names .push(std::any::type_name::()); @@ -291,6 +296,31 @@ impl<'a, T: Resource + FromResources> FetchSystemParam<'a> for FetchLocal { } } +pub struct FetchNonSend(PhantomData); + +impl<'a, T: Resource> SystemParam for NonSend<'a, T> { + type Fetch = FetchNonSend; +} + +impl<'a, T: Resource> FetchSystemParam<'a> for FetchNonSend { + type Item = NonSend<'a, T>; + + fn init(system_state: &mut SystemState, _world: &World, _resources: &mut Resources) { + // !Send systems run only on the main thread, so only one system + // at a time will ever access any thread-local resource. + system_state.is_non_send = true; + } + + #[inline] + unsafe fn get_param( + _system_state: &'a SystemState, + _world: &'a World, + resources: &'a Resources, + ) -> Option { + Some(NonSend::new(resources)) + } +} + pub struct FetchParamTuple(PhantomData); pub struct FetchOr(PhantomData); diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index 3a6164c050c00d..4eae105cc26799 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -5,7 +5,7 @@ use bevy_input::{gamepad::GamepadEventRaw, prelude::*}; use gilrs::{EventType, Gilrs}; pub fn gilrs_event_startup_system(_world: &mut World, resources: &mut Resources) { - let gilrs = resources.get_thread_local::().unwrap(); + let gilrs = resources.get_non_send::().unwrap(); let mut event = resources.get_mut::>().unwrap(); for (id, _) in gilrs.gamepads() { event.send(GamepadEventRaw( @@ -16,7 +16,7 @@ pub fn gilrs_event_startup_system(_world: &mut World, resources: &mut Resources) } pub fn gilrs_event_system(_world: &mut World, resources: &mut Resources) { - let mut gilrs = resources.get_thread_local_mut::().unwrap(); + let mut gilrs = resources.get_non_send_mut::().unwrap(); let mut event = resources.get_mut::>().unwrap(); event.update(); while let Some(gilrs_event) = gilrs.next_event() { diff --git a/crates/bevy_gilrs/src/lib.rs b/crates/bevy_gilrs/src/lib.rs index b1b2cde54cbae6..6391cfd071f5dd 100644 --- a/crates/bevy_gilrs/src/lib.rs +++ b/crates/bevy_gilrs/src/lib.rs @@ -2,7 +2,7 @@ mod converter; mod gilrs_system; use bevy_app::{prelude::*, startup_stage::PRE_STARTUP}; -use bevy_ecs::IntoSystem; +use bevy_ecs::IntoExclusiveSystem; use bevy_utils::tracing::error; use gilrs::GilrsBuilder; use gilrs_system::{gilrs_event_startup_system, gilrs_event_system}; @@ -18,9 +18,12 @@ impl Plugin for GilrsPlugin { .build() { Ok(gilrs) => { - app.insert_thread_local_resource(gilrs) - .add_startup_system_to_stage(PRE_STARTUP, gilrs_event_startup_system.system()) - .add_system_to_stage(stage::PRE_EVENT, gilrs_event_system.system()); + app.insert_non_send_resource(gilrs) + .add_startup_system_to_stage( + PRE_STARTUP, + gilrs_event_startup_system.exclusive_system(), + ) + .add_system_to_stage(stage::PRE_EVENT, gilrs_event_system.exclusive_system()); } Err(err) => error!("Failed to start Gilrs. {}", err), } diff --git a/crates/bevy_log/src/lib.rs b/crates/bevy_log/src/lib.rs index 397d4937d52b5f..f7541ef378862e 100644 --- a/crates/bevy_log/src/lib.rs +++ b/crates/bevy_log/src/lib.rs @@ -71,7 +71,7 @@ impl Plugin for LogPlugin { } })) .build(); - app.resources_mut().insert_thread_local(guard); + app.resources_mut().insert_non_send(guard); let subscriber = subscriber.with(chrome_layer); bevy_utils::tracing::subscriber::set_global_default(subscriber) .expect("Could not set global default tracing subscriber. If you've already set up a tracing subscriber, please disable LogPlugin from Bevy's DefaultPlugins"); diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index c07b5ce6ecab2e..48d58690732798 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -11,7 +11,7 @@ pub mod renderer; pub mod shader; pub mod texture; -use bevy_ecs::{IntoSystem, SystemStage}; +use bevy_ecs::{IntoExclusiveSystem, IntoSystem, SystemStage}; use bevy_reflect::RegisterTypeBuilder; use draw::Visible; pub use once_cell; @@ -173,7 +173,7 @@ impl Plugin for RenderPlugin { ) .add_system_to_stage( stage::RENDER_GRAPH_SYSTEMS, - render_graph::render_graph_schedule_executor_system.system(), + render_graph::render_graph_schedule_executor_system.exclusive_system(), ) .add_system_to_stage(stage::DRAW, pipeline::draw_render_pipelines_system.system()) .add_system_to_stage( diff --git a/crates/bevy_render/src/render_graph/graph.rs b/crates/bevy_render/src/render_graph/graph.rs index 22cbb72893dca0..49317c77df243a 100644 --- a/crates/bevy_render/src/render_graph/graph.rs +++ b/crates/bevy_render/src/render_graph/graph.rs @@ -42,7 +42,7 @@ impl RenderGraph { { let schedule = self.system_node_schedule.as_mut().unwrap(); let stage = schedule.get_stage_mut::("update").unwrap(); - stage.add_system_boxed(node.get_system(&mut self.commands)); + stage.add_system(node.get_system(&mut self.commands)); self.add_node(name, node) } diff --git a/crates/bevy_render/src/render_graph/system.rs b/crates/bevy_render/src/render_graph/system.rs index 78fa184c2469ee..abeac8b1d7e73b 100644 --- a/crates/bevy_render/src/render_graph/system.rs +++ b/crates/bevy_render/src/render_graph/system.rs @@ -1,5 +1,5 @@ use super::RenderGraph; -use bevy_ecs::{Resources, World}; +use bevy_ecs::{Resources, Stage, World}; pub fn render_graph_schedule_executor_system(world: &mut World, resources: &mut Resources) { // run render graph systems @@ -10,7 +10,7 @@ pub fn render_graph_schedule_executor_system(world: &mut World, resources: &mut commands.apply(world, resources); if let Some(schedule) = system_schedule.as_mut() { - schedule.initialize_and_run(world, resources); + schedule.run(world, resources); } let mut render_graph = resources.get_mut::().unwrap(); if let Some(schedule) = system_schedule.take() { diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 349007910a8085..c80b0e4ddd6d04 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -5,7 +5,7 @@ mod scene_loader; mod scene_spawner; pub mod serde; -use bevy_ecs::{IntoSystem, SystemStage}; +use bevy_ecs::{IntoExclusiveSystem, SystemStage}; pub use command::*; pub use dynamic_scene::*; pub use scene::*; @@ -33,6 +33,6 @@ impl Plugin for ScenePlugin { .init_asset_loader::() .init_resource::() .add_stage_after(stage::EVENT, SCENE_STAGE, SystemStage::parallel()) - .add_system_to_stage(SCENE_STAGE, scene_spawner_system.system()); + .add_system_to_stage(SCENE_STAGE, scene_spawner_system.exclusive_system()); } } diff --git a/crates/bevy_text/src/draw.rs b/crates/bevy_text/src/draw.rs index 3a00d7bda02ad5..3ca7f82d1072c9 100644 --- a/crates/bevy_text/src/draw.rs +++ b/crates/bevy_text/src/draw.rs @@ -8,6 +8,7 @@ use bevy_render::{ renderer::{BindGroup, RenderResourceBindings, RenderResourceId}, }; use bevy_sprite::TextureAtlasSprite; +use bevy_utils::tracing::error; use crate::{PositionedGlyph, TextSection}; use bevy_render::pipeline::IndexFormat; @@ -44,7 +45,7 @@ impl<'a> Drawable for DrawableText<'a> { { draw.set_vertex_buffer(0, vertex_attribute_buffer_id, 0); } else { - println!("Could not find vertex buffer for `bevy_sprite::QUAD_HANDLE`.") + error!("Could not find vertex buffer for `bevy_sprite::QUAD_HANDLE`.") } let mut indices = 0..0; diff --git a/crates/bevy_transform/src/hierarchy/hierarchy_maintenance_system.rs b/crates/bevy_transform/src/hierarchy/hierarchy_maintenance_system.rs index 8db0d9813f7f62..468e624046bad4 100644 --- a/crates/bevy_transform/src/hierarchy/hierarchy_maintenance_system.rs +++ b/crates/bevy_transform/src/hierarchy/hierarchy_maintenance_system.rs @@ -71,7 +71,7 @@ pub fn parent_update_system( mod test { use super::*; use crate::{hierarchy::BuildChildren, transform_propagate_system::transform_propagate_system}; - use bevy_ecs::{IntoSystem, Resources, Schedule, SystemStage, World}; + use bevy_ecs::{IntoSystem, Resources, Schedule, Stage, SystemStage, World}; #[test] fn correct_children() { @@ -102,7 +102,7 @@ mod test { }); let parent = parent.unwrap(); commands.apply(&mut world, &mut resources); - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); assert_eq!( world @@ -118,7 +118,7 @@ mod test { // Parent `e1` to `e2`. (*world.get_mut::(children[0]).unwrap()).0 = children[1]; - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); assert_eq!( world @@ -142,7 +142,7 @@ mod test { world.despawn(children[0]).unwrap(); - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); assert_eq!( world diff --git a/crates/bevy_transform/src/transform_propagate_system.rs b/crates/bevy_transform/src/transform_propagate_system.rs index a5b3ef19b08f88..219f2e6c59ee96 100644 --- a/crates/bevy_transform/src/transform_propagate_system.rs +++ b/crates/bevy_transform/src/transform_propagate_system.rs @@ -71,7 +71,7 @@ fn propagate_recursive( mod test { use super::*; use crate::hierarchy::{parent_update_system, BuildChildren, BuildWorldChildren}; - use bevy_ecs::{Resources, Schedule, SystemStage, World}; + use bevy_ecs::{Resources, Schedule, Stage, SystemStage, World}; #[test] fn did_propagate() { @@ -111,7 +111,7 @@ mod test { )) .for_current_entity(|entity| children.push(entity)); }); - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); assert_eq!( *world.get::(children[0]).unwrap(), @@ -159,7 +159,7 @@ mod test { .for_current_entity(|entity| children.push(entity)); }); commands.apply(&mut world, &mut resources); - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); assert_eq!( *world.get::(children[0]).unwrap(), diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 5996bba7cd0f9e..b066e1d29ea7be 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -20,7 +20,7 @@ pub mod prelude { } use bevy_app::prelude::*; -use bevy_ecs::{IntoSystem, SystemStage}; +use bevy_ecs::{IntoSystem, ParallelSystemDescriptorCoercion, SystemStage}; use bevy_render::render_graph::RenderGraph; use update::ui_z_system; @@ -31,6 +31,10 @@ pub mod stage { pub const UI: &str = "ui"; } +pub mod system { + pub const FLEX: &str = "flex"; +} + impl Plugin for UiPlugin { fn build(&self, app: &mut AppBuilder) { app.init_resource::() @@ -41,10 +45,13 @@ impl Plugin for UiPlugin { ) .add_system_to_stage(bevy_app::stage::PRE_UPDATE, ui_focus_system.system()) // add these stages to front because these must run before transform update systems - .add_system_to_stage(stage::UI, widget::text_system.system()) - .add_system_to_stage(stage::UI, widget::image_node_system.system()) + .add_system_to_stage(stage::UI, widget::text_system.system().before(system::FLEX)) + .add_system_to_stage( + stage::UI, + widget::image_node_system.system().before(system::FLEX), + ) + .add_system_to_stage(stage::UI, flex_node_system.system().label(system::FLEX)) .add_system_to_stage(stage::UI, ui_z_system.system()) - .add_system_to_stage(stage::UI, flex_node_system.system()) .add_system_to_stage(bevy_render::stage::DRAW, widget::draw_text_system.system()); let resources = app.resources(); diff --git a/crates/bevy_ui/src/update.rs b/crates/bevy_ui/src/update.rs index 928bcc60b555d9..1d3df0972b743f 100644 --- a/crates/bevy_ui/src/update.rs +++ b/crates/bevy_ui/src/update.rs @@ -48,7 +48,7 @@ fn update_hierarchy( } #[cfg(test)] mod tests { - use bevy_ecs::{Commands, IntoSystem, Resources, Schedule, SystemStage, World}; + use bevy_ecs::{Commands, IntoSystem, Resources, Schedule, Stage, SystemStage, World}; use bevy_transform::{components::Transform, hierarchy::BuildChildren}; use crate::Node; @@ -118,7 +118,7 @@ mod tests { let mut update_stage = SystemStage::parallel(); update_stage.add_system(ui_z_system.system()); schedule.add_stage("update", update_stage); - schedule.initialize_and_run(&mut world, &mut resources); + schedule.run(&mut world, &mut resources); let mut actual_result = world .query::<(&String, &Transform)>() diff --git a/crates/bevy_wgpu/src/lib.rs b/crates/bevy_wgpu/src/lib.rs index 1a3ca89c2b29da..25e6ff7e541331 100644 --- a/crates/bevy_wgpu/src/lib.rs +++ b/crates/bevy_wgpu/src/lib.rs @@ -11,7 +11,7 @@ pub use wgpu_renderer::*; pub use wgpu_resources::*; use bevy_app::prelude::*; -use bevy_ecs::{IntoSystem, Resources, World}; +use bevy_ecs::{IntoExclusiveSystem, IntoSystem, Resources, World}; use bevy_render::renderer::{shared_buffers_update_system, RenderResourceContext, SharedBuffers}; use renderer::WgpuRenderResourceContext; @@ -21,7 +21,7 @@ pub struct WgpuPlugin; impl Plugin for WgpuPlugin { fn build(&self, app: &mut AppBuilder) { let render_system = get_wgpu_render_system(app.resources_mut()); - app.add_system_to_stage(bevy_render::stage::RENDER, render_system.system()) + app.add_system_to_stage(bevy_render::stage::RENDER, render_system.exclusive_system()) .add_system_to_stage( bevy_render::stage::POST_RENDER, shared_buffers_update_system.system(), diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 0f5d082a3739a9..57b4e0d264a5c2 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -11,7 +11,7 @@ pub use winit_config::*; pub use winit_windows::*; use bevy_app::{prelude::*, AppExit, ManualEventReader}; -use bevy_ecs::{IntoSystem, Resources, World}; +use bevy_ecs::{IntoExclusiveSystem, Resources, World}; use bevy_math::{ivec2, Vec2}; use bevy_utils::tracing::{error, trace, warn}; use bevy_window::{ @@ -41,7 +41,7 @@ impl Plugin for WinitPlugin { fn build(&self, app: &mut AppBuilder) { app.init_resource::() .set_runner(winit_runner) - .add_system(change_window.system()); + .add_system(change_window.exclusive_system()); } } @@ -206,7 +206,7 @@ pub fn winit_runner_with(mut app: App, mut event_loop: EventLoop<()>) { let mut create_window_event_reader = ManualEventReader::::default(); let mut app_exit_event_reader = ManualEventReader::::default(); - app.resources.insert_thread_local(event_loop.create_proxy()); + app.resources.insert_non_send(event_loop.create_proxy()); trace!("Entering winit event loop"); diff --git a/examples/scene/scene.rs b/examples/scene/scene.rs index b242617f364936..48eaae999b5332 100644 --- a/examples/scene/scene.rs +++ b/examples/scene/scene.rs @@ -6,7 +6,7 @@ fn main() { .add_plugins(DefaultPlugins) .register_type::() .register_type::() - .add_startup_system(save_scene_system.system()) + .add_startup_system(save_scene_system.exclusive_system()) .add_startup_system(load_scene_system.system()) .add_startup_system(infotext_system.system()) .add_system(print_system.system())