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