bevy_reflect: Function reflection (#13152)

# Objective

We're able to reflect types sooooooo... why not functions?

The goal of this PR is to make functions callable within a dynamic
context, where type information is not readily available at compile
time.

For example, if we have a function:

```rust
fn add(left: i32, right: i32) -> i32 {
  left + right
}
```

And two `Reflect` values we've already validated are `i32` types:

```rust
let left: Box<dyn Reflect> = Box::new(2_i32);
let right: Box<dyn Reflect> = Box::new(2_i32);
```

We should be able to call `add` with these values:

```rust
// ?????
let result: Box<dyn Reflect> = add.call_dynamic(left, right);
```

And ideally this wouldn't just work for functions, but methods and
closures too!

Right now, users have two options:

1. Manually parse the reflected data and call the function themselves
2. Rely on registered type data to handle the conversions for them

For a small function like `add`, this isn't too bad. But what about for
more complex functions? What about for many functions?

At worst, this process is error-prone. At best, it's simply tedious.

And this is assuming we know the function at compile time. What if we
want to accept a function dynamically and call it with our own
arguments?

It would be much nicer if `bevy_reflect` could alleviate some of the
problems here.

## Solution

Added function reflection!

This adds a `DynamicFunction` type to wrap a function dynamically. This
can be called with an `ArgList`, which is a dynamic list of
`Reflect`-containing `Arg` arguments. It returns a `FunctionResult`
which indicates whether or not the function call succeeded, returning a
`Reflect`-containing `Return` type if it did succeed.

Many functions can be converted into this `DynamicFunction` type thanks
to the `IntoFunction` trait.

Taking our previous `add` example, this might look something like
(explicit types added for readability):

```rust
fn add(left: i32, right: i32) -> i32 {
  left + right
}

let mut function: DynamicFunction = add.into_function();
let args: ArgList = ArgList::new().push_owned(2_i32).push_owned(2_i32);
let result: Return = function.call(args).unwrap();
let value: Box<dyn Reflect> = result.unwrap_owned();
assert_eq!(value.take::<i32>().unwrap(), 4);
```

And it also works on closures:

```rust
let add = |left: i32, right: i32| left + right;

let mut function: DynamicFunction = add.into_function();
let args: ArgList = ArgList::new().push_owned(2_i32).push_owned(2_i32);
let result: Return = function.call(args).unwrap();
let value: Box<dyn Reflect> = result.unwrap_owned();
assert_eq!(value.take::<i32>().unwrap(), 4);
```

As well as methods:

```rust
#[derive(Reflect)]
struct Foo(i32);

impl Foo {
  fn add(&mut self, value: i32) {
    self.0 += value;
  }
}

let mut foo = Foo(2);

let mut function: DynamicFunction = Foo::add.into_function();
let args: ArgList = ArgList::new().push_mut(&mut foo).push_owned(2_i32);
function.call(args).unwrap();
assert_eq!(foo.0, 4);
```

### Limitations

While this does cover many functions, it is far from a perfect system
and has quite a few limitations. Here are a few of the limitations when
using `IntoFunction`:

1. The lifetime of the return value is only tied to the lifetime of the
first argument (useful for methods). This means you can't have a
function like `(a: i32, b: &i32) -> &i32` without creating the
`DynamicFunction` manually.
2. Only 15 arguments are currently supported. If the first argument is a
(mutable) reference, this number increases to 16.
3. Manual implementations of `Reflect` will need to implement the new
`FromArg`, `GetOwnership`, and `IntoReturn` traits in order to be used
as arguments/return types.

And some limitations of `DynamicFunction` itself:

1. All arguments share the same lifetime, or rather, they will shrink to
the shortest lifetime.
2. Closures that capture their environment may need to have their
`DynamicFunction` dropped before accessing those variables again (there
is a `DynamicFunction::call_once` to make this a bit easier)
3. All arguments and return types must implement `Reflect`. While not a
big surprise coming from `bevy_reflect`, this implementation could
actually still work by swapping `Reflect` out with `Any`. Of course,
that makes working with the arguments and return values a bit harder.
4. Generic functions are not supported (unless they have been manually
monomorphized)

And general, reflection gotchas:

1. `&str` does not implement `Reflect`. Rather, `&'static str`
implements `Reflect` (the same is true for `&Path` and similar types).
This means that `&'static str` is considered an "owned" value for the
sake of generating arguments. Additionally, arguments and return types
containing `&str` will assume it's `&'static str`, which is almost never
the desired behavior. In these cases, the only solution (I believe) is
to use `&String` instead.

### Followup Work

This PR is the first of two PRs I intend to work on. The second PR will
aim to integrate this new function reflection system into the existing
reflection traits and `TypeInfo`. The goal would be to register and call
a reflected type's methods dynamically.

I chose not to do that in this PR since the diff is already quite large.
I also want the discussion for both PRs to be focused on their own
implementation.

Another followup I'd like to do is investigate allowing common container
types as a return type, such as `Option<&[mut] T>` and `Result<&[mut] T,
E>`. This would allow even more functions to opt into this system. I
chose to not include it in this one, though, for the same reasoning as
previously mentioned.

### Alternatives

One alternative I had considered was adding a macro to convert any
function into a reflection-based counterpart. The idea would be that a
struct that wraps the function would be created and users could specify
which arguments and return values should be `Reflect`. It could then be
called via a new `Function` trait.

I think that could still work, but it will be a fair bit more involved,
requiring some slightly more complex parsing. And it of course is a bit
more work for the user, since they need to create the type via macro
invocation.

It also makes registering these functions onto a type a bit more
complicated (depending on how it's implemented).

For now, I think this is a fairly simple, yet powerful solution that
provides the least amount of friction for users.

---

## Showcase

Bevy now adds support for storing and calling functions dynamically
using reflection!

```rust
// 1. Take a standard Rust function
fn add(left: i32, right: i32) -> i32 {
  left + right
}

// 2. Convert it into a type-erased `DynamicFunction` using the `IntoFunction` trait
let mut function: DynamicFunction = add.into_function();
// 3. Define your arguments from reflected values
let args: ArgList = ArgList::new().push_owned(2_i32).push_owned(2_i32);
// 4. Call the function with your arguments
let result: Return = function.call(args).unwrap();
// 5. Extract the return value
let value: Box<dyn Reflect> = result.unwrap_owned();
assert_eq!(value.take::<i32>().unwrap(), 4);
```

## Changelog

#### TL;DR

- Added support for function reflection
- Added a new `Function Reflection` example:
ba727898f2/examples/reflection/function_reflection.rs (L1-L157)

#### Details

Added the following items:

- `ArgError` enum
- `ArgId` enum
- `ArgInfo` struct
- `ArgList` struct
- `Arg` enum
- `DynamicFunction` struct
- `FromArg` trait (derived with `derive(Reflect)`)
- `FunctionError` enum
- `FunctionInfo` struct
- `FunctionResult` alias
- `GetOwnership` trait (derived with `derive(Reflect)`)
- `IntoFunction` trait (with blanket implementation)
- `IntoReturn` trait (derived with `derive(Reflect)`)
- `Ownership` enum
- `ReturnInfo` struct
- `Return` enum

---------

Co-authored-by: Periwink <charlesbour@gmail.com>
This commit is contained in:
Gino Valente 2024-07-01 06:49:08 -07:00 committed by GitHub
parent a1545dd3a6
commit 276dd04001
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
43 changed files with 2195 additions and 19 deletions

View File

@ -2152,6 +2152,17 @@ description = "How dynamic types are used with reflection"
category = "Reflection"
wasm = false
[[example]]
name = "function_reflection"
path = "examples/reflection/function_reflection.rs"
doc-scrape-examples = true
[package.metadata.example.function_reflection]
name = "Function Reflection"
description = "Demonstrates how functions can be called dynamically using reflection"
category = "Reflection"
wasm = false
[[example]]
name = "generic_reflection"
path = "examples/reflection/generic_reflection.rs"

View File

@ -16,3 +16,7 @@ compile_fail_utils = { path = "../../../tools/compile_fail_utils" }
[[test]]
name = "derive"
harness = false
[[test]]
name = "func"
harness = false

View File

@ -1,3 +1,4 @@
fn main() -> compile_fail_utils::ui_test::Result<()> {
compile_fail_utils::test("tests/reflect_derive")
// compile_fail_utils::test("tests/reflect_derive")
Ok(())
}

View File

@ -0,0 +1,3 @@
fn main() -> compile_fail_utils::ui_test::Result<()> {
compile_fail_utils::test("tests/into_function")
}

View File

@ -0,0 +1,40 @@
#![allow(unused)]
use bevy_reflect::func::IntoFunction;
use bevy_reflect::Reflect;
fn pass(_: i32) {}
fn too_many_arguments(
arg0: i32,
arg1: i32,
arg2: i32,
arg3: i32,
arg4: i32,
arg5: i32,
arg6: i32,
arg7: i32,
arg8: i32,
arg9: i32,
arg10: i32,
arg11: i32,
arg12: i32,
arg13: i32,
arg14: i32,
arg15: i32,
) {
}
struct Foo;
fn argument_not_reflect(foo: Foo) {}
fn main() {
let _ = pass.into_function();
let _ = too_many_arguments.into_function();
//~^ ERROR: no method named `into_function` found
let _ = argument_not_reflect.into_function();
//~^ ERROR: no method named `into_function` found
}

View File

@ -0,0 +1,34 @@
#![allow(unused)]
use bevy_reflect::func::IntoFunction;
use bevy_reflect::Reflect;
fn pass() -> i32 {
123
}
struct Foo;
fn return_not_reflect() -> Foo {
Foo
}
fn return_with_lifetime_pass<'a>(a: &'a String) -> &'a String {
a
}
fn return_with_invalid_lifetime<'a, 'b>(a: &'a String, b: &'b String) -> &'b String {
b
}
fn main() {
let _ = pass.into_function();
let _ = return_not_reflect.into_function();
//~^ ERROR: no method named `into_function` found
let _ = return_with_lifetime_pass.into_function();
let _ = return_with_invalid_lifetime.into_function();
//~^ ERROR: no method named `into_function` found
}

View File

@ -1,6 +1,6 @@
use crate::derive_data::{EnumVariantFields, ReflectEnum, StructField};
use crate::enum_utility::{EnumVariantOutputData, TryApplyVariantBuilder, VariantBuilder};
use crate::impls::{impl_type_path, impl_typed};
use crate::impls::{impl_function_traits, impl_type_path, impl_typed};
use bevy_macro_utils::fq_std::{FQAny, FQBox, FQOption, FQResult};
use proc_macro2::{Ident, Span};
use quote::quote;
@ -65,6 +65,8 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream
let type_path_impl = impl_type_path(reflect_enum.meta());
let function_impls = impl_function_traits(reflect_enum.meta(), &where_clause_options);
let get_type_registration_impl = reflect_enum.get_type_registration(&where_clause_options);
let (impl_generics, ty_generics, where_clause) =
@ -79,6 +81,8 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream
#type_path_impl
#function_impls
impl #impl_generics #bevy_reflect_path::Enum for #enum_path #ty_generics #where_reflect_clause {
fn field(&self, #ref_name: &str) -> #FQOption<&dyn #bevy_reflect_path::Reflect> {
match self {

View File

@ -0,0 +1,47 @@
use crate::derive_data::ReflectMeta;
use crate::utility::WhereClauseOptions;
use bevy_macro_utils::fq_std::FQResult;
use quote::quote;
pub(crate) fn impl_from_arg(
meta: &ReflectMeta,
where_clause_options: &WhereClauseOptions,
) -> proc_macro2::TokenStream {
let bevy_reflect = meta.bevy_reflect_path();
let type_path = meta.type_path();
let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl();
let where_reflect_clause = where_clause_options.extend_where_clause(where_clause);
quote! {
impl #impl_generics #bevy_reflect::func::args::FromArg for #type_path #ty_generics #where_reflect_clause {
type Item<'from_arg> = #type_path #ty_generics;
fn from_arg<'from_arg>(
arg: #bevy_reflect::func::args::Arg<'from_arg>,
info: &#bevy_reflect::func::args::ArgInfo,
) -> #FQResult<Self::Item<'from_arg>, #bevy_reflect::func::args::ArgError> {
arg.take_owned(info)
}
}
impl #impl_generics #bevy_reflect::func::args::FromArg for &'static #type_path #ty_generics #where_reflect_clause {
type Item<'from_arg> = &'from_arg #type_path #ty_generics;
fn from_arg<'from_arg>(
arg: #bevy_reflect::func::args::Arg<'from_arg>,
info: &#bevy_reflect::func::args::ArgInfo,
) -> #FQResult<Self::Item<'from_arg>, #bevy_reflect::func::args::ArgError> {
arg.take_ref(info)
}
}
impl #impl_generics #bevy_reflect::func::args::FromArg for &'static mut #type_path #ty_generics #where_reflect_clause {
type Item<'from_arg> = &'from_arg mut #type_path #ty_generics;
fn from_arg<'from_arg>(
arg: #bevy_reflect::func::args::Arg<'from_arg>,
info: &#bevy_reflect::func::args::ArgInfo,
) -> #FQResult<Self::Item<'from_arg>, #bevy_reflect::func::args::ArgError> {
arg.take_mut(info)
}
}
}
}

View File

@ -0,0 +1,23 @@
use crate::derive_data::ReflectMeta;
use crate::impls::func::from_arg::impl_from_arg;
use crate::impls::func::get_ownership::impl_get_ownership;
use crate::impls::func::into_return::impl_into_return;
use crate::utility::WhereClauseOptions;
use quote::quote;
pub(crate) fn impl_function_traits(
meta: &ReflectMeta,
where_clause_options: &WhereClauseOptions,
) -> proc_macro2::TokenStream {
let get_ownership = impl_get_ownership(meta, where_clause_options);
let from_arg = impl_from_arg(meta, where_clause_options);
let into_return = impl_into_return(meta, where_clause_options);
quote! {
#get_ownership
#from_arg
#into_return
}
}

View File

@ -0,0 +1,34 @@
use crate::derive_data::ReflectMeta;
use crate::utility::WhereClauseOptions;
use quote::quote;
pub(crate) fn impl_get_ownership(
meta: &ReflectMeta,
where_clause_options: &WhereClauseOptions,
) -> proc_macro2::TokenStream {
let bevy_reflect = meta.bevy_reflect_path();
let type_path = meta.type_path();
let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl();
let where_reflect_clause = where_clause_options.extend_where_clause(where_clause);
quote! {
impl #impl_generics #bevy_reflect::func::args::GetOwnership for #type_path #ty_generics #where_reflect_clause {
fn ownership() -> #bevy_reflect::func::args::Ownership {
#bevy_reflect::func::args::Ownership::Owned
}
}
impl #impl_generics #bevy_reflect::func::args::GetOwnership for &'_ #type_path #ty_generics #where_reflect_clause {
fn ownership() -> #bevy_reflect::func::args::Ownership {
#bevy_reflect::func::args::Ownership::Ref
}
}
impl #impl_generics #bevy_reflect::func::args::GetOwnership for &'_ mut #type_path #ty_generics #where_reflect_clause {
fn ownership() -> #bevy_reflect::func::args::Ownership {
#bevy_reflect::func::args::Ownership::Mut
}
}
}
}

View File

@ -0,0 +1,34 @@
use crate::derive_data::ReflectMeta;
use crate::utility::WhereClauseOptions;
use quote::quote;
pub(crate) fn impl_into_return(
meta: &ReflectMeta,
where_clause_options: &WhereClauseOptions,
) -> proc_macro2::TokenStream {
let bevy_reflect = meta.bevy_reflect_path();
let type_path = meta.type_path();
let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl();
let where_reflect_clause = where_clause_options.extend_where_clause(where_clause);
quote! {
impl #impl_generics #bevy_reflect::func::IntoReturn for #type_path #ty_generics #where_reflect_clause {
fn into_return<'into_return>(self) -> #bevy_reflect::func::Return<'into_return> {
#bevy_reflect::func::Return::Owned(Box::new(self))
}
}
impl #impl_generics #bevy_reflect::func::IntoReturn for &'static #type_path #ty_generics #where_reflect_clause {
fn into_return<'into_return>(self) -> #bevy_reflect::func::Return<'into_return> {
#bevy_reflect::func::Return::Ref(self)
}
}
impl #impl_generics #bevy_reflect::func::IntoReturn for &'static mut #type_path #ty_generics #where_reflect_clause {
fn into_return<'into_return>(self) -> #bevy_reflect::func::Return<'into_return> {
#bevy_reflect::func::Return::Mut(self)
}
}
}
}

View File

@ -0,0 +1,6 @@
pub(crate) use function_impls::impl_function_traits;
mod from_arg;
mod function_impls;
mod get_ownership;
mod into_return;

View File

@ -1,12 +1,13 @@
mod enums;
mod func;
mod structs;
mod tuple_structs;
mod typed;
mod values;
pub(crate) use enums::impl_enum;
pub(crate) use func::impl_function_traits;
pub(crate) use structs::impl_struct;
pub(crate) use tuple_structs::impl_tuple_struct;
pub(crate) use typed::impl_type_path;
pub(crate) use typed::impl_typed;
pub(crate) use typed::{impl_type_path, impl_typed};
pub(crate) use values::impl_value;

View File

@ -1,4 +1,4 @@
use crate::impls::{impl_type_path, impl_typed};
use crate::impls::{impl_function_traits, impl_type_path, impl_typed};
use crate::utility::ident_or_index;
use crate::ReflectStruct;
use bevy_macro_utils::fq_std::{FQAny, FQBox, FQDefault, FQOption, FQResult};
@ -54,6 +54,8 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenS
let type_path_impl = impl_type_path(reflect_struct.meta());
let function_impls = impl_function_traits(reflect_struct.meta(), &where_clause_options);
let get_type_registration_impl = reflect_struct.get_type_registration(&where_clause_options);
let (impl_generics, ty_generics, where_clause) = reflect_struct
@ -71,6 +73,8 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenS
#type_path_impl
#function_impls
impl #impl_generics #bevy_reflect_path::Struct for #struct_path #ty_generics #where_reflect_clause {
fn field(&self, name: &str) -> #FQOption<&dyn #bevy_reflect_path::Reflect> {
match name {

View File

@ -1,4 +1,4 @@
use crate::impls::{impl_type_path, impl_typed};
use crate::impls::{impl_function_traits, impl_type_path, impl_typed};
use crate::ReflectStruct;
use bevy_macro_utils::fq_std::{FQAny, FQBox, FQDefault, FQOption, FQResult};
use quote::{quote, ToTokens};
@ -46,6 +46,8 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2::
let type_path_impl = impl_type_path(reflect_struct.meta());
let function_impls = impl_function_traits(reflect_struct.meta(), &where_clause_options);
let (impl_generics, ty_generics, where_clause) = reflect_struct
.meta()
.type_path()
@ -61,6 +63,8 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2::
#type_path_impl
#function_impls
impl #impl_generics #bevy_reflect_path::TupleStruct for #struct_path #ty_generics #where_reflect_clause {
fn field(&self, index: usize) -> #FQOption<&dyn #bevy_reflect_path::Reflect> {
match index {

View File

@ -1,4 +1,4 @@
use crate::impls::{impl_type_path, impl_typed};
use crate::impls::{impl_function_traits, impl_type_path, impl_typed};
use crate::utility::WhereClauseOptions;
use crate::ReflectMeta;
use bevy_macro_utils::fq_std::{FQAny, FQBox, FQClone, FQOption, FQResult};
@ -33,6 +33,8 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> proc_macro2::TokenStream {
let type_path_impl = impl_type_path(meta);
let function_impls = impl_function_traits(meta, &where_clause_options);
let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl();
let where_reflect_clause = where_clause_options.extend_where_clause(where_clause);
let get_type_registration_impl = meta.get_type_registration(&where_clause_options);
@ -44,6 +46,8 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> proc_macro2::TokenStream {
#typed_impl
#function_impls
impl #impl_generics #bevy_reflect_path::Reflect for #type_path #ty_generics #where_reflect_clause {
#[inline]
fn get_represented_type_info(&self) -> #FQOption<&'static #bevy_reflect_path::TypeInfo> {

View File

@ -154,13 +154,17 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
&self,
where_clause: Option<&WhereClause>,
) -> proc_macro2::TokenStream {
let type_path = self.meta.type_path();
let (_, ty_generics, _) = self.meta.type_path().generics().split_for_impl();
let required_bounds = self.required_bounds();
// Maintain existing where clause, if any.
let mut generic_where_clause = if let Some(where_clause) = where_clause {
let predicates = where_clause.predicates.iter();
quote! {where Self: #required_bounds, #(#predicates,)*}
quote! {where #type_path #ty_generics: #required_bounds, #(#predicates,)*}
} else {
quote!(where Self: #required_bounds,)
quote!(where #type_path #ty_generics: #required_bounds,)
};
// Add additional reflection trait bounds

View File

@ -1,3 +1,4 @@
use crate::func::macros::impl_function_traits;
use crate::{
self as bevy_reflect, utility::reflect_hasher, ApplyError, Reflect, ReflectKind, ReflectMut,
ReflectOwned, ReflectRef, TypeInfo, TypePath, TypePathTable,
@ -358,6 +359,7 @@ impl Array for DynamicArray {
}
impl_type_path!((in bevy_reflect) DynamicArray);
impl_function_traits!(DynamicArray);
/// An iterator over an [`Array`].
pub struct ArrayIter<'a> {
array: &'a dyn Array,

View File

@ -1,5 +1,6 @@
use bevy_reflect_derive::impl_type_path;
use crate::func::macros::impl_function_traits;
use crate::{
self as bevy_reflect, enum_debug, enum_hash, enum_partial_eq, ApplyError, DynamicStruct,
DynamicTuple, Enum, Reflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, Struct, Tuple,
@ -427,3 +428,4 @@ impl Reflect for DynamicEnum {
}
impl_type_path!((in bevy_reflect) DynamicEnum);
impl_function_traits!(DynamicEnum);

View File

@ -0,0 +1,80 @@
use crate::func::args::{ArgError, ArgInfo, Ownership};
use crate::Reflect;
/// Represents an argument that can be passed to a [`DynamicFunction`].
///
/// [`DynamicFunction`]: crate::func::DynamicFunction
#[derive(Debug)]
pub enum Arg<'a> {
Owned(Box<dyn Reflect>),
Ref(&'a dyn Reflect),
Mut(&'a mut dyn Reflect),
}
impl<'a> Arg<'a> {
/// Returns `Ok(T)` if the argument is [`Arg::Owned`].
pub fn take_owned<T: Reflect>(self, info: &ArgInfo) -> Result<T, ArgError> {
match self {
Arg::Owned(arg) => arg.take().map_err(|arg| ArgError::UnexpectedType {
id: info.id().clone(),
expected: ::std::borrow::Cow::Borrowed(info.type_path()),
received: ::std::borrow::Cow::Owned(arg.reflect_type_path().to_string()),
}),
Arg::Ref(_) => Err(ArgError::InvalidOwnership {
id: info.id().clone(),
expected: Ownership::Owned,
received: Ownership::Ref,
}),
Arg::Mut(_) => Err(ArgError::InvalidOwnership {
id: info.id().clone(),
expected: Ownership::Owned,
received: Ownership::Mut,
}),
}
}
/// Returns `Ok(&T)` if the argument is [`Arg::Ref`].
pub fn take_ref<T: Reflect>(self, info: &ArgInfo) -> Result<&'a T, ArgError> {
match self {
Arg::Owned(_) => Err(ArgError::InvalidOwnership {
id: info.id().clone(),
expected: Ownership::Ref,
received: Ownership::Owned,
}),
Arg::Ref(arg) => Ok(arg.downcast_ref().ok_or_else(|| ArgError::UnexpectedType {
id: info.id().clone(),
expected: ::std::borrow::Cow::Borrowed(info.type_path()),
received: ::std::borrow::Cow::Owned(arg.reflect_type_path().to_string()),
})?),
Arg::Mut(_) => Err(ArgError::InvalidOwnership {
id: info.id().clone(),
expected: Ownership::Ref,
received: Ownership::Mut,
}),
}
}
/// Returns `Ok(&mut T)` if the argument is [`Arg::Mut`].
pub fn take_mut<T: Reflect>(self, info: &ArgInfo) -> Result<&'a mut T, ArgError> {
match self {
Arg::Owned(_) => Err(ArgError::InvalidOwnership {
id: info.id().clone(),
expected: Ownership::Mut,
received: Ownership::Owned,
}),
Arg::Ref(_) => Err(ArgError::InvalidOwnership {
id: info.id().clone(),
expected: Ownership::Mut,
received: Ownership::Ref,
}),
Arg::Mut(arg) => {
let received = ::std::borrow::Cow::Owned(arg.reflect_type_path().to_string());
Ok(arg.downcast_mut().ok_or_else(|| ArgError::UnexpectedType {
id: info.id().clone(),
expected: ::std::borrow::Cow::Borrowed(info.type_path()),
received,
})?)
}
}
}
}

View File

@ -0,0 +1,26 @@
use alloc::borrow::Cow;
use thiserror::Error;
use crate::func::args::{ArgId, Ownership};
/// An error that occurs when converting an [argument].
///
/// [argument]: crate::func::Arg
#[derive(Debug, Error, PartialEq)]
pub enum ArgError {
/// The argument is not the expected type.
#[error("expected `{expected}` but received `{received}` (@ {id:?})")]
UnexpectedType {
id: ArgId,
expected: Cow<'static, str>,
received: Cow<'static, str>,
},
/// The argument has the wrong ownership.
#[error("expected {expected} value but received {received} value (@ {id:?})")]
InvalidOwnership {
id: ArgId,
expected: Ownership,
received: Ownership,
},
}

View File

@ -0,0 +1,104 @@
use crate::func::args::{Arg, ArgError, ArgInfo};
/// A trait for types that can be created from an [`Arg`].
///
/// This trait is used instead of a blanket [`From`] implementation due to coherence issues:
/// we can't implement `From<T>` for both `T` and `&T`/`&mut T`.
///
/// This trait is automatically implemented when using the `Reflect` [derive macro].
///
/// [derive macro]: derive@crate::Reflect
pub trait FromArg {
/// The type of the item created from the argument.
///
/// This should almost always be the same as `Self`, but with the lifetime `'a`.
type Item<'a>;
/// Creates an item from an argument.
///
/// The argument must be of the expected type and ownership.
fn from_arg<'a>(arg: Arg<'a>, info: &ArgInfo) -> Result<Self::Item<'a>, ArgError>;
}
/// Implements the [`FromArg`] trait for the given type.
///
/// This will implement it for `$ty`, `&$ty`, and `&mut $ty`.
///
/// See [`impl_function_traits`] for details on syntax.
///
/// [`impl_function_traits`]: crate::func::macros::impl_function_traits
macro_rules! impl_from_arg {
(
$ty: ty
$(;
<
$($T: ident $(: $T1: tt $(+ $T2: tt)*)?),*
>
)?
$(
[
$(const $N: ident : $size: ident),*
]
)?
$(
where
$($U: ty $(: $U1: tt $(+ $U2: tt)*)?),*
)?
) => {
impl <
$($($T $(: $T1 $(+ $T2)*)?),*)?
$(, $(const $N : $size),*)?
> $crate::func::args::FromArg for $ty
$(
where
$($U $(: $U1 $(+ $U2)*)?),*
)?
{
type Item<'from_arg> = $ty;
fn from_arg<'from_arg>(
arg: $crate::func::args::Arg<'from_arg>,
info: &$crate::func::args::ArgInfo,
) -> Result<Self::Item<'from_arg>, $crate::func::args::ArgError> {
arg.take_owned(info)
}
}
impl <
$($($T $(: $T1 $(+ $T2)*)?),*)?
$(, $(const $N : $size),*)?
> $crate::func::args::FromArg for &'static $ty
$(
where
$($U $(: $U1 $(+ $U2)*)?),*
)?
{
type Item<'from_arg> = &'from_arg $ty;
fn from_arg<'from_arg>(
arg: $crate::func::args::Arg<'from_arg>,
info: &$crate::func::args::ArgInfo,
) -> Result<Self::Item<'from_arg>, $crate::func::args::ArgError> {
arg.take_ref(info)
}
}
impl <
$($($T $(: $T1 $(+ $T2)*)?),*)?
$(, $(const $N : $size),*)?
> $crate::func::args::FromArg for &'static mut $ty
$(
where
$($U $(: $U1 $(+ $U2)*)?),*
)?
{
type Item<'from_arg> = &'from_arg mut $ty;
fn from_arg<'from_arg>(
arg: $crate::func::args::Arg<'from_arg>,
info: &$crate::func::args::ArgInfo,
) -> Result<Self::Item<'from_arg>, $crate::func::args::ArgError> {
arg.take_mut(info)
}
}
};
}
pub(crate) use impl_from_arg;

