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/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 2d8879fa6b7a4..abf186966e7af 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,77 @@ 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),*) => { + #[allow(non_snake_case)] + 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::wrap($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/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/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 9bc4b22efd17b..ab76912b5dc1c 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -627,6 +627,12 @@ impl<'w, 's> SystemParamFetch<'w, 's> for WorldState { /// ``` 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 {} 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, } 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..b828d46439b95 --- /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(SpawnCount(5)) + .add_system(simple_exclusive_system.exclusive_system()) + .add_system(stateful_exclusive_system.exclusive_system()) + .run(); +} + +#[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. +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. + }); +} + +/// 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.0; + 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); +}