Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bevy_reflect: Add Function trait #15205

Merged
merged 7 commits into from
Sep 22, 2024
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
28 changes: 27 additions & 1 deletion benches/benches/bevy_reflect/function.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand All @@ -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());
});
}

Expand All @@ -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<dyn Fn(i32, i32) -> i32>,
|func| func(75, 25),
BatchSize::SmallInput,
);
})
.bench_function("function", |b| {
let add = add.into_function();
b.iter_batched(
Expand All @@ -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,
);
});
}

Expand Down
159 changes: 158 additions & 1 deletion crates/bevy_reflect/src/func/dynamic_function.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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<Self>) -> Box<dyn PartialReflect> {
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<Self>) -> Result<Box<dyn Reflect>, Box<dyn PartialReflect>> {
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<Self>) -> ReflectOwned {
ReflectOwned::Function(self)
}

fn clone_value(&self) -> Box<dyn PartialReflect> {
Box::new(self.clone())
}

fn reflect_hash(&self) -> Option<u64> {
None
}

fn reflect_partial_eq(&self, _value: &dyn PartialReflect) -> Option<bool> {
None
}

fn debug(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
Debug::fmt(self, f)
}

fn serializable(&self) -> Option<Serializable> {
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})`.
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -229,4 +342,48 @@ mod tests {

assert_eq!(clone_value, "Hello, world!");
}

#[test]
fn should_apply_function() {
let mut func: Box<dyn Function> = 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::<i32>().unwrap(), 25);
}

#[test]
fn should_allow_recursive_dynamic_function() {
let factorial = DynamicFunction::new(
|mut args| {
let curr = args.pop::<i32>()?;
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::<i32>().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::<i32>().unwrap(), 120);
}
}
78 changes: 78 additions & 0 deletions crates/bevy_reflect/src/func/function.rs
Original file line number Diff line number Diff line change
@@ -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<dyn Function> = 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::<i32>().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<dyn Function> = 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::<i32>().unwrap(), 100);
}
}
2 changes: 2 additions & 0 deletions crates/bevy_reflect/src/func/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand All @@ -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;
Expand Down
5 changes: 4 additions & 1 deletion crates/bevy_reflect/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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].
//!
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -591,7 +594,7 @@ pub mod prelude {
};

#[cfg(feature = "functions")]
pub use crate::func::{IntoFunction, IntoFunctionMut};
pub use crate::func::{Function, IntoFunction, IntoFunctionMut};
}

pub use array::*;
Expand Down
Loading