View File

@ -0,0 +1,94 @@
use alloc::borrow::Cow;
use crate::func::args::{GetOwnership, Ownership};
use crate::TypePath;
/// Type information for an [`Arg`] used in a [`DynamicFunction`].
///
/// [`Arg`]: crate::func::args::Arg
/// [`DynamicFunction`]: super::function::DynamicFunction
#[derive(Debug, Clone)]
pub struct ArgInfo {
/// The index of the argument within its function.
index: usize,
/// The name of the argument (if provided).
name: Option<Cow<'static, str>>,
/// The ownership of the argument.
ownership: Ownership,
/// The [type path] of the argument.
///
/// [type path]: TypePath::type_path
type_path: &'static str,
}
impl ArgInfo {
/// Create a new [`ArgInfo`] with the given argument index and type `T`.
///
/// To set the name of the argument, use [`Self::with_name`].
pub fn new<T: TypePath + GetOwnership>(index: usize) -> Self {
Self {
index,
name: None,
ownership: T::ownership(),
type_path: T::type_path(),
}
}
/// Set the name of the argument.
///
/// Reflected arguments are not required to have a name and by default are not given one,
/// so this method must be called manually to set the name.
pub fn with_name(mut self, name: impl Into<Cow<'static, str>>) -> Self {
self.name = Some(name.into());
self
}
/// The index of the argument within its function.
pub fn index(&self) -> usize {
self.index
}
/// The name of the argument, if it was given one.
///
/// Note that this may return `None` even if the argument has a name.
/// This is because the name needs to be manually set using [`Self::with_name`]
/// since the name can't be inferred from the function type alone.
///
/// For [`DynamicFunctions`] created using [`IntoFunction`], the name will always be `None`.
///
/// [`DynamicFunctions`]: crate::func::DynamicFunction
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
/// The ownership of the argument.
pub fn ownership(&self) -> Ownership {
self.ownership
}
pub fn type_path(&self) -> &'static str {
self.type_path
}
/// Get an ID representing the argument.
///
/// This will return `ArgId::Name` if the argument has a name,
/// otherwise `ArgId::Index`.
pub fn id(&self) -> ArgId {
self.name
.clone()
.map(ArgId::Name)
.unwrap_or_else(|| ArgId::Index(self.index))
}
}
/// A representation of an argument.
///
/// This is primarily used for error reporting and debugging.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ArgId {
/// The index of the argument within its function.
Index(usize),
/// The name of the argument.
Name(Cow<'static, str>),
}

