-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
bevy_reflect: Add
Function
trait (#15205)
# Objective While #13152 added function reflection, it didn't really make functions reflectable. Instead, it made it so that they can be called with reflected arguments and return reflected data. But functions themselves cannot be reflected. In other words, we can't go from `DynamicFunction` to `dyn PartialReflect`. ## Solution Allow `DynamicFunction` to actually be reflected. This PR adds the `Function` reflection subtrait (and corresponding `ReflectRef`, `ReflectKind`, etc.). With this new trait, we're able to implement `PartialReflect` on `DynamicFunction`. ### Implementors `Function` is currently only implemented for `DynamicFunction<'static>`. This is because we can't implement it generically over all functions—even those that implement `IntoFunction`. What about `DynamicFunctionMut`? Well, this PR does **not** implement `Function` for `DynamicFunctionMut`. The reasons for this are a little complicated, but it boils down to mutability. `DynamicFunctionMut` requires `&mut self` to be invoked since it wraps a `FnMut`. However, we can't really model this well with `Function`. And if we make `DynamicFunctionMut` wrap its internal `FnMut` in a `Mutex` to allow for `&self` invocations, then we run into either concurrency issues or recursion issues (or, in the worst case, both). So for the time-being, we won't implement `Function` for `DynamicFunctionMut`. It will be better to evaluate it on its own. And we may even consider the possibility of removing it altogether if it adds too much complexity to the crate. ### Dynamic vs Concrete One of the issues with `DynamicFunction` is the fact that it's both a dynamic representation (like `DynamicStruct` or `DynamicList`) and the only way to represent a function. Because of this, it's in a weird middle ground where we can't easily implement full-on `Reflect`. That would require `Typed`, but what static `TypeInfo` could it provide? Just that it's a `DynamicFunction`? None of the other dynamic types implement `Typed`. However, by not implementing `Reflect`, we lose the ability to downcast back to our `DynamicStruct`. Our only option is to call `Function::clone_dynamic`, which clones the data rather than by simply downcasting. This works in favor of the `PartialReflect::try_apply` implementation since it would have to clone anyways, but is definitely not ideal. This is also the reason I had to add `Debug` as a supertrait on `Function`. For now, this PR chooses not to implement `Reflect` for `DynamicFunction`. We may want to explore this in a followup PR (or even this one if people feel strongly that it's strictly required). The same is true for `FromReflect`. We may decide to add an implementation there as well, but it's likely out-of-scope of this PR. ## Testing You can test locally by running: ``` cargo test --package bevy_reflect --all-features ``` --- ## Showcase You can now pass around a `DynamicFunction` as a `dyn PartialReflect`! This also means you can use it as a field on a reflected type without having to ignore it (though you do need to opt out of `FromReflect`). ```rust #[derive(Reflect)] #[reflect(from_reflect = false)] struct ClickEvent { callback: DynamicFunction<'static>, } let event: Box<dyn Struct> = Box::new(ClickEvent { callback: (|| println!("Clicked!")).into_function(), }); // We can access our `DynamicFunction` as a `dyn PartialReflect` let callback: &dyn PartialReflect = event.field("callback").unwrap(); // And access function-related methods via the new `Function` trait let ReflectRef::Function(callback) = callback.reflect_ref() else { unreachable!() }; // Including calling the function callback.reflect_call(ArgList::new()).unwrap(); // Prints: Clicked! ```
- Loading branch information
Showing
10 changed files
with
385 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
use crate::func::{ArgList, DynamicFunction, FunctionInfo, FunctionResult}; | ||
use crate::PartialReflect; | ||
use alloc::borrow::Cow; | ||
use core::fmt::Debug; | ||
|
||
/// A trait used to power [function-like] operations via [reflection]. | ||
/// | ||
/// This trait allows types to be called like regular functions | ||
/// with [`Reflect`]-based [arguments] and return values. | ||
/// | ||
/// By default, this trait is currently only implemented for [`DynamicFunction`], | ||
/// however, it is possible to implement this trait for custom function-like types. | ||
/// | ||
/// # Example | ||
/// | ||
/// ``` | ||
/// # use bevy_reflect::func::{IntoFunction, ArgList, Function}; | ||
/// fn add(a: i32, b: i32) -> i32 { | ||
/// a + b | ||
/// } | ||
/// | ||
/// let func: Box<dyn Function> = Box::new(add.into_function()); | ||
/// let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); | ||
/// let value = func.reflect_call(args).unwrap().unwrap_owned(); | ||
/// assert_eq!(value.try_take::<i32>().unwrap(), 100); | ||
/// ``` | ||
/// | ||
/// [function-like]: crate::func | ||
/// [reflection]: crate::Reflect | ||
/// [`Reflect`]: crate::Reflect | ||
/// [arguments]: crate::func::args | ||
/// [`DynamicFunction`]: crate::func::DynamicFunction | ||
pub trait Function: PartialReflect + Debug { | ||
/// The name of the function, if any. | ||
/// | ||
/// For [`DynamicFunctions`] created using [`IntoFunction`], | ||
/// the default name will always be the full path to the function as returned by [`std::any::type_name`], | ||
/// unless the function is a closure, anonymous function, or function pointer, | ||
/// in which case the name will be `None`. | ||
/// | ||
/// [`DynamicFunctions`]: crate::func::DynamicFunction | ||
/// [`IntoFunction`]: crate::func::IntoFunction | ||
fn name(&self) -> Option<&Cow<'static, str>> { | ||
self.info().name() | ||
} | ||
|
||
/// The number of arguments this function accepts. | ||
fn arg_count(&self) -> usize { | ||
self.info().arg_count() | ||
} | ||
|
||
/// The [`FunctionInfo`] for this function. | ||
fn info(&self) -> &FunctionInfo; | ||
|
||
/// Call this function with the given arguments. | ||
fn reflect_call<'a>(&self, args: ArgList<'a>) -> FunctionResult<'a>; | ||
|
||
/// Clone this function into a [`DynamicFunction`]. | ||
fn clone_dynamic(&self) -> DynamicFunction<'static>; | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use crate::func::IntoFunction; | ||
|
||
#[test] | ||
fn should_call_dyn_function() { | ||
fn add(a: i32, b: i32) -> i32 { | ||
a + b | ||
} | ||
|
||
let func: Box<dyn Function> = Box::new(add.into_function()); | ||
let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); | ||
let value = func.reflect_call(args).unwrap().unwrap_owned(); | ||
assert_eq!(value.try_take::<i32>().unwrap(), 100); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.