diff --git a/crates/bevy_app/Cargo.toml b/crates/bevy_app/Cargo.toml index 9f0f68054b97e..758654b656695 100644 --- a/crates/bevy_app/Cargo.toml +++ b/crates/bevy_app/Cargo.toml @@ -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 diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 721bfe03eb26f..b876aa32b836b 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -96,6 +96,10 @@ impl Default for App { #[cfg(feature = "bevy_reflect")] app.init_resource::(); + + #[cfg(feature = "reflect_functions")] + app.init_resource::(); + 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( + &mut self, + name: impl Into>, + function: F, + ) -> &mut Self + where + F: bevy_reflect::func::IntoFunction + 'static, + { + self.main_mut().register_function(name, function); + self + } + /// Returns a reference to the [`World`]. pub fn world(&self) -> &World { self.main().world() diff --git a/crates/bevy_app/src/sub_app.rs b/crates/bevy_app/src/sub_app.rs index 832f87357deb2..7679ec18b9cc9 100644 --- a/crates/bevy_app/src/sub_app.rs +++ b/crates/bevy_app/src/sub_app.rs @@ -408,6 +408,21 @@ impl SubApp { registry.write().register_type_data::(); self } + + /// See [`App::register_function`]. + #[cfg(feature = "reflect_functions")] + pub fn register_function( + &mut self, + name: impl Into>, + function: F, + ) -> &mut Self + where + F: bevy_reflect::func::IntoFunction + 'static, + { + let registry = self.world.resource_mut::(); + registry.write().register(name, function).unwrap(); + self + } } /// The collection of sub-apps that belong to an [`App`]. diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index cc83f5de0a9a1..173de89ad3c88 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -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" } diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 406beb92d9593..13ff2939fcfef 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -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::{ diff --git a/crates/bevy_ecs/src/reflect/mod.rs b/crates/bevy_ecs/src/reflect/mod.rs index 322bf3c9b544c..5b8a95f1390a7 100644 --- a/crates/bevy_ecs/src/reflect/mod.rs +++ b/crates/bevy_ecs/src/reflect/mod.rs @@ -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: diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index d3fdcc104dab5..2a5d2f6b4d688 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -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 diff --git a/crates/bevy_reflect/src/func/closures/dynamic_closure.rs b/crates/bevy_reflect/src/func/closures/dynamic_closure.rs index bbfe234cae2d7..bd4931e0738ce 100644 --- a/crates/bevy_reflect/src/func/closures/dynamic_closure.rs +++ b/crates/bevy_reflect/src/func/closures/dynamic_closure.rs @@ -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] diff --git a/crates/bevy_reflect/src/func/closures/dynamic_closure_mut.rs b/crates/bevy_reflect/src/func/closures/dynamic_closure_mut.rs index 3111b74760843..eacf6f69fedd5 100644 --- a/crates/bevy_reflect/src/func/closures/dynamic_closure_mut.rs +++ b/crates/bevy_reflect/src/func/closures/dynamic_closure_mut.rs @@ -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] diff --git a/crates/bevy_reflect/src/func/closures/into_closure.rs b/crates/bevy_reflect/src/func/closures/into_closure.rs index eddf762af83bb..c2808d51f927e 100644 --- a/crates/bevy_reflect/src/func/closures/into_closure.rs +++ b/crates/bevy_reflect/src/func/closures/into_closure.rs @@ -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}}" ); } } diff --git a/crates/bevy_reflect/src/func/closures/into_closure_mut.rs b/crates/bevy_reflect/src/func/closures/into_closure_mut.rs index 05a6d8e599cd3..289bb7970925a 100644 --- a/crates/bevy_reflect/src/func/closures/into_closure_mut.rs +++ b/crates/bevy_reflect/src/func/closures/into_closure_mut.rs @@ -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}}" ); } } diff --git a/crates/bevy_reflect/src/func/error.rs b/crates/bevy_reflect/src/func/error.rs index 636b32c8263e2..3bb638d0fd30d 100644 --- a/crates/bevy_reflect/src/func/error.rs +++ b/crates/bevy_reflect/src/func/error.rs @@ -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, 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>), +} diff --git a/crates/bevy_reflect/src/func/function.rs b/crates/bevy_reflect/src/func/function.rs index f602f0f25e07b..bf95e4752ed75 100644 --- a/crates/bevy_reflect/src/func/function.rs +++ b/crates/bevy_reflect/src/func/function.rs @@ -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::("value") /// .with_arg::<&mut Vec>("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 Fn(ArgList<'a>) -> FunctionResult<'a> + 'static>, + func: Arc 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 Fn(ArgList<'a>) -> FunctionResult<'a> + 'static>( + pub fn new 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::()?; Ok(Return::Ref(get(index, list))) }, - FunctionInfo::new() - .with_name("get") + FunctionInfo::new("get") .with_arg::("index") .with_arg::<&Vec>("list") .with_return::<&String>(), diff --git a/crates/bevy_reflect/src/func/info.rs b/crates/bevy_reflect/src/func/info.rs index 059a05373ad70..4a0849f865bb7 100644 --- a/crates/bevy_reflect/src/func/info.rs +++ b/crates/bevy_reflect/src/func/info.rs @@ -14,18 +14,16 @@ use crate::TypePath; /// [`DynamicClosure`]: crate::func::DynamicClosure #[derive(Debug, Clone)] pub struct FunctionInfo { - name: Option>, + name: Cow<'static, str>, args: Vec, 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>) -> 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>) -> 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::()) + FunctionInfo::new(std::borrow::Cow::Borrowed(std::any::type_name::())) .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::()) + FunctionInfo::new(std::borrow::Cow::Borrowed(std::any::type_name::())) .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::()) + FunctionInfo::new(std::borrow::Cow::Borrowed(std::any::type_name::())) .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::()) + FunctionInfo::new(std::borrow::Cow::Borrowed(std::any::type_name::())) .with_args({ #[allow(unused_mut)] let mut _index = 1; diff --git a/crates/bevy_reflect/src/func/into_function.rs b/crates/bevy_reflect/src/func/into_function.rs index dad36867a7095..d5f42cda732a4 100644 --- a/crates/bevy_reflect/src/func/into_function.rs +++ b/crates/bevy_reflect/src/func/into_function.rs @@ -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" ); } } diff --git a/crates/bevy_reflect/src/func/mod.rs b/crates/bevy_reflect/src/func/mod.rs index ce1340cd769fe..4bf4ce149d11b 100644 --- a/crates/bevy_reflect/src/func/mod.rs +++ b/crates/bevy_reflect/src/func/mod.rs @@ -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)] diff --git a/crates/bevy_reflect/src/func/registry.rs b/crates/bevy_reflect/src/func/registry.rs new file mode 100644 index 0000000000000..ec94dc07795ee --- /dev/null +++ b/crates/bevy_reflect/src/func/registry.rs @@ -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, 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( + &mut self, + name: impl Into>, + function: F, + ) -> Result<&mut Self, FunctionRegistrationError> + where + F: IntoFunction + '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( + &mut self, + name: impl Into>, + function: F, + ) where + F: IntoFunction + '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 { + 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>, +} + +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::(), 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::(), 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::(), 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::(), 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::(), 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)}"); + } +} diff --git a/examples/reflection/function_reflection.rs b/examples/reflection/function_reflection.rs index 8935c2096a520..54d5222af08eb 100644 --- a/examples/reflection/function_reflection.rs +++ b/examples/reflection/function_reflection.rs @@ -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