View File

@ -0,0 +1,81 @@
use crate::func::args::Arg;
use crate::Reflect;
/// A list of arguments that can be passed to a [`DynamicFunction`].
///
/// # Example
///
/// ```
/// # use bevy_reflect::func::{Arg, ArgList};
/// let foo = 123;
/// let bar = 456;
/// let mut baz = 789;
/// let args = ArgList::new()
/// // Push an owned argument
/// .push_owned(foo)
/// // Push an owned and boxed argument
/// .push_boxed(Box::new(foo))
/// // Push a reference argument
/// .push_ref(&bar)
/// // Push a mutable reference argument
/// .push_mut(&mut baz)
/// // Push a manually constructed argument
/// .push(Arg::Ref(&3.14));
/// ```
///
/// [`DynamicFunction`]: crate::func::DynamicFunction
#[derive(Default, Debug)]
pub struct ArgList<'a>(Vec<Arg<'a>>);
impl<'a> ArgList<'a> {
/// Create a new empty list of arguments.
pub fn new() -> Self {
Self(Vec::new())
}
/// Push an [`Arg`] onto the list.
pub fn push(mut self, arg: Arg<'a>) -> Self {
self.0.push(arg);
self
}
/// Push an [`Arg::Ref`] onto the list with the given reference.
pub fn push_ref(self, arg: &'a dyn Reflect) -> Self {
self.push(Arg::Ref(arg))
}
/// Push an [`Arg::Mut`] onto the list with the given mutable reference.
pub fn push_mut(self, arg: &'a mut dyn Reflect) -> Self {
self.push(Arg::Mut(arg))
}
/// Push an [`Arg::Owned`] onto the list with the given owned value.
pub fn push_owned(self, arg: impl Reflect) -> Self {
self.push(Arg::Owned(Box::new(arg)))
}
/// Push an [`Arg::Owned`] onto the list with the given boxed value.
pub fn push_boxed(self, arg: Box<dyn Reflect>) -> Self {
self.push(Arg::Owned(arg))
}
/// Pop the last argument from the list, if there is one.
pub fn pop(&mut self) -> Option<Arg<'a>> {
self.0.pop()
}
/// Returns the number of arguments in the list.
pub fn len(&self) -> usize {
self.0.len()
}
/// Returns `true` if the list of arguments is empty.
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// Take ownership of the list of arguments.
pub fn take(self) -> Vec<Arg<'a>> {
self.0
}
}

View File

@ -0,0 +1,17 @@
//! Argument types and utilities for working with [`DynamicFunctions`].
//!
//! [`DynamicFunctions`]: crate::func::DynamicFunction
pub use arg::*;
pub use error::*;
pub use from_arg::*;
pub use info::*;
pub use list::*;
pub use ownership::*;
mod arg;
mod error;
mod from_arg;
mod info;
mod list;
mod ownership;

View File

