Path: blob/main/examples/reflection/function_reflection.rs
6849 views
//! This example demonstrates how functions can be called dynamically using reflection.1//!2//! Function reflection is useful for calling regular Rust functions in a dynamic context,3//! where the types of arguments, return values, and even the function itself aren't known at compile time.4//!5//! This can be used for things like adding scripting support to your application,6//! processing deserialized reflection data, or even just storing type-erased versions of your functions.78use bevy::reflect::{9func::{10ArgList, DynamicFunction, DynamicFunctionMut, FunctionResult, IntoFunction,11IntoFunctionMut, Return, SignatureInfo,12},13PartialReflect, Reflect,14};1516// Note that the `dbg!` invocations are used purely for demonstration purposes17// and are not strictly necessary for the example to work.18fn main() {19// There are times when it may be helpful to store a function away for later.20// In Rust, we can do this by storing either a function pointer or a function trait object.21// For example, say we wanted to store the following function:22fn add(left: i32, right: i32) -> i32 {23left + right24}2526// We could store it as either of the following:27let fn_pointer: fn(i32, i32) -> i32 = add;28let fn_trait_object: Box<dyn Fn(i32, i32) -> i32> = Box::new(add);2930// And we can call them like so:31let result = fn_pointer(2, 2);32assert_eq!(result, 4);33let result = fn_trait_object(2, 2);34assert_eq!(result, 4);3536// However, you'll notice that we have to know the types of the arguments and return value at compile time.37// This means there's not really a way to store or call these functions dynamically at runtime.38// Luckily, Bevy's reflection crate comes with a set of tools for doing just that!39// We do this by first converting our function into the reflection-based `DynamicFunction` type40// using the `IntoFunction` trait.41let function: DynamicFunction<'static> = dbg!(add.into_function());4243// This time, you'll notice that `DynamicFunction` doesn't take any information about the function's arguments or return value.44// This is because `DynamicFunction` checks the types of the arguments and return value at runtime.45// Now we can generate a list of arguments:46let args: ArgList = dbg!(ArgList::new().with_owned(2_i32).with_owned(2_i32));4748// And finally, we can call the function.49// This returns a `Result` indicating whether the function was called successfully.50// For now, we'll just unwrap it to get our `Return` value,51// which is an enum containing the function's return value.52let return_value: Return = dbg!(function.call(args).unwrap());5354// The `Return` value can be pattern matched or unwrapped to get the underlying reflection data.55// For the sake of brevity, we'll just unwrap it here and downcast it to the expected type of `i32`.56let value: Box<dyn PartialReflect> = return_value.unwrap_owned();57assert_eq!(value.try_take::<i32>().unwrap(), 4);5859// The same can also be done for closures that capture references to their environment.60// Closures that capture their environment immutably can be converted into a `DynamicFunction`61// using the `IntoFunction` trait.62let minimum = 5;63let clamp = |value: i32| value.max(minimum);6465let function: DynamicFunction = dbg!(clamp.into_function());66let args = dbg!(ArgList::new().with_owned(2_i32));67let return_value = dbg!(function.call(args).unwrap());68let value: Box<dyn PartialReflect> = return_value.unwrap_owned();69assert_eq!(value.try_take::<i32>().unwrap(), 5);7071// We can also handle closures that capture their environment mutably72// using the `IntoFunctionMut` trait.73let mut count = 0;74let increment = |amount: i32| count += amount;7576let closure: DynamicFunctionMut = dbg!(increment.into_function_mut());77let args = dbg!(ArgList::new().with_owned(5_i32));7879// Because `DynamicFunctionMut` mutably borrows `total`,80// it will need to be dropped before `total` can be accessed again.81// This can be done manually with `drop(closure)` or by using the `DynamicFunctionMut::call_once` method.82dbg!(closure.call_once(args).unwrap());83assert_eq!(count, 5);8485// Generic functions can also be converted into a `DynamicFunction`,86// however, they will need to be manually monomorphized first.87fn stringify<T: ToString>(value: T) -> String {88value.to_string()89}9091// We have to manually specify the concrete generic type we want to use.92let function = stringify::<i32>.into_function();9394let args = ArgList::new().with_owned(123_i32);95let return_value = function.call(args).unwrap();96let value: Box<dyn PartialReflect> = return_value.unwrap_owned();97assert_eq!(value.try_take::<String>().unwrap(), "123");9899// To make things a little easier, we can also "overload" functions.100// This makes it so that a single `DynamicFunction` can represent multiple functions,101// and the correct one is chosen based on the types of the arguments.102// Each function overload must have a unique argument signature.103let function = stringify::<i32>104.into_function()105.with_overload(stringify::<f32>);106107// Now our `function` accepts both `i32` and `f32` arguments.108let args = ArgList::new().with_owned(1.23_f32);109let return_value = function.call(args).unwrap();110let value: Box<dyn PartialReflect> = return_value.unwrap_owned();111assert_eq!(value.try_take::<String>().unwrap(), "1.23");112113// Function overloading even allows us to have a variable number of arguments.114let function = (|| 0)115.into_function()116.with_overload(|a: i32| a)117.with_overload(|a: i32, b: i32| a + b)118.with_overload(|a: i32, b: i32, c: i32| a + b + c);119120let args = ArgList::new()121.with_owned(1_i32)122.with_owned(2_i32)123.with_owned(3_i32);124let return_value = function.call(args).unwrap();125let value: Box<dyn PartialReflect> = return_value.unwrap_owned();126assert_eq!(value.try_take::<i32>().unwrap(), 6);127128// As stated earlier, `IntoFunction` works for many kinds of simple functions.129// Functions with non-reflectable arguments or return values may not be able to be converted.130// Generic functions are also not supported (unless manually monomorphized like `foo::<i32>.into_function()`).131// Additionally, the lifetime of the return value is tied to the lifetime of the first argument.132// However, this means that many methods (i.e. functions with a `self` parameter) are also supported:133#[derive(Reflect, Default)]134struct Data {135value: String,136}137138impl Data {139fn set_value(&mut self, value: String) {140self.value = value;141}142143// Note that only `&'static str` implements `Reflect`.144// To get around this limitation we can use `&String` instead.145fn get_value(&self) -> &String {146&self.value147}148}149150let mut data = Data::default();151152let set_value = dbg!(Data::set_value.into_function());153let args = dbg!(ArgList::new().with_mut(&mut data)).with_owned(String::from("Hello, world!"));154dbg!(set_value.call(args).unwrap());155assert_eq!(data.value, "Hello, world!");156157let get_value = dbg!(Data::get_value.into_function());158let args = dbg!(ArgList::new().with_ref(&data));159let return_value = dbg!(get_value.call(args).unwrap());160let value: &dyn PartialReflect = return_value.unwrap_ref();161assert_eq!(value.try_downcast_ref::<String>().unwrap(), "Hello, world!");162163// For more complex use cases, you can always create a custom `DynamicFunction` manually.164// This is useful for functions that can't be converted via the `IntoFunction` trait.165// For example, this function doesn't implement `IntoFunction` due to the fact that166// the lifetime of the return value is not tied to the lifetime of the first argument.167fn get_or_insert(value: i32, container: &mut Option<i32>) -> &i32 {168if container.is_none() {169*container = Some(value);170}171172container.as_ref().unwrap()173}174175let get_or_insert_function = dbg!(DynamicFunction::new(176|mut args: ArgList| -> FunctionResult {177// The `ArgList` contains the arguments in the order they were pushed.178// The `DynamicFunction` will validate that the list contains179// exactly the number of arguments we expect.180// We can retrieve them out in order (note that this modifies the `ArgList`):181let value = args.take::<i32>()?;182let container = args.take::<&mut Option<i32>>()?;183184// We could have also done the following to make use of type inference:185// let value = args.take_owned()?;186// let container = args.take_mut()?;187188Ok(Return::Ref(get_or_insert(value, container)))189},190// Functions can be either anonymous or named.191// It's good practice, though, to try and name your functions whenever possible.192// This makes it easier to debug and is also required for function registration.193// We can either give it a custom name or use the function's type name as194// derived from `std::any::type_name_of_val`.195SignatureInfo::named(std::any::type_name_of_val(&get_or_insert))196// We can always change the name if needed.197// It's a good idea to also ensure that the name is unique,198// such as by using its type name or by prefixing it with your crate name.199.with_name("my_crate::get_or_insert")200// Since our function takes arguments, we should provide that argument information.201// This is used to validate arguments when calling the function.202// And it aids consumers of the function with their own validation and debugging.203// Arguments should be provided in the order they are defined in the function.204.with_arg::<i32>("value")205.with_arg::<&mut Option<i32>>("container")206// We can provide return information as well.207.with_return::<&i32>(),208));209210let mut container: Option<i32> = None;211212let args = dbg!(ArgList::new().with_owned(5_i32).with_mut(&mut container));213let value = dbg!(get_or_insert_function.call(args).unwrap()).unwrap_ref();214assert_eq!(value.try_downcast_ref::<i32>(), Some(&5));215216let args = dbg!(ArgList::new().with_owned(500_i32).with_mut(&mut container));217let value = dbg!(get_or_insert_function.call(args).unwrap()).unwrap_ref();218assert_eq!(value.try_downcast_ref::<i32>(), Some(&5));219}220221222