Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/reflection/function_reflection.rs
6849 views
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
}
221
222