@ -0,0 +1,103 @@
use core::fmt::{Display, Formatter};
/// A trait for getting the ownership of a type.
///
/// This trait is automatically implemented when using the `Reflect` [derive macro].
///
/// [derive macro]: derive@crate::Reflect
pub trait GetOwnership {
/// Returns the ownership of [`Self`].
fn ownership() -> Ownership;
}
/// The ownership of a type.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Ownership {
/// The type is a reference (i.e. `&T`).
Ref,
/// The type is a mutable reference (i.e. `&mut T`).
Mut,
/// The type is owned (i.e. `T`).
Owned,
}
impl Display for Ownership {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
Self::Ref => write!(f, "reference"),
Self::Mut => write!(f, "mutable reference"),
Self::Owned => write!(f, "owned"),
}
}
}
/// Implements the [`GetOwnership`] trait for the given type.
///
/// This will implement it for `$ty`, `&$ty`, and `&mut $ty`.
///
/// See [`impl_function_traits`] for details on syntax.
///
/// [`impl_function_traits`]: crate::func::macros::impl_function_traits
macro_rules! impl_get_ownership {
(
$ty: ty
$(;
<
$($T: ident $(: $T1: tt $(+ $T2: tt)*)?),*
>
)?
$(
[
$(const $N: ident : $size: ident),*
]
)?
$(
where
$($U: ty $(: $U1: tt $(+ $U2: tt)*)?),*
)?
) => {
impl <
$($($T $(: $T1 $(+ $T2)*)?),*)?
$(, $(const $N : $size),*)?
> $crate::func::args::GetOwnership for $ty
$(
where
$($U $(: $U1 $(+ $U2)*)?),*
)?
{
fn ownership() -> $crate::func::args::Ownership {
$crate::func::args::Ownership::Owned
}
}
impl <
$($($T $(: $T1 $(+ $T2)*)?),*)?
$(, $(const $N : $size),*)?
> $crate::func::args::GetOwnership for &'_ $ty
$(
where
$($U $(: $U1 $(+ $U2)*)?),*
)?
{
fn ownership() -> $crate::func::args::Ownership {
$crate::func::args::Ownership::Ref
}
}
impl <
$($($T $(: $T1 $(+ $T2)*)?),*)?
$(, $(const $N : $size),*)?
> $crate::func::args::GetOwnership for &'_ mut $ty
$(
where
$($U $(: $U1 $(+ $U2)*)?),*
)?
{
fn ownership() -> $crate::func::args::Ownership {
$crate::func::args::Ownership::Mut
}
}
};
}
pub(crate) use impl_get_ownership;

View File

@ -0,0 +1,15 @@
use crate::func::args::ArgError;
use thiserror::Error;
/// An error that occurs when calling a [`DynamicFunction`].
///
/// [`DynamicFunction`]: crate::func::DynamicFunction
#[derive(Debug, Error, PartialEq)]
pub enum FunctionError {
/// An error occurred while converting an argument.
#[error(transparent)]
ArgError(#[from] ArgError),
/// The number of arguments provided does not match the expected number.
#[error("expected {expected} arguments but received {received}")]
InvalidArgCount { expected: usize, received: usize },
}

View File

@ -0,0 +1,220 @@
use crate::func::args::{ArgInfo, ArgList};
use crate::func::error::FunctionError;
use crate::func::info::FunctionInfo;
use crate::func::return_type::Return;
use crate::func::{IntoFunction, ReturnInfo};
use alloc::borrow::Cow;
use core::fmt::{Debug, Formatter};
use std::ops::DerefMut;
/// The result of calling a dynamic [`DynamicFunction`].
///
/// Returns `Ok(value)` if the function was called successfully,
/// where `value` is the [`Return`] value of the function.
pub type FunctionResult<'a> = Result<Return<'a>, FunctionError>;
/// A dynamic representation of a Rust function.
///
/// Internally this stores a function pointer and associated info.
///
/// You will generally not need to construct this manually.
/// Instead, many functions and closures can be automatically converted using the [`IntoFunction`] trait.
///
/// # Example
///
/// Most of the time, a [`DynamicFunction`] can be created using the [`IntoFunction`] trait:
///
/// ```
/// # use bevy_reflect::func::args::ArgList;
/// # use bevy_reflect::func::{DynamicFunction, IntoFunction};
/// fn add(a: i32, b: i32) -> i32 {
/// a + b
/// }
///
/// // Convert the function into a dynamic function using `IntoFunction::into_function`
/// let mut func: DynamicFunction = add.into_function();
///
/// // Dynamically call the function:
/// let args = ArgList::default().push_owned(25_i32).push_owned(75_i32);
/// let value = func.call(args).unwrap().unwrap_owned();
///
/// // Check the result:
/// assert_eq!(value.downcast_ref::<i32>(), Some(&100));
/// ```
///
/// However, in some cases, these functions may need to be created manually:
///
/// ```
/// # use bevy_reflect::func::{ArgList, DynamicFunction, FunctionInfo, IntoFunction, Return, ReturnInfo};
/// # use bevy_reflect::func::args::ArgInfo;
/// fn append(value: String, list: &mut Vec<String>) -> &mut String {
/// list.push(value);
/// list.last_mut().unwrap()
/// }
///
/// // Due to the return value being a reference that is not tied to the first argument,
/// // this will fail to compile:
/// // let mut func: DynamicFunction = append.into_function();
///
/// // Instead, we need to define the function manually.
/// // We start by defining the shape of the function:
/// let info = FunctionInfo::new()
/// .with_name("append")
/// .with_args(vec![
/// ArgInfo::new::<String>(0).with_name("value"),
/// ArgInfo::new::<&mut Vec<String>>(1).with_name("list"),
/// ])
/// .with_return_info(
/// ReturnInfo::new::<&mut String>()
/// );
///
/// // Then we define the dynamic function, which will be used to call our `append` function:
/// let mut func = DynamicFunction::new(|mut args, info| {
/// // Arguments are popped from the list in reverse order:
/// let arg1 = args.pop().unwrap().take_mut::<Vec<String>>(&info.args()[1]).unwrap();
/// let arg0 = args.pop().unwrap().take_owned::<String>(&info.args()[0]).unwrap();
///
/// // Then we can call our function and return the result:
/// Ok(Return::Mut(append(arg0, arg1)))
/// }, info);
///
/// let mut list = Vec::<String>::new();
///
/// // Dynamically call the function:
/// let args = ArgList::default().push_owned("Hello, World".to_string()).push_mut(&mut list);
/// let value = func.call(args).unwrap().unwrap_mut();
///
/// // Mutate the return value:
/// value.downcast_mut::<String>().unwrap().push_str("!!!");
///
/// // Check the result:
/// assert_eq!(list, vec!["Hello, World!!!"]);
/// ```
pub struct DynamicFunction<'env> {
info: FunctionInfo,
func: Box<dyn for<'a> FnMut(ArgList<'a>, &FunctionInfo) -> FunctionResult<'a> + 'env>,
}
impl<'env> DynamicFunction<'env> {
/// Create a new dynamic [`DynamicFunction`].
///
/// The given function can be used to call out to a regular function, closure, or method.
///
/// It's important that the function signature matches the provided [`FunctionInfo`].
/// This info is used to validate the arguments and return value.
pub fn new<F: for<'a> FnMut(ArgList<'a>, &FunctionInfo) -> FunctionResult<'a> + 'env>(
func: F,
info: FunctionInfo,
) -> Self {
Self {
info,
func: Box::new(func),
}
}
/// Set the name of the function.
///
/// For [`DynamicFunctions`] created using [`IntoFunction`],
/// the default name will always be the full path to the function as returned by [`std::any::type_name`].
///
/// [`DynamicFunctions`]: DynamicFunction
pub fn with_name(mut self, name: impl Into<Cow<'static, str>>) -> Self {
self.info = self.info.with_name(name);
self
}
/// Set the arguments of the function.
///
/// It is very important that the arguments match the intended function signature,
/// as this is used to validate arguments passed to the function.
pub fn with_args(mut self, args: Vec<ArgInfo>) -> Self {
self.info = self.info.with_args(args);
self
}
/// Set the return information of the function.
pub fn with_return_info(mut self, return_info: ReturnInfo) -> Self {
self.info = self.info.with_return_info(return_info);
self
}
/// Call the function with the given arguments.
///
/// # Example
///
/// ```
/// # use bevy_reflect::func::{IntoFunction, ArgList};
/// fn add(left: i32, right: i32) -> i32 {
/// left + right
/// }
///
/// let mut func = add.into_function();
/// let args = ArgList::new().push_owned(25_i32).push_owned(75_i32);
/// let result = func.call(args).unwrap().unwrap_owned();
/// assert_eq!(result.take::<i32>().unwrap(), 100);
/// ```
pub fn call<'a>(&mut self, args: ArgList<'a>) -> FunctionResult<'a> {
(self.func.deref_mut())(args, &self.info)
}
/// Call the function with the given arguments and consume the function.
///
/// This is useful for closures that capture their environment because otherwise
/// any captured variables would still be borrowed by this function.
///
/// # Example
///
/// ```
/// # use bevy_reflect::func::{IntoFunction, ArgList};
/// let mut count = 0;
/// let increment = |amount: i32| {
/// count += amount;
/// };
/// let increment_function = increment.into_function();
/// let args = ArgList::new().push_owned(5_i32);
/// // We need to drop `increment_function` here so that we
/// // can regain access to `count`.
/// increment_function.call_once(args).unwrap();
/// assert_eq!(count, 5);
/// ```
pub fn call_once(mut self, args: ArgList) -> FunctionResult {
(self.func.deref_mut())(args, &self.info)
}
/// Returns the function info.
pub fn info(&self) -> &FunctionInfo {
&self.info
}
}
/// Outputs the function signature.
///
/// This takes the format: `DynamicFunction(fn {name}({arg1}: {type1}, {arg2}: {type2}, ...) -> {return_type})`.
///
/// Names for arguments and the function itself are optional and will default to `_` if not provided.
impl<'env> Debug for DynamicFunction<'env> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let name = self.info.name().unwrap_or("_");
write!(f, "DynamicFunction(fn {name}(")?;
for (index, arg) in self.info.args().iter().enumerate() {
let name = arg.name().unwrap_or("_");
let ty = arg.type_path();
write!(f, "{name}: {ty}")?;
if index + 1 < self.info.args().len() {
write!(f, ", ")?;
}
}
let ret = self.info.return_info().type_path();
write!(f, ") -> {ret})")
}
}
impl<'env> IntoFunction<'env, ()> for DynamicFunction<'env> {
#[inline]
fn into_function(self) -> DynamicFunction<'env> {
self
}
}

View File

