diff --git a/benches/benches/bevy_reflect/function.rs b/benches/benches/bevy_reflect/function.rs index 4eb97eee827b2..cd6e1a75db354 100644 --- a/benches/benches/bevy_reflect/function.rs +++ b/benches/benches/bevy_reflect/function.rs @@ -1,4 +1,4 @@ -use bevy_reflect::func::{ArgList, IntoFunction, TypedFunction}; +use bevy_reflect::func::{ArgList, IntoFunction, IntoFunctionMut, TypedFunction}; use bevy_reflect::prelude::*; use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; @@ -18,6 +18,11 @@ fn typed(c: &mut Criterion) { let capture = 25; let closure = |a: i32| a + capture; b.iter(|| closure.get_function_info()); + }) + .bench_function("closure_mut", |b| { + let mut capture = 25; + let closure = |a: i32| capture += a; + b.iter(|| closure.get_function_info()); }); } @@ -30,11 +35,23 @@ fn into(c: &mut Criterion) { let capture = 25; let closure = |a: i32| a + capture; b.iter(|| closure.into_function()); + }) + .bench_function("closure_mut", |b| { + let mut _capture = 25; + let closure = move |a: i32| _capture += a; + b.iter(|| closure.into_function_mut()); }); } fn call(c: &mut Criterion) { c.benchmark_group("call") + .bench_function("trait_object", |b| { + b.iter_batched( + || Box::new(add) as Box i32>, + |func| func(75, 25), + BatchSize::SmallInput, + ); + }) .bench_function("function", |b| { let add = add.into_function(); b.iter_batched( @@ -51,6 +68,15 @@ fn call(c: &mut Criterion) { |args| add.call(args), BatchSize::SmallInput, ); + }) + .bench_function("closure_mut", |b| { + let mut capture = 25; + let mut add = (|a: i32| capture += a).into_function_mut(); + b.iter_batched( + || ArgList::new().push_owned(75_i32), + |args| add.call(args), + BatchSize::SmallInput, + ); }); } diff --git a/crates/bevy_reflect/src/func/dynamic_function.rs b/crates/bevy_reflect/src/func/dynamic_function.rs index 2794f1cb9ae89..fba0031489a28 100644 --- a/crates/bevy_reflect/src/func/dynamic_function.rs +++ b/crates/bevy_reflect/src/func/dynamic_function.rs @@ -1,7 +1,17 @@ +use crate as bevy_reflect; +use crate::__macro_exports::RegisterForReflection; use crate::func::args::{ArgInfo, ArgList}; use crate::func::info::FunctionInfo; -use crate::func::{DynamicFunctionMut, FunctionResult, IntoFunction, IntoFunctionMut, ReturnInfo}; +use crate::func::{ + DynamicFunctionMut, Function, FunctionResult, IntoFunction, IntoFunctionMut, ReturnInfo, +}; +use crate::serde::Serializable; +use crate::{ + ApplyError, MaybeTyped, PartialReflect, Reflect, ReflectKind, ReflectMut, ReflectOwned, + ReflectRef, TypeInfo, TypePath, +}; use alloc::borrow::Cow; +use bevy_reflect_derive::impl_type_path; use core::fmt::{Debug, Formatter}; use std::sync::Arc; @@ -136,6 +146,108 @@ impl<'env> DynamicFunction<'env> { } } +impl Function for DynamicFunction<'static> { + fn info(&self) -> &FunctionInfo { + self.info() + } + + fn reflect_call<'a>(&self, args: ArgList<'a>) -> FunctionResult<'a> { + self.call(args) + } + + fn clone_dynamic(&self) -> DynamicFunction<'static> { + self.clone() + } +} + +impl PartialReflect for DynamicFunction<'static> { + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + None + } + + fn into_partial_reflect(self: Box) -> Box { + self + } + + fn as_partial_reflect(&self) -> &dyn PartialReflect { + self + } + + fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { + self + } + + fn try_into_reflect(self: Box) -> Result, Box> { + Err(self) + } + + fn try_as_reflect(&self) -> Option<&dyn Reflect> { + None + } + + fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { + None + } + + fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { + match value.reflect_ref() { + ReflectRef::Function(func) => { + *self = func.clone_dynamic(); + Ok(()) + } + _ => Err(ApplyError::MismatchedTypes { + from_type: value.reflect_type_path().into(), + to_type: Self::type_path().into(), + }), + } + } + + fn reflect_kind(&self) -> ReflectKind { + ReflectKind::Function + } + + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::Function(self) + } + + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::Function(self) + } + + fn reflect_owned(self: Box) -> ReflectOwned { + ReflectOwned::Function(self) + } + + fn clone_value(&self) -> Box { + Box::new(self.clone()) + } + + fn reflect_hash(&self) -> Option { + None + } + + fn reflect_partial_eq(&self, _value: &dyn PartialReflect) -> Option { + None + } + + fn debug(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + Debug::fmt(self, f) + } + + fn serializable(&self) -> Option { + None + } + + fn is_dynamic(&self) -> bool { + true + } +} + +impl MaybeTyped for DynamicFunction<'static> {} +impl RegisterForReflection for DynamicFunction<'static> {} + +impl_type_path!((in bevy_reflect) DynamicFunction<'env>); + /// Outputs the function's signature. /// /// This takes the format: `DynamicFunction(fn {name}({arg1}: {type1}, {arg2}: {type2}, ...) -> {return_type})`. @@ -187,6 +299,7 @@ impl<'env> IntoFunctionMut<'env, ()> for DynamicFunction<'env> { #[cfg(test)] mod tests { use super::*; + use crate::func::IntoReturn; #[test] fn should_overwrite_function_name() { @@ -229,4 +342,48 @@ mod tests { assert_eq!(clone_value, "Hello, world!"); } + + #[test] + fn should_apply_function() { + let mut func: Box = Box::new((|a: i32, b: i32| a + b).into_function()); + func.apply(&((|a: i32, b: i32| a * b).into_function())); + + let args = ArgList::new().push_owned(5_i32).push_owned(5_i32); + let result = func.reflect_call(args).unwrap().unwrap_owned(); + assert_eq!(result.try_take::().unwrap(), 25); + } + + #[test] + fn should_allow_recursive_dynamic_function() { + let factorial = DynamicFunction::new( + |mut args| { + let curr = args.pop::()?; + if curr == 0 { + return Ok(1_i32.into_return()); + } + + let arg = args.pop_arg()?; + let this = arg.value(); + + match this.reflect_ref() { + ReflectRef::Function(func) => { + let result = func.reflect_call( + ArgList::new() + .push_ref(this.as_partial_reflect()) + .push_owned(curr - 1), + ); + let value = result.unwrap().unwrap_owned().try_take::().unwrap(); + Ok((curr * value).into_return()) + } + _ => panic!("expected function"), + } + }, + // The `FunctionInfo` doesn't really matter for this test + FunctionInfo::anonymous(), + ); + + let args = ArgList::new().push_ref(&factorial).push_owned(5_i32); + let value = factorial.call(args).unwrap().unwrap_owned(); + assert_eq!(value.try_take::().unwrap(), 120); + } } diff --git a/crates/bevy_reflect/src/func/function.rs b/crates/bevy_reflect/src/func/function.rs new file mode 100644 index 0000000000000..f70cfbf9c2804 --- /dev/null +++ b/crates/bevy_reflect/src/func/function.rs @@ -0,0 +1,78 @@ +use crate::func::{ArgList, DynamicFunction, FunctionInfo, FunctionResult}; +use crate::PartialReflect; +use alloc::borrow::Cow; +use core::fmt::Debug; + +/// A trait used to power [function-like] operations via [reflection]. +/// +/// This trait allows types to be called like regular functions +/// with [`Reflect`]-based [arguments] and return values. +/// +/// By default, this trait is currently only implemented for [`DynamicFunction`], +/// however, it is possible to implement this trait for custom function-like types. +/// +/// # Example +/// +/// ``` +/// # use bevy_reflect::func::{IntoFunction, ArgList, Function}; +/// fn add(a: i32, b: i32) -> i32 { +/// a + b +/// } +/// +/// let func: Box = Box::new(add.into_function()); +/// let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); +/// let value = func.reflect_call(args).unwrap().unwrap_owned(); +/// assert_eq!(value.try_take::().unwrap(), 100); +/// ``` +/// +/// [function-like]: crate::func +/// [reflection]: crate::Reflect +/// [`Reflect`]: crate::Reflect +/// [arguments]: crate::func::args +/// [`DynamicFunction`]: crate::func::DynamicFunction +pub trait Function: PartialReflect + Debug { + /// The name of the function, if any. + /// + /// For [`DynamicFunctions`] created using [`IntoFunction`], + /// the default name will always be the full path to the function as returned by [`std::any::type_name`], + /// unless the function is a closure, anonymous function, or function pointer, + /// in which case the name will be `None`. + /// + /// [`DynamicFunctions`]: crate::func::DynamicFunction + /// [`IntoFunction`]: crate::func::IntoFunction + fn name(&self) -> Option<&Cow<'static, str>> { + self.info().name() + } + + /// The number of arguments this function accepts. + fn arg_count(&self) -> usize { + self.info().arg_count() + } + + /// The [`FunctionInfo`] for this function. + fn info(&self) -> &FunctionInfo; + + /// Call this function with the given arguments. + fn reflect_call<'a>(&self, args: ArgList<'a>) -> FunctionResult<'a>; + + /// Clone this function into a [`DynamicFunction`]. + fn clone_dynamic(&self) -> DynamicFunction<'static>; +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::func::IntoFunction; + + #[test] + fn should_call_dyn_function() { + fn add(a: i32, b: i32) -> i32 { + a + b + } + + let func: Box = Box::new(add.into_function()); + let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); + let value = func.reflect_call(args).unwrap().unwrap_owned(); + assert_eq!(value.try_take::().unwrap(), 100); + } +} diff --git a/crates/bevy_reflect/src/func/mod.rs b/crates/bevy_reflect/src/func/mod.rs index 770f0fa0c2633..0a5b1d789598a 100644 --- a/crates/bevy_reflect/src/func/mod.rs +++ b/crates/bevy_reflect/src/func/mod.rs @@ -132,6 +132,7 @@ pub use args::{ArgError, ArgList, ArgValue}; pub use dynamic_function::*; pub use dynamic_function_mut::*; pub use error::*; +pub use function::*; pub use info::*; pub use into_function::*; pub use into_function_mut::*; @@ -144,6 +145,7 @@ pub mod args; mod dynamic_function; mod dynamic_function_mut; mod error; +mod function; mod info; mod into_function; mod into_function_mut; diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index c4326cff6f49c..99451e43f0e41 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -119,10 +119,12 @@ //! * [`Tuple`] //! * [`Array`] //! * [`List`] +//! * [`Set`] //! * [`Map`] //! * [`Struct`] //! * [`TupleStruct`] //! * [`Enum`] +//! * [`Function`] (requires the `functions` feature) //! //! As mentioned previously, the last three are automatically implemented by the [derive macro]. //! @@ -516,6 +518,7 @@ //! [the language feature for dyn upcasting coercion]: https://github.com/rust-lang/rust/issues/65991 //! [derive macro]: derive@crate::Reflect //! [`'static` lifetime]: https://doc.rust-lang.org/rust-by-example/scope/lifetime/static_lifetime.html#trait-bound +//! [`Function`]: func::Function //! [derive macro documentation]: derive@crate::Reflect //! [deriving `Reflect`]: derive@crate::Reflect //! [type data]: TypeData @@ -593,7 +596,7 @@ pub mod prelude { }; #[cfg(feature = "functions")] - pub use crate::func::{IntoFunction, IntoFunctionMut}; + pub use crate::func::{Function, IntoFunction, IntoFunctionMut}; } pub use array::*; diff --git a/crates/bevy_reflect/src/reflect.rs b/crates/bevy_reflect/src/reflect.rs index 6538d55c29a3e..3025709a77e8f 100644 --- a/crates/bevy_reflect/src/reflect.rs +++ b/crates/bevy_reflect/src/reflect.rs @@ -1,7 +1,7 @@ use crate::{ - array_debug, enum_debug, list_debug, map_debug, serde::Serializable, struct_debug, tuple_debug, - tuple_struct_debug, Array, DynamicTypePath, DynamicTyped, Enum, List, Map, Set, Struct, Tuple, - TupleStruct, TypeInfo, TypePath, Typed, ValueInfo, + array_debug, enum_debug, list_debug, map_debug, serde::Serializable, set_debug, struct_debug, + tuple_debug, tuple_struct_debug, Array, DynamicTypePath, DynamicTyped, Enum, List, Map, Set, + Struct, Tuple, TupleStruct, TypeInfo, TypePath, Typed, ValueInfo, }; use std::{ any::{Any, TypeId}, @@ -26,6 +26,8 @@ macro_rules! impl_reflect_enum { Self::Map(_) => ReflectKind::Map, Self::Set(_) => ReflectKind::Set, Self::Enum(_) => ReflectKind::Enum, + #[cfg(feature = "functions")] + Self::Function(_) => ReflectKind::Function, Self::Value(_) => ReflectKind::Value, } } @@ -42,6 +44,8 @@ macro_rules! impl_reflect_enum { $name::Map(_) => Self::Map, $name::Set(_) => Self::Set, $name::Enum(_) => Self::Enum, + #[cfg(feature = "functions")] + $name::Function(_) => Self::Function, $name::Value(_) => Self::Value, } } @@ -64,6 +68,8 @@ pub enum ReflectRef<'a> { Map(&'a dyn Map), Set(&'a dyn Set), Enum(&'a dyn Enum), + #[cfg(feature = "functions")] + Function(&'a dyn crate::func::Function), Value(&'a dyn PartialReflect), } impl_reflect_enum!(ReflectRef<'_>); @@ -83,6 +89,8 @@ pub enum ReflectMut<'a> { Map(&'a mut dyn Map), Set(&'a mut dyn Set), Enum(&'a mut dyn Enum), + #[cfg(feature = "functions")] + Function(&'a mut dyn crate::func::Function), Value(&'a mut dyn PartialReflect), } impl_reflect_enum!(ReflectMut<'_>); @@ -102,6 +110,8 @@ pub enum ReflectOwned { Map(Box), Set(Box), Enum(Box), + #[cfg(feature = "functions")] + Function(Box), Value(Box), } impl_reflect_enum!(ReflectOwned); @@ -156,6 +166,8 @@ pub enum ReflectKind { Map, Set, Enum, + #[cfg(feature = "functions")] + Function, Value, } @@ -170,6 +182,8 @@ impl std::fmt::Display for ReflectKind { ReflectKind::Map => f.pad("map"), ReflectKind::Set => f.pad("set"), ReflectKind::Enum => f.pad("enum"), + #[cfg(feature = "functions")] + ReflectKind::Function => f.pad("function"), ReflectKind::Value => f.pad("value"), } } @@ -361,8 +375,11 @@ where ReflectRef::List(dyn_list) => list_debug(dyn_list, f), ReflectRef::Array(dyn_array) => array_debug(dyn_array, f), ReflectRef::Map(dyn_map) => map_debug(dyn_map, f), + ReflectRef::Set(dyn_set) => set_debug(dyn_set, f), ReflectRef::Enum(dyn_enum) => enum_debug(dyn_enum, f), - _ => write!(f, "Reflect({})", self.reflect_type_path()), + #[cfg(feature = "functions")] + ReflectRef::Function(dyn_function) => dyn_function.fmt(f), + ReflectRef::Value(_) => write!(f, "Reflect({})", self.reflect_type_path()), } } diff --git a/crates/bevy_reflect/src/serde/de/mod.rs b/crates/bevy_reflect/src/serde/de/mod.rs index bc92d3ccf8e8e..4b89803e1d557 100644 --- a/crates/bevy_reflect/src/serde/de/mod.rs +++ b/crates/bevy_reflect/src/serde/de/mod.rs @@ -517,6 +517,54 @@ mod tests { assert_eq!(error, ron::Error::Message("type `core::ops::RangeInclusive` did not register the `ReflectDeserialize` type data. For certain types, this may need to be registered manually using `register_type_data`".to_string())); } + #[cfg(feature = "functions")] + mod functions { + use super::*; + use crate::func::DynamicFunction; + + #[test] + fn should_not_deserialize_function() { + #[derive(Reflect)] + #[reflect(from_reflect = false)] + struct MyStruct { + func: DynamicFunction<'static>, + } + + let mut registry = TypeRegistry::new(); + registry.register::(); + + let input = r#"{ + "bevy_reflect::serde::de::tests::functions::MyStruct": ( + func: (), + ), + }"#; + + let reflect_deserializer = ReflectDeserializer::new(®istry); + let mut ron_deserializer = ron::de::Deserializer::from_str(input).unwrap(); + + let error = reflect_deserializer + .deserialize(&mut ron_deserializer) + .unwrap_err(); + + #[cfg(feature = "debug_stack")] + assert_eq!( + error, + ron::Error::Message( + "no registration found for type `bevy_reflect::DynamicFunction` (stack: `bevy_reflect::serde::de::tests::functions::MyStruct`)" + .to_string() + ) + ); + + #[cfg(not(feature = "debug_stack"))] + assert_eq!( + error, + ron::Error::Message( + "no registration found for type `bevy_reflect::DynamicFunction`".to_string() + ) + ); + } + } + #[cfg(feature = "debug_stack")] mod debug_stack { use super::*; diff --git a/crates/bevy_reflect/src/serde/ser/mod.rs b/crates/bevy_reflect/src/serde/ser/mod.rs index ee2a283e82ecb..30d684d4aac92 100644 --- a/crates/bevy_reflect/src/serde/ser/mod.rs +++ b/crates/bevy_reflect/src/serde/ser/mod.rs @@ -472,6 +472,42 @@ mod tests { ); } + #[cfg(feature = "functions")] + mod functions { + use super::*; + use crate::func::{DynamicFunction, IntoFunction}; + + #[test] + fn should_not_serialize_function() { + #[derive(Reflect)] + #[reflect(from_reflect = false)] + struct MyStruct { + func: DynamicFunction<'static>, + } + + let value: Box = Box::new(MyStruct { + func: String::new.into_function(), + }); + + let registry = TypeRegistry::new(); + let serializer = ReflectSerializer::new(value.as_partial_reflect(), ®istry); + + let error = ron::ser::to_string(&serializer).unwrap_err(); + + #[cfg(feature = "debug_stack")] + assert_eq!( + error, + ron::Error::Message("functions cannot be serialized (stack: `bevy_reflect::serde::ser::tests::functions::MyStruct`)".to_string()) + ); + + #[cfg(not(feature = "debug_stack"))] + assert_eq!( + error, + ron::Error::Message("functions cannot be serialized".to_string()) + ); + } + } + #[cfg(feature = "debug_stack")] mod debug_stack { use super::*; diff --git a/crates/bevy_reflect/src/serde/ser/serializer.rs b/crates/bevy_reflect/src/serde/ser/serializer.rs index cdc00452aadee..a6c6d56db6f47 100644 --- a/crates/bevy_reflect/src/serde/ser/serializer.rs +++ b/crates/bevy_reflect/src/serde/ser/serializer.rs @@ -154,14 +154,9 @@ impl<'a> Serialize for TypedReflectSerializer<'a> { { #[cfg(feature = "debug_stack")] { - let info = self.value.get_represented_type_info().ok_or_else(|| { - make_custom_error(format_args!( - "type `{}` does not represent any type", - self.value.reflect_type_path(), - )) - })?; - - TYPE_INFO_STACK.with_borrow_mut(|stack| stack.push(info)); + if let Some(info) = self.value.get_represented_type_info() { + TYPE_INFO_STACK.with_borrow_mut(|stack| stack.push(info)); + } } // Handle both Value case and types that have a custom `Serialize` @@ -199,6 +194,8 @@ impl<'a> Serialize for TypedReflectSerializer<'a> { ReflectRef::Enum(value) => { EnumSerializer::new(value, self.registry).serialize(serializer) } + #[cfg(feature = "functions")] + ReflectRef::Function(_) => Err(make_custom_error("functions cannot be serialized")), ReflectRef::Value(_) => Err(serializable.err().unwrap()), }; diff --git a/examples/reflection/reflection_types.rs b/examples/reflection/reflection_types.rs index 673092a69448b..ae083995d89f9 100644 --- a/examples/reflection/reflection_types.rs +++ b/examples/reflection/reflection_types.rs @@ -112,6 +112,12 @@ fn setup() { // This exposes "set" operations on your type, such as getting / inserting by value. // Set is automatically implemented for relevant core types like HashSet ReflectRef::Set(_) => {} + // `Function` is a special trait that can be manually implemented (instead of deriving Reflect). + // This exposes "function" operations on your type, such as calling it with arguments. + // This trait is automatically implemented for types like DynamicFunction. + // This variant only exists if the `reflect_functions` feature is enabled. + #[cfg(feature = "reflect_functions")] + ReflectRef::Function(_) => {} // `Value` types do not implement any of the other traits above. They are simply a Reflect // implementation. Value is implemented for core types like i32, usize, f32, and // String.