diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index b876aa32b836b..7188102837dea 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -603,19 +603,95 @@ impl App { self } + /// Registers the given function into the [`AppFunctionRegistry`] resource. + /// + /// The given function will internally be stored as a [`DynamicFunction`] + /// and mapped according to its [name]. + /// + /// Because the function must have a name, + /// anonymous functions (e.g. `|a: i32, b: i32| { a + b }`) must instead + /// be registered using [`register_function_with_name`] or converted to a [`DynamicFunction`] + /// and named using [`DynamicFunction::with_name`]. + /// Failure to do so will result in an error being returned. + /// + /// 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 + /// or if the function is missing a name (such as when it is an anonymous function). + /// + /// # Examples + /// + /// ``` + /// use bevy_app::App; + /// + /// fn add(a: i32, b: i32) -> i32 { + /// a + b + /// } + /// + /// App::new().register_function(add); + /// ``` + /// + /// Functions cannot be registered more than once. + /// + /// ```should_panic + /// use bevy_app::App; + /// + /// fn add(a: i32, b: i32) -> i32 { + /// a + b + /// } + /// + /// App::new() + /// .register_function(add) + /// // Panic! A function has already been registered with the name "my_function" + /// .register_function(add); + /// ``` + /// + /// Anonymous functions should be registered using [`register_function_with_name`] or given a name using [`DynamicFunction::with_name`]. + /// + /// ```should_panic + /// use bevy_app::App; + /// + /// // Panic! Anonymous functions cannot be registered using `register_function` + /// App::new().register_function(|a: i32, b: i32| a + b); + /// ``` + /// + /// [`register_function_with_name`]: Self::register_function_with_name + /// [`DynamicFunction`]: bevy_reflect::func::DynamicFunction + /// [name]: bevy_reflect::func::FunctionInfo::name + /// [`DynamicFunction::with_name`]: bevy_reflect::func::DynamicFunction::with_name + /// [`IntoFunction`]: bevy_reflect::func::IntoFunction + /// [`FunctionRegistry::register`]: bevy_reflect::func::FunctionRegistry::register + #[cfg(feature = "reflect_functions")] + pub fn register_function(&mut self, function: F) -> &mut Self + where + F: bevy_reflect::func::IntoFunction + 'static, + { + self.main_mut().register_function(function); + 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, + /// This can be achieved 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"`. /// + /// Another approach could be to use the [type name] of the function, + /// however, it should be noted that anonymous functions do _not_ have unique type names. + /// + /// For named functions (e.g. `fn add(a: i32, b: i32) -> i32 { a + b }`) where a custom name is not needed, + /// it's recommended to use [`register_function`] instead as the generated name is guaranteed to be unique. + /// /// Only functions that implement [`IntoFunction`] may be registered via this method. /// - /// See [`FunctionRegistry::register`] for more information. + /// See [`FunctionRegistry::register_with_name`] for more information. /// /// # Panics /// @@ -626,19 +702,24 @@ impl App { /// ``` /// use bevy_app::App; /// - /// fn yell(text: String) { - /// println!("{}!", text); + /// fn mul(a: i32, b: i32) -> i32 { + /// a * b /// } /// + /// let div = |a: i32, b: i32| a / b; + /// /// App::new() /// // Registering an anonymous function with a unique name - /// .register_function("my_crate::yell_louder", |text: String| { - /// println!("{}!!!", text.to_uppercase()); + /// .register_function_with_name("my_crate::add", |a: i32, b: i32| { + /// a + b /// }) /// // Registering an existing function with its type name - /// .register_function(std::any::type_name_of_val(&yell), yell) + /// .register_function_with_name(std::any::type_name_of_val(&mul), mul) /// // Registering an existing function with a custom name - /// .register_function("my_crate::yell", yell); + /// .register_function_with_name("my_crate::mul", mul) + /// // Be careful not to register anonymous functions with their type name. + /// // This code works but registers the function with the non-unique name of `fn(i32, i32) -> i32` + /// .register_function_with_name(std::any::type_name_of_val(&div), div); /// ``` /// /// Names must be unique. @@ -650,16 +731,17 @@ impl App { /// fn two() {} /// /// App::new() - /// .register_function("my_function", one) + /// .register_function_with_name("my_function", one) /// // Panic! A function has already been registered with the name "my_function" - /// .register_function("my_function", two); + /// .register_function_with_name("my_function", two); /// ``` /// /// [type name]: std::any::type_name + /// [`register_function`]: Self::register_function /// [`IntoFunction`]: bevy_reflect::func::IntoFunction - /// [`FunctionRegistry::register`]: bevy_reflect::func::FunctionRegistry::register + /// [`FunctionRegistry::register_with_name`]: bevy_reflect::func::FunctionRegistry::register_with_name #[cfg(feature = "reflect_functions")] - pub fn register_function( + pub fn register_function_with_name( &mut self, name: impl Into>, function: F, @@ -667,7 +749,7 @@ impl App { where F: bevy_reflect::func::IntoFunction + 'static, { - self.main_mut().register_function(name, function); + self.main_mut().register_function_with_name(name, function); self } diff --git a/crates/bevy_app/src/sub_app.rs b/crates/bevy_app/src/sub_app.rs index 7679ec18b9cc9..ff9eb5d2cb5e7 100644 --- a/crates/bevy_app/src/sub_app.rs +++ b/crates/bevy_app/src/sub_app.rs @@ -411,7 +411,18 @@ impl SubApp { /// See [`App::register_function`]. #[cfg(feature = "reflect_functions")] - pub fn register_function( + pub fn register_function(&mut self, function: F) -> &mut Self + where + F: bevy_reflect::func::IntoFunction + 'static, + { + let registry = self.world.resource_mut::(); + registry.write().register(function).unwrap(); + self + } + + /// See [`App::register_function_with_name`]. + #[cfg(feature = "reflect_functions")] + pub fn register_function_with_name( &mut self, name: impl Into>, function: F, @@ -420,7 +431,7 @@ impl SubApp { F: bevy_reflect::func::IntoFunction + 'static, { let registry = self.world.resource_mut::(); - registry.write().register(name, function).unwrap(); + registry.write().register_with_name(name, function).unwrap(); self } } diff --git a/crates/bevy_reflect/src/func/closures/dynamic_closure.rs b/crates/bevy_reflect/src/func/closures/dynamic_closure.rs index bd4931e0738ce..e409e4967ad51 100644 --- a/crates/bevy_reflect/src/func/closures/dynamic_closure.rs +++ b/crates/bevy_reflect/src/func/closures/dynamic_closure.rs @@ -120,6 +120,19 @@ impl<'env> DynamicClosure<'env> { pub fn info(&self) -> &FunctionInfo { &self.info } + + /// The [name] of the closure. + /// + /// If this [`DynamicClosure`] was created using [`IntoClosure`], + /// then the default name will always be `None`. + /// + /// This can be overridden using [`with_name`]. + /// + /// [name]: FunctionInfo::name + /// [`with_name`]: Self::with_name + pub fn name(&self) -> Option<&Cow<'static, str>> { + self.info.name() + } } /// Outputs the closure's signature. @@ -129,7 +142,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(); + let name = self.info.name().unwrap_or(&Cow::Borrowed("_")); write!(f, "DynamicClosure(fn {name}(")?; for (index, arg) in self.info.args().iter().enumerate() { @@ -164,7 +177,7 @@ mod tests { let func = (|a: i32, b: i32| a + b + c) .into_closure() .with_name("my_closure"); - assert_eq!(func.info().name(), "my_closure"); + assert_eq!(func.info().name().unwrap(), "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 eacf6f69fedd5..e49fad2203303 100644 --- a/crates/bevy_reflect/src/func/closures/dynamic_closure_mut.rs +++ b/crates/bevy_reflect/src/func/closures/dynamic_closure_mut.rs @@ -162,6 +162,19 @@ impl<'env> DynamicClosureMut<'env> { pub fn info(&self) -> &FunctionInfo { &self.info } + + /// The [name] of the closure. + /// + /// If this [`DynamicClosureMut`] was created using [`IntoClosureMut`], + /// then the default name will always be `None`. + /// + /// This can be overridden using [`with_name`]. + /// + /// [name]: FunctionInfo::name + /// [`with_name`]: Self::with_name + pub fn name(&self) -> Option<&Cow<'static, str>> { + self.info.name() + } } /// Outputs the closure's signature. @@ -171,7 +184,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(); + let name = self.info.name().unwrap_or(&Cow::Borrowed("_")); write!(f, "DynamicClosureMut(fn {name}(")?; for (index, arg) in self.info.args().iter().enumerate() { @@ -206,7 +219,7 @@ mod tests { let func = (|a: i32, b: i32| total = a + b) .into_closure_mut() .with_name("my_closure"); - assert_eq!(func.info().name(), "my_closure"); + assert_eq!(func.info().name().unwrap(), "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 c2808d51f927e..a53e2124487c5 100644 --- a/crates/bevy_reflect/src/func/closures/into_closure.rs +++ b/crates/bevy_reflect/src/func/closures/into_closure.rs @@ -52,12 +52,9 @@ mod tests { } #[test] - fn should_default_with_closure_type_name() { + fn should_default_closure_name_to_none() { let c = 23; let func = (|a: i32, b: i32| a + b + c).into_closure(); - assert_eq!( - func.info().name(), - "bevy_reflect::func::closures::into_closure::tests::should_default_with_closure_type_name::{{closure}}" - ); + assert_eq!(func.info().name(), None); } } 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 289bb7970925a..52860cb49a99e 100644 --- a/crates/bevy_reflect/src/func/closures/into_closure_mut.rs +++ b/crates/bevy_reflect/src/func/closures/into_closure_mut.rs @@ -65,12 +65,9 @@ mod tests { } #[test] - fn should_default_with_closure_type_name() { + fn should_default_closure_name_to_none() { let mut total = 0; let func = (|a: i32, b: i32| total = a + b).into_closure_mut(); - assert_eq!( - func.info().name(), - "bevy_reflect::func::closures::into_closure_mut::tests::should_default_with_closure_type_name::{{closure}}" - ); + assert_eq!(func.info().name(), None); } } diff --git a/crates/bevy_reflect/src/func/error.rs b/crates/bevy_reflect/src/func/error.rs index 3bb638d0fd30d..30ef98b0a80d0 100644 --- a/crates/bevy_reflect/src/func/error.rs +++ b/crates/bevy_reflect/src/func/error.rs @@ -36,4 +36,7 @@ pub enum FunctionRegistrationError { /// Contains the duplicate function name. #[error("a function has already been registered with name {0:?}")] DuplicateName(Cow<'static, str>), + /// The function is missing a name by which it can be registered. + #[error("function name is missing")] + MissingName, } diff --git a/crates/bevy_reflect/src/func/function.rs b/crates/bevy_reflect/src/func/function.rs index bf95e4752ed75..f2eecc4b1722d 100644 --- a/crates/bevy_reflect/src/func/function.rs +++ b/crates/bevy_reflect/src/func/function.rs @@ -60,7 +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("append") +/// let info = FunctionInfo::named("append") /// .with_arg::("value") /// .with_arg::<&mut Vec>("list") /// .with_return::<&mut String>(); @@ -164,14 +164,18 @@ impl DynamicFunction { /// 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`]. + /// If this [`DynamicFunction`] was created using [`IntoFunction`], + /// then the name will default to one of the following: + /// - If the function was anonymous (e.g. `|a: i32, b: i32| { a + b }`), + /// then the name will be `None` + /// - If the function was named (e.g. `fn add(a: i32, b: i32) -> i32 { a + b }`), + /// then the name will 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> { + pub fn name(&self) -> Option<&Cow<'static, str>> { self.info.name() } } @@ -183,7 +187,7 @@ impl DynamicFunction { /// 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.name(); + let name = self.info.name().unwrap_or(&Cow::Borrowed("_")); write!(f, "DynamicFunction(fn {name}(")?; for (index, arg) in self.info.args().iter().enumerate() { @@ -227,7 +231,7 @@ mod tests { fn foo() {} let func = foo.into_function().with_name("my_function"); - assert_eq!(func.info().name(), "my_function"); + assert_eq!(func.info().name().unwrap(), "my_function"); } #[test] @@ -253,7 +257,8 @@ mod tests { let index = args.pop::()?; Ok(Return::Ref(get(index, list))) }, - FunctionInfo::new("get") + FunctionInfo::anonymous() + .with_name("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 4a0849f865bb7..7ef862ef5b509 100644 --- a/crates/bevy_reflect/src/func/info.rs +++ b/crates/bevy_reflect/src/func/info.rs @@ -14,16 +14,30 @@ use crate::TypePath; /// [`DynamicClosure`]: crate::func::DynamicClosure #[derive(Debug, Clone)] pub struct FunctionInfo { - name: Cow<'static, str>, + name: Option>, args: Vec, return_info: ReturnInfo, } impl FunctionInfo { /// Create a new [`FunctionInfo`] for a function with the given name. - pub fn new(name: impl Into>) -> Self { + pub fn named(name: impl Into>) -> Self { Self { - name: name.into(), + name: Some(name.into()), + args: Vec::new(), + return_info: ReturnInfo::new::<()>(), + } + } + + /// Create a new [`FunctionInfo`] with no name. + /// + /// For the purposes of debugging and [registration], + /// it's recommended to use [`FunctionInfo::named`] instead. + /// + /// [registration]: crate::func::FunctionRegistry + pub fn anonymous() -> Self { + Self { + name: None, args: Vec::new(), return_info: ReturnInfo::new::<()>(), } @@ -39,7 +53,7 @@ impl FunctionInfo { /// Set the name of the function. pub fn with_name(mut self, name: impl Into>) -> Self { - self.name = name.into(); + self.name = Some(name.into()); self } @@ -98,8 +112,8 @@ impl FunctionInfo { /// [`IntoFunction`]: crate::func::IntoFunction /// [`DynamicClosures`]: crate::func::DynamicClosure /// [`IntoClosure`]: crate::func::IntoClosure - pub fn name(&self) -> &Cow<'static, str> { - &self.name + pub fn name(&self) -> Option<&Cow<'static, str>> { + self.name.as_ref() } /// The arguments of the function. @@ -181,7 +195,7 @@ impl ReturnInfo { /// } /// /// let info = print.get_function_info(); -/// assert!(info.name().ends_with("print")); +/// assert!(info.name().unwrap().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(), "()"); @@ -222,7 +236,7 @@ macro_rules! impl_typed_function { Function: FnMut($($Arg),*) -> ReturnType, { fn function_info() -> FunctionInfo { - FunctionInfo::new(std::borrow::Cow::Borrowed(std::any::type_name::())) + create_info::() .with_args({ #[allow(unused_mut)] let mut _index = 0; @@ -246,7 +260,7 @@ macro_rules! impl_typed_function { Function: for<'a> FnMut(&'a Receiver, $($Arg),*) -> &'a ReturnType, { fn function_info() -> $crate::func::FunctionInfo { - FunctionInfo::new(std::borrow::Cow::Borrowed(std::any::type_name::())) + create_info::() .with_args({ #[allow(unused_mut)] let mut _index = 1; @@ -271,7 +285,7 @@ macro_rules! impl_typed_function { Function: for<'a> FnMut(&'a mut Receiver, $($Arg),*) -> &'a mut ReturnType, { fn function_info() -> FunctionInfo { - FunctionInfo::new(std::borrow::Cow::Borrowed(std::any::type_name::())) + create_info::() .with_args({ #[allow(unused_mut)] let mut _index = 1; @@ -296,7 +310,7 @@ macro_rules! impl_typed_function { Function: for<'a> FnMut(&'a mut Receiver, $($Arg),*) -> &'a ReturnType, { fn function_info() -> FunctionInfo { - FunctionInfo::new(std::borrow::Cow::Borrowed(std::any::type_name::())) + create_info::() .with_args({ #[allow(unused_mut)] let mut _index = 1; @@ -315,3 +329,74 @@ macro_rules! impl_typed_function { } all_tuples!(impl_typed_function, 0, 15, Arg, arg); + +/// Helper function for creating [`FunctionInfo`] with the proper name value. +/// +/// Names are only given if: +/// - The function is not a closure +/// - The function is not a function pointer +/// - The function is not an anonymous function +/// +/// This function relies on the [`type_name`] of `F` to determine this. +/// The following table describes the behavior for different types of functions: +/// +/// | Category | `type_name` | `FunctionInfo::name` | +/// | ------------------ | ----------------------- | ----------------------- | +/// | Named function | `foo::bar::baz` | `Some("foo::bar::baz")` | +/// | Closure | `foo::bar::{{closure}}` | `None` | +/// | Anonymous function | `fn() -> String` | `None` | +/// | Function pointer | `fn() -> String` | `None` | +/// +/// [`type_name`]: std::any::type_name +fn create_info() -> FunctionInfo { + let name = std::any::type_name::(); + + if name.ends_with("{{closure}}") || name.starts_with("fn(") { + FunctionInfo::anonymous() + } else { + FunctionInfo::named(name) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn should_create_function_info() { + fn add(a: i32, b: i32) -> i32 { + a + b + } + + let info = add.get_function_info(); + assert_eq!( + info.name().unwrap(), + "bevy_reflect::func::info::tests::should_create_function_info::add" + ); + assert_eq!(info.arg_count(), 2); + assert_eq!(info.args()[0].type_path(), "i32"); + assert_eq!(info.args()[1].type_path(), "i32"); + assert_eq!(info.return_info().type_path(), "i32"); + } + + #[test] + fn should_create_anonymous_function_info() { + let info = (|a: i32, b: i32| a + b).get_function_info(); + assert!(info.name().is_none()); + assert_eq!(info.arg_count(), 2); + assert_eq!(info.args()[0].type_path(), "i32"); + assert_eq!(info.args()[1].type_path(), "i32"); + assert_eq!(info.return_info().type_path(), "i32"); + } + + #[test] + fn should_create_closure_info() { + let mut total = 0; + let info = (|a: i32, b: i32| total = a + b).get_function_info(); + assert!(info.name().is_none()); + assert_eq!(info.arg_count(), 2); + assert_eq!(info.args()[0].type_path(), "i32"); + assert_eq!(info.args()[1].type_path(), "i32"); + assert_eq!(info.return_info().type_path(), "()"); + } +} diff --git a/crates/bevy_reflect/src/func/into_function.rs b/crates/bevy_reflect/src/func/into_function.rs index d5f42cda732a4..a42f91fcfee6d 100644 --- a/crates/bevy_reflect/src/func/into_function.rs +++ b/crates/bevy_reflect/src/func/into_function.rs @@ -173,7 +173,7 @@ mod tests { let func = foo.into_function(); assert_eq!( - func.info().name(), + func.info().name().unwrap(), "bevy_reflect::func::into_function::tests::should_default_with_function_type_name::foo" ); } diff --git a/crates/bevy_reflect/src/func/registry.rs b/crates/bevy_reflect/src/func/registry.rs index ec94dc07795ee..e10af437e6439 100644 --- a/crates/bevy_reflect/src/func/registry.rs +++ b/crates/bevy_reflect/src/func/registry.rs @@ -22,109 +22,153 @@ pub struct FunctionRegistry { } impl FunctionRegistry { - /// Attempts to register the given function with the given name. + /// Attempts to register the given function. /// /// 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. + /// and mapped according to its [name]. + /// + /// Because the function must have a name, + /// anonymous functions (e.g. `|a: i32, b: i32| { a + b }`) must instead + /// be registered using [`register_with_name`] or converted to a [`DynamicFunction`] + /// and named using [`DynamicFunction::with_name`]. + /// Failure to do so will result in an error being returned. /// /// 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 add(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)?; + /// registry.register(add)?; /// # Ok(()) /// # } /// ``` /// - /// Names must be unique. + /// Functions cannot be registered more than once. /// - /// ```should_panic - /// # use bevy_reflect::func::FunctionRegistry; - /// fn one() {} - /// fn two() {} + /// ``` + /// # use bevy_reflect::func::{DynamicFunction, FunctionRegistrationError, FunctionRegistry, IntoFunction}; + /// fn add(a: i32, b: i32) -> i32 { + /// a + b + /// } /// /// let mut registry = FunctionRegistry::default(); - /// registry.register("my_function", one).unwrap(); + /// registry.register(add).unwrap(); + /// + /// let result = registry.register(add); + /// assert!(matches!(result, Err(FunctionRegistrationError::DuplicateName(_)))); + /// + /// // Note that this simply relies on the name of the function to determine uniqueness. + /// // You can rename the function to register a separate instance of it. + /// let result = registry.register(add.into_function().with_name("add2")); + /// assert!(result.is_ok()); + /// ``` + /// + /// Anonymous functions should be registered using [`register_with_name`] or given a name using [`DynamicFunction::with_name`]. /// - /// // Panic! A function has already been registered with the name "my_function" - /// registry.register("my_function", two).unwrap(); + /// ``` + /// # use bevy_reflect::func::{DynamicFunction, FunctionRegistrationError, FunctionRegistry, IntoFunction}; + /// + /// let anonymous = || -> i32 { 123 }; + /// + /// let mut registry = FunctionRegistry::default(); + /// + /// let result = registry.register(|a: i32, b: i32| a + b); + /// assert!(matches!(result, Err(FunctionRegistrationError::MissingName))); + /// + /// let result = registry.register_with_name("my_crate::add", |a: i32, b: i32| a + b); + /// assert!(result.is_ok()); + /// + /// let result = registry.register((|a: i32, b: i32| a * b).into_function().with_name("my_crate::mul")); + /// assert!(result.is_ok()); /// ``` /// /// [name]: DynamicFunction::name + /// [`register_with_name`]: Self::register_with_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) + let function = function.into_function(); + let name = function + .name() + .ok_or(FunctionRegistrationError::MissingName)? + .clone(); + self.functions + .try_insert(name, function) + .map_err(|err| FunctionRegistrationError::DuplicateName(err.entry.key().clone()))?; + + Ok(self) } - /// Attempts to register a [`DynamicFunction`] directly using its [name] as the key. + /// 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. + /// + /// For named functions (e.g. `fn add(a: i32, b: i32) -> i32 { a + b }`) where a custom name is not needed, + /// it's recommended to use [`register`] instead as the generated name is guaranteed to be unique. /// /// 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. + /// use [`overwrite_registration_with_name`] instead. /// - /// You can change the name of the function using [`DynamicFunction::with_name`]. + /// To avoid conflicts, it's recommended to use a unique name for the function. + /// This can be achieved 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"`. + /// + /// Another approach could be to use the [type name] of the function, + /// however, it should be noted that anonymous functions do _not_ have unique type names. + /// + /// 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`] method. /// /// # Examples /// /// ``` - /// # use bevy_reflect::func::{DynamicFunction, FunctionRegistrationError, FunctionRegistry, IntoFunction}; - /// fn add(a: i32, b: i32) -> i32 { - /// a + b - /// } - /// + /// # use bevy_reflect::func::{FunctionRegistrationError, FunctionRegistry}; /// # fn main() -> Result<(), FunctionRegistrationError> { - /// let mut registry = FunctionRegistry::default(); + /// fn mul(a: i32, b: i32) -> i32 { + /// a * b + /// } /// - /// // Register a `DynamicFunction` directly - /// let function: DynamicFunction = add.into_function(); - /// registry.register_dynamic(function)?; + /// let div = |a: i32, b: i32| a / b; /// - /// // Register a `DynamicFunction` with a custom name - /// let function: DynamicFunction = add.into_function().with_name("my_crate::add"); - /// registry.register_dynamic(function)?; + /// let mut registry = FunctionRegistry::default(); + /// registry + /// // Registering an anonymous function with a unique name + /// .register_with_name("my_crate::add", |a: i32, b: i32| { + /// a + b + /// })? + /// // Registering an existing function with its type name + /// .register_with_name(std::any::type_name_of_val(&mul), mul)? + /// // Registering an existing function with a custom name + /// .register_with_name("my_crate::mul", mul)?; + /// + /// // Be careful not to register anonymous functions with their type name. + /// // This code works but registers the function with the non-unique name of `fn(i32, i32) -> i32` + /// registry.register_with_name(std::any::type_name_of_val(&div), div)?; /// # Ok(()) /// # } /// ``` @@ -132,29 +176,31 @@ impl FunctionRegistry { /// Names must be unique. /// /// ```should_panic - /// # use bevy_reflect::func::{DynamicFunction, FunctionRegistry, IntoFunction}; + /// # use bevy_reflect::func::FunctionRegistry; /// fn one() {} /// fn two() {} /// /// let mut registry = FunctionRegistry::default(); - /// registry.register_dynamic(one.into_function().with_name("my_function")).unwrap(); + /// registry.register_with_name("my_function", one).unwrap(); /// /// // Panic! A function has already been registered with the name "my_function" - /// registry.register_dynamic(two.into_function().with_name("my_function")).unwrap(); + /// registry.register_with_name("my_function", two).unwrap(); /// ``` /// /// [name]: DynamicFunction::name - /// [`overwrite_registration_dynamic`]: Self::overwrite_registration_dynamic - pub fn register_dynamic( + /// [`register`]: Self::register + /// [`overwrite_registration_with_name`]: Self::overwrite_registration_with_name + /// [type name]: std::any::type_name + pub fn register_with_name( &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) + name: impl Into>, + function: F, + ) -> Result<&mut Self, FunctionRegistrationError> + where + F: IntoFunction + 'static, + { + let function = function.into_function().with_name(name); + self.register(function) } /// Registers the given function, overwriting any existing registration. @@ -162,43 +208,75 @@ impl FunctionRegistry { /// 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. + /// and mapped according to its [name]. + /// + /// Because the function must have a name, + /// anonymous functions (e.g. `|a: i32, b: i32| { a + b }`) must instead + /// be registered using [`overwrite_registration_with_name`] or converted to a [`DynamicFunction`] + /// and named using [`DynamicFunction::with_name`]. + /// Failure to do so will result in an error being returned. /// - /// 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. + /// Returns the previous function with the same name, if any. /// /// [name]: DynamicFunction::name + /// [`overwrite_registration_with_name`]: Self::overwrite_registration_with_name /// [`register`]: Self::register - /// [`overwrite_registration_dynamic`]: Self::overwrite_registration_dynamic pub fn overwrite_registration( &mut self, - name: impl Into>, function: F, - ) where + ) -> Result, FunctionRegistrationError> + where F: IntoFunction + 'static, { - let function = function.into_function().with_name(name); - self.overwrite_registration_dynamic(function); + let function = function.into_function(); + let name = function + .name() + .ok_or(FunctionRegistrationError::MissingName)? + .clone(); + + Ok(self.functions.insert(name, function)) } - /// Registers the given [`DynamicFunction`], overwriting any existing registration. + /// 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_dynamic`] method instead. + /// it's recommended to use the [`register_with_name`] 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`] method. + /// + /// Returns the previous function with the same name, if any. /// /// [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); + /// [`register_with_name`]: Self::register_with_name + /// [`overwrite_registration`]: Self::overwrite_registration + pub fn overwrite_registration_with_name( + &mut self, + name: impl Into>, + function: F, + ) -> Option + where + F: IntoFunction + 'static, + { + let function = function.into_function().with_name(name); + match self.overwrite_registration(function) { + Ok(existing) => existing, + Err(FunctionRegistrationError::MissingName) => { + unreachable!("the function should have a name") + } + Err(FunctionRegistrationError::DuplicateName(_)) => { + unreachable!("should overwrite functions with the same name") + } + } } /// Get a reference to a registered function by [name]. @@ -269,9 +347,9 @@ mod tests { } let mut registry = FunctionRegistry::default(); - registry.register("foo", foo).unwrap(); + registry.register(foo).unwrap(); - let function = registry.get("foo").unwrap(); + 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)); } @@ -279,7 +357,7 @@ mod tests { #[test] fn should_register_anonymous_function() { let mut registry = FunctionRegistry::default(); - registry.register("foo", || 123_i32).unwrap(); + registry.register_with_name("foo", || 123_i32).unwrap(); let function = registry.get("foo").unwrap(); let value = function.call(ArgList::new()).unwrap().unwrap_owned(); @@ -295,7 +373,7 @@ mod tests { let function = foo.into_function().with_name("custom_name"); let mut registry = FunctionRegistry::default(); - registry.register_dynamic(function).unwrap(); + registry.register(function).unwrap(); let function = registry.get("custom_name").unwrap(); let value = function.call(ArgList::new()).unwrap().unwrap_owned(); @@ -315,8 +393,8 @@ mod tests { 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)); + registry.register(foo).unwrap(); + let result = registry.register(bar.into_function().with_name(name)); assert!(matches!( result, @@ -324,7 +402,7 @@ mod tests { )); assert_eq!(registry.len(), 1); - let function = registry.get(std::any::type_name_of_val(&foo)).unwrap(); + let function = registry.get(name).unwrap(); let value = function.call(ArgList::new()).unwrap().unwrap_owned(); assert_eq!(value.downcast_ref::(), Some(&123)); } @@ -342,16 +420,33 @@ mod tests { 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)); + registry.register(foo).unwrap(); + registry + .overwrite_registration(bar.into_function().with_name(name)) + .unwrap(); assert_eq!(registry.len(), 1); - let function = registry.get(std::any::type_name_of_val(&foo)).unwrap(); + let function = registry.get(name).unwrap(); let value = function.call(ArgList::new()).unwrap().unwrap_owned(); assert_eq!(value.downcast_ref::(), Some(&321)); } + #[test] + fn should_error_on_missing_name() { + let foo = || -> i32 { 123 }; + + let function = foo.into_function(); + + let mut registry = FunctionRegistry::default(); + let result = registry.register(function); + + assert!(matches!( + result, + Err(FunctionRegistrationError::MissingName) + )); + } + #[test] fn should_debug_function_registry() { fn foo() -> i32 { @@ -359,7 +454,7 @@ mod tests { } let mut registry = FunctionRegistry::default(); - registry.register("foo", foo).unwrap(); + registry.register_with_name("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 54d5222af08eb..a1de336f66ec4 100644 --- a/examples/reflection/function_reflection.rs +++ b/examples/reflection/function_reflection.rs @@ -148,12 +148,16 @@ fn main() { Ok(Return::Ref(get_or_insert(value, container))) }, - // All functions require a name. - // We can either give it a custom name or use the function's name as + // Functions can be either anonymous or named. + // It's good practice, though, to try and name your functions whenever possible. + // This makes it easier to debug and is also required for function registration. + // We can either give it a custom name or use the function's type name as // derived from `std::any::type_name_of_val`. - FunctionInfo::new(std::any::type_name_of_val(&get_or_insert)) + FunctionInfo::named(std::any::type_name_of_val(&get_or_insert)) // We can always change the name if needed. - .with_name("get_or_insert") + // It's a good idea to also ensure that the name is unique, + // such as by using its type name or by prefixing it with your crate name. + .with_name("my_crate::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 // pass into the function and helps for debugging.