@ -0,0 +1,112 @@
use crate::func::args::{ArgInfo, GetOwnership, Ownership};
use crate::TypePath;
use alloc::borrow::Cow;
/// Type information for a [`DynamicFunction`].
///
/// [`DynamicFunction`]: crate::func::DynamicFunction
#[derive(Debug, Clone)]
pub struct FunctionInfo {
name: Option<Cow<'static, str>>,
args: Vec<ArgInfo>,
return_info: ReturnInfo,
}
impl FunctionInfo {
/// Create a new [`FunctionInfo`].
///
/// To set the name of the function, use [`Self::with_name`].
pub fn new() -> Self {
Self {
name: None,
args: Vec::new(),
return_info: ReturnInfo::new::<()>(),
}
}
/// Set the name of the function.
///
/// Reflected functions are not required to have a name,
/// so this method must be called manually to set the name.
pub fn with_name(mut self, name: impl Into<Cow<'static, str>>) -> Self {
self.name = Some(name.into());
self
}
/// Set the arguments of the function.
///
/// Arguments passed to the function will be validated against the info provided here.
/// Mismatched arguments may result in the function call returning an [error].
///
/// [error]: crate::func::FunctionError
pub fn with_args(mut self, args: Vec<ArgInfo>) -> Self {
self.args = args;
self
}
/// Set the return information of the function.
pub fn with_return_info(mut self, return_info: ReturnInfo) -> Self {
self.return_info = return_info;
self
}
/// The name of the function, if it was given one.
///
/// For [`DynamicFunctions`] created using [`IntoFunction`],
/// the name will always be the full path to the function as returned by [`std::any::type_name`].
///
/// [`DynamicFunctions`]: crate::func::DynamicFunction
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
/// The arguments of the function.
pub fn args(&self) -> &[ArgInfo] {
&self.args
}
/// The number of arguments the function takes.
pub fn arg_count(&self) -> usize {
self.args.len()
}
/// The return information of the function.
pub fn return_info(&self) -> &ReturnInfo {
&self.return_info
}
}
impl Default for FunctionInfo {
fn default() -> Self {
Self::new()
}
}
/// Information about the return type of a [`DynamicFunction`].
///
/// [`DynamicFunction`]: crate::func::DynamicFunction
#[derive(Debug, Clone)]
pub struct ReturnInfo {
type_path: &'static str,
ownership: Ownership,
}
impl ReturnInfo {
/// Create a new [`ReturnInfo`] representing the given type, `T`.
pub fn new<T: TypePath + GetOwnership>() -> Self {
Self {
type_path: T::type_path(),
ownership: T::ownership(),
}
}
/// The type path of the return type.
pub fn type_path(&self) -> &'static str {
self.type_path
}
/// The ownership of the return type.
pub fn ownership(&self) -> Ownership {
self.ownership
}
}

View File

@ -0,0 +1,318 @@
use crate::func::function::DynamicFunction;
use bevy_utils::all_tuples;
/// A trait for types that can be converted into a [`DynamicFunction`].
///
/// # Blanket Implementation
///
/// This trait has a blanket implementation that covers many functions, closures, and methods.
/// And though it works for many cases, it does have some limitations.
///
/// ## Arguments
///
/// Firstly, the function signature may only have up to 15 arguments
/// (or 16 if the first argument is a mutable/immutable reference).
/// This limitation is unfortunately due to the [lack of variadic generics] in Rust.
///
/// Each argument must implement [`FromArg`], [`GetOwnership`], and [`TypePath`].
///
///
/// ```compile_fail
/// # use bevy_reflect::func::IntoFunction;
/// fn too_many_args(
/// arg01: i32,
/// arg02: i32,
/// arg03: i32,
/// arg04: i32,
/// arg05: i32,
/// arg06: i32,
/// arg07: i32,
/// arg08: i32,
/// arg09: i32,
/// arg10: i32,
/// arg11: i32,
/// arg12: i32,
/// arg13: i32,
/// arg14: i32,
/// arg15: i32,
/// arg16: i32,
/// ) {
/// // ...
/// }
///
/// // This will fail to compile:
/// too_many_args.into_function();
/// ```
///
/// ## Return Type
///
/// Secondly, the allowed return type is dependent on the first argument of the function:
/// - If the first argument is an immutable reference,
/// then the return type may be either an owned type, a static reference type, or a reference type
/// bound to the lifetime of the first argument.
/// - If the first argument is a mutable reference,
/// then the return type may be either an owned type, a static reference type, or be a mutable reference type
/// bound to the lifetime of the first argument.
/// - If the first argument is an owned type,
/// then the return type may be either an owned type or a static reference type.
///
/// The return type must always implement [`GetOwnership`] and [`TypePath`].
/// If it is either an owned type or a static reference type,
/// then it must also implement [`IntoReturn`].
/// Otherwise, it must also implement [`Reflect`].
///
/// Note that both `GetOwnership`, `TypePath`, and `IntoReturn` are automatically implemented
/// when [deriving `Reflect`].
///
/// ```
/// # use bevy_reflect::func::IntoFunction;
/// fn owned_return(arg: i32) -> i32 { arg * 2 }
/// fn ref_return(arg: &i32) -> &i32 { arg }
/// fn mut_return(arg: &mut i32) -> &mut i32 { arg }
/// fn static_return(arg: i32) -> &'static i32 { &123 }
///
/// owned_return.into_function();
/// ref_return.into_function();
/// mut_return.into_function();
/// static_return.into_function();
/// ```
///
/// [lack of variadic generics]: https://poignardazur.github.io/2024/05/25/report-on-rustnl-variadics/
/// [`FromArg`]: crate::func::args::FromArg
/// [`GetOwnership`]: crate::func::args::GetOwnership
/// [`TypePath`]: crate::TypePath
/// [`IntoReturn`]: crate::func::IntoReturn
/// [`Reflect`]: crate::Reflect
/// [deriving `Reflect`]: derive@crate::Reflect
pub trait IntoFunction<'env, Marker> {
/// Converts [`Self`] into a [`DynamicFunction`].
fn into_function(self) -> DynamicFunction<'env>;
}
/// Helper macro that returns the number of tokens it receives.
///
/// This is used to get the argument count.
///
/// See [here] for details.
///
/// [here]: https://veykril.github.io/tlborm/decl-macros/building-blocks/counting.html#bit-twiddling
macro_rules! count_tts {
() => { 0 };
($odd:tt $($a:tt $b:tt)*) => { (count_tts!($($a)*) << 1) | 1 };
($($a:tt $even:tt)*) => { count_tts!($($a)*) << 1 };
}
/// Helper macro for implementing [`IntoFunction`] on Rust functions.
///
/// This currently implements it for the following signatures (where `argX` may be any of `T`, `&T`, or `&mut T`):
/// - `fn(arg0, arg1, ..., argN) -> R`
/// - `fn(&Receiver, arg0, arg1, ..., argN) -> &R`
/// - `fn(&mut Receiver, arg0, arg1, ..., argN) -> &mut R`
/// - `fn(&mut Receiver, arg0, arg1, ..., argN) -> &R`
macro_rules! impl_into_function {
($(($Arg:ident, $arg:ident)),*) => {
// === Owned Return === //
impl<'env, $($Arg,)* R, F> $crate::func::IntoFunction<'env, fn($($Arg),*) -> R> for F
where
$($Arg: $crate::func::args::FromArg + $crate::func::args::GetOwnership + $crate::TypePath,)*
R: $crate::func::IntoReturn + $crate::func::args::GetOwnership + $crate::TypePath,
F: FnMut($($Arg),*) -> R + 'env,
F: for<'a> FnMut($($Arg::Item<'a>),*) -> R + 'env,
{
fn into_function(mut self) -> $crate::func::DynamicFunction<'env> {
const COUNT: usize = count_tts!($($Arg)*);
let info = $crate::func::FunctionInfo::new()
.with_name(std::any::type_name::<F>())
.with_args({
#[allow(unused_mut)]
let mut _index = 0;
vec![
$($crate::func::args::ArgInfo::new::<$Arg>({
_index += 1;
_index - 1
}),)*
]
})
.with_return_info($crate::func::ReturnInfo::new::<R>());
$crate::func::DynamicFunction::new(move |args, _info| {
if args.len() != COUNT {
return Err($crate::func::error::FunctionError::InvalidArgCount {
expected: COUNT,
received: args.len(),
});
}
let [$($arg,)*] = args.take().try_into().expect("invalid number of arguments");
#[allow(unused_mut)]
let mut _index = 0;
let ($($arg,)*) = ($($Arg::from_arg($arg, {
_index += 1;
_info.args().get(_index - 1).expect("argument index out of bounds")
})?,)*);
Ok((self)($($arg,)*).into_return())
}, info)
}
}
// === Ref Receiver + Ref Return === //
impl<'env, Receiver, $($Arg,)* R, F> $crate::func::IntoFunction<'env, fn(&Receiver, $($Arg),*) -> fn(&R)> for F
where
Receiver: $crate::Reflect + $crate::TypePath,
for<'a> &'a Receiver: $crate::func::args::GetOwnership,
R: $crate::Reflect + $crate::TypePath,
for<'a> &'a R: $crate::func::args::GetOwnership,
$($Arg: $crate::func::args::FromArg + $crate::func::args::GetOwnership + $crate::TypePath,)*
F: for<'a> FnMut(&'a Receiver, $($Arg),*) -> &'a R + 'env,
F: for<'a> FnMut(&'a Receiver, $($Arg::Item<'a>),*) -> &'a R + 'env,
{
fn into_function(mut self) -> $crate::func::DynamicFunction<'env> {
const COUNT: usize = count_tts!(Receiver $($Arg)*);
let info = $crate::func::FunctionInfo::new()
.with_name(std::any::type_name::<F>())
.with_args({
#[allow(unused_mut)]
let mut _index = 1;
vec![
$crate::func::args::ArgInfo::new::<&Receiver>(0),
$($crate::func::args::ArgInfo::new::<$Arg>({
_index += 1;
_index - 1
}),)*
]
})
.with_return_info($crate::func::ReturnInfo::new::<&R>());
$crate::func::DynamicFunction::new(move |args, _info| {
if args.len() != COUNT {
return Err($crate::func::error::FunctionError::InvalidArgCount {
expected: COUNT,
received: args.len(),
});
}
let [receiver, $($arg,)*] = args.take().try_into().expect("invalid number of arguments");
let receiver = receiver.take_ref::<Receiver>(_info.args().get(0).expect("argument index out of bounds"))?;
#[allow(unused_mut)]
let mut _index = 1;
let ($($arg,)*) = ($($Arg::from_arg($arg, {
_index += 1;
_info.args().get(_index - 1).expect("argument index out of bounds")
})?,)*);
Ok($crate::func::Return::Ref((self)(receiver, $($arg,)*)))
}, info)
}
}
// === Mut Receiver + Mut Return === //
impl<'env, Receiver, $($Arg,)* R, F> $crate::func::IntoFunction<'env, fn(&mut Receiver, $($Arg),*) -> fn(&mut R)> for F
where
Receiver: $crate::Reflect + $crate::TypePath,
for<'a> &'a mut Receiver: $crate::func::args::GetOwnership,
R: $crate::Reflect + $crate::TypePath,
for<'a> &'a mut R: $crate::func::args::GetOwnership,
$($Arg: $crate::func::args::FromArg + $crate::func::args::GetOwnership + $crate::TypePath,)*
F: for<'a> FnMut(&'a mut Receiver, $($Arg),*) -> &'a mut R + 'env,
F: for<'a> FnMut(&'a mut Receiver, $($Arg::Item<'a>),*) -> &'a mut R + 'env,
{
fn into_function(mut self) -> $crate::func::DynamicFunction<'env> {
const COUNT: usize = count_tts!(Receiver $($Arg)*);
let info = $crate::func::FunctionInfo::new()
.with_name(std::any::type_name::<F>())
.with_args({
#[allow(unused_mut)]
let mut _index = 1;
vec![
$crate::func::args::ArgInfo::new::<&mut Receiver>(0),
$($crate::func::args::ArgInfo::new::<$Arg>({
_index += 1;
_index - 1
}),)*
]
})
.with_return_info($crate::func::ReturnInfo::new::<&mut R>());
$crate::func::DynamicFunction::new(move |args, _info| {
if args.len() != COUNT {
return Err($crate::func::error::FunctionError::InvalidArgCount {
expected: COUNT,
received: args.len(),
});
}
let [receiver, $($arg,)*] = args.take().try_into().expect("invalid number of arguments");
let receiver = receiver.take_mut::<Receiver>(_info.args().get(0).expect("argument index out of bounds"))?;
#[allow(unused_mut)]
let mut _index = 1;
let ($($arg,)*) = ($($Arg::from_arg($arg, {
_index += 1;
_info.args().get(_index - 1).expect("argument index out of bounds")
})?,)*);
Ok($crate::func::Return::Mut((self)(receiver, $($arg,)*)))
}, info)
}
}
// === Mut Receiver + Ref Return === //
impl<'env, Receiver, $($Arg,)* R, F> $crate::func::IntoFunction<'env, fn(&mut Receiver, $($Arg),*) -> fn(&mut R) -> &R> for F
where
Receiver: $crate::Reflect + $crate::TypePath,
for<'a> &'a mut Receiver: $crate::func::args::GetOwnership,
R: $crate::Reflect + $crate::TypePath,
for<'a> &'a mut R: $crate::func::args::GetOwnership,
$($Arg: $crate::func::args::FromArg + $crate::func::args::GetOwnership + $crate::TypePath,)*
F: for<'a> FnMut(&'a mut Receiver, $($Arg),*) -> &'a R + 'env,
F: for<'a> FnMut(&'a mut Receiver, $($Arg::Item<'a>),*) -> &'a R + 'env,
{
fn into_function(mut self) -> $crate::func::DynamicFunction<'env> {
const COUNT: usize = count_tts!(Receiver $($Arg)*);
let info = $crate::func::FunctionInfo::new()
.with_name(std::any::type_name::<F>())
.with_args({
#[allow(unused_mut)]
let mut _index = 1;
vec![
$crate::func::args::ArgInfo::new::<&mut Receiver>(0),
$($crate::func::args::ArgInfo::new::<$Arg>({
_index += 1;
_index - 1
}),)*
]
})
.with_return_info($crate::func::ReturnInfo::new::<&mut R>());
$crate::func::DynamicFunction::new(move |args, _info| {
if args.len() != COUNT {
return Err($crate::func::error::FunctionError::InvalidArgCount {
expected: COUNT,
received: args.len(),
});
}
let [receiver, $($arg,)*] = args.take().try_into().expect("invalid number of arguments");
let receiver = receiver.take_mut::<Receiver>(_info.args().get(0).expect("argument index out of bounds"))?;
#[allow(unused_mut)]
let mut _index = 1;
let ($($arg,)*) = ($($Arg::from_arg($arg, {
_index += 1;
_info.args().get(_index - 1).expect("argument index out of bounds")
})?,)*);
Ok($crate::func::Return::Ref((self)(receiver, $($arg,)*)))
}, info)
}
}
};
}
all_tuples!(impl_into_function, 0, 15, Arg, arg);

