From a27e47f57b7202eccfaebc41c0525e77ec613f56 Mon Sep 17 00:00:00 2001 From: Stepan Koltsov Date: Tue, 21 Nov 2023 16:58:01 +0000 Subject: [PATCH] RFC: SystemParam::user_meta --- crates/bevy_ecs/macros/src/lib.rs | 4 + crates/bevy_ecs/src/system/adapter_system.rs | 6 +- crates/bevy_ecs/src/system/combinator.rs | 7 +- .../src/system/exclusive_function_system.rs | 5 + crates/bevy_ecs/src/system/function_system.rs | 6 +- crates/bevy_ecs/src/system/mod.rs | 2 + crates/bevy_ecs/src/system/system.rs | 18 +- crates/bevy_ecs/src/system/system_param.rs | 15 ++ .../src/system/system_param_user_meta.rs | 167 ++++++++++++++++++ 9 files changed, 226 insertions(+), 4 deletions(-) create mode 100644 crates/bevy_ecs/src/system/system_param_user_meta.rs diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index ecb3a57c9c477..4ebdfbb344839 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -410,6 +410,10 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { type State = #state_struct_name<#punctuated_generic_idents>; type Item<'w, 's> = #struct_name #ty_generics; + fn user_meta(request: &mut #path::system::SystemParamUserMetaRequest) { + <#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::user_meta(request); + } + fn init_state(world: &mut #path::world::World, system_meta: &mut #path::system::SystemMeta) -> Self::State { #state_struct_name { state: <#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::init_state(world, system_meta), diff --git a/crates/bevy_ecs/src/system/adapter_system.rs b/crates/bevy_ecs/src/system/adapter_system.rs index 228b5ddca2700..baa605a964de7 100644 --- a/crates/bevy_ecs/src/system/adapter_system.rs +++ b/crates/bevy_ecs/src/system/adapter_system.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use super::{ReadOnlySystem, System}; +use super::{ReadOnlySystem, System, SystemParamUserMetaRequest}; use crate::{schedule::InternedSystemSet, world::unsafe_world_cell::UnsafeWorldCell}; /// Customizes the behavior of an [`AdapterSystem`] @@ -146,6 +146,10 @@ where fn default_system_sets(&self) -> Vec { self.system.default_system_sets() } + + fn param_user_meta(&self, request: &mut SystemParamUserMetaRequest) { + self.system.param_user_meta(request); + } } // SAFETY: The inner system is read-only. diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index f70712711cc95..edf58a03ba584 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -11,7 +11,7 @@ use crate::{ world::unsafe_world_cell::UnsafeWorldCell, }; -use super::{ReadOnlySystem, System}; +use super::{ReadOnlySystem, System, SystemParamUserMetaRequest}; /// Customizes the behavior of a [`CombinatorSystem`]. /// @@ -232,6 +232,11 @@ where default_sets.append(&mut self.b.default_system_sets()); default_sets } + + fn param_user_meta(&self, request: &mut SystemParamUserMetaRequest) { + self.a.param_user_meta(request); + self.b.param_user_meta(request); + } } /// SAFETY: Both systems are read-only, so any system created by combining them will only read from the world. diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index 1a06f1eb03d71..ad611068a0cf5 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -12,6 +12,7 @@ use crate::{ use bevy_utils::all_tuples; use std::{any::TypeId, borrow::Cow, marker::PhantomData}; +use crate::system::{ SystemParamUserMetaRequest}; /// A function system that runs with exclusive [`World`] access. /// @@ -155,6 +156,10 @@ where let set = crate::schedule::SystemTypeSet::::new(); vec![set.intern()] } + + fn param_user_meta(&self, _request: &mut SystemParamUserMetaRequest) { + // Exclusive system param does not have user meta. + } } /// A trait implemented for all exclusive system functions that can be used as [`System`]s. diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index 5b6b68d7859e2..ccb3d297c1a35 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -14,7 +14,7 @@ use std::{any::TypeId, borrow::Cow, marker::PhantomData}; #[cfg(feature = "trace")] use bevy_utils::tracing::{info_span, Span}; -use super::{In, IntoSystem, ReadOnlySystem}; +use super::{In, IntoSystem, ReadOnlySystem, SystemParamUserMetaRequest}; /// The metadata of a [`System`]. #[derive(Clone)] @@ -533,6 +533,10 @@ where let set = crate::schedule::SystemTypeSet::::new(); vec![set.intern()] } + + fn param_user_meta(&self, request: &mut SystemParamUserMetaRequest) { + F::Param::user_meta(request); + } } /// SAFETY: `F`'s param is [`ReadOnlySystemParam`], so this system will only read from the world. diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index a8578ae3d4357..f9eb9c95d7822 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -112,6 +112,7 @@ mod query; #[allow(clippy::module_inception)] mod system; mod system_param; +mod system_param_user_meta; mod system_registry; use std::borrow::Cow; @@ -126,6 +127,7 @@ pub use query::*; pub use system::*; pub use system_param::*; pub use system_registry::*; +pub use system_param_user_meta::*; use crate::world::World; diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index fd03b94fa2b55..7cfb768852da1 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -9,7 +9,7 @@ use crate::{archetype::ArchetypeComponentId, component::ComponentId, query::Acce use std::any::TypeId; use std::borrow::Cow; -use super::IntoSystem; +use super::{IntoSystem, SystemParamUserMetaRequest}; /// An ECS system that can be added to a [`Schedule`](crate::schedule::Schedule) /// @@ -39,6 +39,12 @@ pub trait System: Send + Sync + 'static { /// Returns true if the system is [`Send`]. fn is_send(&self) -> bool; + /// Request user metadata for the parameters of the system. + /// + /// This function cannot be called from outside of `bevy_ecs`. + /// Use [`dyn System::param_user_meta_dyn`] instead. + fn param_user_meta(&self, request: &mut SystemParamUserMetaRequest); + /// Returns true if the system must be run exclusively. fn is_exclusive(&self) -> bool; @@ -108,6 +114,16 @@ pub trait System: Send + Sync + 'static { fn set_last_run(&mut self, last_run: Tick); } +// TODO(stepancheg): we shouldn't need 'static here. +impl dyn System { + /// Request user metadata from the system parameters. + /// + /// Metadata is defined with [`SystemParam::user_meta`](crate::system::SystemParam::user_meta). + pub fn param_user_meta_dyn(&self) -> Vec { + SystemParamUserMetaRequest::with(|req| self.param_user_meta(req)) + } +} + /// [`System`] types that do not modify the [`World`] when run. /// This is implemented for any systems whose parameters all implement [`ReadOnlySystemParam`]. /// diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 2b5d73d1ccfe0..d77cc12541c78 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -22,6 +22,7 @@ use std::{ marker::PhantomData, ops::{Deref, DerefMut}, }; +use crate::system::system_param_user_meta::SystemParamUserMetaRequest; /// A parameter that can be used in a [`System`](super::System). /// @@ -104,6 +105,16 @@ pub unsafe trait SystemParam: Sized { /// You could think of `SystemParam::Item<'w, 's>` as being an *operation* that changes the lifetimes bound to `Self`. type Item<'world, 'state>: SystemParam; + /// Provide user (i.e. unknown to bevy) metadata for the parameter. + /// + /// This function cannot be called directly. [`dyn System::param_user_meta_dyn] + /// can be used to fetch the metadata. + /// + /// Default implementation of this function does not do anything in Bevy, + /// except for composite params (i.e. tuples and `#[derive(SystemParam)]`) + /// which collect metadata from all of their fields. + fn user_meta(_request: &mut SystemParamUserMetaRequest) {} + /// Registers any [`World`] access used by this [`SystemParam`] /// and creates a new instance of this param's [`State`](Self::State). fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State; @@ -1363,6 +1374,10 @@ macro_rules! impl_system_param_tuple { type State = ($($param::State,)*); type Item<'w, 's> = ($($param::Item::<'w, 's>,)*); + fn user_meta(_request: &mut SystemParamUserMetaRequest) { + $($param::user_meta(_request);)* + } + #[inline] fn init_state(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State { (($($param::init_state(_world, _system_meta),)*)) diff --git a/crates/bevy_ecs/src/system/system_param_user_meta.rs b/crates/bevy_ecs/src/system/system_param_user_meta.rs new file mode 100644 index 0000000000000..a10c1c70736be --- /dev/null +++ b/crates/bevy_ecs/src/system/system_param_user_meta.rs @@ -0,0 +1,167 @@ +use std::any::TypeId; +use std::mem; +use std::mem::MaybeUninit; + +struct SystemParamUserMetaRequestImpl { + values: Vec, +} + +trait SystemParamUserMetaRequestDyn { + fn item_type_id(&self) -> TypeId; + unsafe fn provide_value(&mut self, value: *const ()); +} + +/// Request arbitrary metadata from [`SystemParam`](crate::system::SystemParam). +/// +/// See [`SystemParam::user_meta`](crate::system::SystemParam::user_meta) for more information. +pub struct SystemParamUserMetaRequest<'a> { + request: &'a mut dyn SystemParamUserMetaRequestDyn, +} + +impl SystemParamUserMetaRequestDyn for SystemParamUserMetaRequestImpl { + fn item_type_id(&self) -> TypeId { + TypeId::of::() + } + + unsafe fn provide_value(&mut self, value: *const ()) { + let value = value as *const T; + self.values.push(mem::transmute_copy(&*value)); + } +} + +impl<'a> SystemParamUserMetaRequest<'a> { + /// Provide the metadata value. + /// + /// This is a shortcut for [`provide_value_with`](Self::provide_value_with). + pub fn provide_value(&mut self, value: T) { + self.provide_value_with(|| value) + } + + /// Provide the metadata value. + pub fn provide_value_with(&mut self, value: impl FnOnce() -> T) { + unsafe { + if self.request.item_type_id() == TypeId::of::() { + let value = value(); + let value = MaybeUninit::new(value); + self.request.provide_value(value.as_ptr() as *const ()); + } + } + } + + pub(crate) fn with( + mut cb: impl FnMut(&mut SystemParamUserMetaRequest<'_>), + ) -> Vec { + let mut req_typed = SystemParamUserMetaRequestImpl { values: Vec::new() }; + let mut req = SystemParamUserMetaRequest { + request: &mut req_typed, + }; + cb(&mut req); + req_typed.values + } +} + +#[cfg(test)] +mod tests { + use crate as bevy_ecs; + use crate::component::Tick; + use crate::prelude::IntoSystem; + use crate::prelude::World; + use crate::system::system::System; + use crate::system::{ + ReadOnlySystemParam, Res, SystemMeta, SystemParam, SystemParamUserMetaRequest, + }; + use crate::world::unsafe_world_cell::UnsafeWorldCell; + use bevy_ecs_macros::Resource; + use std::any; + use std::marker::PhantomData; + + // Shortcut for test. + fn system_param_user_meta_request() -> Vec { + SystemParamUserMetaRequest::with(|req| P::user_meta(req)) + } + + struct MyTestParam(PhantomData); + + unsafe impl SystemParam for MyTestParam { + type State = (); + type Item<'world, 'state> = MyTestParam; + + fn init_state(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State { + unreachable!("not needed in test") + } + + fn user_meta(request: &mut SystemParamUserMetaRequest) { + // In test we provide metadata as strings. + // In production we may provide something like `InternedSystemSet`. + request.provide_value(format!("meta {}", any::type_name::())); + // We can provide multiple values. This is not used in test. + request.provide_value(10); + } + + unsafe fn get_param<'world, 'state>( + _state: &'state mut Self::State, + _system_meta: &SystemMeta, + _world: UnsafeWorldCell<'world>, + _change_tick: Tick, + ) -> Self::Item<'world, 'state> { + unreachable!("not needed in test") + } + } + + unsafe impl ReadOnlySystemParam for MyTestParam {} + + #[derive(Resource)] + struct MyRes(f32); + + #[derive(SystemParam)] + struct DerivedParam<'w, T: 'static> { + _p0: MyTestParam, + _p1: MyTestParam, + _p2: Res<'w, MyRes>, + } + + #[test] + fn test_param_meta() { + // Simple + assert_eq!( + vec![format!("meta {}", any::type_name::())], + system_param_user_meta_request::, String>() + ); + + // Tuple + assert_eq!( + vec![ + format!("meta {}", any::type_name::()), + format!("meta {}", any::type_name::>()), + ], + system_param_user_meta_request::<(MyTestParam, MyTestParam>), String>() + ); + + // Derive + assert_eq!( + vec![ + format!("meta {}", any::type_name::()), + format!("meta {}", any::type_name::>()), + ], + system_param_user_meta_request::>, String>() + ); + } + + #[test] + fn test_system_param_meta() { + fn my_system(_a: MyTestParam, _b: DerivedParam>) {} + + let my_system = IntoSystem::into_system(my_system); + + let my_system: &dyn System = &my_system; + + assert_eq!( + vec![ + format!("meta {}", any::type_name::()), + format!("meta {}", any::type_name::()), + format!("meta {}", any::type_name::>()), + ], + my_system.param_user_meta_dyn::() + ); + } +}