Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions crates/bevy_app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ trace = []
bevy_debug_stepping = []
default = ["bevy_reflect"]
bevy_reflect = ["dep:bevy_reflect", "bevy_ecs/bevy_reflect"]
reflect_functions = [
"bevy_reflect",
"bevy_reflect/functions",
"bevy_ecs/reflect_functions",
]

[dependencies]
# bevy
Expand Down
76 changes: 74 additions & 2 deletions crates/bevy_app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ impl Default for App {

#[cfg(feature = "bevy_reflect")]
app.init_resource::<AppTypeRegistry>();

#[cfg(feature = "reflect_functions")]
app.init_resource::<AppFunctionRegistry>();

app.add_plugins(MainSchedulePlugin);
app.add_systems(
First,
Expand Down Expand Up @@ -553,7 +557,7 @@ impl App {
self
}

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

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

/// Registers the given function into the [`AppFunctionRegistry`] resource using the given name.
///
/// To avoid conflicts, it's recommended to use a unique name for the function.
/// This can be achieved by either using the function's [type name] or
/// by "namespacing" the function with a unique identifier,
/// such as the name of your crate.
///
/// For example, to register a function, `add`, from a crate, `my_crate`,
/// you could use the name, `"my_crate::add"`.
///
/// Only functions that implement [`IntoFunction`] may be registered via this method.
///
/// See [`FunctionRegistry::register`] for more information.
///
/// # Panics
///
/// Panics if a function has already been registered with the given name.
Comment on lines +620 to +622
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we panic here or just return the Result?

If we return the Result, then users will be forced to unwrap it themselves, but it gives them more options on handling the error (not that they can't just get the AppFunctionRegistry directly).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Long term, I would prefer is this returned a result. But since none of the other methods on App do currently, I vote we panic for consistency. We can take a more general look at error handling during app setup later.

///
/// # Examples
///
/// ```
/// use bevy_app::App;
///
/// fn yell(text: String) {
/// println!("{}!", text);
/// }
///
/// App::new()
/// // Registering an anonymous function with a unique name
/// .register_function("my_crate::yell_louder", |text: String| {
/// println!("{}!!!", text.to_uppercase());
/// })
/// // Registering an existing function with its type name
/// .register_function(std::any::type_name_of_val(&yell), yell)
/// // Registering an existing function with a custom name
/// .register_function("my_crate::yell", yell);
/// ```
///
/// Names must be unique.
///
/// ```should_panic
/// use bevy_app::App;
///
/// fn one() {}
/// fn two() {}
///
/// App::new()
/// .register_function("my_function", one)
/// // Panic! A function has already been registered with the name "my_function"
/// .register_function("my_function", two);
/// ```
///
/// [type name]: std::any::type_name
/// [`IntoFunction`]: bevy_reflect::func::IntoFunction
/// [`FunctionRegistry::register`]: bevy_reflect::func::FunctionRegistry::register
#[cfg(feature = "reflect_functions")]
pub fn register_function<F, Marker>(
&mut self,
name: impl Into<std::borrow::Cow<'static, str>>,
function: F,
) -> &mut Self
where
F: bevy_reflect::func::IntoFunction<Marker> + 'static,
{
self.main_mut().register_function(name, function);
self
}

/// Returns a reference to the [`World`].
pub fn world(&self) -> &World {
self.main().world()
Expand Down
15 changes: 15 additions & 0 deletions crates/bevy_app/src/sub_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,21 @@ impl SubApp {
registry.write().register_type_data::<T, D>();
self
}

/// See [`App::register_function`].
#[cfg(feature = "reflect_functions")]
pub fn register_function<F, Marker>(
&mut self,
name: impl Into<std::borrow::Cow<'static, str>>,
function: F,
) -> &mut Self
where
F: bevy_reflect::func::IntoFunction<Marker> + 'static,
{
let registry = self.world.resource_mut::<AppFunctionRegistry>();
registry.write().register(name, function).unwrap();
self
}
}

/// The collection of sub-apps that belong to an [`App`].
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_ecs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ multi_threaded = ["bevy_tasks/multi_threaded", "arrayvec"]
bevy_debug_stepping = []
serialize = ["dep:serde"]
track_change_detection = []
reflect_functions = ["bevy_reflect", "bevy_reflect/functions"]

[dependencies]
bevy_ptr = { path = "../bevy_ptr", version = "0.15.0-dev" }
Expand Down
3 changes: 3 additions & 0 deletions crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ pub use bevy_ptr as ptr;

/// Most commonly used re-exported types.
pub mod prelude {
#[doc(hidden)]
#[cfg(feature = "reflect_functions")]
pub use crate::reflect::AppFunctionRegistry;
#[doc(hidden)]
#[cfg(feature = "bevy_reflect")]
pub use crate::reflect::{
Expand Down
26 changes: 26 additions & 0 deletions crates/bevy_ecs/src/reflect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,32 @@ impl DerefMut for AppTypeRegistry {
}
}

/// A [`Resource`] storing [`FunctionRegistry`] for
/// function registrations relevant to a whole app.
///
/// [`FunctionRegistry`]: bevy_reflect::func::FunctionRegistry
#[cfg(feature = "reflect_functions")]
#[derive(Resource, Clone, Default)]
pub struct AppFunctionRegistry(pub bevy_reflect::func::FunctionRegistryArc);

#[cfg(feature = "reflect_functions")]
impl Deref for AppFunctionRegistry {
type Target = bevy_reflect::func::FunctionRegistryArc;

#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}

#[cfg(feature = "reflect_functions")]
impl DerefMut for AppFunctionRegistry {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

/// Creates a `T` from a `&dyn Reflect`.
///
/// This will try the following strategies, in this order:
Expand Down
6 changes: 5 additions & 1 deletion crates/bevy_internal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,11 @@ bevy_state = ["dep:bevy_state"]
track_change_detection = ["bevy_ecs/track_change_detection"]

# Enable function reflection
reflect_functions = ["bevy_reflect/functions"]
reflect_functions = [
"bevy_reflect/functions",
"bevy_app/reflect_functions",
"bevy_ecs/reflect_functions",
]

[dependencies]
# bevy
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_reflect/src/func/closures/dynamic_closure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ impl<'env> DynamicClosure<'env> {
/// Names for arguments and the closure itself are optional and will default to `_` if not provided.
impl<'env> Debug for DynamicClosure<'env> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let name = self.info.name().unwrap_or("_");
let name = self.info.name();
write!(f, "DynamicClosure(fn {name}(")?;

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

#[test]
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_reflect/src/func/closures/dynamic_closure_mut.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ impl<'env> DynamicClosureMut<'env> {
/// Names for arguments and the closure itself are optional and will default to `_` if not provided.
impl<'env> Debug for DynamicClosureMut<'env> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let name = self.info.name().unwrap_or("_");
let name = self.info.name();
write!(f, "DynamicClosureMut(fn {name}(")?;

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

#[test]
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_reflect/src/func/closures/into_closure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ mod tests {
let func = (|a: i32, b: i32| a + b + c).into_closure();
assert_eq!(
func.info().name(),
Some("bevy_reflect::func::closures::into_closure::tests::should_default_with_closure_type_name::{{closure}}")
"bevy_reflect::func::closures::into_closure::tests::should_default_with_closure_type_name::{{closure}}"
);
}
}
2 changes: 1 addition & 1 deletion crates/bevy_reflect/src/func/closures/into_closure_mut.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ mod tests {
let func = (|a: i32, b: i32| total = a + b).into_closure_mut();
assert_eq!(
func.info().name(),
Some("bevy_reflect::func::closures::into_closure_mut::tests::should_default_with_closure_type_name::{{closure}}")
"bevy_reflect::func::closures::into_closure_mut::tests::should_default_with_closure_type_name::{{closure}}"
);
}
}
13 changes: 13 additions & 0 deletions crates/bevy_reflect/src/func/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::func::args::ArgError;
use crate::func::Return;
use alloc::borrow::Cow;
use thiserror::Error;

/// An error that occurs when calling a [`DynamicFunction`] or [`DynamicClosure`].
Expand All @@ -24,3 +25,15 @@ pub enum FunctionError {
/// [`DynamicFunction`]: crate::func::DynamicFunction
/// [`DynamicClosure`]: crate::func::DynamicClosure
pub type FunctionResult<'a> = Result<Return<'a>, FunctionError>;

/// An error that occurs when registering a function into a [`FunctionRegistry`].
///
/// [`FunctionRegistry`]: crate::func::FunctionRegistry
#[derive(Debug, Error, PartialEq)]
pub enum FunctionRegistrationError {
/// A function with the given name has already been registered.
///
/// Contains the duplicate function name.
#[error("a function has already been registered with name {0:?}")]
DuplicateName(Cow<'static, str>),
}
29 changes: 20 additions & 9 deletions crates/bevy_reflect/src/func/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ use crate::func::{FunctionResult, IntoFunction, ReturnInfo};
///
/// // Instead, we need to define the function manually.
/// // We start by defining the shape of the function:
/// let info = FunctionInfo::new()
/// .with_name("append")
/// let info = FunctionInfo::new("append")
/// .with_arg::<String>("value")
/// .with_arg::<&mut Vec<String>>("list")
/// .with_return::<&mut String>();
Expand Down Expand Up @@ -93,7 +92,7 @@ use crate::func::{FunctionResult, IntoFunction, ReturnInfo};
/// [module-level documentation]: crate::func
pub struct DynamicFunction {
info: FunctionInfo,
func: Arc<dyn for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + 'static>,
func: Arc<dyn for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + Send + Sync + 'static>,
}

impl DynamicFunction {
Expand All @@ -103,7 +102,7 @@ impl DynamicFunction {
///
/// It's important that the function signature matches the provided [`FunctionInfo`].
/// This info may be used by consumers of the function for validation and debugging.
pub fn new<F: for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + 'static>(
pub fn new<F: for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + Send + Sync + 'static>(
func: F,
info: FunctionInfo,
) -> Self {
Expand Down Expand Up @@ -162,16 +161,29 @@ impl DynamicFunction {
pub fn info(&self) -> &FunctionInfo {
&self.info
}

/// The [name] of the function.
///
/// For [`DynamicFunctions`] created using [`IntoFunction`],
/// the name will always be the full path to the function as returned by [`std::any::type_name`].
/// This can be overridden using [`with_name`].
///
/// [name]: FunctionInfo::name
/// [`DynamicFunctions`]: DynamicFunction
/// [`with_name`]: Self::with_name
pub fn name(&self) -> &Cow<'static, str> {
self.info.name()
}
}

/// Outputs the function signature.
///
/// This takes the format: `DynamicFunction(fn {name}({arg1}: {type1}, {arg2}: {type2}, ...) -> {return_type})`.
///
/// Names for arguments and the function itself are optional and will default to `_` if not provided.
/// Names for arguments are optional and will default to `_` if not provided.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doc line seems out of date.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Argument names are still optional since we can't infer these through the type system unfortunately :(

impl Debug for DynamicFunction {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let name = self.info.name().unwrap_or("_");
let name = self.name();
write!(f, "DynamicFunction(fn {name}(")?;

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

let func = foo.into_function().with_name("my_function");
assert_eq!(func.info().name(), Some("my_function"));
assert_eq!(func.info().name(), "my_function");
}

#[test]
Expand All @@ -241,8 +253,7 @@ mod tests {
let index = args.pop::<usize>()?;
Ok(Return::Ref(get(index, list)))
},
FunctionInfo::new()
.with_name("get")
FunctionInfo::new("get")
.with_arg::<usize>("index")
.with_arg::<&Vec<String>>("list")
.with_return::<&String>(),
Expand Down
Loading