Skip to content

Commit 503326f

Browse files
committed
Add function_reflection example
1 parent 3753cab commit 503326f

File tree

4 files changed

+173
-0
lines changed

4 files changed

+173
-0
lines changed

Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1996,6 +1996,17 @@ description = "Demonstrates how reflection in Bevy provides a way to dynamically
19961996
category = "Reflection"
19971997
wasm = false
19981998

1999+
[[example]]
2000+
name = "function_reflection"
2001+
path = "examples/reflection/function_reflection.rs"
2002+
doc-scrape-examples = true
2003+
2004+
[package.metadata.example.function_reflection]
2005+
name = "Function Reflection"
2006+
description = "Demonstrates how functions can be called dynamically using reflection"
2007+
category = "Reflection"
2008+
wasm = false
2009+
19992010
[[example]]
20002011
name = "generic_reflection"
20012012
path = "examples/reflection/generic_reflection.rs"

crates/bevy_reflect/src/func/args/list.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ impl<'a> ArgList<'a> {
5959
self.push(Arg::Owned(arg))
6060
}
6161

62+
/// Pop the last argument from the list, if there is one.
63+
pub fn pop(&mut self) -> Option<Arg<'a>> {
64+
self.0.pop()
65+
}
66+
6267
/// Returns the number of arguments in the list.
6368
pub fn len(&self) -> usize {
6469
self.0.len()

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ Example | Description
315315

316316
Example | Description
317317
--- | ---
318+
[Function Reflection](../examples/reflection/function_reflection.rs) | Demonstrates how functions can be called dynamically using reflection
318319
[Generic Reflection](../examples/reflection/generic_reflection.rs) | Registers concrete instances of generic types that may be used with reflection
319320
[Reflection](../examples/reflection/reflection.rs) | Demonstrates how reflection in Bevy provides a way to dynamically interact with Rust types
320321
[Reflection Types](../examples/reflection/reflection_types.rs) | Illustrates the various reflection types available
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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

Comments
 (0)