Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 96 additions & 14 deletions crates/bevy_app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<F, Marker>(&mut self, function: F) -> &mut Self
where
F: bevy_reflect::func::IntoFunction<Marker> + '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
///
Expand All @@ -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.
Expand All @@ -650,24 +731,25 @@ 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<F, Marker>(
pub fn register_function_with_name<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.main_mut().register_function_with_name(name, function);
self
}

Expand Down
15 changes: 13 additions & 2 deletions crates/bevy_app/src/sub_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,18 @@ impl SubApp {

/// See [`App::register_function`].
#[cfg(feature = "reflect_functions")]
pub fn register_function<F, Marker>(
pub fn register_function<F, Marker>(&mut self, function: F) -> &mut Self
where
F: bevy_reflect::func::IntoFunction<Marker> + 'static,
{
let registry = self.world.resource_mut::<AppFunctionRegistry>();
registry.write().register(function).unwrap();
self
}

/// See [`App::register_function_with_name`].
#[cfg(feature = "reflect_functions")]
pub fn register_function_with_name<F, Marker>(
&mut self,
name: impl Into<std::borrow::Cow<'static, str>>,
function: F,
Expand All @@ -420,7 +431,7 @@ impl SubApp {
F: bevy_reflect::func::IntoFunction<Marker> + 'static,
{
let registry = self.world.resource_mut::<AppFunctionRegistry>();
registry.write().register(name, function).unwrap();
registry.write().register_with_name(name, function).unwrap();
self
}
}
Expand Down
17 changes: 15 additions & 2 deletions crates/bevy_reflect/src/func/closures/dynamic_closure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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() {
Expand Down Expand Up @@ -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]
Expand Down
17 changes: 15 additions & 2 deletions crates/bevy_reflect/src/func/closures/dynamic_closure_mut.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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() {
Expand Down Expand Up @@ -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]
Expand Down
7 changes: 2 additions & 5 deletions crates/bevy_reflect/src/func/closures/into_closure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
7 changes: 2 additions & 5 deletions crates/bevy_reflect/src/func/closures/into_closure_mut.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
3 changes: 3 additions & 0 deletions crates/bevy_reflect/src/func/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
21 changes: 13 additions & 8 deletions crates/bevy_reflect/src/func/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<String>("value")
/// .with_arg::<&mut Vec<String>>("list")
/// .with_return::<&mut String>();
Expand Down Expand Up @@ -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()
}
}
Expand All @@ -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() {
Expand Down Expand Up @@ -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]
Expand All @@ -253,7 +257,8 @@ mod tests {
let index = args.pop::<usize>()?;
Ok(Return::Ref(get(index, list)))
},
FunctionInfo::new("get")
FunctionInfo::anonymous()
.with_name("get")
.with_arg::<usize>("index")
.with_arg::<&Vec<String>>("list")
.with_return::<&String>(),
Expand Down
Loading