From 9e67da3766feeb5b62d166b00335092ff04193f3 Mon Sep 17 00:00:00 2001 From: TheRawMeatball Date: Mon, 14 Feb 2022 19:49:26 +0300 Subject: [PATCH 1/6] Add more FromWorld implementations --- crates/bevy_ecs/src/system/function_system.rs | 7 +++++++ crates/bevy_ecs/src/world/mod.rs | 20 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index 36835c1d90a5d..33c19360a14aa 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -1,6 +1,7 @@ use crate::{ archetype::{Archetype, ArchetypeComponentId, ArchetypeGeneration, ArchetypeId}, component::ComponentId, + prelude::FromWorld, query::{Access, FilteredAccessSet}, system::{ check_system_change_tick, ReadOnlySystemParamFetch, System, SystemParam, SystemParamFetch, @@ -168,6 +169,12 @@ impl SystemState { } } +impl FromWorld for SystemState { + fn from_world(world: &mut World) -> Self { + Self::new(world) + } +} + /// A trait for defining systems with a [`SystemParam`] associated type. /// /// This facilitates the creation of systems that are generic over some trait diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index ad037819e89f7..69d7462596373 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -3,6 +3,7 @@ mod spawn_batch; mod world_cell; pub use crate::change_detection::Mut; +use bevy_ecs_macros::all_tuples; pub use entity_ref::*; pub use spawn_batch::*; pub use world_cell::*; @@ -1224,6 +1225,25 @@ impl FromWorld for T { } } +/// Wrapper type to enable getting multiple [`FromWorld`] types in one type. +/// +/// This wrapper is necessary because a [`FromWorld`] implementation for a tuple of +/// [`FromWorld`] implementing types would conflict with the [`FromWorld`] impl over all +/// [`Default`] type. +pub struct FromWorldWrap(pub T); + +macro_rules! impl_from_world { + ($($t:ident),*) => { + impl<$($t: FromWorld,)*> FromWorld for FromWorldWrap<($($t,)*)> { + fn from_world(_world: &mut World) -> Self { + FromWorldWrap(($($t::from_world(_world),)*)) + } + } + }; +} + +all_tuples!(impl_from_world, 0, 12, T); + struct MainThreadValidator { main_thread: std::thread::ThreadId, } From 3667ac404bf586f71f640fddf139c3bc57ed9ba6 Mon Sep 17 00:00:00 2001 From: TheRawMeatball Date: Mon, 14 Feb 2022 20:08:08 +0300 Subject: [PATCH 2/6] Add Local support to exclusive systems --- .../bevy_ecs/src/system/exclusive_system.rs | 94 +++++++++++-------- crates/bevy_ecs/src/system/system_param.rs | 2 +- 2 files changed, 58 insertions(+), 38 deletions(-) diff --git a/crates/bevy_ecs/src/system/exclusive_system.rs b/crates/bevy_ecs/src/system/exclusive_system.rs index 2d8879fa6b7a4..aef9e1c1430c6 100644 --- a/crates/bevy_ecs/src/system/exclusive_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_system.rs @@ -1,6 +1,9 @@ +use bevy_ecs_macros::all_tuples; + use crate::{ archetype::ArchetypeGeneration, - system::{check_system_change_tick, BoxedSystem, IntoSystem}, + prelude::{FromWorld, Local}, + system::{check_system_change_tick, BoxedSystem, IntoSystem, Resource}, world::World, }; use std::borrow::Cow; @@ -15,59 +18,76 @@ pub trait ExclusiveSystem: Send + Sync + 'static { fn check_change_tick(&mut self, change_tick: u32); } -pub struct ExclusiveSystemFn { +pub struct ExclusiveSystemFn { func: F, name: Cow<'static, str>, last_change_tick: u32, + locals: Option, } -impl ExclusiveSystem for ExclusiveSystemFn -where - F: FnMut(&mut World) + Send + Sync + 'static, -{ - fn name(&self) -> Cow<'static, str> { - self.name.clone() - } +macro_rules! impl_exclusive_system { + ($($t:ident),*) => { + impl ExclusiveSystem for ExclusiveSystemFn + where + F: FnMut(&mut World, $(Local<$t>,)*) + Send + Sync + 'static, + { + fn name(&self) -> Cow<'static, str> { + self.name.clone() + } - fn run(&mut self, world: &mut World) { - // The previous value is saved in case this exclusive system is run by another exclusive - // system - let saved_last_tick = world.last_change_tick; - world.last_change_tick = self.last_change_tick; + fn run(&mut self, world: &mut World) { + // The previous value is saved in case this exclusive system is run by another exclusive + // system + let saved_last_tick = world.last_change_tick; + world.last_change_tick = self.last_change_tick; - (self.func)(world); + let ($($t,)*) = self.locals.as_mut().unwrap(); + (self.func)(world, $(Local(&mut $t),)*); - let change_tick = world.change_tick.get_mut(); - self.last_change_tick = *change_tick; - *change_tick += 1; + let change_tick = world.change_tick.get_mut(); + self.last_change_tick = *change_tick; + *change_tick += 1; - world.last_change_tick = saved_last_tick; - } + world.last_change_tick = saved_last_tick; + } - fn initialize(&mut self, _: &mut World) {} + fn initialize(&mut self, _world: &mut World) { + self.locals = Some(($($t::from_world(_world),)*)); + } - fn check_change_tick(&mut self, change_tick: u32) { - check_system_change_tick(&mut self.last_change_tick, change_tick, self.name.as_ref()); - } + fn check_change_tick(&mut self, change_tick: u32) { + check_system_change_tick( + &mut self.last_change_tick, + change_tick, + self.name.as_ref(), + ); + } + } + + + impl IntoExclusiveSystem<&mut World, ExclusiveSystemFn> for F + where + F: FnMut(&mut World, $(Local<$t>,)*) + Send + Sync + 'static, + { + fn exclusive_system(self) -> ExclusiveSystemFn { + ExclusiveSystemFn { + func: self, + name: core::any::type_name::().into(), + last_change_tick: 0, + locals: None, + } + } + } + + }; } +all_tuples!(impl_exclusive_system, 0, 12, L); + pub trait IntoExclusiveSystem { fn exclusive_system(self) -> SystemType; } -impl IntoExclusiveSystem<&mut World, ExclusiveSystemFn> for F -where - F: FnMut(&mut World) + Send + Sync + 'static, -{ - fn exclusive_system(self) -> ExclusiveSystemFn { - ExclusiveSystemFn { - func: self, - name: core::any::type_name::().into(), - last_change_tick: 0, - } - } -} - pub struct ExclusiveSystemCoerced { system: BoxedSystem<(), ()>, archetype_generation: ArchetypeGeneration, diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 9bc4b22efd17b..a4a7d1406a98e 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -625,7 +625,7 @@ impl<'w, 's> SystemParamFetch<'w, 's> for WorldState { /// // Note how the read local is still 0 due to the locals not being shared. /// assert_eq!(read_system.run((), world), 0); /// ``` -pub struct Local<'a, T: Resource>(&'a mut T); +pub struct Local<'a, T: Resource>(pub(crate) &'a mut T); // SAFE: Local only accesses internal state unsafe impl ReadOnlySystemParamFetch for LocalState {} From 5912dd53e69d90d020d494e61cef540f3490abd8 Mon Sep 17 00:00:00 2001 From: TheRawMeatball Date: Mon, 14 Feb 2022 20:18:28 +0300 Subject: [PATCH 3/6] fix errors --- crates/bevy_ecs/src/schedule/system_descriptor.rs | 4 ++-- crates/bevy_ecs/src/system/exclusive_system.rs | 3 ++- crates/bevy_ecs/src/system/system_param.rs | 8 +++++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/system_descriptor.rs b/crates/bevy_ecs/src/schedule/system_descriptor.rs index 825b581135094..2e80f84cc1820 100644 --- a/crates/bevy_ecs/src/schedule/system_descriptor.rs +++ b/crates/bevy_ecs/src/schedule/system_descriptor.rs @@ -78,9 +78,9 @@ impl IntoSystemDescriptor<()> for ExclusiveSystemDescriptor { } } -impl IntoSystemDescriptor<()> for ExclusiveSystemFn +impl IntoSystemDescriptor<()> for ExclusiveSystemFn where - F: FnMut(&mut crate::prelude::World) + Send + Sync + 'static, + ExclusiveSystemFn: ExclusiveSystem, { fn into_descriptor(self) -> SystemDescriptor { new_exclusive_descriptor(Box::new(self)).into_descriptor() diff --git a/crates/bevy_ecs/src/system/exclusive_system.rs b/crates/bevy_ecs/src/system/exclusive_system.rs index aef9e1c1430c6..abf186966e7af 100644 --- a/crates/bevy_ecs/src/system/exclusive_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_system.rs @@ -27,6 +27,7 @@ pub struct ExclusiveSystemFn { macro_rules! impl_exclusive_system { ($($t:ident),*) => { + #[allow(non_snake_case)] impl ExclusiveSystem for ExclusiveSystemFn where F: FnMut(&mut World, $(Local<$t>,)*) + Send + Sync + 'static, @@ -42,7 +43,7 @@ macro_rules! impl_exclusive_system { world.last_change_tick = self.last_change_tick; let ($($t,)*) = self.locals.as_mut().unwrap(); - (self.func)(world, $(Local(&mut $t),)*); + (self.func)(world, $(Local::wrap($t),)*); let change_tick = world.change_tick.get_mut(); self.last_change_tick = *change_tick; diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index a4a7d1406a98e..ab76912b5dc1c 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -625,7 +625,13 @@ impl<'w, 's> SystemParamFetch<'w, 's> for WorldState { /// // Note how the read local is still 0 due to the locals not being shared. /// assert_eq!(read_system.run((), world), 0); /// ``` -pub struct Local<'a, T: Resource>(pub(crate) &'a mut T); +pub struct Local<'a, T: Resource>(&'a mut T); + +impl<'a, T: Resource> Local<'a, T> { + pub(crate) fn wrap(x: &'a mut T) -> Self { + Self(x) + } +} // SAFE: Local only accesses internal state unsafe impl ReadOnlySystemParamFetch for LocalState {} From b7ff9a4784e5092703dd6de91c82e08cd6540de0 Mon Sep 17 00:00:00 2001 From: TheRawMeatball Date: Mon, 14 Feb 2022 21:13:24 +0300 Subject: [PATCH 4/6] Add example --- Cargo.toml | 4 ++ examples/README.md | 1 + examples/ecs/exclusive_system_tricks.rs | 57 +++++++++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 examples/ecs/exclusive_system_tricks.rs diff --git a/Cargo.toml b/Cargo.toml index ef6cb40a7da7b..c438d46b3dee3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -319,6 +319,10 @@ path = "examples/ecs/component_change_detection.rs" name = "event" path = "examples/ecs/event.rs" +[[example]] +name = "exclusive_system_tricks" +path = "examples/ecs/exclusive_system_tricks.rs" + [[example]] name = "fixed_timestep" path = "examples/ecs/fixed_timestep.rs" diff --git a/examples/README.md b/examples/README.md index aeaff8c7845f2..317b79a0cedf3 100644 --- a/examples/README.md +++ b/examples/README.md @@ -167,6 +167,7 @@ Example | File | Description `ecs_guide` | [`ecs/ecs_guide.rs`](./ecs/ecs_guide.rs) | Full guide to Bevy's ECS `component_change_detection` | [`ecs/component_change_detection.rs`](./ecs/component_change_detection.rs) | Change detection on components `event` | [`ecs/event.rs`](./ecs/event.rs) | Illustrates event creation, activation, and reception +`exclusive_system_tricks` | [`ecs/exclusive_system_tricks.rs`](./ecs/exclusive_system_tricks.rs) | A basic guide to using exclusive systems and exclusive world access. `fixed_timestep` | [`ecs/fixed_timestep.rs`](./ecs/fixed_timestep.rs) | Shows how to create systems that run every fixed timestep, rather than every tick `generic_system` | [`ecs/generic_system.rs`](./ecs/generic_system.rs) | Shows how to create systems that can be reused with different types `hierarchy` | [`ecs/hierarchy.rs`](./ecs/hierarchy.rs) | Creates a hierarchy of parents and children entities diff --git a/examples/ecs/exclusive_system_tricks.rs b/examples/ecs/exclusive_system_tricks.rs new file mode 100644 index 0000000000000..209cd0828d4ad --- /dev/null +++ b/examples/ecs/exclusive_system_tricks.rs @@ -0,0 +1,57 @@ +use bevy::{ + ecs::system::{lifetimeless::*, SystemState}, + prelude::*, +}; + +fn main() { + App::new() + .init_resource::() + .insert_resource(5u32) + .add_system(simple_exclusive_system.exclusive_system()) + .add_system(stateful_exclusive_system.exclusive_system()) + .run(); +} + +#[derive(Default)] +struct MyCustomSchedule(Schedule); + +#[derive(Component)] +struct MyComponent; + +/// Just a simple exclusive system - this function will run with mutable access to +/// the main app world. This lets it call into other schedules, or modify and query the +/// world in hard-to-predict ways, which makes it a powerful primitive. However, because +/// this is usually not needed, and because such wide access makes parallelism impossible, +/// it should generally be avoided. +fn simple_exclusive_system(world: &mut World) { + world.resource_scope(|world, mut my_schedule: Mut| { + // The resource_scope method is one of the main tools for working with &mut World. + // This method will temporarily remove the resource from the ECS world and let you + // access it while still keeping &mut World. A particularly popular pattern is storing + // schedules, stages, and other similar "runnables" in the world, taking them out + // using resource_scope, and running them with the world: + my_schedule.0.run(world); + // This is fairly simple, but you can implement rather complex custom executors in this manner. + }); +} + +/// While it's usually not recommended due to parallelism concerns, you can also use exclusive systems +/// as mostly-normal systems but with the ability to change parameter sets and flush commands midway through. +fn stateful_exclusive_system( + world: &mut World, + mut part_one_state: Local, SCommands)>>, + mut part_two_state: Local>>>, +) { + let (resource, mut commands) = part_one_state.get(world); + let res = *resource as usize; + commands.spawn_batch((0..res).map(|_| (MyComponent,))); + + // Don't forget to apply your state, or commands won't take effect! + part_one_state.apply(world); + let query = part_two_state.get(world); + let entity_count = query.iter().len(); + // note how the entities spawned in this system are observed, + // and how resources fetched in earlier stages can still be + // used if they're cloned out, or small enough to copy out. + assert_eq!(entity_count, res); +} From 87c335bcdcab7b70b1a1af409b5859d7abc333fd Mon Sep 17 00:00:00 2001 From: TheRawMeatball Date: Mon, 14 Feb 2022 22:16:17 +0300 Subject: [PATCH 5/6] Update examples/ecs/exclusive_system_tricks.rs Co-authored-by: Alice Cecile --- examples/ecs/exclusive_system_tricks.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ecs/exclusive_system_tricks.rs b/examples/ecs/exclusive_system_tricks.rs index 209cd0828d4ad..58e2d78879f75 100644 --- a/examples/ecs/exclusive_system_tricks.rs +++ b/examples/ecs/exclusive_system_tricks.rs @@ -19,7 +19,7 @@ struct MyCustomSchedule(Schedule); struct MyComponent; /// Just a simple exclusive system - this function will run with mutable access to -/// the main app world. This lets it call into other schedules, or modify and query the +/// the main app world. This lets it run other schedules, or modify and query the /// world in hard-to-predict ways, which makes it a powerful primitive. However, because /// this is usually not needed, and because such wide access makes parallelism impossible, /// it should generally be avoided. From 7f833c9a676987d87c0b1e18c4436b4ee4008f7d Mon Sep 17 00:00:00 2001 From: TheRawMeatball Date: Mon, 14 Feb 2022 22:21:40 +0300 Subject: [PATCH 6/6] Improve example --- examples/ecs/exclusive_system_tricks.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/ecs/exclusive_system_tricks.rs b/examples/ecs/exclusive_system_tricks.rs index 58e2d78879f75..b828d46439b95 100644 --- a/examples/ecs/exclusive_system_tricks.rs +++ b/examples/ecs/exclusive_system_tricks.rs @@ -6,7 +6,7 @@ use bevy::{ fn main() { App::new() .init_resource::() - .insert_resource(5u32) + .insert_resource(SpawnCount(5)) .add_system(simple_exclusive_system.exclusive_system()) .add_system(stateful_exclusive_system.exclusive_system()) .run(); @@ -15,14 +15,14 @@ fn main() { #[derive(Default)] struct MyCustomSchedule(Schedule); +struct SpawnCount(usize); + #[derive(Component)] struct MyComponent; /// Just a simple exclusive system - this function will run with mutable access to /// the main app world. This lets it run other schedules, or modify and query the -/// world in hard-to-predict ways, which makes it a powerful primitive. However, because -/// this is usually not needed, and because such wide access makes parallelism impossible, -/// it should generally be avoided. +/// world in hard-to-predict ways, which makes it a powerful primitive. fn simple_exclusive_system(world: &mut World) { world.resource_scope(|world, mut my_schedule: Mut| { // The resource_scope method is one of the main tools for working with &mut World. @@ -35,15 +35,15 @@ fn simple_exclusive_system(world: &mut World) { }); } -/// While it's usually not recommended due to parallelism concerns, you can also use exclusive systems -/// as mostly-normal systems but with the ability to change parameter sets and flush commands midway through. +/// You can also use exclusive systems as mostly-normal systems but with the ability to +/// change parameter sets and flush commands midway through. fn stateful_exclusive_system( world: &mut World, - mut part_one_state: Local, SCommands)>>, + mut part_one_state: Local, SCommands)>>, mut part_two_state: Local>>>, ) { let (resource, mut commands) = part_one_state.get(world); - let res = *resource as usize; + let res = resource.0; commands.spawn_batch((0..res).map(|_| (MyComponent,))); // Don't forget to apply your state, or commands won't take effect!