mirror of
https://github.com/bevyengine/bevy
synced 2024-12-25 04:23:08 +00:00
efda7f3f9c
Takes the first two commits from #15375 and adds suggestions from this comment: https://github.com/bevyengine/bevy/pull/15375#issuecomment-2366968300 See #15375 for more reasoning/motivation. ## Rebasing (rerunning) ```rust git switch simpler-lint-fixes git reset --hard main cargo fmt --all -- --unstable-features --config normalize_comments=true,imports_granularity=Crate cargo fmt --all git add --update git commit --message "rustfmt" cargo clippy --workspace --all-targets --all-features --fix cargo fmt --all -- --unstable-features --config normalize_comments=true,imports_granularity=Crate cargo fmt --all git add --update git commit --message "clippy" git cherry-pick e6c0b94f6795222310fb812fa5c4512661fc7887 ```
177 lines
8.6 KiB
Rust
177 lines
8.6 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, DynamicFunction, DynamicFunctionMut, FunctionInfo, FunctionResult, IntoFunction,
|
|
IntoFunctionMut, Return,
|
|
},
|
|
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<'static> = 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 `DynamicFunction`
|
|
// using the `IntoFunction` trait.
|
|
let minimum = 5;
|
|
let clamp = |value: i32| value.max(minimum);
|
|
|
|
let function: DynamicFunction = dbg!(clamp.into_function());
|
|
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 `IntoFunctionMut` trait.
|
|
let mut count = 0;
|
|
let increment = |amount: i32| count += amount;
|
|
|
|
let closure: DynamicFunctionMut = dbg!(increment.into_function_mut());
|
|
let args = dbg!(ArgList::new().push_owned(5_i32));
|
|
|
|
// Because `DynamicFunctionMut` 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 `DynamicFunctionMut::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: ArgList| -> FunctionResult {
|
|
// The `ArgList` contains the arguments in the order they were pushed.
|
|
// The `DynamicFunction` will validate that the list contains
|
|
// exactly the number of arguments we expect.
|
|
// 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 is used to validate arguments when calling the function.
|
|
// And it aids consumers of the function with their own validation and 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));
|
|
}
|