bevy_reflect: Anonymous function parsing (#14641)

# Objective

### TL;DR

#14098 added the `FunctionRegistry` but had some last minute
complications due to anonymous functions. It ended up going with a
"required name" approach to ensure anonymous functions would always have
a name.

However, this approach isn't ideal for named functions since, by
definition, they will always have a name.

Therefore, this PR aims to modify function reflection such that we can
make function registration easier for named functions, while still
allowing anonymous functions to be registered as well.

### Context

Function registration (#14098) ran into a little problem: anonymous
functions.

Anonymous functions, including function pointers, have very non-unique
type names. For example, the anonymous function `|a: i32, b: i32| a + b`
has the type name of `fn(i32, i32) -> i32`. This obviously means we'd
conflict with another function like `|a: i32, b: i32| a - b`.

The solution that #14098 landed on was to always require a name during
function registration.

The downside with this is that named functions (e.g. `fn add(a: i32, b:
i32) -> i32 { a + b }`) had to redundantly provide a name. Additionally,
manually constructed `DynamicFunction`s also ran into this ergonomics
issue.

I don't entirely know how the function registry will be used, but I have
a strong suspicion that most of its registrations will either be named
functions or manually constructed `DynamicFunction`s, with anonymous
functions only being used here and there for quick prototyping or adding
small functionality.

Why then should the API prioritize the anonymous function use case by
always requiring a name during registration?

#### Telling Functions Apart

Rust doesn't provide a lot of out-of-the-box tools for reflecting
functions. One of the biggest hurdles in attempting to solve the problem
outlined above would be to somehow tell the different kinds of functions
apart.

Let's briefly recap on the categories of functions in Rust:

| Category           | Example                                   |
| ------------------ | ----------------------------------------- |
| Named function     | `fn add(a: i32, b: i32) -> i32 { a + b }` |
| Closure            | `\|a: i32\| a + captured_variable`          |
| Anonymous function | `\|a: i32, b: i32\| a + b`                  |
| Function pointer   | `fn(i32, i32) -> i32`                     |

My first thought was to try and differentiate these categories based on
their size. However, we can see that this doesn't quite work:

| Category           | `size_of` |
| ------------------ | --------- |
| Named function     | 0         |
| Closure            | 0+        |
| Anonymous function | 0         |
| Function pointer   | 8         |

Not only does this not tell anonymous functions from named ones, but it
struggles with pretty much all of them.

My second then was to differentiate based on type name:

| Category           | `type_name`             |
| ------------------ | ----------------------- |
| Named function     | `foo::bar::baz`         |
| Closure            | `foo::bar::{{closure}}` |
| Anonymous function | `fn() -> String`        |
| Function pointer   | `fn() -> String`        |

This is much better. While it can't distinguish between function
pointers and anonymous functions, this doesn't matter too much since we
only care about whether we can _name_ the function.

So why didn't we implement this in #14098?

#### Relying on `type_name`

While this solution was known about while working on #14098, it was left
out from that PR due to it being potentially controversial.

The [docs](https://doc.rust-lang.org/stable/std/any/fn.type_name.html)
for `std::any::type_name` state:

> The returned string must not be considered to be a unique identifier
of a type as multiple types may map to the same type name. Similarly,
there is no guarantee that all parts of a type will appear in the
returned string: for example, lifetime specifiers are currently not
included. In addition, the output may change between versions of the
compiler.

So that's it then? We can't use `type_name`?

Well, this statement isn't so much a rule as it is a guideline. And Bevy
is no stranger to bending the rules to make things work or to improve
ergonomics. Remember that before `TypePath`, Bevy's scene system was
entirely dependent on `type_name`. Not to mention that `type_name` is
being used as a key into both the `TypeRegistry` and the
`FunctionRegistry`.

Bevy's practices aside, can we reliably use `type_name` for this?

My answer would be "yes".

Anonymous functions are anonymous. They have no name. There's nothing
Rust could do to give them a name apart from generating a random string
of characters. But remember that this is a diagnostic tool, it doesn't
make sense to obfuscate the type by randomizing the output. So changing
it to be anything other than what it is now is very unlikely.

The only changes that I could potentially see happening are:

1. Closures replace `{{closure}}` with the name of their variable
2. Lifetimes are included in the output

I don't think the first is likely to happen, but if it does then it
actually works out in our favor: closures are now named!

The second point is probably the likeliest. However, adding lifetimes
doesn't mean we can't still rely on `type_name` to determine whether or
not a function is named. So we should be okay in this case as well.

## Solution

Parse the `type_name` of the function in the `TypedFunction` impl to
determine if the function is named or anonymous.

This once again makes `FunctionInfo::name` optional. For manual
constructions of `DynamicFunction`, `FunctionInfo::named` or
``FunctionInfo::anonymous` can be used.

The `FunctionRegistry` API has also been reworked to account for this
change.

`FunctionRegistry::register` no longer takes a name and instead takes it
from the supplied function, returning a
`FunctionRegistrationError::MissingName` error if the name is `None`.
This also doubles as a replacement for the old
`FunctionRegistry::register_dynamic` method, which has been removed.

To handle anonymous functions, a `FunctionRegistry::register_with_name`
method has been added. This works in the same way
`FunctionRegistry::register` used to work before this PR.

The overwriting methods have been updated in a similar manner, with
modifications to `FunctionRegistry::overwrite_registration`, the removal
of `FunctionRegistry::overwrite_registration_dynamic`, and the addition
of `FunctionRegistry::overwrite_registration_with_name`.

This PR also updates the methods on `App` in a similar way:
`App::register_function` no longer requires a name argument and
`App::register_function_with_name` has been added to handle anonymous
functions (and eventually closures).

## Testing

You can run the tests locally by running:

```
cargo test --package bevy_reflect --features functions
```

---

## 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.

> [!note]
> This list is not exhaustive. It only contains some of the most
important changes.

`FunctionRegistry::register` no longer requires a name string for named
functions. Anonymous functions, however, need to be registered using
`FunctionRegistry::register_with_name`.

```rust
// BEFORE
registry
  .register(std::any::type_name_of_val(&foo), foo)?
  .register("bar", || println!("Hello world!"));

// AFTER
registry
  .register(foo)?
  .register_with_name("bar", || println!("Hello world!"));
```

`FunctionInfo::name` is now optional. Anonymous functions and closures
will now have their name set to `None` by default. Additionally,
`FunctionInfo::new` has been renamed to `FunctionInfo::named`.
This commit is contained in:
Gino Valente 2024-08-06 20:11:08 -07:00 committed by GitHub
parent 5abc32ceda
commit a0cc636ea3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 456 additions and 151 deletions

View File

@ -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
///
@ -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<F, Marker>(
pub fn register_function_with_name<F, Marker>(
&mut self,
name: impl Into<std::borrow::Cow<'static, str>>,
function: F,
@ -667,7 +749,7 @@ impl App {
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
}

View File

@ -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,
@ -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
}
}

View File

@ -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]

View File

@ -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]

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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,
}

View File

@ -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>();
@ -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::<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>(),

View File

@ -14,16 +14,30 @@ use crate::TypePath;
/// [`DynamicClosure`]: crate::func::DynamicClosure
#[derive(Debug, Clone)]
pub struct FunctionInfo {
name: Cow<'static, str>,
name: Option<Cow<'static, str>>,
args: Vec<ArgInfo>,
return_info: ReturnInfo,
}
impl FunctionInfo {
/// Create a new [`FunctionInfo`] for a function with the given name.
pub fn new(name: impl Into<Cow<'static, str>>) -> Self {
pub fn named(name: impl Into<Cow<'static, str>>) -> 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<Cow<'static, str>>) -> 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::<Function>()))
create_info::<Function>()
.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::<Function>()))
create_info::<Function>()
.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::<Function>()))
create_info::<Function>()
.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::<Function>()))
create_info::<Function>()
.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<F>() -> FunctionInfo {
let name = std::any::type_name::<F>();
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(), "()");
}
}

View File

@ -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"
);
}

View File

@ -22,6 +22,100 @@ pub struct FunctionRegistry {
}
impl FunctionRegistry {
/// 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`]
/// 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.
///
/// # Examples
///
/// ```
/// # use bevy_reflect::func::{FunctionRegistrationError, FunctionRegistry};
/// fn add(a: i32, b: i32) -> i32 {
/// a + b
/// }
///
/// # fn main() -> Result<(), FunctionRegistrationError> {
/// let mut registry = FunctionRegistry::default();
/// registry.register(add)?;
/// # Ok(())
/// # }
/// ```
///
/// Functions cannot be registered more than once.
///
/// ```
/// # use bevy_reflect::func::{DynamicFunction, FunctionRegistrationError, FunctionRegistry, IntoFunction};
/// fn add(a: i32, b: i32) -> i32 {
/// a + b
/// }
///
/// let mut registry = FunctionRegistry::default();
/// 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`].
///
/// ```
/// # 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
pub fn register<F, Marker>(
&mut self,
function: F,
) -> Result<&mut Self, FunctionRegistrationError>
where
F: IntoFunction<Marker> + 'static,
{
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 the given function with the given name.
///
/// This function accepts both functions that satisfy [`IntoFunction`]
@ -29,41 +123,52 @@ impl FunctionRegistry {
/// 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`] instead.
/// use [`overwrite_registration_with_name`] 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,
/// 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_dynamic`] method.
/// on the function and inserting it into the registry using the [`register`] method.
///
/// # Examples
///
/// ```
/// # use bevy_reflect::func::{FunctionRegistrationError, FunctionRegistry};
/// # fn main() -> Result<(), FunctionRegistrationError> {
/// fn mul(a: i32, b: i32) -> i32 {
/// a * b
/// }
///
/// # fn main() -> Result<(), FunctionRegistrationError> {
/// let div = |a: i32, b: i32| a / b;
///
/// let mut registry = FunctionRegistry::default();
/// registry
/// // Registering an anonymous function with a unique name
/// .register("my_crate::add", |a: i32, b: i32| {
/// .register_with_name("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)?
/// .register_with_name(std::any::type_name_of_val(&mul), mul)?
/// // Registering an existing function with a custom name
/// .register("my_crate::mul", mul)?;
/// .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(())
/// # }
/// ```
@ -76,17 +181,17 @@ impl FunctionRegistry {
/// fn two() {}
///
/// let mut registry = FunctionRegistry::default();
/// registry.register("my_function", one).unwrap();
/// registry.register_with_name("my_function", one).unwrap();
///
/// // Panic! A function has already been registered with the name "my_function"
/// registry.register("my_function", two).unwrap();
/// registry.register_with_name("my_function", two).unwrap();
/// ```
///
/// [name]: DynamicFunction::name
/// [`overwrite_registration`]: Self::overwrite_registration
/// [`register`]: Self::register
/// [`overwrite_registration_with_name`]: Self::overwrite_registration_with_name
/// [type name]: std::any::type_name
/// [`register_dynamic`]: Self::register_dynamic
pub fn register<F, Marker>(
pub fn register_with_name<F, Marker>(
&mut self,
name: impl Into<Cow<'static, str>>,
function: F,
@ -95,66 +200,44 @@ impl FunctionRegistry {
F: IntoFunction<Marker> + 'static,
{
let function = function.into_function().with_name(name);
self.register_dynamic(function)
self.register(function)
}
/// Attempts to register a [`DynamicFunction`] directly using its [name] as the key.
/// Registers the given function, overwriting any existing registration.
///
/// 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.
/// This function accepts both functions that satisfy [`IntoFunction`]
/// and direct [`DynamicFunction`] instances.
/// The given function will internally be stored as a [`DynamicFunction`]
/// and mapped according to its [name].
///
/// You can change the name of the function using [`DynamicFunction::with_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.
///
/// # Examples
/// To avoid overwriting existing registrations,
/// it's recommended to use the [`register`] method instead.
///
/// ```
/// # 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();
/// ```
/// Returns the previous function with the same name, if any.
///
/// [name]: DynamicFunction::name
/// [`overwrite_registration_dynamic`]: Self::overwrite_registration_dynamic
pub fn register_dynamic(
/// [`overwrite_registration_with_name`]: Self::overwrite_registration_with_name
/// [`register`]: Self::register
pub fn overwrite_registration<F, Marker>(
&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()))?;
function: F,
) -> Result<Option<DynamicFunction>, FunctionRegistrationError>
where
F: IntoFunction<Marker> + 'static,
{
let function = function.into_function();
let name = function
.name()
.ok_or(FunctionRegistrationError::MissingName)?
.clone();
Ok(self)
Ok(self.functions.insert(name, function))
}
/// Registers the given function, overwriting any existing registration.
@ -166,39 +249,34 @@ impl FunctionRegistry {
///
/// Functions are mapped according to their name.
/// To avoid overwriting existing registrations,
/// it's recommended to use the [`register`] 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_dynamic`] method.
/// 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`]: Self::register
/// [`overwrite_registration_dynamic`]: Self::overwrite_registration_dynamic
pub fn overwrite_registration<F, Marker>(
/// [`register_with_name`]: Self::register_with_name
/// [`overwrite_registration`]: Self::overwrite_registration
pub fn overwrite_registration_with_name<F, Marker>(
&mut self,
name: impl Into<Cow<'static, str>>,
function: F,
) where
) -> Option<DynamicFunction>
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);
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::<i32>(), 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::<i32>(), 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::<i32>(), 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)}");

View File

@ -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.