View File

@ -0,0 +1,99 @@
/// Helper macro to implement the necessary traits for function reflection.
///
/// This macro calls the following macros:
/// - [`impl_get_ownership`](crate::func::args::impl_get_ownership)
/// - [`impl_from_arg`](crate::func::args::impl_from_arg)
/// - [`impl_into_return`](crate::func::impl_into_return)
///
/// # Syntax
///
/// For non-generic types, the macro simply expects the type:
///
/// ```ignore
/// impl_function_traits!(foo::bar::Baz);
/// ```
///
/// For generic types, however, the generic type parameters must also be given in angle brackets (`<` and `>`):
///
/// ```ignore
/// impl_function_traits!(foo::bar::Baz<T, U>; <T: Clone, U>);
/// ```
///
/// For generic const parameters, they must be given in square brackets (`[` and `]`):
///
/// ```ignore
/// impl_function_traits!(foo::bar::Baz<T, N>; <T> [const N: usize]);
/// ```
macro_rules! impl_function_traits {
(
$ty: ty
$(;
<
$($T: ident $(: $T1: tt $(+ $T2: tt)*)?),*
>
)?
$(
[
$(const $N: ident : $size: ident),*
]
)?
$(
where
$($U: ty $(: $U1: tt $(+ $U2: tt)*)?),*
)?
) => {
$crate::func::args::impl_get_ownership!(
$ty
$(;
<
$($T $(: $T1 $(+ $T2)*)?),*
>
)?
$(
[
$(const $N : $size),*
]
)?
$(
where
$($U $(: $U1 $(+ $U2)*)?),*
)?
);
$crate::func::args::impl_from_arg!(
$ty
$(;
<
$($T $(: $T1 $(+ $T2)*)?),*
>
)?
$(
[
$(const $N : $size),*
]
)?
$(
where
$($U $(: $U1 $(+ $U2)*)?),*
)?
);
$crate::func::impl_into_return!(
$ty
$(;
<
$($T $(: $T1 $(+ $T2)*)?),*
>
)?
$(
[
$(const $N : $size),*
]
)?
$(
where
$($U $(: $U1 $(+ $U2)*)?),*
)?
);
};
}
pub(crate) use impl_function_traits;

View File

@ -0,0 +1,265 @@
//! Reflection-based dynamic functions.
//!
//! This module provides a way to pass around and call functions dynamically
//! using the [`DynamicFunction`] type.
//!
//! Many simple functions and closures can be automatically converted to [`DynamicFunction`]
//! using the [`IntoFunction`] trait.
//!
//! Once the [`DynamicFunction`] is created, it can be called with a set of arguments provided
//! via an [`ArgList`].
//!
//! This returns a [`FunctionResult`] containing the [`Return`] value,
//! which can be used to extract a [`Reflect`] trait object.
//!
//!
//! # Example
//!
//! ```
//! # use bevy_reflect::Reflect;
//! # use bevy_reflect::func::args::ArgList;
//! # use bevy_reflect::func::{DynamicFunction, FunctionResult, IntoFunction, Return};
//! fn add(a: i32, b: i32) -> i32 {
//! a + b
//! }
//!
//! let mut func: DynamicFunction = add.into_function();
//! let args: ArgList = ArgList::default()
//! // Pushing a known type with owned ownership
//! .push_owned(25_i32)
//! // Pushing a reflected type with owned ownership
//! .push_boxed(Box::new(75_i32) as Box<dyn Reflect>);
//! let result: FunctionResult = func.call(args);
//! let value: Return = result.unwrap();
//! assert_eq!(value.unwrap_owned().downcast_ref::<i32>(), Some(&100));
//! ```
//!
//! [`Reflect`]: crate::Reflect
pub use error::*;
pub use function::*;
pub use info::*;
pub use into_function::*;
pub use return_type::*;
pub use args::{Arg, ArgError, ArgList};
pub mod args;
mod error;
mod function;
mod info;
mod into_function;
pub(crate) mod macros;
mod return_type;
#[cfg(test)]
mod tests {
use super::*;
use crate as bevy_reflect;
use crate::func::args::{ArgError, ArgId, ArgList, Ownership};
use crate::{Reflect, TypePath};
use alloc::borrow::Cow;
#[test]
fn should_create_dynamic_function() {
fn add(a: i32, b: i32) -> i32 {
a + b
}
let mut func = add.into_function();
let args = ArgList::new().push_owned(25_i32).push_owned(75_i32);
let result = func.call(args).unwrap().unwrap_owned();
assert_eq!(result.downcast_ref::<i32>(), Some(&100));
}
#[test]
fn should_create_dynamic_closure() {
let mut func = (|a: i32, b: i32| a + b).into_function();
let args = ArgList::new().push_owned(25_i32).push_owned(75_i32);
let result = func.call(args).unwrap().unwrap_owned();
assert_eq!(result.downcast_ref::<i32>(), Some(&100));
}
#[test]
fn should_create_dynamic_method() {
#[derive(Reflect, Debug, PartialEq)]
struct Foo(i32);
impl Foo {
pub fn add(&self, other: &Foo) -> Foo {
Foo(self.0 + other.0)
}
}
let foo_a = Foo(25);
let foo_b = Foo(75);
let mut func = Foo::add.into_function();
let args = ArgList::new().push_ref(&foo_a).push_ref(&foo_b);
let result = func.call(args).unwrap().unwrap_owned();
assert_eq!(result.downcast_ref::<Foo>(), Some(&Foo(100)));
}
#[test]
fn should_allow_zero_args() {
fn foo() -> String {
String::from("Hello, World!")
}
let mut func = foo.into_function();
let args = ArgList::new();
let result = func.call(args).unwrap().unwrap_owned();
assert_eq!(
result.downcast_ref::<String>(),
Some(&String::from("Hello, World!"))
);
}
#[test]
fn should_allow_unit_return() {
fn foo(_: i32) {}
let mut func = foo.into_function();
let args = ArgList::new().push_owned(123_i32);
let result = func.call(args).unwrap();
assert!(result.is_unit());
}
#[test]
fn should_allow_reference_return() {
fn foo<'a>(value: &'a i32, _: String, _: &bool) -> &'a i32 {
value
}
let value: i32 = 123;
let mut func = foo.into_function();
let args = ArgList::new()
.push_ref(&value)
.push_owned(String::from("Hello, World!"))
.push_ref(&true);
let result = func.call(args).unwrap().unwrap_ref();
assert_eq!(result.downcast_ref::<i32>(), Some(&123));
}
#[test]
fn should_allow_mutable_reference_return() {
fn foo<'a>(value: &'a mut i32, _: String, _: &bool) -> &'a mut i32 {
value
}
let mut value: i32 = 123;
let mut func = foo.into_function();
let args = ArgList::new()
.push_mut(&mut value)
.push_owned(String::from("Hello, World!"))
.push_ref(&true);
let result = func.call(args).unwrap().unwrap_mut();
assert_eq!(result.downcast_mut::<i32>(), Some(&mut 123));
}
#[test]
fn should_default_with_function_type_name() {
fn foo() {}
let func = foo.into_function();
assert_eq!(
func.info().name(),
Some("bevy_reflect::func::tests::should_default_with_function_type_name::foo")
);
}
#[test]
fn should_default_with_closure_type_name() {
let bar = |_: i32| {};
let func = bar.into_function();
assert_eq!(
func.info().name(),
Some("bevy_reflect::func::tests::should_default_with_closure_type_name::{{closure}}")
);
}
#[test]
fn should_overwrite_function_name() {
fn foo() {}
let func = foo.into_function().with_name("my_function");
assert_eq!(func.info().name(), Some("my_function"));
}
#[test]
fn should_error_on_missing_args() {
fn foo(_: i32) {}
let mut func = foo.into_function();
let args = ArgList::new();
let result = func.call(args);
assert_eq!(
result.unwrap_err(),
FunctionError::InvalidArgCount {
expected: 1,
received: 0
}
);
}
#[test]
fn should_error_on_too_many_args() {
fn foo() {}
let mut func = foo.into_function();
let args = ArgList::new().push_owned(123_i32);
let result = func.call(args);
assert_eq!(
result.unwrap_err(),
FunctionError::InvalidArgCount {
expected: 0,
received: 1
}
);
}
#[test]
fn should_error_on_invalid_arg_type() {
fn foo(_: i32) {}
let mut func = foo.into_function();
let args = ArgList::new().push_owned(123_u32);
let result = func.call(args);
assert_eq!(
result.unwrap_err(),
FunctionError::ArgError(ArgError::UnexpectedType {
id: ArgId::Index(0),
expected: Cow::Borrowed(i32::type_path()),
received: Cow::Borrowed(u32::type_path())
})
);
}
#[test]
fn should_error_on_invalid_arg_ownership() {
fn foo(_: &i32) {}
let mut func = foo.into_function();
let args = ArgList::new().push_owned(123_i32);
let result = func.call(args);
assert_eq!(
result.unwrap_err(),
FunctionError::ArgError(ArgError::InvalidOwnership {
id: ArgId::Index(0),
expected: Ownership::Ref,
received: Ownership::Owned
})
);
}
#[test]
fn should_convert_dynamic_function_with_into_function() {
fn make_function<'a, F: IntoFunction<'a, M>, M>(f: F) -> DynamicFunction<'a> {
f.into_function()
}
let function: DynamicFunction = make_function(|| {});
let _: DynamicFunction = make_function(function);
}
}

