mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
6ab8767d3b
# Objective
- Implements the [Unique Reflect
RFC](https://github.com/nicopap/rfcs/blob/bevy-reflect-api/rfcs/56-better-reflect.md).
## Solution
- Implements the RFC.
- This implementation differs in some ways from the RFC:
- In the RFC, it was suggested `Reflect: Any` but `PartialReflect:
?Any`. During initial implementation I tried this, but we assume the
`PartialReflect: 'static` in a lot of places and the changes required
crept out of the scope of this PR.
- `PartialReflect::try_into_reflect` originally returned `Option<Box<dyn
Reflect>>` but i changed this to `Result<Box<dyn Reflect>, Box<dyn
PartialReflect>>` since the method takes by value and otherwise there
would be no way to recover the type. `as_full` and `as_full_mut` both
still return `Option<&(mut) dyn Reflect>`.
---
## Changelog
- Added `PartialReflect`.
- `Reflect` is now a subtrait of `PartialReflect`.
- Moved most methods on `Reflect` to the new `PartialReflect`.
- Added `PartialReflect::{as_partial_reflect, as_partial_reflect_mut,
into_partial_reflect}`.
- Added `PartialReflect::{try_as_reflect, try_as_reflect_mut,
try_into_reflect}`.
- Added `<dyn PartialReflect>::{try_downcast_ref, try_downcast_mut,
try_downcast, try_take}` supplementing the methods on `dyn Reflect`.
## Migration Guide
- Most instances of `dyn Reflect` should be changed to `dyn
PartialReflect` which is less restrictive, however trait bounds should
generally stay as `T: Reflect`.
- The new `PartialReflect::{as_partial_reflect, as_partial_reflect_mut,
into_partial_reflect, try_as_reflect, try_as_reflect_mut,
try_into_reflect}` methods as well as `Reflect::{as_reflect,
as_reflect_mut, into_reflect}` will need to be implemented for manual
implementors of `Reflect`.
## Future Work
- This PR is designed to be followed up by another "Unique Reflect Phase
2" that addresses the following points:
- Investigate making serialization revolve around `Reflect` instead of
`PartialReflect`.
- [Remove the `try_*` methods on `dyn PartialReflect` since they are
stop
gaps](https://github.com/bevyengine/bevy/pull/7207#discussion_r1083476050).
- Investigate usages like `ReflectComponent`. In the places they
currently use `PartialReflect`, should they be changed to use `Reflect`?
- Merging this opens the door to lots of reflection features we haven't
been able to implement.
- We could re-add [the `Reflectable`
trait](8e3488c880/crates/bevy_reflect/src/reflect.rs (L337-L342)
)
and make `FromReflect` a requirement to improve [`FromReflect`
ergonomics](https://github.com/bevyengine/rfcs/pull/59). This is
currently not possible because dynamic types cannot sensibly be
`FromReflect`.
- Since this is an alternative to #5772, #5781 would be made cleaner.
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
180 lines
8.8 KiB
Rust
180 lines
8.8 KiB
Rust
//! 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::{
|
|
ArgList, DynamicClosure, DynamicClosureMut, DynamicFunction, FunctionError, FunctionInfo,
|
|
IntoClosure, IntoClosureMut, IntoFunction, Return,
|
|
};
|
|
use bevy::reflect::{PartialReflect, 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 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 PartialReflect> = return_value.unwrap_owned();
|
|
assert_eq!(value.try_take::<i32>().unwrap(), 4);
|
|
|
|
// The same can also be done for closures that capture references to their environment.
|
|
// Closures that capture their environment immutably can be converted into a `DynamicClosure`
|
|
// using the `IntoClosure` trait.
|
|
let minimum = 5;
|
|
let clamp = |value: i32| value.max(minimum);
|
|
|
|
let function: DynamicClosure = dbg!(clamp.into_closure());
|
|
let args = dbg!(ArgList::new().push_owned(2_i32));
|
|
let return_value = dbg!(function.call(args).unwrap());
|
|
let value: Box<dyn PartialReflect> = return_value.unwrap_owned();
|
|
assert_eq!(value.try_take::<i32>().unwrap(), 5);
|
|
|
|
// We can also handle closures that capture their environment mutably
|
|
// using the `IntoClosureMut` trait.
|
|
let mut count = 0;
|
|
let increment = |amount: i32| count += amount;
|
|
|
|
let closure: DynamicClosureMut = dbg!(increment.into_closure_mut());
|
|
let args = dbg!(ArgList::new().push_owned(5_i32));
|
|
// Because `DynamicClosureMut` mutably borrows `total`,
|
|
// it will need to be dropped before `total` can be accessed again.
|
|
// This can be done manually with `drop(closure)` or by using the `DynamicClosureMut::call_once` method.
|
|
dbg!(closure.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 (unless manually monomorphized like `foo::<i32>.into_function()`).
|
|
// 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 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 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 PartialReflect = return_value.unwrap_ref();
|
|
assert_eq!(value.try_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 get_or_insert_function = dbg!(DynamicFunction::new(
|
|
|mut args| {
|
|
// We can optionally add a check to ensure we were given the correct number of arguments.
|
|
if args.len() != 2 {
|
|
return Err(FunctionError::ArgCountMismatch {
|
|
expected: 2,
|
|
received: args.len(),
|
|
});
|
|
}
|
|
|
|
// The `ArgList` contains the arguments in the order they were pushed.
|
|
// We can retrieve them out in order (note that this modifies the `ArgList`):
|
|
let value = args.take::<i32>()?;
|
|
let container = args.take::<&mut Option<i32>>()?;
|
|
|
|
// We could have also done the following to make use of type inference:
|
|
// let value = args.take_owned()?;
|
|
// let container = args.take_mut()?;
|
|
|
|
Ok(Return::Ref(get_or_insert(value, container)))
|
|
},
|
|
// 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::named(std::any::type_name_of_val(&get_or_insert))
|
|
// We can always change the name if needed.
|
|
// 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.
|
|
// Arguments should be provided in the order they are defined in the function.
|
|
.with_arg::<i32>("value")
|
|
.with_arg::<&mut Option<i32>>("container")
|
|
// We can provide return information as well.
|
|
.with_return::<&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.try_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.try_downcast_ref::<i32>(), Some(&5));
|
|
}
|