Skip to content

RFC: SystemParam::user_meta #10678

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

Closed
wants to merge 1 commit 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 crates/bevy_ecs/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
6 changes: 5 additions & 1 deletion crates/bevy_ecs/src/system/adapter_system.rs
Original file line number Diff line number Diff line change
@@ -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`]
Expand Down Expand Up @@ -146,6 +146,10 @@ where
fn default_system_sets(&self) -> Vec<InternedSystemSet> {
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.
Expand Down
7 changes: 6 additions & 1 deletion crates/bevy_ecs/src/system/combinator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`].
///
Expand Down Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions crates/bevy_ecs/src/system/exclusive_function_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -155,6 +156,10 @@ where
let set = crate::schedule::SystemTypeSet::<F>::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.
Expand Down
6 changes: 5 additions & 1 deletion crates/bevy_ecs/src/system/function_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -533,6 +533,10 @@ where
let set = crate::schedule::SystemTypeSet::<F>::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.
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_ecs/src/system/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down
18 changes: 17 additions & 1 deletion crates/bevy_ecs/src/system/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
///
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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<In: 'static, Out: 'static > dyn System<In=In, Out=Out> {
/// 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<T: 'static>(&self) -> Vec<T> {
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`].
///
Expand Down
15 changes: 15 additions & 0 deletions crates/bevy_ecs/src/system/system_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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).
///
Expand Down Expand Up @@ -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<State = Self::State>;

/// 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;
Expand Down Expand Up @@ -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),)*))
Expand Down
167 changes: 167 additions & 0 deletions crates/bevy_ecs/src/system/system_param_user_meta.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
use std::any::TypeId;
use std::mem;
use std::mem::MaybeUninit;

struct SystemParamUserMetaRequestImpl<T: 'static> {
values: Vec<T>,
}

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<T: 'static> SystemParamUserMetaRequestDyn for SystemParamUserMetaRequestImpl<T> {
fn item_type_id(&self) -> TypeId {
TypeId::of::<T>()
}

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<T: 'static>(&mut self, value: T) {
self.provide_value_with(|| value)
}

/// Provide the metadata value.
pub fn provide_value_with<T: 'static>(&mut self, value: impl FnOnce() -> T) {
unsafe {
if self.request.item_type_id() == TypeId::of::<T>() {
let value = value();
let value = MaybeUninit::new(value);
self.request.provide_value(value.as_ptr() as *const ());
}
}
}

pub(crate) fn with<T: 'static>(
mut cb: impl FnMut(&mut SystemParamUserMetaRequest<'_>),
) -> Vec<T> {
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<P: SystemParam, T: 'static>() -> Vec<T> {
SystemParamUserMetaRequest::with(|req| P::user_meta(req))
}

struct MyTestParam<T: 'static>(PhantomData<T>);

unsafe impl<T> SystemParam for MyTestParam<T> {
type State = ();
type Item<'world, 'state> = MyTestParam<T>;

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::<T>()));
// 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<T> ReadOnlySystemParam for MyTestParam<T> {}

#[derive(Resource)]
struct MyRes(f32);

#[derive(SystemParam)]
struct DerivedParam<'w, T: 'static> {
_p0: MyTestParam<u32>,
_p1: MyTestParam<T>,
_p2: Res<'w, MyRes>,
}

#[test]
fn test_param_meta() {
// Simple
assert_eq!(
vec![format!("meta {}", any::type_name::<u32>())],
system_param_user_meta_request::<MyTestParam<u32>, String>()
);

// Tuple
assert_eq!(
vec![
format!("meta {}", any::type_name::<u32>()),
format!("meta {}", any::type_name::<Vec<u32>>()),
],
system_param_user_meta_request::<(MyTestParam<u32>, MyTestParam<Vec<u32>>), String>()
);

// Derive
assert_eq!(
vec![
format!("meta {}", any::type_name::<u32>()),
format!("meta {}", any::type_name::<Vec<u32>>()),
],
system_param_user_meta_request::<DerivedParam<'_, Vec<u32>>, String>()
);
}

#[test]
fn test_system_param_meta() {
fn my_system(_a: MyTestParam<u8>, _b: DerivedParam<Vec<u32>>) {}

let my_system = IntoSystem::into_system(my_system);

let my_system: &dyn System<In = (), Out = ()> = &my_system;

assert_eq!(
vec![
format!("meta {}", any::type_name::<u8>()),
format!("meta {}", any::type_name::<u32>()),
format!("meta {}", any::type_name::<Vec<u32>>()),
],
my_system.param_user_meta_dyn::<String>()
);
}
}