Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Local with required config as a type parameter #2491

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
5 changes: 3 additions & 2 deletions crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_ecs/src/system/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};
Expand Down Expand Up @@ -396,7 +396,7 @@ mod tests {
}
}

fn sys(local: Local<Foo>, mut modified: ResMut<bool>) {
fn sys(local: Local<Foo, local_value::FromWorld>, mut modified: ResMut<bool>) {
assert_eq!(local.value, 2);
*modified = true;
}
Expand Down
209 changes: 193 additions & 16 deletions crates/bevy_ecs/src/system/system_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe SystemConfig would fit better?

impl LocalValue for NeedConfig {}
}

/// A system local [`SystemParam`].
///
Expand Down Expand Up @@ -556,58 +576,176 @@ 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<Foo>) {
/// 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::<u32>().unwrap() + 1)
/// }
/// }
/// fn from_world_local(local: Local<Foo, local_value::FromWorld>) {
/// 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<Foo, local_value::NeedConfig>) {
/// 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> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about ValueFrom -> InitializeWith?

value: &'a mut T,
value_from: std::marker::PhantomData<ValueFrom>,
}

// SAFE: Local only accesses internal state
unsafe impl<T: Resource> ReadOnlySystemParamFetch for LocalState<T> {}
unsafe impl<T: Resource, ValueFrom: local_value::LocalValue> ReadOnlySystemParamFetch
for LocalState<T, ValueFrom>
{
}

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<T>`].
pub struct LocalState<T: Resource>(T);
pub struct LocalState<T: Resource, ValueFrom: local_value::LocalValue> {
value: T,
value_from: std::marker::PhantomData<ValueFrom>,
}

impl<'a, T: Resource + FromWorld> SystemParam for Local<'a, T, local_value::FromWorld> {
type Fetch = LocalState<T, local_value::FromWorld>;
}

impl<'a, T: Resource + Default> SystemParam for Local<'a, T, local_value::Default> {
type Fetch = LocalState<T, local_value::Default>;
}

impl<'a, T: Resource + FromWorld> SystemParam for Local<'a, T> {
type Fetch = LocalState<T>;
impl<'a, T: Resource> SystemParam for Local<'a, T, local_value::NeedConfig> {
type Fetch = LocalState<T, local_value::NeedConfig>;
}

// SAFE: only local state is accessed
unsafe impl<T: Resource + FromWorld> SystemParamState for LocalState<T> {
unsafe impl<T: Resource + FromWorld> SystemParamState for LocalState<T, local_value::FromWorld> {
type Config = Option<T>;

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<T> {
None
}
}

impl<'w, 's, T: Resource + FromWorld> SystemParamFetch<'w, 's> for LocalState<T> {
type Item = Local<'s, T>;
// SAFE: only local state is accessed
unsafe impl<T: Resource + Default> SystemParamState for LocalState<T, local_value::Default> {
type Config = Option<T>;

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<T> {
None
}
}

// SAFE: only local state is accessed
unsafe impl<T: Resource> SystemParamState for LocalState<T, local_value::NeedConfig> {
type Config = Option<T>;

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<T> {
None
}
}

impl<'w, 's, T: Resource + FromWorld> SystemParamFetch<'w, 's>
for LocalState<T, local_value::FromWorld>
{
type Item = Local<'s, T, local_value::FromWorld>;

#[inline]
unsafe fn get_param(
Expand All @@ -616,7 +754,46 @@ impl<'w, 's, T: Resource + FromWorld> SystemParamFetch<'w, 's> for LocalState<T>
_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<T, local_value::Default>
{
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<T, local_value::NeedConfig> {
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(),
}
}
}

Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
24 changes: 2 additions & 22 deletions examples/ecs/ecs_guide.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,28 +225,8 @@ fn thread_local_system(world: &mut World) {
}
}

// Sometimes systems need their own unique "local" state. Bevy's ECS provides Local<T> resources for
// this case. Local<T> 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<State>, 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)]
Expand All @@ -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).
Expand Down
Loading