diff --git a/Cargo.toml b/Cargo.toml index ff97fb1b45e27..89b9dc12bfaa9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -283,6 +283,10 @@ path = "examples/ecs/hierarchy.rs" name = "iter_combinations" path = "examples/ecs/iter_combinations.rs" +[[example]] +name = "local_parameter" +path = "examples/ecs/local_parameter.rs" + [[example]] name = "parallel_query" path = "examples/ecs/parallel_query.rs" diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 6fb347a2a7e8f..046400440d5f6 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -31,8 +31,9 @@ pub mod prelude { Schedule, Stage, StageLabel, State, SystemLabel, SystemSet, SystemStage, }, system::{ - Commands, ConfigurableSystem, In, IntoChainSystem, IntoExclusiveSystem, IntoSystem, - Local, NonSend, NonSendMut, Query, QuerySet, RemovedComponents, Res, ResMut, System, + local_value, Commands, ConfigurableSystem, In, IntoChainSystem, IntoExclusiveSystem, + IntoSystem, Local, NonSend, NonSendMut, Query, QuerySet, RemovedComponents, Res, + ResMut, System, }, world::{FromWorld, Mut, World}, }; diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index a123d871b3a01..4268dd8c26d28 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -95,8 +95,8 @@ mod tests { query::{Added, Changed, Or, QueryState, With, Without}, schedule::{Schedule, Stage, SystemStage}, system::{ - ConfigurableSystem, IntoExclusiveSystem, IntoSystem, Local, NonSend, NonSendMut, Query, - QuerySet, RemovedComponents, Res, ResMut, System, SystemState, + local_value, ConfigurableSystem, IntoExclusiveSystem, IntoSystem, Local, NonSend, + NonSendMut, Query, QuerySet, RemovedComponents, Res, ResMut, System, SystemState, }, world::{FromWorld, World}, }; @@ -396,7 +396,7 @@ mod tests { } } - fn sys(local: Local, mut modified: ResMut) { + fn sys(local: Local, mut modified: ResMut) { assert_eq!(local.value, 2); *modified = true; } diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index f9f5f60794e38..a814be8077fff 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -529,6 +529,26 @@ impl<'w, 's> SystemParamFetch<'w, 's> for CommandQueue { Commands::new(state, world) } } +pub mod local_value { + + /// How should a [`Local`](crate::system::Local) system parameter be initialized. + pub trait LocalValue {} + + /// [`Local`](crate::system::Local) should be initialized using the + /// [`Default`](std::default::Default) implementation. + pub struct Default; + impl LocalValue for Default {} + + /// [`Local`](crate::system::Local) should be initialized from the + /// [`FromWorld`](crate::world::FromWorld) implementation. + pub struct FromWorld; + impl LocalValue for FromWorld {} + + /// [`Local`](crate::system::Local) should be initialized by calling + /// [`FunctionSystem::config()`](crate::system::FunctionSystem::config) on the system. + pub struct NeedConfig; + impl LocalValue for NeedConfig {} +} /// A system local [`SystemParam`]. /// @@ -556,49 +576,133 @@ impl<'w, 's> SystemParamFetch<'w, 's> for CommandQueue { /// // 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); +/// +/// # Local value initialization +/// +/// The value used to initialized `Local` can have several origins: +/// +/// ## `Default` value +/// +/// This is the default option if you don't explicitly set the `ValueFrom` type parameter. +/// This is equivalent to setting it to [`Default`](local_value::Default). +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # let world = &mut World::default(); +/// #[derive(Default)] +/// struct Foo(u32); +/// fn default_local(local: Local) { +/// assert_eq!(local.0, 0); +/// } +/// let mut system = default_local.system(); +/// system.initialize(world); +/// system.run((), world); +/// ``` +/// +/// ## `FromWorld` value +/// +/// The local value can be initialized from the `World` the system runs in by using +/// [`FromWorld`](local_value::FromWorld). +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # let world = &mut World::default(); +/// world.insert_resource(1u32); +/// struct Foo(u32); +/// impl FromWorld for Foo { +/// fn from_world(world: &mut World) -> Self { +/// Foo(*world.get_resource::().unwrap() + 1) +/// } +/// } +/// fn from_world_local(local: Local) { +/// assert_eq!(local.0, 2); +/// } +/// let mut system = from_world_local.system(); +/// system.initialize(world); +/// system.run((), world); +/// ``` +/// +/// ## `NeedConfig` value +/// +/// With [`NeedConfig`](local_value::NeedConfig), the local value needs to be configured when +/// setting up the system with [`FunctionSystem::config()`](crate::system::FunctionSystem::config). +/// This will panic if the system using this `Local` value has not been properly configured before +/// running. +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # let world = &mut World::default(); +/// struct Foo(u32); +/// fn need_config_local(local: Local) { +/// assert_eq!(local.0, 7); +/// } +/// let mut system = need_config_local.system().config(|config| config.0 = Some(Foo(7))); +/// system.initialize(world); +/// system.run((), world); +/// ``` +pub struct Local<'a, T: Resource, ValueFrom: local_value::LocalValue = local_value::Default> { + value: &'a mut T, + value_from: std::marker::PhantomData, +} // SAFE: Local only accesses internal state -unsafe impl ReadOnlySystemParamFetch for LocalState {} +unsafe impl ReadOnlySystemParamFetch + for LocalState +{ +} -impl<'a, T: Resource> Debug for Local<'a, T> +impl<'a, T: Resource, ValueFrom: local_value::LocalValue> Debug for Local<'a, T, ValueFrom> where T: Debug, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("Local").field(&self.0).finish() + f.debug_tuple("Local").field(&self.value).finish() } } -impl<'a, T: Resource> Deref for Local<'a, T> { +impl<'a, T: Resource, ValueFrom: local_value::LocalValue> Deref for Local<'a, T, ValueFrom> { type Target = T; #[inline] fn deref(&self) -> &Self::Target { - self.0 + self.value } } -impl<'a, T: Resource> DerefMut for Local<'a, T> { +impl<'a, T: Resource, ValueFrom: local_value::LocalValue> DerefMut for Local<'a, T, ValueFrom> { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { - self.0 + self.value } } /// The [`SystemParamState`] of [`Local`]. -pub struct LocalState(T); +pub struct LocalState { + value: T, + value_from: std::marker::PhantomData, +} + +impl<'a, T: Resource + FromWorld> SystemParam for Local<'a, T, local_value::FromWorld> { + type Fetch = LocalState; +} + +impl<'a, T: Resource + Default> SystemParam for Local<'a, T, local_value::Default> { + type Fetch = LocalState; +} -impl<'a, T: Resource + FromWorld> SystemParam for Local<'a, T> { - type Fetch = LocalState; +impl<'a, T: Resource> SystemParam for Local<'a, T, local_value::NeedConfig> { + type Fetch = LocalState; } // SAFE: only local state is accessed -unsafe impl SystemParamState for LocalState { +unsafe impl SystemParamState for LocalState { type Config = Option; fn init(world: &mut World, _system_meta: &mut SystemMeta, config: Self::Config) -> Self { - Self(config.unwrap_or_else(|| T::from_world(world))) + Self { + value: config.unwrap_or_else(|| T::from_world(world)), + value_from: Default::default(), + } } fn default_config() -> Option { @@ -606,8 +710,42 @@ unsafe impl SystemParamState for LocalState { } } -impl<'w, 's, T: Resource + FromWorld> SystemParamFetch<'w, 's> for LocalState { - type Item = Local<'s, T>; +// SAFE: only local state is accessed +unsafe impl SystemParamState for LocalState { + type Config = Option; + + fn init(_world: &mut World, _system_meta: &mut SystemMeta, config: Self::Config) -> Self { + Self { + value: config.unwrap_or_default(), + value_from: Default::default(), + } + } + + fn default_config() -> Option { + None + } +} + +// SAFE: only local state is accessed +unsafe impl SystemParamState for LocalState { + type Config = Option; + + fn init(_world: &mut World, _system_meta: &mut SystemMeta, config: Self::Config) -> Self { + Self { + value: config.expect("Local must be initialized using config!"), + value_from: Default::default(), + } + } + + fn default_config() -> Option { + None + } +} + +impl<'w, 's, T: Resource + FromWorld> SystemParamFetch<'w, 's> + for LocalState +{ + type Item = Local<'s, T, local_value::FromWorld>; #[inline] unsafe fn get_param( @@ -616,7 +754,46 @@ impl<'w, 's, T: Resource + FromWorld> SystemParamFetch<'w, 's> for LocalState _world: &'w World, _change_tick: u32, ) -> Self::Item { - Local(&mut state.0) + Local { + value: &mut state.value, + value_from: Default::default(), + } + } +} + +impl<'w, 's, T: Resource + Default> SystemParamFetch<'w, 's> + for LocalState +{ + type Item = Local<'s, T, local_value::Default>; + + #[inline] + unsafe fn get_param( + state: &'s mut Self, + _system_meta: &SystemMeta, + _world: &'w World, + _change_tick: u32, + ) -> Self::Item { + Local { + value: &mut state.value, + value_from: Default::default(), + } + } +} + +impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for LocalState { + type Item = Local<'s, T, local_value::NeedConfig>; + + #[inline] + unsafe fn get_param( + state: &'s mut Self, + _system_meta: &SystemMeta, + _world: &'w World, + _change_tick: u32, + ) -> Self::Item { + Local { + value: &mut state.value, + value_from: Default::default(), + } } } diff --git a/examples/README.md b/examples/README.md index 7796afdb16223..e25ecbf8762ba 100644 --- a/examples/README.md +++ b/examples/README.md @@ -161,6 +161,7 @@ Example | File | Description `fixed_timestep` | [`ecs/fixed_timestep.rs`](./ecs/fixed_timestep.rs) | Shows how to create systems that run every fixed timestep, rather than every tick `hierarchy` | [`ecs/hierarchy.rs`](./ecs/hierarchy.rs) | Creates a hierarchy of parents and children entities `iter_combinations` | [`ecs/iter_combinations.rs`](./ecs/iter_combinations.rs) | Shows how to iterate over combinations of query results. +`local_parameter` | [`ecs/local_parameter.rs`](./ecs/local_parameter.rs) | How to use `Local` parameter and the different way to initialize one. `parallel_query` | [`ecs/parallel_query.rs`](./ecs/parallel_query.rs) | Illustrates parallel queries with `ParallelIterator` `removal_detection` | [`ecs/removal_detection.rs`](./ecs/removal_detection.rs) | Query for entities that had a specific component removed in a previous stage during the current frame. `startup_system` | [`ecs/startup_system.rs`](./ecs/startup_system.rs) | Demonstrates a startup system (one that runs once when the app starts up) diff --git a/examples/ecs/ecs_guide.rs b/examples/ecs/ecs_guide.rs index d575613ae7106..46c60f814a8ee 100644 --- a/examples/ecs/ecs_guide.rs +++ b/examples/ecs/ecs_guide.rs @@ -225,28 +225,8 @@ fn thread_local_system(world: &mut World) { } } -// Sometimes systems need their own unique "local" state. Bevy's ECS provides Local resources for -// this case. Local resources are unique to their system and are automatically initialized on -// your behalf (if they don't already exist). If you have a system's id, you can also access local -// resources directly in the Resources collection using `Resources::get_local()`. In general you -// should only need this feature in the following cases: 1. You have multiple instances of the same -// system and they each need their own unique state 2. You already have a global version of a -// resource that you don't want to overwrite for your current system 3. You are too lazy to -// register the system's resource as a global resource - -#[derive(Default)] struct State { - counter: usize, -} - -// NOTE: this doesn't do anything relevant to our game, it is just here for illustrative purposes -#[allow(dead_code)] -fn local_state_system(mut state: Local, query: Query<(&Player, &Score)>) { - for (player, score) in query.iter() { - println!("processed: {} {}", player.name, score.value); - } - println!("this system ran {} times", state.counter); - state.counter += 1; + _counter: usize, } #[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] @@ -266,7 +246,7 @@ fn main() { // resources, and plugins to our app App::new() // Resources can be added to our app like this - .insert_resource(State { counter: 0 }) + .insert_resource(State { _counter: 0 }) // Some systems are configured by adding their settings as a resource .insert_resource(ScheduleRunnerSettings::run_loop(Duration::from_secs(5))) // Plugins are just a grouped set of app builder calls (just like we're doing here). diff --git a/examples/ecs/local_parameter.rs b/examples/ecs/local_parameter.rs new file mode 100644 index 0000000000000..15e1772264d5b --- /dev/null +++ b/examples/ecs/local_parameter.rs @@ -0,0 +1,72 @@ +use std::time::Instant; + +use bevy::{prelude::*, utils::Duration}; + +// Sometimes systems need their own unique "local" state. Bevy's ECS provides Local resources +// for this case. Local resources are unique to their system and can be initialized either +// automatically (with a `FromWorld` or `Default` implementation) or manually (by calling `config` +// on the system). +// This can be useful when: +// - You won't need access to the value from other systems +// - You have multiple instances of the same system and they each need their own unique state +// - You already have a global version of a resource that you don't want to overwrite for your +// current system + +#[derive(Default)] +struct RoundCount(u32); + +struct TimeSinceLastReset(Instant); +impl FromWorld for TimeSinceLastReset { + fn from_world(world: &mut World) -> Self { + let time = world.get_resource::