Skip to content

Commit df61117

Browse files
authored
bevy_reflect: Function registry (#14098)
# Objective #13152 added support for reflecting functions. Now, we need a way to register those functions such that they may be accessed anywhere within the ECS. ## Solution Added a `FunctionRegistry` type similar to `TypeRegistry`. This allows a function to be registered and retrieved by name. ```rust fn foo() -> i32 { 123 } let mut registry = FunctionRegistry::default(); registry.register("my_function", foo); let function = registry.get_mut("my_function").unwrap(); let value = function.call(ArgList::new()).unwrap().unwrap_owned(); assert_eq!(value.downcast_ref::<i32>(), Some(&123)); ``` Additionally, I added an `AppFunctionRegistry` resource which wraps a `FunctionRegistryArc`. Functions can be registered into this resource using `App::register_function` or by getting a mutable reference to the resource itself. ### Limitations #### `Send + Sync` In order to get this registry to work across threads, it needs to be `Send + Sync`. This means that `DynamicFunction` needs to be `Send + Sync`, which means that its internal function also needs to be `Send + Sync`. In most cases, this won't be an issue because standard Rust functions (the type most likely to be registered) are always `Send + Sync`. Additionally, closures tend to be `Send + Sync` as well, granted they don't capture any `!Send` or `!Sync` variables. This PR adds this `Send + Sync` requirement, but as mentioned above, it hopefully shouldn't be too big of an issue. #### Closures Unfortunately, closures can't be registered yet. This will likely be explored and added in a followup PR. ### Future Work Besides addressing the limitations listed above, another thing we could look into is improving the lookup of registered functions. One aspect is in the performance of hashing strings. The other is in the developer experience of having to call `std::any::type_name_of_val` to get the name of their function (assuming they didn't give it a custom name). ## Testing You can run the tests locally with: ``` cargo test --package bevy_reflect ``` --- ## Changelog - Added `FunctionRegistry` - Added `AppFunctionRegistry` (a `Resource` available from `bevy_ecs`) - Added `FunctionRegistryArc` - Added `FunctionRegistrationError` - Added `reflect_functions` feature to `bevy_ecs` and `bevy_app` - `FunctionInfo` is no longer `Default` - `DynamicFunction` now requires its wrapped function be `Send + Sync` ## Internal Migration Guide > [!important] > Function reflection was introduced as part of the 0.15 dev cycle. This migration guide was written for developers relying on `main` during this cycle, and is not a breaking change coming from 0.14. `DynamicFunction` (both those created manually and those created with `IntoFunction`), now require `Send + Sync`. All standard Rust functions should meet that requirement. Closures, on the other hand, may not if they capture any `!Send` or `!Sync` variables from its environment.
1 parent ec4cf02 commit df61117

File tree

18 files changed

+556
-49
lines changed

18 files changed

+556
-49
lines changed

crates/bevy_app/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ trace = []
1313
bevy_debug_stepping = []
1414
default = ["bevy_reflect"]
1515
bevy_reflect = ["dep:bevy_reflect", "bevy_ecs/bevy_reflect"]
16+
reflect_functions = [
17+
"bevy_reflect",
18+
"bevy_reflect/functions",
19+
"bevy_ecs/reflect_functions",
20+
]
1621

1722
[dependencies]
1823
# bevy

crates/bevy_app/src/app.rs

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ impl Default for App {
9696

9797
#[cfg(feature = "bevy_reflect")]
9898
app.init_resource::<AppTypeRegistry>();
99+
100+
#[cfg(feature = "reflect_functions")]
101+
app.init_resource::<AppFunctionRegistry>();
102+
99103
app.add_plugins(MainSchedulePlugin);
100104
app.add_systems(
101105
First,
@@ -553,7 +557,7 @@ impl App {
553557
self
554558
}
555559

556-
/// Registers the type `T` in the [`TypeRegistry`](bevy_reflect::TypeRegistry) resource,
560+
/// Registers the type `T` in the [`AppTypeRegistry`] resource,
557561
/// adding reflect data as specified in the [`Reflect`](bevy_reflect::Reflect) derive:
558562
/// ```ignore (No serde "derive" feature)
559563
/// #[derive(Component, Serialize, Deserialize, Reflect)]
@@ -567,7 +571,7 @@ impl App {
567571
self
568572
}
569573

570-
/// Associates type data `D` with type `T` in the [`TypeRegistry`](bevy_reflect::TypeRegistry) resource.
574+
/// Associates type data `D` with type `T` in the [`AppTypeRegistry`] resource.
571575
///
572576
/// Most of the time [`register_type`](Self::register_type) can be used instead to register a
573577
/// type you derived [`Reflect`](bevy_reflect::Reflect) for. However, in cases where you want to
@@ -599,6 +603,74 @@ impl App {
599603
self
600604
}
601605

606+
/// Registers the given function into the [`AppFunctionRegistry`] resource using the given name.
607+
///
608+
/// To avoid conflicts, it's recommended to use a unique name for the function.
609+
/// This can be achieved by either using the function's [type name] or
610+
/// by "namespacing" the function with a unique identifier,
611+
/// such as the name of your crate.
612+
///
613+
/// For example, to register a function, `add`, from a crate, `my_crate`,
614+
/// you could use the name, `"my_crate::add"`.
615+
///
616+
/// Only functions that implement [`IntoFunction`] may be registered via this method.
617+
///
618+
/// See [`FunctionRegistry::register`] for more information.
619+
///
620+
/// # Panics
621+
///
622+
/// Panics if a function has already been registered with the given name.
623+
///
624+
/// # Examples
625+
///
626+
/// ```
627+
/// use bevy_app::App;
628+
///
629+
/// fn yell(text: String) {
630+
/// println!("{}!", text);
631+
/// }
632+
///
633+
/// App::new()
634+
/// // Registering an anonymous function with a unique name
635+
/// .register_function("my_crate::yell_louder", |text: String| {
636+
/// println!("{}!!!", text.to_uppercase());
637+
/// })
638+
/// // Registering an existing function with its type name
639+
/// .register_function(std::any::type_name_of_val(&yell), yell)
640+
/// // Registering an existing function with a custom name
641+
/// .register_function("my_crate::yell", yell);
642+
/// ```
643+
///
644+
/// Names must be unique.
645+
///
646+
/// ```should_panic
647+
/// use bevy_app::App;
648+
///
649+
/// fn one() {}
650+
/// fn two() {}
651+
///
652+
/// App::new()
653+
/// .register_function("my_function", one)
654+
/// // Panic! A function has already been registered with the name "my_function"
655+
/// .register_function("my_function", two);
656+
/// ```
657+
///
658+
/// [type name]: std::any::type_name
659+
/// [`IntoFunction`]: bevy_reflect::func::IntoFunction
660+
/// [`FunctionRegistry::register`]: bevy_reflect::func::FunctionRegistry::register
661+
#[cfg(feature = "reflect_functions")]
662+
pub fn register_function<F, Marker>(
663+
&mut self,
664+
name: impl Into<std::borrow::Cow<'static, str>>,
665+
function: F,
666+
) -> &mut Self
667+
where
668+
F: bevy_reflect::func::IntoFunction<Marker> + 'static,
669+
{
670+
self.main_mut().register_function(name, function);
671+
self
672+
}
673+
602674
/// Returns a reference to the [`World`].
603675
pub fn world(&self) -> &World {
604676
self.main().world()

crates/bevy_app/src/sub_app.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,21 @@ impl SubApp {
408408
registry.write().register_type_data::<T, D>();
409409
self
410410
}
411+
412+
/// See [`App::register_function`].
413+
#[cfg(feature = "reflect_functions")]
414+
pub fn register_function<F, Marker>(
415+
&mut self,
416+
name: impl Into<std::borrow::Cow<'static, str>>,
417+
function: F,
418+
) -> &mut Self
419+
where
420+
F: bevy_reflect::func::IntoFunction<Marker> + 'static,
421+
{
422+
let registry = self.world.resource_mut::<AppFunctionRegistry>();
423+
registry.write().register(name, function).unwrap();
424+
self
425+
}
411426
}
412427

413428
/// The collection of sub-apps that belong to an [`App`].

crates/bevy_ecs/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ multi_threaded = ["bevy_tasks/multi_threaded", "arrayvec"]
1717
bevy_debug_stepping = []
1818
serialize = ["dep:serde"]
1919
track_change_detection = []
20+
reflect_functions = ["bevy_reflect", "bevy_reflect/functions"]
2021

2122
[dependencies]
2223
bevy_ptr = { path = "../bevy_ptr", version = "0.15.0-dev" }

crates/bevy_ecs/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ pub use bevy_ptr as ptr;
3636

3737
/// Most commonly used re-exported types.
3838
pub mod prelude {
39+
#[doc(hidden)]
40+
#[cfg(feature = "reflect_functions")]
41+
pub use crate::reflect::AppFunctionRegistry;
3942
#[doc(hidden)]
4043
#[cfg(feature = "bevy_reflect")]
4144
pub use crate::reflect::{

crates/bevy_ecs/src/reflect/mod.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,32 @@ impl DerefMut for AppTypeRegistry {
4343
}
4444
}
4545

46+
/// A [`Resource`] storing [`FunctionRegistry`] for
47+
/// function registrations relevant to a whole app.
48+
///
49+
/// [`FunctionRegistry`]: bevy_reflect::func::FunctionRegistry
50+
#[cfg(feature = "reflect_functions")]
51+
#[derive(Resource, Clone, Default)]
52+
pub struct AppFunctionRegistry(pub bevy_reflect::func::FunctionRegistryArc);
53+
54+
#[cfg(feature = "reflect_functions")]
55+
impl Deref for AppFunctionRegistry {
56+
type Target = bevy_reflect::func::FunctionRegistryArc;
57+
58+
#[inline]
59+
fn deref(&self) -> &Self::Target {
60+
&self.0
61+
}
62+
}
63+
64+
#[cfg(feature = "reflect_functions")]
65+
impl DerefMut for AppFunctionRegistry {
66+
#[inline]
67+
fn deref_mut(&mut self) -> &mut Self::Target {
68+
&mut self.0
69+
}
70+
}
71+
4672
/// Creates a `T` from a `&dyn Reflect`.
4773
///
4874
/// This will try the following strategies, in this order:

crates/bevy_internal/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,11 @@ bevy_state = ["dep:bevy_state"]
199199
track_change_detection = ["bevy_ecs/track_change_detection"]
200200

201201
# Enable function reflection
202-
reflect_functions = ["bevy_reflect/functions"]
202+
reflect_functions = [
203+
"bevy_reflect/functions",
204+
"bevy_app/reflect_functions",
205+
"bevy_ecs/reflect_functions",
206+
]
203207

204208
[dependencies]
205209
# bevy

crates/bevy_reflect/src/func/closures/dynamic_closure.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ impl<'env> DynamicClosure<'env> {
129129
/// Names for arguments and the closure itself are optional and will default to `_` if not provided.
130130
impl<'env> Debug for DynamicClosure<'env> {
131131
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
132-
let name = self.info.name().unwrap_or("_");
132+
let name = self.info.name();
133133
write!(f, "DynamicClosure(fn {name}(")?;
134134

135135
for (index, arg) in self.info.args().iter().enumerate() {
@@ -164,7 +164,7 @@ mod tests {
164164
let func = (|a: i32, b: i32| a + b + c)
165165
.into_closure()
166166
.with_name("my_closure");
167-
assert_eq!(func.info().name(), Some("my_closure"));
167+
assert_eq!(func.info().name(), "my_closure");
168168
}
169169

170170
#[test]

crates/bevy_reflect/src/func/closures/dynamic_closure_mut.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ impl<'env> DynamicClosureMut<'env> {
171171
/// Names for arguments and the closure itself are optional and will default to `_` if not provided.
172172
impl<'env> Debug for DynamicClosureMut<'env> {
173173
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
174-
let name = self.info.name().unwrap_or("_");
174+
let name = self.info.name();
175175
write!(f, "DynamicClosureMut(fn {name}(")?;
176176

177177
for (index, arg) in self.info.args().iter().enumerate() {
@@ -206,7 +206,7 @@ mod tests {
206206
let func = (|a: i32, b: i32| total = a + b)
207207
.into_closure_mut()
208208
.with_name("my_closure");
209-
assert_eq!(func.info().name(), Some("my_closure"));
209+
assert_eq!(func.info().name(), "my_closure");
210210
}
211211

212212
#[test]

crates/bevy_reflect/src/func/closures/into_closure.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ mod tests {
5757
let func = (|a: i32, b: i32| a + b + c).into_closure();
5858
assert_eq!(
5959
func.info().name(),
60-
Some("bevy_reflect::func::closures::into_closure::tests::should_default_with_closure_type_name::{{closure}}")
60+
"bevy_reflect::func::closures::into_closure::tests::should_default_with_closure_type_name::{{closure}}"
6161
);
6262
}
6363
}

crates/bevy_reflect/src/func/closures/into_closure_mut.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ mod tests {
7070
let func = (|a: i32, b: i32| total = a + b).into_closure_mut();
7171
assert_eq!(
7272
func.info().name(),
73-
Some("bevy_reflect::func::closures::into_closure_mut::tests::should_default_with_closure_type_name::{{closure}}")
73+
"bevy_reflect::func::closures::into_closure_mut::tests::should_default_with_closure_type_name::{{closure}}"
7474
);
7575
}
7676
}

crates/bevy_reflect/src/func/error.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::func::args::ArgError;
22
use crate::func::Return;
3+
use alloc::borrow::Cow;
34
use thiserror::Error;
45

56
/// An error that occurs when calling a [`DynamicFunction`] or [`DynamicClosure`].
@@ -24,3 +25,15 @@ pub enum FunctionError {
2425
/// [`DynamicFunction`]: crate::func::DynamicFunction
2526
/// [`DynamicClosure`]: crate::func::DynamicClosure
2627
pub type FunctionResult<'a> = Result<Return<'a>, FunctionError>;
28+
29+
/// An error that occurs when registering a function into a [`FunctionRegistry`].
30+
///
31+
/// [`FunctionRegistry`]: crate::func::FunctionRegistry
32+
#[derive(Debug, Error, PartialEq)]
33+
pub enum FunctionRegistrationError {
34+
/// A function with the given name has already been registered.
35+
///
36+
/// Contains the duplicate function name.
37+
#[error("a function has already been registered with name {0:?}")]
38+
DuplicateName(Cow<'static, str>),
39+
}

crates/bevy_reflect/src/func/function.rs

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,7 @@ use crate::func::{FunctionResult, IntoFunction, ReturnInfo};
6060
///
6161
/// // Instead, we need to define the function manually.
6262
/// // We start by defining the shape of the function:
63-
/// let info = FunctionInfo::new()
64-
/// .with_name("append")
63+
/// let info = FunctionInfo::new("append")
6564
/// .with_arg::<String>("value")
6665
/// .with_arg::<&mut Vec<String>>("list")
6766
/// .with_return::<&mut String>();
@@ -93,7 +92,7 @@ use crate::func::{FunctionResult, IntoFunction, ReturnInfo};
9392
/// [module-level documentation]: crate::func
9493
pub struct DynamicFunction {
9594
info: FunctionInfo,
96-
func: Arc<dyn for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + 'static>,
95+
func: Arc<dyn for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + Send + Sync + 'static>,
9796
}
9897

9998
impl DynamicFunction {
@@ -103,7 +102,7 @@ impl DynamicFunction {
103102
///
104103
/// It's important that the function signature matches the provided [`FunctionInfo`].
105104
/// This info may be used by consumers of the function for validation and debugging.
106-
pub fn new<F: for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + 'static>(
105+
pub fn new<F: for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + Send + Sync + 'static>(
107106
func: F,
108107
info: FunctionInfo,
109108
) -> Self {
@@ -162,16 +161,29 @@ impl DynamicFunction {
162161
pub fn info(&self) -> &FunctionInfo {
163162
&self.info
164163
}
164+
165+
/// The [name] of the function.
166+
///
167+
/// For [`DynamicFunctions`] created using [`IntoFunction`],
168+
/// the name will always be the full path to the function as returned by [`std::any::type_name`].
169+
/// This can be overridden using [`with_name`].
170+
///
171+
/// [name]: FunctionInfo::name
172+
/// [`DynamicFunctions`]: DynamicFunction
173+
/// [`with_name`]: Self::with_name
174+
pub fn name(&self) -> &Cow<'static, str> {
175+
self.info.name()
176+
}
165177
}
166178

167179
/// Outputs the function signature.
168180
///
169181
/// This takes the format: `DynamicFunction(fn {name}({arg1}: {type1}, {arg2}: {type2}, ...) -> {return_type})`.
170182
///
171-
/// Names for arguments and the function itself are optional and will default to `_` if not provided.
183+
/// Names for arguments are optional and will default to `_` if not provided.
172184
impl Debug for DynamicFunction {
173185
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
174-
let name = self.info.name().unwrap_or("_");
186+
let name = self.name();
175187
write!(f, "DynamicFunction(fn {name}(")?;
176188

177189
for (index, arg) in self.info.args().iter().enumerate() {
@@ -215,7 +227,7 @@ mod tests {
215227
fn foo() {}
216228

217229
let func = foo.into_function().with_name("my_function");
218-
assert_eq!(func.info().name(), Some("my_function"));
230+
assert_eq!(func.info().name(), "my_function");
219231
}
220232

221233
#[test]
@@ -241,8 +253,7 @@ mod tests {
241253
let index = args.pop::<usize>()?;
242254
Ok(Return::Ref(get(index, list)))
243255
},
244-
FunctionInfo::new()
245-
.with_name("get")
256+
FunctionInfo::new("get")
246257
.with_arg::<usize>("index")
247258
.with_arg::<&Vec<String>>("list")
248259
.with_return::<&String>(),

0 commit comments

Comments
 (0)