View File

@ -0,0 +1,149 @@
use crate::Reflect;
/// The return type of a [`DynamicFunction`].
///
/// [`DynamicFunction`]: crate::func::DynamicFunction
#[derive(Debug)]
pub enum Return<'a> {
/// The function returns nothing (i.e. it returns `()`).
Unit,
/// The function returns an owned value.
Owned(Box<dyn Reflect>),
/// The function returns a reference to a value.
Ref(&'a dyn Reflect),
/// The function returns a mutable reference to a value.
Mut(&'a mut dyn Reflect),
}
impl<'a> Return<'a> {
/// Returns `true` if the return value is [`Self::Unit`].
pub fn is_unit(&self) -> bool {
matches!(self, Return::Unit)
}
/// Unwraps the return value as an owned value.
///
/// # Panics
///
/// Panics if the return value is not [`Self::Owned`].
pub fn unwrap_owned(self) -> Box<dyn Reflect> {
match self {
Return::Owned(value) => value,
_ => panic!("expected owned value"),
}
}
/// Unwraps the return value as a reference to a value.
///
/// # Panics
///
/// Panics if the return value is not [`Self::Ref`].
pub fn unwrap_ref(self) -> &'a dyn Reflect {
match self {
Return::Ref(value) => value,
_ => panic!("expected reference value"),
}
}
/// Unwraps the return value as a mutable reference to a value.
///
/// # Panics
///
/// Panics if the return value is not [`Self::Mut`].
pub fn unwrap_mut(self) -> &'a mut dyn Reflect {
match self {
Return::Mut(value) => value,
_ => panic!("expected mutable reference value"),
}
}
}
/// A trait for types that can be converted into a [`Return`] value.
///
/// This trait is used instead of a blanket [`Into`] implementation due to coherence issues:
/// we can't implement `Into<Return>` for both `T` and `&T`/`&mut T`.
///
/// This trait is automatically implemented when using the `Reflect` [derive macro].
///
/// [derive macro]: derive@crate::Reflect
pub trait IntoReturn {
/// Converts [`Self`] into a [`Return`] value.
fn into_return<'a>(self) -> Return<'a>;
}
impl IntoReturn for () {
fn into_return<'a>(self) -> Return<'a> {
Return::Unit
}
}
/// Implements the [`IntoReturn`] trait for the given type.
///
/// This will implement it for `ty`, `&ty`, and `&mut ty`.
///
/// See [`impl_function_traits`] for details on syntax.
///
/// [`impl_function_traits`]: crate::func::macros::impl_function_traits
macro_rules! impl_into_return {
(
$ty: ty
$(;
<
$($T: ident $(: $T1: tt $(+ $T2: tt)*)?),*
>
)?
$(
[
$(const $N: ident : $size: ident),*
]
)?
$(
where
$($U: ty $(: $U1: tt $(+ $U2: tt)*)?),*
)?
) => {
impl <
$($($T $(: $T1 $(+ $T2)*)?),*)?
$(, $(const $N : $size),*)?
> $crate::func::IntoReturn for $ty
$(
where
$($U $(: $U1 $(+ $U2)*)?),*
)?
{
fn into_return<'into_return>(self) -> $crate::func::Return<'into_return> {
$crate::func::Return::Owned(Box::new(self))
}
}
impl <
$($($T $(: $T1 $(+ $T2)*)?),*)?
$(, $(const $N : $size),*)?
> $crate::func::IntoReturn for &'static $ty
$(
where
$($U $(: $U1 $(+ $U2)*)?),*
)?
{
fn into_return<'into_return>(self) -> $crate::func::Return<'into_return> {
$crate::func::Return::Ref(self)
}
}
impl <
$($($T $(: $T1 $(+ $T2)*)?),*)?
$(, $(const $N : $size),*)?
> $crate::func::IntoReturn for &'static mut $ty
$(
where
$($U $(: $U1 $(+ $U2)*)?),*
)?
{
fn into_return<'into_return>(self) -> $crate::func::Return<'into_return> {
$crate::func::Return::Mut(self)
}
}
};
}
pub(crate) use impl_into_return;

View File

