|
| 1 | +//! This example demonstrates how functions can be called dynamically using reflection. |
| 2 | +
|
| 3 | +use bevy::reflect::func::args::ArgInfo; |
| 4 | +use bevy::reflect::func::{ArgList, Function, FunctionInfo, IntoFunction, Return, ReturnInfo}; |
| 5 | +use bevy::reflect::Reflect; |
| 6 | + |
| 7 | +// Note that the `dbg!` invocations are used purely for reference purposes |
| 8 | +// and are not strictly necessary for the example to work. |
| 9 | +fn main() { |
| 10 | + // There are times when it may be helpful to store a function away for later. |
| 11 | + // In Rust, we can do this by storing either a function pointer or a function trait object. |
| 12 | + // For example, say we wanted to store the following function: |
| 13 | + fn add(left: i32, right: i32) -> i32 { |
| 14 | + left + right |
| 15 | + } |
| 16 | + |
| 17 | + // We could store it as either of the following: |
| 18 | + let fn_pointer: fn(i32, i32) -> i32 = add; |
| 19 | + let fn_trait_object: Box<dyn Fn(i32, i32) -> i32> = Box::new(add); |
| 20 | + |
| 21 | + // And we can call them like so: |
| 22 | + let result = fn_pointer(2, 2); |
| 23 | + assert_eq!(result, 4); |
| 24 | + let result = fn_trait_object(2, 2); |
| 25 | + assert_eq!(result, 4); |
| 26 | + |
| 27 | + // However, you'll notice that we have to know the types of the arguments and return value at compile time. |
| 28 | + // This means there's not really a way to store or call these functions dynamically at runtime. |
| 29 | + // Luckily, Bevy's reflection crate comes with a set of tools for doing just that! |
| 30 | + // We do this by first converting our function into the reflection-based `Function` type |
| 31 | + // using the `IntoFunction` trait. |
| 32 | + let mut function: Function = dbg!(add.into_function()); |
| 33 | + |
| 34 | + // This time, you'll notice that `Function` doesn't take any information about the function's arguments or return value. |
| 35 | + // This is because `Function` checks the types of the arguments and return value at runtime. |
| 36 | + // Now we can generate a list of arguments: |
| 37 | + let args: ArgList = dbg!(ArgList::new().push_owned(2_i32).push_owned(2_i32)); |
| 38 | + |
| 39 | + // And finally, we can call the function. |
| 40 | + // This returns a `Result` indicating whether the function was called successfully. |
| 41 | + // For now, we'll just unwrap it to get our `Return` value, |
| 42 | + // which is an enum containing the function's return value. |
| 43 | + let result: Return = dbg!(function.call(args).unwrap()); |
| 44 | + |
| 45 | + // The `Return` value can be pattern matched or unwrapped to get the underlying reflection data. |
| 46 | + // For the sake of brevity, we'll just unwrap it here. |
| 47 | + let result: Box<dyn Reflect> = result.unwrap_owned(); |
| 48 | + assert_eq!(result.take::<i32>().unwrap(), 4); |
| 49 | + |
| 50 | + // The same can also be done for closures. |
| 51 | + let mut count = 0; |
| 52 | + let increment = |amount: i32| { |
| 53 | + count += amount; |
| 54 | + }; |
| 55 | + let increment_function: Function = dbg!(increment.into_function()); |
| 56 | + let args = dbg!(ArgList::new().push_owned(5_i32)); |
| 57 | + // Functions containing closures that capture their environment like this one |
| 58 | + // may need to be dropped before those captured variables may be used again. |
| 59 | + // This can be done manually with `drop` or by using the `Function::call_once` method. |
| 60 | + dbg!(increment_function.call_once(args).unwrap()); |
| 61 | + assert_eq!(count, 5); |
| 62 | + |
| 63 | + // All closures must be `'static`— that is, they take full ownership of any captured variables. |
| 64 | + let add_closure = |left: i32, right: i32| -> i32 { left + right }; |
| 65 | + let mut count_function = dbg!(add_closure.into_function()); |
| 66 | + let args = dbg!(ArgList::new().push_owned(2_i32).push_owned(2_i32)); |
| 67 | + let result = dbg!(count_function.call(args).unwrap()).unwrap_owned(); |
| 68 | + assert_eq!(result.take::<i32>().unwrap(), 4); |
| 69 | + |
| 70 | + // As stated before, this works for many kinds of simple functions. |
| 71 | + // Functions with non-reflectable arguments or return values may not be able to be converted. |
| 72 | + // Generic functions are also not supported. |
| 73 | + // Additionally, the lifetime of the return value is tied to the lifetime of the first argument. |
| 74 | + // However, this means that many methods are also supported: |
| 75 | + #[derive(Reflect, Default)] |
| 76 | + struct Data { |
| 77 | + value: String, |
| 78 | + } |
| 79 | + |
| 80 | + impl Data { |
| 81 | + fn set_value(&mut self, value: String) { |
| 82 | + self.value = value; |
| 83 | + } |
| 84 | + |
| 85 | + // Note that only `&'static str` implements `Reflect`. |
| 86 | + // To get around this limitation we can use `&String` instead. |
| 87 | + fn get_value(&self) -> &String { |
| 88 | + &self.value |
| 89 | + } |
| 90 | + } |
| 91 | + |
| 92 | + let mut data = Data::default(); |
| 93 | + |
| 94 | + let mut set_value = dbg!(Data::set_value.into_function()); |
| 95 | + let args = dbg!(ArgList::new().push_mut(&mut data)).push_owned(String::from("Hello, world!")); |
| 96 | + dbg!(set_value.call(args).unwrap()); |
| 97 | + assert_eq!(data.value, "Hello, world!"); |
| 98 | + |
| 99 | + let mut get_value = dbg!(Data::get_value.into_function()); |
| 100 | + let args = dbg!(ArgList::new().push_ref(&data)); |
| 101 | + let result = dbg!(get_value.call(args).unwrap()); |
| 102 | + let result: &dyn Reflect = result.unwrap_ref(); |
| 103 | + assert_eq!(result.downcast_ref::<String>().unwrap(), "Hello, world!"); |
| 104 | + |
| 105 | + // Lastly, for more complex use cases, you can always create a custom `Function` manually. |
| 106 | + // This is useful for functions that can't be converted via the `IntoFunction` trait. |
| 107 | + // For example, this function doesn't implement `IntoFunction` due to the fact that |
| 108 | + // the lifetime of the return value is not tied to the lifetime of the first argument. |
| 109 | + fn get_or_insert(value: i32, container: &mut Option<i32>) -> &i32 { |
| 110 | + if container.is_none() { |
| 111 | + *container = Some(value); |
| 112 | + } |
| 113 | + |
| 114 | + container.as_ref().unwrap() |
| 115 | + } |
| 116 | + |
| 117 | + let mut get_or_insert_function = dbg!(Function::new( |
| 118 | + |mut args, info| { |
| 119 | + let container_info = &info.args()[1]; |
| 120 | + let value_info = &info.args()[0]; |
| 121 | + |
| 122 | + // The `ArgList` contains the arguments in the order they were pushed. |
| 123 | + // Therefore, we need to pop them in reverse order. |
| 124 | + let container = args |
| 125 | + .pop() |
| 126 | + .unwrap() |
| 127 | + .take_mut::<Option<i32>>(container_info) |
| 128 | + .unwrap(); |
| 129 | + let value = args.pop().unwrap().take_owned::<i32>(value_info).unwrap(); |
| 130 | + |
| 131 | + Ok(Return::Ref(get_or_insert(value, container))) |
| 132 | + }, |
| 133 | + FunctionInfo::new() |
| 134 | + // We can optionally provide a name for the function |
| 135 | + .with_name("get_or_insert") |
| 136 | + // Since our function takes arguments, we MUST provide that argument information. |
| 137 | + // The arguments should be provided in the order they are defined in the function. |
| 138 | + // This is used to validate any arguments given at runtime. |
| 139 | + .with_args(vec![ |
| 140 | + ArgInfo::new::<i32>(0).with_name("value"), |
| 141 | + ArgInfo::new::<&mut Option<i32>>(1).with_name("container"), |
| 142 | + ]) |
| 143 | + // We can optionally provide return information as well. |
| 144 | + .with_return_info(ReturnInfo::new::<&i32>()), |
| 145 | + )); |
| 146 | + |
| 147 | + let mut container: Option<i32> = None; |
| 148 | + |
| 149 | + let args = dbg!(ArgList::new().push_owned(5_i32).push_mut(&mut container)); |
| 150 | + let result = dbg!(get_or_insert_function.call(args).unwrap()).unwrap_ref(); |
| 151 | + assert_eq!(result.downcast_ref::<i32>(), Some(&5)); |
| 152 | + |
| 153 | + let args = dbg!(ArgList::new().push_owned(500_i32).push_mut(&mut container)); |
| 154 | + let result = dbg!(get_or_insert_function.call(args).unwrap()).unwrap_ref(); |
| 155 | + assert_eq!(result.downcast_ref::<i32>(), Some(&5)); |
| 156 | +} |
0 commit comments