mirror of
https://github.com/bevyengine/bevy
synced 2025-02-16 14:08:32 +00:00
bevy_reflect: Function registry (#14098)
# Objective #13152 added support for reflecting functions. Now, we need a way to register those functions such that they may be accessed anywhere within the ECS. ## Solution Added a `FunctionRegistry` type similar to `TypeRegistry`. This allows a function to be registered and retrieved by name. ```rust fn foo() -> i32 { 123 } let mut registry = FunctionRegistry::default(); registry.register("my_function", foo); let function = registry.get_mut("my_function").unwrap(); let value = function.call(ArgList::new()).unwrap().unwrap_owned(); assert_eq!(value.downcast_ref::<i32>(), Some(&123)); ``` Additionally, I added an `AppFunctionRegistry` resource which wraps a `FunctionRegistryArc`. Functions can be registered into this resource using `App::register_function` or by getting a mutable reference to the resource itself. ### Limitations #### `Send + Sync` In order to get this registry to work across threads, it needs to be `Send + Sync`. This means that `DynamicFunction` needs to be `Send + Sync`, which means that its internal function also needs to be `Send + Sync`. In most cases, this won't be an issue because standard Rust functions (the type most likely to be registered) are always `Send + Sync`. Additionally, closures tend to be `Send + Sync` as well, granted they don't capture any `!Send` or `!Sync` variables. This PR adds this `Send + Sync` requirement, but as mentioned above, it hopefully shouldn't be too big of an issue. #### Closures Unfortunately, closures can't be registered yet. This will likely be explored and added in a followup PR. ### Future Work Besides addressing the limitations listed above, another thing we could look into is improving the lookup of registered functions. One aspect is in the performance of hashing strings. The other is in the developer experience of having to call `std::any::type_name_of_val` to get the name of their function (assuming they didn't give it a custom name). ## Testing You can run the tests locally with: ``` cargo test --package bevy_reflect ``` --- ## Changelog - Added `FunctionRegistry` - Added `AppFunctionRegistry` (a `Resource` available from `bevy_ecs`) - Added `FunctionRegistryArc` - Added `FunctionRegistrationError` - Added `reflect_functions` feature to `bevy_ecs` and `bevy_app` - `FunctionInfo` is no longer `Default` - `DynamicFunction` now requires its wrapped function be `Send + Sync` ## Internal Migration Guide > [!important] > Function reflection was introduced as part of the 0.15 dev cycle. This migration guide was written for developers relying on `main` during this cycle, and is not a breaking change coming from 0.14. `DynamicFunction` (both those created manually and those created with `IntoFunction`), now require `Send + Sync`. All standard Rust functions should meet that requirement. Closures, on the other hand, may not if they capture any `!Send` or `!Sync` variables from its environment.
This commit is contained in:
parent
ec4cf024f8
commit
df61117850
18 changed files with 556 additions and 49 deletions
|
@ -13,6 +13,11 @@ trace = []
|
|||
bevy_debug_stepping = []
|
||||
default = ["bevy_reflect"]
|
||||
bevy_reflect = ["dep:bevy_reflect", "bevy_ecs/bevy_reflect"]
|
||||
reflect_functions = [
|
||||
"bevy_reflect",
|
||||
"bevy_reflect/functions",
|
||||
"bevy_ecs/reflect_functions",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
|
|
|
@ -96,6 +96,10 @@ impl Default for App {
|
|||
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
app.init_resource::<AppTypeRegistry>();
|
||||
|
||||
#[cfg(feature = "reflect_functions")]
|
||||
app.init_resource::<AppFunctionRegistry>();
|
||||
|
||||
app.add_plugins(MainSchedulePlugin);
|
||||
app.add_systems(
|
||||
First,
|
||||
|
@ -553,7 +557,7 @@ impl App {
|
|||
self
|
||||
}
|
||||
|
||||
/// Registers the type `T` in the [`TypeRegistry`](bevy_reflect::TypeRegistry) resource,
|
||||
/// Registers the type `T` in the [`AppTypeRegistry`] resource,
|
||||
/// adding reflect data as specified in the [`Reflect`](bevy_reflect::Reflect) derive:
|
||||
/// ```ignore (No serde "derive" feature)
|
||||
/// #[derive(Component, Serialize, Deserialize, Reflect)]
|
||||
|
@ -567,7 +571,7 @@ impl App {
|
|||
self
|
||||
}
|
||||
|
||||
/// Associates type data `D` with type `T` in the [`TypeRegistry`](bevy_reflect::TypeRegistry) resource.
|
||||
/// Associates type data `D` with type `T` in the [`AppTypeRegistry`] resource.
|
||||
///
|
||||
/// Most of the time [`register_type`](Self::register_type) can be used instead to register a
|
||||
/// type you derived [`Reflect`](bevy_reflect::Reflect) for. However, in cases where you want to
|
||||
|
@ -599,6 +603,74 @@ impl App {
|
|||
self
|
||||
}
|
||||
|
||||
/// Registers the given function into the [`AppFunctionRegistry`] resource using the given name.
|
||||
///
|
||||
/// To avoid conflicts, it's recommended to use a unique name for the function.
|
||||
/// This can be achieved by either using the function's [type name] or
|
||||
/// by "namespacing" the function with a unique identifier,
|
||||
/// such as the name of your crate.
|
||||
///
|
||||
/// For example, to register a function, `add`, from a crate, `my_crate`,
|
||||
/// you could use the name, `"my_crate::add"`.
|
||||
///
|
||||
/// Only functions that implement [`IntoFunction`] may be registered via this method.
|
||||
///
|
||||
/// See [`FunctionRegistry::register`] for more information.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if a function has already been registered with the given name.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use bevy_app::App;
|
||||
///
|
||||
/// fn yell(text: String) {
|
||||
/// println!("{}!", text);
|
||||
/// }
|
||||
///
|
||||
/// App::new()
|
||||
/// // Registering an anonymous function with a unique name
|
||||
/// .register_function("my_crate::yell_louder", |text: String| {
|
||||
/// println!("{}!!!", text.to_uppercase());
|
||||
/// })
|
||||
/// // Registering an existing function with its type name
|
||||
/// .register_function(std::any::type_name_of_val(&yell), yell)
|
||||
/// // Registering an existing function with a custom name
|
||||
/// .register_function("my_crate::yell", yell);
|
||||
/// ```
|
||||
///
|
||||
/// Names must be unique.
|
||||
///
|
||||
/// ```should_panic
|
||||
/// use bevy_app::App;
|
||||
///
|
||||
/// fn one() {}
|
||||
/// fn two() {}
|
||||
///
|
||||
/// App::new()
|
||||
/// .register_function("my_function", one)
|
||||
/// // Panic! A function has already been registered with the name "my_function"
|
||||
/// .register_function("my_function", two);
|
||||
/// ```
|
||||
///
|
||||
/// [type name]: std::any::type_name
|
||||
/// [`IntoFunction`]: bevy_reflect::func::IntoFunction
|
||||
/// [`FunctionRegistry::register`]: bevy_reflect::func::FunctionRegistry::register
|
||||
#[cfg(feature = "reflect_functions")]
|
||||
pub fn register_function<F, Marker>(
|
||||
&mut self,
|
||||
name: impl Into<std::borrow::Cow<'static, str>>,
|
||||
function: F,
|
||||
) -> &mut Self
|
||||
where
|
||||
F: bevy_reflect::func::IntoFunction<Marker> + 'static,
|
||||
{
|
||||
self.main_mut().register_function(name, function);
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns a reference to the [`World`].
|
||||
pub fn world(&self) -> &World {
|
||||
self.main().world()
|
||||
|
|
|
@ -408,6 +408,21 @@ impl SubApp {
|
|||
registry.write().register_type_data::<T, D>();
|
||||
self
|
||||
}
|
||||
|
||||
/// See [`App::register_function`].
|
||||
#[cfg(feature = "reflect_functions")]
|
||||
pub fn register_function<F, Marker>(
|
||||
&mut self,
|
||||
name: impl Into<std::borrow::Cow<'static, str>>,
|
||||
function: F,
|
||||
) -> &mut Self
|
||||
where
|
||||
F: bevy_reflect::func::IntoFunction<Marker> + 'static,
|
||||
{
|
||||
let registry = self.world.resource_mut::<AppFunctionRegistry>();
|
||||
registry.write().register(name, function).unwrap();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// The collection of sub-apps that belong to an [`App`].
|
||||
|
|
|
@ -17,6 +17,7 @@ multi_threaded = ["bevy_tasks/multi_threaded", "arrayvec"]
|
|||
bevy_debug_stepping = []
|
||||
serialize = ["dep:serde"]
|
||||
track_change_detection = []
|
||||
reflect_functions = ["bevy_reflect", "bevy_reflect/functions"]
|
||||
|
||||
[dependencies]
|
||||
bevy_ptr = { path = "../bevy_ptr", version = "0.15.0-dev" }
|
||||
|
|
|
@ -36,6 +36,9 @@ pub use bevy_ptr as ptr;
|
|||
|
||||
/// Most commonly used re-exported types.
|
||||
pub mod prelude {
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "reflect_functions")]
|
||||
pub use crate::reflect::AppFunctionRegistry;
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
pub use crate::reflect::{
|
||||
|
|
|
@ -43,6 +43,32 @@ impl DerefMut for AppTypeRegistry {
|
|||
}
|
||||
}
|
||||
|
||||
/// A [`Resource`] storing [`FunctionRegistry`] for
|
||||
/// function registrations relevant to a whole app.
|
||||
///
|
||||
/// [`FunctionRegistry`]: bevy_reflect::func::FunctionRegistry
|
||||
#[cfg(feature = "reflect_functions")]
|
||||
#[derive(Resource, Clone, Default)]
|
||||
pub struct AppFunctionRegistry(pub bevy_reflect::func::FunctionRegistryArc);
|
||||
|
||||
#[cfg(feature = "reflect_functions")]
|
||||
impl Deref for AppFunctionRegistry {
|
||||
type Target = bevy_reflect::func::FunctionRegistryArc;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "reflect_functions")]
|
||||
impl DerefMut for AppFunctionRegistry {
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a `T` from a `&dyn Reflect`.
|
||||
///
|
||||
/// This will try the following strategies, in this order:
|
||||
|
|
|
@ -199,7 +199,11 @@ bevy_state = ["dep:bevy_state"]
|
|||
track_change_detection = ["bevy_ecs/track_change_detection"]
|
||||
|
||||
# Enable function reflection
|
||||
reflect_functions = ["bevy_reflect/functions"]
|
||||
reflect_functions = [
|
||||
"bevy_reflect/functions",
|
||||
"bevy_app/reflect_functions",
|
||||
"bevy_ecs/reflect_functions",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
|
|
|
@ -129,7 +129,7 @@ impl<'env> DynamicClosure<'env> {
|
|||
/// Names for arguments and the closure itself are optional and will default to `_` if not provided.
|
||||
impl<'env> Debug for DynamicClosure<'env> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||
let name = self.info.name().unwrap_or("_");
|
||||
let name = self.info.name();
|
||||
write!(f, "DynamicClosure(fn {name}(")?;
|
||||
|
||||
for (index, arg) in self.info.args().iter().enumerate() {
|
||||
|
@ -164,7 +164,7 @@ mod tests {
|
|||
let func = (|a: i32, b: i32| a + b + c)
|
||||
.into_closure()
|
||||
.with_name("my_closure");
|
||||
assert_eq!(func.info().name(), Some("my_closure"));
|
||||
assert_eq!(func.info().name(), "my_closure");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -171,7 +171,7 @@ impl<'env> DynamicClosureMut<'env> {
|
|||
/// Names for arguments and the closure itself are optional and will default to `_` if not provided.
|
||||
impl<'env> Debug for DynamicClosureMut<'env> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||
let name = self.info.name().unwrap_or("_");
|
||||
let name = self.info.name();
|
||||
write!(f, "DynamicClosureMut(fn {name}(")?;
|
||||
|
||||
for (index, arg) in self.info.args().iter().enumerate() {
|
||||
|
@ -206,7 +206,7 @@ mod tests {
|
|||
let func = (|a: i32, b: i32| total = a + b)
|
||||
.into_closure_mut()
|
||||
.with_name("my_closure");
|
||||
assert_eq!(func.info().name(), Some("my_closure"));
|
||||
assert_eq!(func.info().name(), "my_closure");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -57,7 +57,7 @@ mod tests {
|
|||
let func = (|a: i32, b: i32| a + b + c).into_closure();
|
||||
assert_eq!(
|
||||
func.info().name(),
|
||||
Some("bevy_reflect::func::closures::into_closure::tests::should_default_with_closure_type_name::{{closure}}")
|
||||
"bevy_reflect::func::closures::into_closure::tests::should_default_with_closure_type_name::{{closure}}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ mod tests {
|
|||
let func = (|a: i32, b: i32| total = a + b).into_closure_mut();
|
||||
assert_eq!(
|
||||
func.info().name(),
|
||||
Some("bevy_reflect::func::closures::into_closure_mut::tests::should_default_with_closure_type_name::{{closure}}")
|
||||
"bevy_reflect::func::closures::into_closure_mut::tests::should_default_with_closure_type_name::{{closure}}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::func::args::ArgError;
|
||||
use crate::func::Return;
|
||||
use alloc::borrow::Cow;
|
||||
use thiserror::Error;
|
||||
|
||||
/// An error that occurs when calling a [`DynamicFunction`] or [`DynamicClosure`].
|
||||
|
@ -24,3 +25,15 @@ pub enum FunctionError {
|
|||
/// [`DynamicFunction`]: crate::func::DynamicFunction
|
||||
/// [`DynamicClosure`]: crate::func::DynamicClosure
|
||||
pub type FunctionResult<'a> = Result<Return<'a>, FunctionError>;
|
||||
|
||||
/// An error that occurs when registering a function into a [`FunctionRegistry`].
|
||||
///
|
||||
/// [`FunctionRegistry`]: crate::func::FunctionRegistry
|
||||
#[derive(Debug, Error, PartialEq)]
|
||||
pub enum FunctionRegistrationError {
|
||||
/// A function with the given name has already been registered.
|
||||
///
|
||||
/// Contains the duplicate function name.
|
||||
#[error("a function has already been registered with name {0:?}")]
|
||||
DuplicateName(Cow<'static, str>),
|
||||
}
|
||||
|
|
|
@ -60,8 +60,7 @@ use crate::func::{FunctionResult, IntoFunction, ReturnInfo};
|
|||
///
|
||||
/// // Instead, we need to define the function manually.
|
||||
/// // We start by defining the shape of the function:
|
||||
/// let info = FunctionInfo::new()
|
||||
/// .with_name("append")
|
||||
/// let info = FunctionInfo::new("append")
|
||||
/// .with_arg::<String>("value")
|
||||
/// .with_arg::<&mut Vec<String>>("list")
|
||||
/// .with_return::<&mut String>();
|
||||
|
@ -93,7 +92,7 @@ use crate::func::{FunctionResult, IntoFunction, ReturnInfo};
|
|||
/// [module-level documentation]: crate::func
|
||||
pub struct DynamicFunction {
|
||||
info: FunctionInfo,
|
||||
func: Arc<dyn for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + 'static>,
|
||||
func: Arc<dyn for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + Send + Sync + 'static>,
|
||||
}
|
||||
|
||||
impl DynamicFunction {
|
||||
|
@ -103,7 +102,7 @@ impl DynamicFunction {
|
|||
///
|
||||
/// It's important that the function signature matches the provided [`FunctionInfo`].
|
||||
/// This info may be used by consumers of the function for validation and debugging.
|
||||
pub fn new<F: for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + 'static>(
|
||||
pub fn new<F: for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + Send + Sync + 'static>(
|
||||
func: F,
|
||||
info: FunctionInfo,
|
||||
) -> Self {
|
||||
|
@ -162,16 +161,29 @@ impl DynamicFunction {
|
|||
pub fn info(&self) -> &FunctionInfo {
|
||||
&self.info
|
||||
}
|
||||
|
||||
/// The [name] of the function.
|
||||
///
|
||||
/// For [`DynamicFunctions`] created using [`IntoFunction`],
|
||||
/// the name will always be the full path to the function as returned by [`std::any::type_name`].
|
||||
/// This can be overridden using [`with_name`].
|
||||
///
|
||||
/// [name]: FunctionInfo::name
|
||||
/// [`DynamicFunctions`]: DynamicFunction
|
||||
/// [`with_name`]: Self::with_name
|
||||
pub fn name(&self) -> &Cow<'static, str> {
|
||||
self.info.name()
|
||||
}
|
||||
}
|
||||
|
||||
/// Outputs the function signature.
|
||||
///
|
||||
/// This takes the format: `DynamicFunction(fn {name}({arg1}: {type1}, {arg2}: {type2}, ...) -> {return_type})`.
|
||||
///
|
||||
/// Names for arguments and the function itself are optional and will default to `_` if not provided.
|
||||
/// Names for arguments are optional and will default to `_` if not provided.
|
||||
impl Debug for DynamicFunction {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||
let name = self.info.name().unwrap_or("_");
|
||||
let name = self.name();
|
||||
write!(f, "DynamicFunction(fn {name}(")?;
|
||||
|
||||
for (index, arg) in self.info.args().iter().enumerate() {
|
||||
|
@ -215,7 +227,7 @@ mod tests {
|
|||
fn foo() {}
|
||||
|
||||
let func = foo.into_function().with_name("my_function");
|
||||
assert_eq!(func.info().name(), Some("my_function"));
|
||||
assert_eq!(func.info().name(), "my_function");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -241,8 +253,7 @@ mod tests {
|
|||
let index = args.pop::<usize>()?;
|
||||
Ok(Return::Ref(get(index, list)))
|
||||
},
|
||||
FunctionInfo::new()
|
||||
.with_name("get")
|
||||
FunctionInfo::new("get")
|
||||
.with_arg::<usize>("index")
|
||||
.with_arg::<&Vec<String>>("list")
|
||||
.with_return::<&String>(),
|
||||
|
|
|
@ -14,18 +14,16 @@ use crate::TypePath;
|
|||
/// [`DynamicClosure`]: crate::func::DynamicClosure
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FunctionInfo {
|
||||
name: Option<Cow<'static, str>>,
|
||||
name: Cow<'static, str>,
|
||||
args: Vec<ArgInfo>,
|
||||
return_info: ReturnInfo,
|
||||
}
|
||||
|
||||
impl FunctionInfo {
|
||||
/// Create a new [`FunctionInfo`].
|
||||
///
|
||||
/// To set the name of the function, use [`Self::with_name`].
|
||||
pub fn new() -> Self {
|
||||
/// Create a new [`FunctionInfo`] for a function with the given name.
|
||||
pub fn new(name: impl Into<Cow<'static, str>>) -> Self {
|
||||
Self {
|
||||
name: None,
|
||||
name: name.into(),
|
||||
args: Vec::new(),
|
||||
return_info: ReturnInfo::new::<()>(),
|
||||
}
|
||||
|
@ -40,11 +38,8 @@ impl FunctionInfo {
|
|||
}
|
||||
|
||||
/// Set the name of the function.
|
||||
///
|
||||
/// Reflected functions are not required to have a name,
|
||||
/// so this method must be called manually to set the name.
|
||||
pub fn with_name(mut self, name: impl Into<Cow<'static, str>>) -> Self {
|
||||
self.name = Some(name.into());
|
||||
self.name = name.into();
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -94,7 +89,7 @@ impl FunctionInfo {
|
|||
self
|
||||
}
|
||||
|
||||
/// The name of the function, if it was given one.
|
||||
/// The name of the function.
|
||||
///
|
||||
/// For [`DynamicFunctions`] created using [`IntoFunction`] or [`DynamicClosures`] created using [`IntoClosure`],
|
||||
/// the name will always be the full path to the function as returned by [`std::any::type_name`].
|
||||
|
@ -103,8 +98,8 @@ impl FunctionInfo {
|
|||
/// [`IntoFunction`]: crate::func::IntoFunction
|
||||
/// [`DynamicClosures`]: crate::func::DynamicClosure
|
||||
/// [`IntoClosure`]: crate::func::IntoClosure
|
||||
pub fn name(&self) -> Option<&str> {
|
||||
self.name.as_deref()
|
||||
pub fn name(&self) -> &Cow<'static, str> {
|
||||
&self.name
|
||||
}
|
||||
|
||||
/// The arguments of the function.
|
||||
|
@ -123,12 +118,6 @@ impl FunctionInfo {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for FunctionInfo {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about the return type of a [`DynamicFunction`] or [`DynamicClosure`].
|
||||
///
|
||||
/// [`DynamicFunction`]: crate::func::DynamicFunction
|
||||
|
@ -192,7 +181,7 @@ impl ReturnInfo {
|
|||
/// }
|
||||
///
|
||||
/// let info = print.get_function_info();
|
||||
/// assert!(info.name().unwrap().ends_with("print"));
|
||||
/// assert!(info.name().ends_with("print"));
|
||||
/// assert_eq!(info.arg_count(), 1);
|
||||
/// assert_eq!(info.args()[0].type_path(), "alloc::string::String");
|
||||
/// assert_eq!(info.return_info().type_path(), "()");
|
||||
|
@ -233,8 +222,7 @@ macro_rules! impl_typed_function {
|
|||
Function: FnMut($($Arg),*) -> ReturnType,
|
||||
{
|
||||
fn function_info() -> FunctionInfo {
|
||||
FunctionInfo::new()
|
||||
.with_name(std::any::type_name::<Function>())
|
||||
FunctionInfo::new(std::borrow::Cow::Borrowed(std::any::type_name::<Function>()))
|
||||
.with_args({
|
||||
#[allow(unused_mut)]
|
||||
let mut _index = 0;
|
||||
|
@ -258,8 +246,7 @@ macro_rules! impl_typed_function {
|
|||
Function: for<'a> FnMut(&'a Receiver, $($Arg),*) -> &'a ReturnType,
|
||||
{
|
||||
fn function_info() -> $crate::func::FunctionInfo {
|
||||
FunctionInfo::new()
|
||||
.with_name(std::any::type_name::<Function>())
|
||||
FunctionInfo::new(std::borrow::Cow::Borrowed(std::any::type_name::<Function>()))
|
||||
.with_args({
|
||||
#[allow(unused_mut)]
|
||||
let mut _index = 1;
|
||||
|
@ -284,8 +271,7 @@ macro_rules! impl_typed_function {
|
|||
Function: for<'a> FnMut(&'a mut Receiver, $($Arg),*) -> &'a mut ReturnType,
|
||||
{
|
||||
fn function_info() -> FunctionInfo {
|
||||
FunctionInfo::new()
|
||||
.with_name(std::any::type_name::<Function>())
|
||||
FunctionInfo::new(std::borrow::Cow::Borrowed(std::any::type_name::<Function>()))
|
||||
.with_args({
|
||||
#[allow(unused_mut)]
|
||||
let mut _index = 1;
|
||||
|
@ -310,8 +296,7 @@ macro_rules! impl_typed_function {
|
|||
Function: for<'a> FnMut(&'a mut Receiver, $($Arg),*) -> &'a ReturnType,
|
||||
{
|
||||
fn function_info() -> FunctionInfo {
|
||||
FunctionInfo::new()
|
||||
.with_name(std::any::type_name::<Function>())
|
||||
FunctionInfo::new(std::borrow::Cow::Borrowed(std::any::type_name::<Function>()))
|
||||
.with_args({
|
||||
#[allow(unused_mut)]
|
||||
let mut _index = 1;
|
||||
|
|
|
@ -174,7 +174,7 @@ mod tests {
|
|||
let func = foo.into_function();
|
||||
assert_eq!(
|
||||
func.info().name(),
|
||||
Some("bevy_reflect::func::into_function::tests::should_default_with_function_type_name::foo")
|
||||
"bevy_reflect::func::into_function::tests::should_default_with_function_type_name::foo"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,6 +107,7 @@ pub use info::*;
|
|||
pub use into_function::*;
|
||||
pub use reflect_fn::*;
|
||||
pub use reflect_fn_mut::*;
|
||||
pub use registry::*;
|
||||
pub use return_type::*;
|
||||
|
||||
pub mod args;
|
||||
|
@ -118,6 +119,7 @@ mod into_function;
|
|||
pub(crate) mod macros;
|
||||
mod reflect_fn;
|
||||
mod reflect_fn_mut;
|
||||
mod registry;
|
||||
mod return_type;
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
367
crates/bevy_reflect/src/func/registry.rs
Normal file
367
crates/bevy_reflect/src/func/registry.rs
Normal file
|
@ -0,0 +1,367 @@
|
|||
use alloc::borrow::Cow;
|
||||
use core::fmt::Debug;
|
||||
use std::sync::{Arc, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
|
||||
use bevy_utils::HashMap;
|
||||
|
||||
use crate::func::{DynamicFunction, FunctionRegistrationError, IntoFunction};
|
||||
|
||||
/// A registry of [reflected functions].
|
||||
///
|
||||
/// This is the function-equivalent to the [`TypeRegistry`].
|
||||
///
|
||||
/// [reflected functions]: crate::func
|
||||
/// [`TypeRegistry`]: crate::TypeRegistry
|
||||
#[derive(Default)]
|
||||
pub struct FunctionRegistry {
|
||||
/// Maps function [names] to their respective [`DynamicFunctions`].
|
||||
///
|
||||
/// [names]: DynamicFunction::name
|
||||
/// [`DynamicFunctions`]: DynamicFunction
|
||||
functions: HashMap<Cow<'static, str>, DynamicFunction>,
|
||||
}
|
||||
|
||||
impl FunctionRegistry {
|
||||
/// Attempts to register the given function with the given name.
|
||||
///
|
||||
/// This function accepts both functions that satisfy [`IntoFunction`]
|
||||
/// and direct [`DynamicFunction`] instances.
|
||||
/// The given function will internally be stored as a [`DynamicFunction`]
|
||||
/// with its [name] set to the given name.
|
||||
///
|
||||
/// If a registered function with the same name already exists,
|
||||
/// it will not be registered again and an error will be returned.
|
||||
/// To register the function anyway, overwriting any existing registration,
|
||||
/// use [`overwrite_registration`] instead.
|
||||
///
|
||||
/// To avoid conflicts, it's recommended to use a unique name for the function.
|
||||
/// This can be achieved by either using the function's [type name] or
|
||||
/// by "namespacing" the function with a unique identifier,
|
||||
/// such as the name of your crate.
|
||||
///
|
||||
/// For example, to register a function, `add`, from a crate, `my_crate`,
|
||||
/// you could use the name, `"my_crate::add"`.
|
||||
///
|
||||
/// This method is a convenience around calling [`IntoFunction::into_function`] and [`DynamicFunction::with_name`]
|
||||
/// on the function and inserting it into the registry using the [`register_dynamic`] method.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_reflect::func::{FunctionRegistrationError, FunctionRegistry};
|
||||
/// fn mul(a: i32, b: i32) -> i32 {
|
||||
/// a * b
|
||||
/// }
|
||||
///
|
||||
/// # fn main() -> Result<(), FunctionRegistrationError> {
|
||||
/// let mut registry = FunctionRegistry::default();
|
||||
/// registry
|
||||
/// // Registering an anonymous function with a unique name
|
||||
/// .register("my_crate::add", |a: i32, b: i32| {
|
||||
/// a + b
|
||||
/// })?
|
||||
/// // Registering an existing function with its type name
|
||||
/// .register(std::any::type_name_of_val(&mul), mul)?
|
||||
/// // Registering an existing function with a custom name
|
||||
/// .register("my_crate::mul", mul)?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Names must be unique.
|
||||
///
|
||||
/// ```should_panic
|
||||
/// # use bevy_reflect::func::FunctionRegistry;
|
||||
/// fn one() {}
|
||||
/// fn two() {}
|
||||
///
|
||||
/// let mut registry = FunctionRegistry::default();
|
||||
/// registry.register("my_function", one).unwrap();
|
||||
///
|
||||
/// // Panic! A function has already been registered with the name "my_function"
|
||||
/// registry.register("my_function", two).unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// [name]: DynamicFunction::name
|
||||
/// [`overwrite_registration`]: Self::overwrite_registration
|
||||
/// [type name]: std::any::type_name
|
||||
/// [`register_dynamic`]: Self::register_dynamic
|
||||
pub fn register<F, Marker>(
|
||||
&mut self,
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
function: F,
|
||||
) -> Result<&mut Self, FunctionRegistrationError>
|
||||
where
|
||||
F: IntoFunction<Marker> + 'static,
|
||||
{
|
||||
let function = function.into_function().with_name(name);
|
||||
self.register_dynamic(function)
|
||||
}
|
||||
|
||||
/// Attempts to register a [`DynamicFunction`] directly using its [name] as the key.
|
||||
///
|
||||
/// If a registered function with the same name already exists,
|
||||
/// it will not be registered again and an error will be returned.
|
||||
/// To register the function anyway, overwriting any existing registration,
|
||||
/// use [`overwrite_registration_dynamic`] instead.
|
||||
///
|
||||
/// You can change the name of the function using [`DynamicFunction::with_name`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_reflect::func::{DynamicFunction, FunctionRegistrationError, FunctionRegistry, IntoFunction};
|
||||
/// fn add(a: i32, b: i32) -> i32 {
|
||||
/// a + b
|
||||
/// }
|
||||
///
|
||||
/// # fn main() -> Result<(), FunctionRegistrationError> {
|
||||
/// let mut registry = FunctionRegistry::default();
|
||||
///
|
||||
/// // Register a `DynamicFunction` directly
|
||||
/// let function: DynamicFunction = add.into_function();
|
||||
/// registry.register_dynamic(function)?;
|
||||
///
|
||||
/// // Register a `DynamicFunction` with a custom name
|
||||
/// let function: DynamicFunction = add.into_function().with_name("my_crate::add");
|
||||
/// registry.register_dynamic(function)?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Names must be unique.
|
||||
///
|
||||
/// ```should_panic
|
||||
/// # use bevy_reflect::func::{DynamicFunction, FunctionRegistry, IntoFunction};
|
||||
/// fn one() {}
|
||||
/// fn two() {}
|
||||
///
|
||||
/// let mut registry = FunctionRegistry::default();
|
||||
/// registry.register_dynamic(one.into_function().with_name("my_function")).unwrap();
|
||||
///
|
||||
/// // Panic! A function has already been registered with the name "my_function"
|
||||
/// registry.register_dynamic(two.into_function().with_name("my_function")).unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// [name]: DynamicFunction::name
|
||||
/// [`overwrite_registration_dynamic`]: Self::overwrite_registration_dynamic
|
||||
pub fn register_dynamic(
|
||||
&mut self,
|
||||
function: DynamicFunction,
|
||||
) -> Result<&mut Self, FunctionRegistrationError> {
|
||||
let name = function.name().clone();
|
||||
self.functions
|
||||
.try_insert(name, function)
|
||||
.map_err(|err| FunctionRegistrationError::DuplicateName(err.entry.key().clone()))?;
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Registers the given function, overwriting any existing registration.
|
||||
///
|
||||
/// This function accepts both functions that satisfy [`IntoFunction`]
|
||||
/// and direct [`DynamicFunction`] instances.
|
||||
/// The given function will internally be stored as a [`DynamicFunction`]
|
||||
/// with its [name] set to the given name.
|
||||
///
|
||||
/// Functions are mapped according to their name.
|
||||
/// To avoid overwriting existing registrations,
|
||||
/// it's recommended to use the [`register`] method instead.
|
||||
///
|
||||
/// This method is a convenience around calling [`IntoFunction::into_function`] and [`DynamicFunction::with_name`]
|
||||
/// on the function and inserting it into the registry using the [`overwrite_registration_dynamic`] method.
|
||||
///
|
||||
/// [name]: DynamicFunction::name
|
||||
/// [`register`]: Self::register
|
||||
/// [`overwrite_registration_dynamic`]: Self::overwrite_registration_dynamic
|
||||
pub fn overwrite_registration<F, Marker>(
|
||||
&mut self,
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
function: F,
|
||||
) where
|
||||
F: IntoFunction<Marker> + 'static,
|
||||
{
|
||||
let function = function.into_function().with_name(name);
|
||||
self.overwrite_registration_dynamic(function);
|
||||
}
|
||||
|
||||
/// Registers the given [`DynamicFunction`], overwriting any existing registration.
|
||||
///
|
||||
/// The given function will internally be stored as a [`DynamicFunction`]
|
||||
/// with its [name] set to the given name.
|
||||
///
|
||||
/// Functions are mapped according to their name.
|
||||
/// To avoid overwriting existing registrations,
|
||||
/// it's recommended to use the [`register_dynamic`] method instead.
|
||||
///
|
||||
/// [name]: DynamicFunction::name
|
||||
/// [`register_dynamic`]: Self::register_dynamic
|
||||
pub fn overwrite_registration_dynamic(&mut self, function: DynamicFunction) {
|
||||
let name = function.name().clone();
|
||||
self.functions.insert(name, function);
|
||||
}
|
||||
|
||||
/// Get a reference to a registered function by [name].
|
||||
///
|
||||
/// [name]: DynamicFunction::name
|
||||
pub fn get(&self, name: &str) -> Option<&DynamicFunction> {
|
||||
self.functions.get(name)
|
||||
}
|
||||
|
||||
/// Returns `true` if a function with the given [name] is registered.
|
||||
///
|
||||
/// [name]: DynamicFunction::name
|
||||
pub fn contains(&self, name: &str) -> bool {
|
||||
self.functions.contains_key(name)
|
||||
}
|
||||
|
||||
/// Returns an iterator over all registered functions.
|
||||
pub fn iter(&self) -> impl ExactSizeIterator<Item = &DynamicFunction> {
|
||||
self.functions.values()
|
||||
}
|
||||
|
||||
/// Returns the number of registered functions.
|
||||
pub fn len(&self) -> usize {
|
||||
self.functions.len()
|
||||
}
|
||||
|
||||
/// Returns `true` if no functions are registered.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.functions.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for FunctionRegistry {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_set().entries(self.functions.values()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// A synchronized wrapper around a [`FunctionRegistry`].
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct FunctionRegistryArc {
|
||||
pub internal: Arc<RwLock<FunctionRegistry>>,
|
||||
}
|
||||
|
||||
impl FunctionRegistryArc {
|
||||
/// Takes a read lock on the underlying [`FunctionRegistry`].
|
||||
pub fn read(&self) -> RwLockReadGuard<'_, FunctionRegistry> {
|
||||
self.internal.read().unwrap_or_else(PoisonError::into_inner)
|
||||
}
|
||||
|
||||
/// Takes a write lock on the underlying [`FunctionRegistry`].
|
||||
pub fn write(&self) -> RwLockWriteGuard<'_, FunctionRegistry> {
|
||||
self.internal
|
||||
.write()
|
||||
.unwrap_or_else(PoisonError::into_inner)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::func::ArgList;
|
||||
|
||||
#[test]
|
||||
fn should_register_function() {
|
||||
fn foo() -> i32 {
|
||||
123
|
||||
}
|
||||
|
||||
let mut registry = FunctionRegistry::default();
|
||||
registry.register("foo", foo).unwrap();
|
||||
|
||||
let function = registry.get("foo").unwrap();
|
||||
let value = function.call(ArgList::new()).unwrap().unwrap_owned();
|
||||
assert_eq!(value.downcast_ref::<i32>(), Some(&123));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_register_anonymous_function() {
|
||||
let mut registry = FunctionRegistry::default();
|
||||
registry.register("foo", || 123_i32).unwrap();
|
||||
|
||||
let function = registry.get("foo").unwrap();
|
||||
let value = function.call(ArgList::new()).unwrap().unwrap_owned();
|
||||
assert_eq!(value.downcast_ref::<i32>(), Some(&123));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_register_dynamic_function() {
|
||||
fn foo() -> i32 {
|
||||
123
|
||||
}
|
||||
|
||||
let function = foo.into_function().with_name("custom_name");
|
||||
|
||||
let mut registry = FunctionRegistry::default();
|
||||
registry.register_dynamic(function).unwrap();
|
||||
|
||||
let function = registry.get("custom_name").unwrap();
|
||||
let value = function.call(ArgList::new()).unwrap().unwrap_owned();
|
||||
assert_eq!(value.downcast_ref::<i32>(), Some(&123));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_only_register_function_once() {
|
||||
fn foo() -> i32 {
|
||||
123
|
||||
}
|
||||
|
||||
fn bar() -> i32 {
|
||||
321
|
||||
}
|
||||
|
||||
let name = std::any::type_name_of_val(&foo);
|
||||
|
||||
let mut registry = FunctionRegistry::default();
|
||||
registry.register(name, foo).unwrap();
|
||||
let result = registry.register_dynamic(bar.into_function().with_name(name));
|
||||
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(FunctionRegistrationError::DuplicateName(_))
|
||||
));
|
||||
assert_eq!(registry.len(), 1);
|
||||
|
||||
let function = registry.get(std::any::type_name_of_val(&foo)).unwrap();
|
||||
let value = function.call(ArgList::new()).unwrap().unwrap_owned();
|
||||
assert_eq!(value.downcast_ref::<i32>(), Some(&123));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_allow_overwriting_registration() {
|
||||
fn foo() -> i32 {
|
||||
123
|
||||
}
|
||||
|
||||
fn bar() -> i32 {
|
||||
321
|
||||
}
|
||||
|
||||
let name = std::any::type_name_of_val(&foo);
|
||||
|
||||
let mut registry = FunctionRegistry::default();
|
||||
registry.register(name, foo).unwrap();
|
||||
registry.overwrite_registration_dynamic(bar.into_function().with_name(name));
|
||||
|
||||
assert_eq!(registry.len(), 1);
|
||||
|
||||
let function = registry.get(std::any::type_name_of_val(&foo)).unwrap();
|
||||
let value = function.call(ArgList::new()).unwrap().unwrap_owned();
|
||||
assert_eq!(value.downcast_ref::<i32>(), Some(&321));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_debug_function_registry() {
|
||||
fn foo() -> i32 {
|
||||
123
|
||||
}
|
||||
|
||||
let mut registry = FunctionRegistry::default();
|
||||
registry.register("foo", foo).unwrap();
|
||||
|
||||
let debug = format!("{:?}", registry);
|
||||
assert_eq!(debug, "{DynamicFunction(fn foo() -> i32)}");
|
||||
}
|
||||
}
|
|
@ -148,8 +148,11 @@ fn main() {
|
|||
|
||||
Ok(Return::Ref(get_or_insert(value, container)))
|
||||
},
|
||||
FunctionInfo::new()
|
||||
// We can optionally provide a name for the function.
|
||||
// All functions require a name.
|
||||
// We can either give it a custom name or use the function's name as
|
||||
// derived from `std::any::type_name_of_val`.
|
||||
FunctionInfo::new(std::any::type_name_of_val(&get_or_insert))
|
||||
// We can always change the name if needed.
|
||||
.with_name("get_or_insert")
|
||||
// Since our function takes arguments, we should provide that argument information.
|
||||
// This helps ensure that consumers of the function can validate the arguments they
|
||||
|
|
Loading…
Add table
Reference in a new issue