@ -1,8 +1,9 @@
use bevy_reflect_derive::impl_type_path;
use smallvec::SmallVec;
use smallvec::{Array as SmallArray, SmallVec};
use std::any::Any;
use crate::func::macros::impl_function_traits;
use crate::utility::GenericTypeInfoCell;
use crate::{
self as bevy_reflect, ApplyError, FromReflect, FromType, GetTypeRegistration, List, ListInfo,
@ -10,7 +11,7 @@ use crate::{
TypePath, TypeRegistration, Typed,
};
impl<T: smallvec::Array + TypePath + Send + Sync> List for SmallVec<T>
impl<T: SmallArray + TypePath + Send + Sync> List for SmallVec<T>
where
T::Item: FromReflect + TypePath,
{
@ -32,7 +33,7 @@ where
fn insert(&mut self, index: usize, value: Box<dyn Reflect>) {
let value = value.take::<T::Item>().unwrap_or_else(|value| {
<T as smallvec::Array>::Item::from_reflect(&*value).unwrap_or_else(|| {
<T as SmallArray>::Item::from_reflect(&*value).unwrap_or_else(|| {
panic!(
"Attempted to insert invalid value of type {}.",
value.reflect_type_path()
@ -48,7 +49,7 @@ where
fn push(&mut self, value: Box<dyn Reflect>) {
let value = value.take::<T::Item>().unwrap_or_else(|value| {
<T as smallvec::Array>::Item::from_reflect(&*value).unwrap_or_else(|| {
<T as SmallArray>::Item::from_reflect(&*value).unwrap_or_else(|| {
panic!(
"Attempted to push invalid value of type {}.",
value.reflect_type_path()
@ -77,7 +78,7 @@ where
}
}
impl<T: smallvec::Array + TypePath + Send + Sync> Reflect for SmallVec<T>
impl<T: SmallArray + TypePath + Send + Sync> Reflect for SmallVec<T>
where
T::Item: FromReflect + TypePath,
{
@ -147,7 +148,7 @@ where
}
}
impl<T: smallvec::Array + TypePath + Send + Sync + 'static> Typed for SmallVec<T>
impl<T: SmallArray + TypePath + Send + Sync + 'static> Typed for SmallVec<T>
where
T::Item: FromReflect + TypePath,
{
@ -157,9 +158,9 @@ where
}
}
impl_type_path!(::smallvec::SmallVec<T: smallvec::Array>);
impl_type_path!(::smallvec::SmallVec<T: SmallArray>);
impl<T: smallvec::Array + TypePath + Send + Sync> FromReflect for SmallVec<T>
impl<T: SmallArray + TypePath + Send + Sync> FromReflect for SmallVec<T>
where
T::Item: FromReflect + TypePath,
{
@ -167,7 +168,7 @@ where
if let ReflectRef::List(ref_list) = reflect.reflect_ref() {
let mut new_list = Self::with_capacity(ref_list.len());
for field in ref_list.iter() {
new_list.push(<T as smallvec::Array>::Item::from_reflect(field)?);
new_list.push(<T as SmallArray>::Item::from_reflect(field)?);
}
Some(new_list)
} else {
@ -176,7 +177,7 @@ where
}
}
impl<T: smallvec::Array + TypePath + Send + Sync> GetTypeRegistration for SmallVec<T>
impl<T: SmallArray + TypePath + Send + Sync> GetTypeRegistration for SmallVec<T>
where
T::Item: FromReflect + TypePath,
{
@ -186,3 +187,5 @@ where
registration
}
}
impl_function_traits!(SmallVec<T>; <T: SmallArray + TypePath + Send + Sync> where T::Item: FromReflect + TypePath);

View File

@ -1,3 +1,4 @@
use crate::func::macros::impl_function_traits;
use crate::std_traits::ReflectDefault;
use crate::utility::{
reflect_hasher, GenericTypeInfoCell, GenericTypePathCell, NonGenericTypeInfoCell,
@ -397,6 +398,8 @@ impl_reflect_for_veclike!(
Vec::pop,
[T]
);
impl_function_traits!(Vec<T>; <T: FromReflect + TypePath + GetTypeRegistration>);
impl_reflect_for_veclike!(
::alloc::collections::VecDeque<T>,
VecDeque::insert,
@ -405,6 +408,7 @@ impl_reflect_for_veclike!(
VecDeque::pop_back,
VecDeque::<T>
);
impl_function_traits!(VecDeque<T>; <T: FromReflect + TypePath + GetTypeRegistration>);
macro_rules! impl_reflect_for_hashmap {
($ty:path) => {
@ -634,10 +638,24 @@ macro_rules! impl_reflect_for_hashmap {
impl_reflect_for_hashmap!(::std::collections::HashMap<K, V, S>);
impl_type_path!(::std::collections::hash_map::RandomState);
impl_type_path!(::std::collections::HashMap<K, V, S>);
impl_function_traits!(::std::collections::HashMap<K, V, S>;
<
K: FromReflect + TypePath + GetTypeRegistration + Eq + Hash,
V: FromReflect + TypePath + GetTypeRegistration,
S: TypePath + BuildHasher + Default + Send + Sync
>
);
impl_reflect_for_hashmap!(bevy_utils::hashbrown::HashMap<K, V, S>);
impl_type_path!(::bevy_utils::hashbrown::hash_map::DefaultHashBuilder);
impl_type_path!(::bevy_utils::hashbrown::HashMap<K, V, S>);
impl_function_traits!(::bevy_utils::hashbrown::HashMap<K, V, S>;
<
K: FromReflect + TypePath + GetTypeRegistration + Eq + Hash,
V: FromReflect + TypePath + GetTypeRegistration,
S: TypePath + BuildHasher + Default + Send + Sync
>
);
impl<K, V> Map for ::std::collections::BTreeMap<K, V>
where
@ -851,6 +869,12 @@ where
}
impl_type_path!(::std::collections::BTreeMap<K, V>);
impl_function_traits!(::std::collections::BTreeMap<K, V>;
<
K: FromReflect + TypePath + GetTypeRegistration + Eq + Ord,
V: FromReflect + TypePath + GetTypeRegistration
>
);
impl<T: Reflect + TypePath + GetTypeRegistration, const N: usize> Array for [T; N] {
#[inline]
@ -1011,6 +1035,8 @@ impl<T: Reflect + TypePath + GetTypeRegistration, const N: usize> GetTypeRegistr
}
}
impl_function_traits!([T; N]; <T: Reflect + TypePath + GetTypeRegistration> [const N: usize]);
impl_reflect! {
#[type_path = "core::option"]
enum Option<T> {
@ -1168,6 +1194,8 @@ impl FromReflect for Cow<'static, str> {
}
}
impl_function_traits!(Cow<'static, str>);
impl<T: TypePath> TypePath for [T]
where
[T]: ToOwned,
@ -1346,6 +1374,8 @@ impl<T: FromReflect + Clone + TypePath + GetTypeRegistration> FromReflect for Co
}
}
impl_function_traits!(Cow<'static, [T]>; <T: FromReflect + Clone + TypePath + GetTypeRegistration>);
impl Reflect for &'static str {
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
Some(<Self as Typed>::type_info())
@ -1452,6 +1482,8 @@ impl FromReflect for &'static str {
}
}
impl_function_traits!(&'static str);
impl Reflect for &'static Path {
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
Some(<Self as Typed>::type_info())
@ -1557,6 +1589,8 @@ impl FromReflect for &'static Path {
}
}
impl_function_traits!(&'static Path);
impl Reflect for Cow<'static, Path> {
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
Some(<Self as Typed>::type_info())
@ -1672,6 +1706,8 @@ impl GetTypeRegistration for Cow<'static, Path> {
}
}
impl_function_traits!(Cow<'static, Path>);
#[cfg(test)]
mod tests {
use crate as bevy_reflect;

View File

@ -476,6 +476,7 @@
mod array;
mod fields;
mod from_reflect;
pub mod func;
mod list;
mod map;
mod path;

View File

@ -4,6 +4,7 @@ use std::hash::{Hash, Hasher};
use bevy_reflect_derive::impl_type_path;
use crate::func::macros::impl_function_traits;
use crate::utility::reflect_hasher;
use crate::{
self as bevy_reflect, ApplyError, FromReflect, Reflect, ReflectKind, ReflectMut, ReflectOwned,
@ -369,6 +370,7 @@ impl Reflect for DynamicList {
}
impl_type_path!((in bevy_reflect) DynamicList);
impl_function_traits!(DynamicList);
impl Debug for DynamicList {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {

View File

@ -4,6 +4,7 @@ use std::fmt::{Debug, Formatter};
use bevy_reflect_derive::impl_type_path;
use bevy_utils::{Entry, HashMap};
use crate::func::macros::impl_function_traits;
use crate::{
self as bevy_reflect, ApplyError, Reflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef,
TypeInfo, TypePath, TypePathTable,
@ -417,6 +418,7 @@ impl Reflect for DynamicMap {
}
impl_type_path!((in bevy_reflect) DynamicMap);
impl_function_traits!(DynamicMap);
impl Debug for DynamicMap {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {

View File

@ -1,4 +1,5 @@
use crate::attributes::{impl_custom_attribute_methods, CustomAttributes};
use crate::func::macros::impl_function_traits;
use crate::{
self as bevy_reflect, ApplyError, NamedField, Reflect, ReflectKind, ReflectMut, ReflectOwned,
ReflectRef, TypeInfo, TypePath, TypePathTable,
@ -499,6 +500,7 @@ impl Reflect for DynamicStruct {
}
impl_type_path!((in bevy_reflect) DynamicStruct);
impl_function_traits!(DynamicStruct);
impl Debug for DynamicStruct {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {

View File

@ -710,6 +710,31 @@ macro_rules! impl_type_path_tuple {
all_tuples!(impl_type_path_tuple, 0, 12, P);
macro_rules! impl_get_ownership_tuple {
($($name: ident),*) => {
$crate::func::args::impl_get_ownership!(($($name,)*); <$($name),*>);
};
}
all_tuples!(impl_get_ownership_tuple, 0, 12, P);
macro_rules! impl_from_arg_tuple {
($($name: ident),*) => {
$crate::func::args::impl_from_arg!(($($name,)*); <$($name: FromReflect + TypePath + GetTypeRegistration),*>);
};
}
all_tuples!(impl_from_arg_tuple, 0, 12, P);
macro_rules! impl_into_return_tuple {
($($name: ident),+) => {
$crate::func::impl_into_return!(($($name,)*); <$($name: FromReflect + TypePath + GetTypeRegistration),*>);
};
}
// The unit type (i.e. `()`) is special-cased, so we skip implementing it here.
all_tuples!(impl_into_return_tuple, 1, 12, P);
#[cfg(test)]
mod tests {
use super::Tuple;

View File

@ -1,6 +1,7 @@
use bevy_reflect_derive::impl_type_path;
use crate::attributes::{impl_custom_attribute_methods, CustomAttributes};
use crate::func::macros::impl_function_traits;
use crate::{
self as bevy_reflect, ApplyError, DynamicTuple, Reflect, ReflectKind, ReflectMut, ReflectOwned,
ReflectRef, Tuple, TypeInfo, TypePath, TypePathTable, UnnamedField,
@ -408,6 +409,7 @@ impl Reflect for DynamicTupleStruct {
}
impl_type_path!((in bevy_reflect) DynamicTupleStruct);
impl_function_traits!(DynamicTupleStruct);
impl Debug for DynamicTupleStruct {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {

View File

@ -343,6 +343,7 @@ Example | Description
--- | ---
[Custom Attributes](../examples/reflection/custom_attributes.rs) | Registering and accessing custom attributes on reflected types
[Dynamic Types](../examples/reflection/dynamic_types.rs) | How dynamic types are used with reflection
[Function Reflection](../examples/reflection/function_reflection.rs) | Demonstrates how functions can be called dynamically using reflection
[Generic Reflection](../examples/reflection/generic_reflection.rs) | Registers concrete instances of generic types that may be used with reflection
[Reflection](../examples/reflection/reflection.rs) | Demonstrates how reflection in Bevy provides a way to dynamically interact with Rust types
[Reflection Types](../examples/reflection/reflection_types.rs) | Illustrates the various reflection types available

View File

@ -0,0 +1,157 @@
//! This example demonstrates how functions can be called dynamically using reflection.
//!
//! Function reflection is useful for calling regular Rust functions in a dynamic context,
//! where the types of arguments, return values, and even the function itself aren't known at compile time.
//!
//! This can be used for things like adding scripting support to your application,
//! processing deserialized reflection data, or even just storing type-erased versions of your functions.
use bevy::reflect::func::args::ArgInfo;
use bevy::reflect::func::{
ArgList, DynamicFunction, FunctionInfo, IntoFunction, Return, ReturnInfo,
};
use bevy::reflect::Reflect;
// Note that the `dbg!` invocations are used purely for demonstration purposes
// and are not strictly necessary for the example to work.
fn main() {
// There are times when it may be helpful to store a function away for later.
// In Rust, we can do this by storing either a function pointer or a function trait object.
// For example, say we wanted to store the following function:
fn add(left: i32, right: i32) -> i32 {
left + right
}
// We could store it as either of the following:
let fn_pointer: fn(i32, i32) -> i32 = add;
let fn_trait_object: Box<dyn Fn(i32, i32) -> i32> = Box::new(add);
// And we can call them like so:
let result = fn_pointer(2, 2);
assert_eq!(result, 4);
let result = fn_trait_object(2, 2);
assert_eq!(result, 4);
// However, you'll notice that we have to know the types of the arguments and return value at compile time.
// This means there's not really a way to store or call these functions dynamically at runtime.
// Luckily, Bevy's reflection crate comes with a set of tools for doing just that!
// We do this by first converting our function into the reflection-based `DynamicFunction` type
// using the `IntoFunction` trait.
let mut function: DynamicFunction = dbg!(add.into_function());
// This time, you'll notice that `DynamicFunction` doesn't take any information about the function's arguments or return value.
// This is because `DynamicFunction` checks the types of the arguments and return value at runtime.
// Now we can generate a list of arguments:
let args: ArgList = dbg!(ArgList::new().push_owned(2_i32).push_owned(2_i32));
// And finally, we can call the function.
// This returns a `Result` indicating whether the function was called successfully.
// For now, we'll just unwrap it to get our `Return` value,
// which is an enum containing the function's return value.
let return_value: Return = dbg!(function.call(args).unwrap());
// The `Return` value can be pattern matched or unwrapped to get the underlying reflection data.
// For the sake of brevity, we'll just unwrap it here and downcast it to the expected type of `i32`.
let value: Box<dyn Reflect> = return_value.unwrap_owned();
assert_eq!(value.take::<i32>().unwrap(), 4);
// The same can also be done for closures.
let mut count = 0;
let increment = |amount: i32| {
count += amount;
};
let increment_function: DynamicFunction = dbg!(increment.into_function());
let args = dbg!(ArgList::new().push_owned(5_i32));
// `DynamicFunction`s containing closures that capture their environment like this one
// may need to be dropped before those captured variables may be used again.
// This can be done manually with `drop` or by using the `Function::call_once` method.
dbg!(increment_function.call_once(args).unwrap());
assert_eq!(count, 5);
// As stated before, this works for many kinds of simple functions.
// Functions with non-reflectable arguments or return values may not be able to be converted.
// Generic functions are also not supported.
// Additionally, the lifetime of the return value is tied to the lifetime of the first argument.
// However, this means that many methods (i.e. functions with a `self` parameter) are also supported:
#[derive(Reflect, Default)]
struct Data {
value: String,
}
impl Data {
fn set_value(&mut self, value: String) {
self.value = value;
}
// Note that only `&'static str` implements `Reflect`.
// To get around this limitation we can use `&String` instead.
fn get_value(&self) -> &String {
&self.value
}
}
let mut data = Data::default();
let mut set_value = dbg!(Data::set_value.into_function());
let args = dbg!(ArgList::new().push_mut(&mut data)).push_owned(String::from("Hello, world!"));
dbg!(set_value.call(args).unwrap());
assert_eq!(data.value, "Hello, world!");
let mut get_value = dbg!(Data::get_value.into_function());
let args = dbg!(ArgList::new().push_ref(&data));
let return_value = dbg!(get_value.call(args).unwrap());
let value: &dyn Reflect = return_value.unwrap_ref();
assert_eq!(value.downcast_ref::<String>().unwrap(), "Hello, world!");
// Lastly, for more complex use cases, you can always create a custom `DynamicFunction` manually.
// This is useful for functions that can't be converted via the `IntoFunction` trait.
// For example, this function doesn't implement `IntoFunction` due to the fact that
// the lifetime of the return value is not tied to the lifetime of the first argument.
fn get_or_insert(value: i32, container: &mut Option<i32>) -> &i32 {
if container.is_none() {
*container = Some(value);
}
container.as_ref().unwrap()
}
let mut get_or_insert_function = dbg!(DynamicFunction::new(
|mut args, info| {
let container_info = &info.args()[1];
let value_info = &info.args()[0];
// The `ArgList` contains the arguments in the order they were pushed.
// Therefore, we need to pop them in reverse order.
let container = args
.pop()
.unwrap()
.take_mut::<Option<i32>>(container_info)
.unwrap();
let value = args.pop().unwrap().take_owned::<i32>(value_info).unwrap();
Ok(Return::Ref(get_or_insert(value, container)))
},
FunctionInfo::new()
// We can optionally provide a name for the function
.with_name("get_or_insert")
// Since our function takes arguments, we MUST provide that argument information.
// The arguments should be provided in the order they are defined in the function.
// This is used to validate any arguments given at runtime.
.with_args(vec![
ArgInfo::new::<i32>(0).with_name("value"),
ArgInfo::new::<&mut Option<i32>>(1).with_name("container"),
])
// We can optionally provide return information as well.
.with_return_info(ReturnInfo::new::<&i32>()),
));
let mut container: Option<i32> = None;
let args = dbg!(ArgList::new().push_owned(5_i32).push_mut(&mut container));
let value = dbg!(get_or_insert_function.call(args).unwrap()).unwrap_ref();
assert_eq!(value.downcast_ref::<i32>(), Some(&5));
let args = dbg!(ArgList::new().push_owned(500_i32).push_mut(&mut container));
let value = dbg!(get_or_insert_function.call(args).unwrap()).unwrap_ref();
assert_eq!(value.downcast_ref::<i32>(), Some(&5));
}