mirror of
https://github.com/bevyengine/bevy
synced 2025-01-02 08:18:59 +00:00
Added reflection function overloading
This commit is contained in:
parent
ba987ef60d
commit
8ce37d975a
7 changed files with 552 additions and 37 deletions
|
@ -5,7 +5,10 @@ use crate::{
|
|||
},
|
||||
PartialReflect, Reflect, TypePath,
|
||||
};
|
||||
use alloc::{boxed::Box, collections::VecDeque};
|
||||
use alloc::{
|
||||
boxed::Box,
|
||||
collections::vec_deque::{Iter, VecDeque},
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::{boxed::Box, format, vec};
|
||||
|
@ -286,6 +289,11 @@ impl<'a> ArgList<'a> {
|
|||
self.pop_arg()?.take_mut()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the arguments in the list.
|
||||
pub fn iter(&self) -> Iter<'_, Arg<'a>> {
|
||||
self.list.iter()
|
||||
}
|
||||
|
||||
/// Returns the number of arguments in the list.
|
||||
pub fn len(&self) -> usize {
|
||||
self.list.len()
|
||||
|
|
|
@ -3,7 +3,9 @@ use crate::{
|
|||
__macro_exports::RegisterForReflection,
|
||||
func::{
|
||||
args::ArgList,
|
||||
info::{FunctionInfo, FunctionInfoType},
|
||||
function_map::{merge_function_map, FunctionMap},
|
||||
info::FunctionInfoType,
|
||||
signature::ArgumentSignature,
|
||||
DynamicFunctionMut, Function, FunctionError, FunctionResult, IntoFunction, IntoFunctionMut,
|
||||
},
|
||||
serde::Serializable,
|
||||
|
@ -12,11 +14,22 @@ use crate::{
|
|||
};
|
||||
use alloc::{borrow::Cow, boxed::Box, sync::Arc};
|
||||
use bevy_reflect_derive::impl_type_path;
|
||||
use bevy_utils::HashMap;
|
||||
use core::fmt::{Debug, Formatter};
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::{boxed::Box, format, vec};
|
||||
|
||||
/// An [`Arc`] containing a callback to a reflected function.
|
||||
///
|
||||
/// The `Arc` is used to both ensure that it is `Send + Sync`
|
||||
/// and to allow for the callback to be cloned.
|
||||
///
|
||||
/// Note that cloning is okay since we only ever need an immutable reference
|
||||
/// to call a `dyn Fn` function.
|
||||
/// If we were to contain a `dyn FnMut` instead, cloning would be a lot more complicated.
|
||||
type ArcFn<'env> = Arc<dyn for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + Send + Sync + 'env>;
|
||||
|
||||
/// A dynamic representation of a function.
|
||||
///
|
||||
/// This type can be used to represent any callable that satisfies [`Fn`]
|
||||
|
@ -58,7 +71,7 @@ use alloc::{boxed::Box, format, vec};
|
|||
pub struct DynamicFunction<'env> {
|
||||
pub(super) name: Option<Cow<'static, str>>,
|
||||
pub(super) info: FunctionInfoType,
|
||||
pub(super) func: Arc<dyn for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + Send + Sync + 'env>,
|
||||
pub(super) function_map: FunctionMap<ArcFn<'env>>,
|
||||
}
|
||||
|
||||
impl<'env> DynamicFunction<'env> {
|
||||
|
@ -69,6 +82,7 @@ impl<'env> DynamicFunction<'env> {
|
|||
///
|
||||
/// It's important that the function signature matches the provided [`FunctionInfo`].
|
||||
/// as this will be used to validate arguments when [calling] the function.
|
||||
/// This is also required in order for [function overloading] to work correctly.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
|
@ -76,19 +90,30 @@ impl<'env> DynamicFunction<'env> {
|
|||
///
|
||||
/// [calling]: crate::func::dynamic_function::DynamicFunction::call
|
||||
/// [`FunctionInfo`]: crate::func::FunctionInfo
|
||||
/// [function overloading]: Self::with_overload
|
||||
pub fn new<F: for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + Send + Sync + 'env>(
|
||||
func: F,
|
||||
info: impl TryInto<FunctionInfoType, Error: Debug>,
|
||||
) -> Self {
|
||||
let info = info.try_into().unwrap();
|
||||
|
||||
let func: ArcFn = Arc::new(func);
|
||||
|
||||
Self {
|
||||
name: match &info {
|
||||
FunctionInfoType::Standard(info) => info.name().cloned(),
|
||||
FunctionInfoType::Overloaded(_) => None,
|
||||
},
|
||||
function_map: match &info {
|
||||
FunctionInfoType::Standard(_) => FunctionMap::Standard(func),
|
||||
FunctionInfoType::Overloaded(infos) => {
|
||||
FunctionMap::Overloaded(HashMap::from_iter(infos.iter().map(|info| {
|
||||
let sig = ArgumentSignature::from(info);
|
||||
(sig, func.clone())
|
||||
})))
|
||||
}
|
||||
},
|
||||
info,
|
||||
func: Arc::new(func),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -105,6 +130,104 @@ impl<'env> DynamicFunction<'env> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Add an overload to this function.
|
||||
///
|
||||
/// Overloads allow a single [`DynamicFunction`] to represent multiple functions of different signatures.
|
||||
///
|
||||
/// This can be used to handle multiple monomorphizations of a generic function
|
||||
/// or to allow functions with a variable number of arguments.
|
||||
///
|
||||
/// Any functions with the same [argument signature] will be overwritten by the one from the new function, `F`.
|
||||
/// For example, if the existing function had the signature `(i32, i32) -> i32`,
|
||||
/// and the new function, `F`, also had the signature `(i32, i32) -> i32`,
|
||||
/// the one from `F` would replace the one from the existing function.
|
||||
///
|
||||
/// Overloaded functions retain the [name] of the original function.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use std::ops::Add;
|
||||
/// # use bevy_reflect::func::{ArgList, IntoFunction};
|
||||
/// #
|
||||
/// fn add<T: Add<Output = T>>(a: T, b: T) -> T {
|
||||
/// a + b
|
||||
/// }
|
||||
///
|
||||
/// // Currently, the only generic type `func` supports is `i32`:
|
||||
/// let mut func = add::<i32>.into_function();
|
||||
///
|
||||
/// // However, we can add an overload to handle `f32` as well:
|
||||
/// func = func.with_overload(add::<f32>);
|
||||
///
|
||||
/// // Test `i32`:
|
||||
/// let args = ArgList::default().push_owned(25_i32).push_owned(75_i32);
|
||||
/// let result = func.call(args).unwrap().unwrap_owned();
|
||||
/// assert_eq!(result.try_take::<i32>().unwrap(), 100);
|
||||
///
|
||||
/// // Test `f32`:
|
||||
/// let args = ArgList::default().push_owned(25.0_f32).push_owned(75.0_f32);
|
||||
/// let result = func.call(args).unwrap().unwrap_owned();
|
||||
/// assert_eq!(result.try_take::<f32>().unwrap(), 100.0);
|
||||
///```
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_reflect::func::{ArgList, IntoFunction};
|
||||
/// #
|
||||
/// fn add_2(a: i32, b: i32) -> i32 {
|
||||
/// a + b
|
||||
/// }
|
||||
///
|
||||
/// fn add_3(a: i32, b: i32, c: i32) -> i32 {
|
||||
/// a + b + c
|
||||
/// }
|
||||
///
|
||||
/// // Currently, `func` only supports two arguments.
|
||||
/// let mut func = add_2.into_function();
|
||||
///
|
||||
/// // However, we can add an overload to handle three arguments as well.
|
||||
/// func = func.with_overload(add_3);
|
||||
///
|
||||
/// // Test two arguments:
|
||||
/// let args = ArgList::default().push_owned(25_i32).push_owned(75_i32);
|
||||
/// let result = func.call(args).unwrap().unwrap_owned();
|
||||
/// assert_eq!(result.try_take::<i32>().unwrap(), 100);
|
||||
///
|
||||
/// // Test three arguments:
|
||||
/// let args = ArgList::default()
|
||||
/// .push_owned(25_i32)
|
||||
/// .push_owned(75_i32)
|
||||
/// .push_owned(100_i32);
|
||||
/// let result = func.call(args).unwrap().unwrap_owned();
|
||||
/// assert_eq!(result.try_take::<i32>().unwrap(), 200);
|
||||
/// ```
|
||||
///
|
||||
/// [argument signature]: ArgumentSignature
|
||||
/// [name]: Self::name
|
||||
pub fn with_overload<'a, F: IntoFunction<'a, Marker>, Marker>(
|
||||
self,
|
||||
function: F,
|
||||
) -> DynamicFunction<'a>
|
||||
where
|
||||
'env: 'a,
|
||||
{
|
||||
let function = function.into_function();
|
||||
|
||||
let name = self.name;
|
||||
let (function_map, info) = merge_function_map(
|
||||
self.function_map,
|
||||
self.info,
|
||||
function.function_map,
|
||||
function.info,
|
||||
);
|
||||
|
||||
DynamicFunction {
|
||||
name,
|
||||
info,
|
||||
function_map,
|
||||
}
|
||||
}
|
||||
|
||||
/// Call the function with the given arguments.
|
||||
///
|
||||
/// # Example
|
||||
|
@ -132,13 +255,26 @@ impl<'env> DynamicFunction<'env> {
|
|||
let expected_arg_count = self.info.arg_count();
|
||||
let received_arg_count = args.len();
|
||||
|
||||
match self.function_map {
|
||||
FunctionMap::Standard(ref func) => {
|
||||
if expected_arg_count != received_arg_count {
|
||||
Err(FunctionError::ArgCountMismatch {
|
||||
expected: expected_arg_count,
|
||||
received: received_arg_count,
|
||||
})
|
||||
} else {
|
||||
(self.func)(args)
|
||||
func(args)
|
||||
}
|
||||
}
|
||||
FunctionMap::Overloaded(ref map) => {
|
||||
let sig = ArgumentSignature::from(&args);
|
||||
let func = map.get(&sig).ok_or_else(|| FunctionError::NoOverload {
|
||||
expected: map.keys().cloned().collect(),
|
||||
received: sig,
|
||||
})?;
|
||||
|
||||
func(args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,8 +292,11 @@ impl<'env> DynamicFunction<'env> {
|
|||
///
|
||||
/// This can be overridden using [`with_name`].
|
||||
///
|
||||
/// If the function was [overloaded], it will retain its original name if it had one.
|
||||
///
|
||||
/// [`DynamicFunctions`]: DynamicFunction
|
||||
/// [`with_name`]: Self::with_name
|
||||
/// [overloaded]: Self::with_overload
|
||||
pub fn name(&self) -> Option<&Cow<'static, str>> {
|
||||
self.name.as_ref()
|
||||
}
|
||||
|
@ -306,7 +445,7 @@ impl<'env> Clone for DynamicFunction<'env> {
|
|||
Self {
|
||||
name: self.name.clone(),
|
||||
info: self.info.clone(),
|
||||
func: Arc::clone(&self.func),
|
||||
function_map: self.function_map.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -329,6 +468,9 @@ impl<'env> IntoFunctionMut<'env, ()> for DynamicFunction<'env> {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::func::{FunctionInfo, IntoReturn};
|
||||
use crate::Type;
|
||||
use bevy_utils::HashSet;
|
||||
use std::ops::Add;
|
||||
|
||||
#[test]
|
||||
fn should_overwrite_function_name() {
|
||||
|
@ -476,4 +618,115 @@ mod tests {
|
|||
let result = func.call(args).unwrap().unwrap_owned();
|
||||
assert_eq!(result.try_take::<f32>().unwrap(), 100.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "called `Result::unwrap()` on an `Err` value: MissingFunctionInfoError"
|
||||
)]
|
||||
fn should_panic_on_missing_function_info() {
|
||||
let _ = DynamicFunction::new(|_| Ok(().into_return()), Vec::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_allow_function_overloading() {
|
||||
fn add<T: Add<Output = T>>(a: T, b: T) -> T {
|
||||
a + b
|
||||
}
|
||||
|
||||
let func = add::<i32>.into_function().with_overload(add::<f32>);
|
||||
|
||||
let args = ArgList::default().push_owned(25_i32).push_owned(75_i32);
|
||||
let result = func.call(args).unwrap().unwrap_owned();
|
||||
assert_eq!(result.try_take::<i32>().unwrap(), 100);
|
||||
|
||||
let args = ArgList::default().push_owned(25.0_f32).push_owned(75.0_f32);
|
||||
let result = func.call(args).unwrap().unwrap_owned();
|
||||
assert_eq!(result.try_take::<f32>().unwrap(), 100.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_allow_variable_arguments_via_overloading() {
|
||||
fn add_2(a: i32, b: i32) -> i32 {
|
||||
a + b
|
||||
}
|
||||
|
||||
fn add_3(a: i32, b: i32, c: i32) -> i32 {
|
||||
a + b + c
|
||||
}
|
||||
|
||||
let func = add_2.into_function().with_overload(add_3);
|
||||
|
||||
let args = ArgList::default().push_owned(25_i32).push_owned(75_i32);
|
||||
let result = func.call(args).unwrap().unwrap_owned();
|
||||
assert_eq!(result.try_take::<i32>().unwrap(), 100);
|
||||
|
||||
let args = ArgList::default()
|
||||
.push_owned(25_i32)
|
||||
.push_owned(75_i32)
|
||||
.push_owned(100_i32);
|
||||
let result = func.call(args).unwrap().unwrap_owned();
|
||||
assert_eq!(result.try_take::<i32>().unwrap(), 200);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_allow_function_overloading_with_manual_overload() {
|
||||
let manual = DynamicFunction::new(
|
||||
|mut args| {
|
||||
let a = args.take_arg()?;
|
||||
let b = args.take_arg()?;
|
||||
|
||||
if a.is::<i32>() {
|
||||
let a = a.take::<i32>()?;
|
||||
let b = b.take::<i32>()?;
|
||||
Ok((a + b).into_return())
|
||||
} else {
|
||||
let a = a.take::<f32>()?;
|
||||
let b = b.take::<f32>()?;
|
||||
Ok((a + b).into_return())
|
||||
}
|
||||
},
|
||||
vec![
|
||||
FunctionInfo::named("add::<i32>")
|
||||
.with_arg::<i32>("a")
|
||||
.with_arg::<i32>("b")
|
||||
.with_return::<i32>(),
|
||||
FunctionInfo::named("add::<f32>")
|
||||
.with_arg::<f32>("a")
|
||||
.with_arg::<f32>("b")
|
||||
.with_return::<f32>(),
|
||||
],
|
||||
);
|
||||
|
||||
let func = manual.with_overload(|a: u32, b: u32| a + b);
|
||||
|
||||
let args = ArgList::default().push_owned(25_i32).push_owned(75_i32);
|
||||
let result = func.call(args).unwrap().unwrap_owned();
|
||||
assert_eq!(result.try_take::<i32>().unwrap(), 100);
|
||||
|
||||
let args = ArgList::default().push_owned(25_u32).push_owned(75_u32);
|
||||
let result = func.call(args).unwrap().unwrap_owned();
|
||||
assert_eq!(result.try_take::<u32>().unwrap(), 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_error_on_unknown_overload() {
|
||||
fn add<T: Add<Output = T>>(a: T, b: T) -> T {
|
||||
a + b
|
||||
}
|
||||
|
||||
let func = add::<i32>.into_function().with_overload(add::<f32>);
|
||||
|
||||
let args = ArgList::default().push_owned(25_u32).push_owned(75_u32);
|
||||
let result = func.call(args);
|
||||
assert_eq!(
|
||||
result.unwrap_err(),
|
||||
FunctionError::NoOverload {
|
||||
expected: HashSet::from([
|
||||
ArgumentSignature::from_iter(vec![Type::of::<i32>(), Type::of::<i32>()]),
|
||||
ArgumentSignature::from_iter(vec![Type::of::<f32>(), Type::of::<f32>()])
|
||||
]),
|
||||
received: ArgumentSignature::from_iter(vec![Type::of::<u32>(), Type::of::<u32>()]),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
use alloc::{borrow::Cow, boxed::Box};
|
||||
use alloc::{borrow::Cow, boxed::Box, sync::Arc};
|
||||
use core::fmt::{Debug, Formatter};
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::{boxed::Box, format, vec};
|
||||
|
||||
use crate::func::{
|
||||
args::ArgList, DynamicFunction, FunctionError, FunctionInfoType, FunctionResult,
|
||||
IntoFunctionMut,
|
||||
args::ArgList,
|
||||
function_map::{merge_function_map, FunctionMap},
|
||||
signature::ArgumentSignature,
|
||||
DynamicFunction, FunctionError, FunctionInfoType, FunctionResult, IntoFunctionMut,
|
||||
};
|
||||
|
||||
/// A [`Box`] containing a callback to a reflected function.
|
||||
type BoxFnMut<'env> = Box<dyn for<'a> FnMut(ArgList<'a>) -> FunctionResult<'a> + 'env>;
|
||||
|
||||
/// A dynamic representation of a function.
|
||||
///
|
||||
/// This type can be used to represent any callable that satisfies [`FnMut`]
|
||||
|
@ -68,7 +73,7 @@ use crate::func::{
|
|||
pub struct DynamicFunctionMut<'env> {
|
||||
name: Option<Cow<'static, str>>,
|
||||
info: FunctionInfoType,
|
||||
func: Box<dyn for<'a> FnMut(ArgList<'a>) -> FunctionResult<'a> + 'env>,
|
||||
function_map: FunctionMap<BoxFnMut<'env>>,
|
||||
}
|
||||
|
||||
impl<'env> DynamicFunctionMut<'env> {
|
||||
|
@ -79,6 +84,7 @@ impl<'env> DynamicFunctionMut<'env> {
|
|||
///
|
||||
/// It's important that the function signature matches the provided [`FunctionInfo`].
|
||||
/// as this will be used to validate arguments when [calling] the function.
|
||||
/// This is also required in order for [function overloading] to work correctly.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
|
@ -86,6 +92,7 @@ impl<'env> DynamicFunctionMut<'env> {
|
|||
///
|
||||
/// [calling]: crate::func::dynamic_function_mut::DynamicFunctionMut::call
|
||||
/// [`FunctionInfo`]: crate::func::FunctionInfo
|
||||
/// [function overloading]: Self::with_overload
|
||||
pub fn new<F: for<'a> FnMut(ArgList<'a>) -> FunctionResult<'a> + 'env>(
|
||||
func: F,
|
||||
info: impl TryInto<FunctionInfoType, Error: Debug>,
|
||||
|
@ -98,7 +105,7 @@ impl<'env> DynamicFunctionMut<'env> {
|
|||
FunctionInfoType::Overloaded(_) => None,
|
||||
},
|
||||
info,
|
||||
func: Box::new(func),
|
||||
function_map: FunctionMap::Standard(Box::new(func)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,6 +122,80 @@ impl<'env> DynamicFunctionMut<'env> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Add an overload to this function.
|
||||
///
|
||||
/// Overloads allow a single [`DynamicFunctionMut`] to represent multiple functions of different signatures.
|
||||
///
|
||||
/// This can be used to handle multiple monomorphizations of a generic function
|
||||
/// or to allow functions with a variable number of arguments.
|
||||
///
|
||||
/// Any functions with the same [argument signature] will be overwritten by the one from the new function, `F`.
|
||||
/// For example, if the existing function had the signature `(i32, i32) -> i32`,
|
||||
/// and the new function, `F`, also had the signature `(i32, i32) -> i32`,
|
||||
/// the one from `F` would replace the one from the existing function.
|
||||
///
|
||||
/// Overloaded functions retain the [name] of the original function.
|
||||
///
|
||||
/// Note that it may be impossible to overload closures that mutably borrow from their environment
|
||||
/// due to Rust's borrowing rules.
|
||||
/// However, it's still possible to overload functions that do not capture their environment mutably,
|
||||
/// or those that maintain mutually exclusive mutable references to their environment.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_reflect::func::IntoFunctionMut;
|
||||
/// let mut total_i32 = 0;
|
||||
/// let mut add_i32 = |a: i32| total_i32 += a;
|
||||
///
|
||||
/// let mut total_f32 = 0.0;
|
||||
/// let mut add_f32 = |a: f32| total_f32 += a;
|
||||
///
|
||||
/// // Currently, the only generic type `func` supports is `i32`.
|
||||
/// let mut func = add_i32.into_function_mut();
|
||||
///
|
||||
/// // However, we can add an overload to handle `f32` as well:
|
||||
/// func = func.with_overload(add_f32);
|
||||
///
|
||||
/// // Test `i32`:
|
||||
/// let args = bevy_reflect::func::ArgList::new().push_owned(123_i32);
|
||||
/// func.call(args).unwrap();
|
||||
///
|
||||
/// // Test `f32`:
|
||||
/// let args = bevy_reflect::func::ArgList::new().push_owned(1.23_f32);
|
||||
/// func.call(args).unwrap();
|
||||
///
|
||||
/// drop(func);
|
||||
/// assert_eq!(total_i32, 123);
|
||||
/// assert_eq!(total_f32, 1.23);
|
||||
/// ```
|
||||
///
|
||||
/// [argument signature]: ArgumentSignature
|
||||
/// [name]: Self::name
|
||||
pub fn with_overload<'a, F: IntoFunctionMut<'a, Marker>, Marker>(
|
||||
self,
|
||||
function: F,
|
||||
) -> DynamicFunctionMut<'a>
|
||||
where
|
||||
'env: 'a,
|
||||
{
|
||||
let function = function.into_function_mut();
|
||||
|
||||
let name = self.name;
|
||||
let (function_map, info) = merge_function_map(
|
||||
self.function_map,
|
||||
self.info,
|
||||
function.function_map,
|
||||
function.info,
|
||||
);
|
||||
|
||||
DynamicFunctionMut {
|
||||
name,
|
||||
info,
|
||||
function_map,
|
||||
}
|
||||
}
|
||||
|
||||
/// Call the function with the given arguments.
|
||||
///
|
||||
/// Variables that are captured mutably by this function
|
||||
|
@ -150,13 +231,28 @@ impl<'env> DynamicFunctionMut<'env> {
|
|||
let expected_arg_count = self.info.arg_count();
|
||||
let received_arg_count = args.len();
|
||||
|
||||
match self.function_map {
|
||||
FunctionMap::Standard(ref mut func) => {
|
||||
if expected_arg_count != received_arg_count {
|
||||
Err(FunctionError::ArgCountMismatch {
|
||||
expected: expected_arg_count,
|
||||
received: received_arg_count,
|
||||
})
|
||||
} else {
|
||||
(self.func)(args)
|
||||
func(args)
|
||||
}
|
||||
}
|
||||
FunctionMap::Overloaded(ref mut map) => {
|
||||
let sig = ArgumentSignature::from(&args);
|
||||
if let Some(func) = map.get_mut(&sig) {
|
||||
func(args)
|
||||
} else {
|
||||
Err(FunctionError::NoOverload {
|
||||
expected: map.keys().cloned().collect(),
|
||||
received: sig,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -189,17 +285,7 @@ impl<'env> DynamicFunctionMut<'env> {
|
|||
///
|
||||
/// The function itself may also return any errors it needs to.
|
||||
pub fn call_once(mut self, args: ArgList) -> FunctionResult {
|
||||
let expected_arg_count = self.info.arg_count();
|
||||
let received_arg_count = args.len();
|
||||
|
||||
if expected_arg_count != received_arg_count {
|
||||
Err(FunctionError::ArgCountMismatch {
|
||||
expected: expected_arg_count,
|
||||
received: received_arg_count,
|
||||
})
|
||||
} else {
|
||||
(self.func)(args)
|
||||
}
|
||||
self.call(args)
|
||||
}
|
||||
|
||||
/// Returns the function info.
|
||||
|
@ -261,11 +347,28 @@ impl<'env> From<DynamicFunction<'env>> for DynamicFunctionMut<'env> {
|
|||
Self {
|
||||
name: function.name,
|
||||
info: function.info,
|
||||
func: Box::new(move |args| (function.func)(args)),
|
||||
function_map: match function.function_map {
|
||||
FunctionMap::Standard(func) => FunctionMap::Standard(arc_to_box(func)),
|
||||
FunctionMap::Overloaded(functions) => FunctionMap::Overloaded(
|
||||
functions
|
||||
.into_iter()
|
||||
.map(|(name, func)| (name, arc_to_box(func)))
|
||||
.collect(),
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function from converting an [`Arc`] function to a [`Box`] function.
|
||||
///
|
||||
/// This is needed to help the compiler infer the correct types.
|
||||
fn arc_to_box<'env>(
|
||||
f: Arc<dyn for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + 'env>,
|
||||
) -> BoxFnMut<'env> {
|
||||
Box::new(move |args| f(args))
|
||||
}
|
||||
|
||||
impl<'env> IntoFunctionMut<'env, ()> for DynamicFunctionMut<'env> {
|
||||
#[inline]
|
||||
fn into_function_mut(self) -> DynamicFunctionMut<'env> {
|
||||
|
@ -277,6 +380,7 @@ impl<'env> IntoFunctionMut<'env, ()> for DynamicFunctionMut<'env> {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::func::{FunctionInfo, IntoReturn};
|
||||
use std::ops::Add;
|
||||
|
||||
#[test]
|
||||
fn should_overwrite_function_name() {
|
||||
|
@ -360,4 +464,25 @@ mod tests {
|
|||
drop(func);
|
||||
assert_eq!(total, 100);
|
||||
}
|
||||
|
||||
// Closures that mutably borrow from their environment cannot realistically
|
||||
// be overloaded since that would break Rust's borrowing rules.
|
||||
// However, we still need to verify overloaded functions work since a
|
||||
// `DynamicFunctionMut` can also be made from a non-mutably borrowing closure/function.
|
||||
#[test]
|
||||
fn should_allow_function_overloading() {
|
||||
fn add<T: Add<Output = T>>(a: T, b: T) -> T {
|
||||
a + b
|
||||
}
|
||||
|
||||
let mut func = add::<i32>.into_function_mut().with_overload(add::<f32>);
|
||||
|
||||
let args = ArgList::default().push_owned(25_i32).push_owned(75_i32);
|
||||
let result = func.call(args).unwrap().unwrap_owned();
|
||||
assert_eq!(result.try_take::<i32>().unwrap(), 100);
|
||||
|
||||
let args = ArgList::default().push_owned(25.0_f32).push_owned(75.0_f32);
|
||||
let result = func.call(args).unwrap().unwrap_owned();
|
||||
assert_eq!(result.try_take::<f32>().unwrap(), 100.0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use crate::func::signature::ArgumentSignature;
|
||||
use crate::func::{args::ArgError, Return};
|
||||
use alloc::borrow::Cow;
|
||||
use bevy_utils::HashSet;
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
|
@ -17,6 +19,12 @@ pub enum FunctionError {
|
|||
/// The number of arguments provided does not match the expected number.
|
||||
#[error("expected {expected} arguments but received {received}")]
|
||||
ArgCountMismatch { expected: usize, received: usize },
|
||||
/// No overload was found for the given set of arguments.
|
||||
#[error("no overload found for arguments with signature `{received:?}`, expected one of `{expected:?}`")]
|
||||
NoOverload {
|
||||
expected: HashSet<ArgumentSignature>,
|
||||
received: ArgumentSignature,
|
||||
},
|
||||
}
|
||||
|
||||
/// The result of calling a [`DynamicFunction`] or [`DynamicFunctionMut`].
|
||||
|
|
102
crates/bevy_reflect/src/func/function_map.rs
Normal file
102
crates/bevy_reflect/src/func/function_map.rs
Normal file
|
@ -0,0 +1,102 @@
|
|||
use crate::func::signature::ArgumentSignature;
|
||||
use crate::func::FunctionInfoType;
|
||||
use bevy_utils::HashMap;
|
||||
|
||||
/// A helper type for storing either a single function or a mapping of overloaded functions.
|
||||
#[derive(Clone)]
|
||||
pub(super) enum FunctionMap<F> {
|
||||
Standard(F),
|
||||
Overloaded(HashMap<ArgumentSignature, F>),
|
||||
}
|
||||
|
||||
/// Merges the given [`FunctionMap`]s and [`FunctionInfoType`]s into a new [`FunctionMap`] and [`FunctionInfoType`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if a [`FunctionMap`]'s corresponding [`FunctionInfoType`] does not match its overload status.
|
||||
pub(super) fn merge_function_map<F>(
|
||||
map_a: FunctionMap<F>,
|
||||
info_a: FunctionInfoType,
|
||||
map_b: FunctionMap<F>,
|
||||
info_b: FunctionInfoType,
|
||||
) -> (FunctionMap<F>, FunctionInfoType) {
|
||||
match (map_a, info_a, map_b, info_b) {
|
||||
(
|
||||
FunctionMap::Standard(old),
|
||||
FunctionInfoType::Standard(info_a),
|
||||
FunctionMap::Standard(new),
|
||||
FunctionInfoType::Standard(info_b),
|
||||
) => {
|
||||
let sig_a = ArgumentSignature::from(&info_a);
|
||||
let sig_b = ArgumentSignature::from(&info_b);
|
||||
|
||||
if sig_a == sig_b {
|
||||
(
|
||||
FunctionMap::Standard(new),
|
||||
FunctionInfoType::Standard(info_b),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
FunctionMap::Overloaded(HashMap::from([(sig_a, old), (sig_b, new)])),
|
||||
FunctionInfoType::Overloaded(Box::new([info_a, info_b])),
|
||||
)
|
||||
}
|
||||
}
|
||||
(
|
||||
FunctionMap::Overloaded(old),
|
||||
FunctionInfoType::Overloaded(info_a),
|
||||
FunctionMap::Standard(new),
|
||||
FunctionInfoType::Standard(info_b),
|
||||
) => {
|
||||
let sig_b = ArgumentSignature::from(&info_b);
|
||||
let mut map = old;
|
||||
map.insert(sig_b, new);
|
||||
|
||||
let mut info = Vec::from_iter(info_a);
|
||||
info.push(info_b);
|
||||
|
||||
(
|
||||
FunctionMap::Overloaded(map),
|
||||
FunctionInfoType::Overloaded(info.into_boxed_slice()),
|
||||
)
|
||||
}
|
||||
(
|
||||
FunctionMap::Standard(old),
|
||||
FunctionInfoType::Standard(info_a),
|
||||
FunctionMap::Overloaded(new),
|
||||
FunctionInfoType::Overloaded(info_b),
|
||||
) => {
|
||||
let sig_a = ArgumentSignature::from(&info_a);
|
||||
let mut map = new;
|
||||
map.insert(sig_a, old);
|
||||
|
||||
let mut info = vec![info_a];
|
||||
info.extend(info_b);
|
||||
|
||||
(
|
||||
FunctionMap::Overloaded(map),
|
||||
FunctionInfoType::Overloaded(info.into_boxed_slice()),
|
||||
)
|
||||
}
|
||||
(
|
||||
FunctionMap::Overloaded(map1),
|
||||
FunctionInfoType::Overloaded(info_a),
|
||||
FunctionMap::Overloaded(map2),
|
||||
FunctionInfoType::Overloaded(info_b),
|
||||
) => {
|
||||
let mut map = map1;
|
||||
map.extend(map2);
|
||||
|
||||
let mut info = Vec::from_iter(info_a);
|
||||
info.extend(info_b);
|
||||
|
||||
(
|
||||
FunctionMap::Overloaded(map),
|
||||
FunctionInfoType::Overloaded(info.into_boxed_slice()),
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
panic!("`FunctionMap` and `FunctionInfoType` mismatch");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -146,6 +146,7 @@ mod dynamic_function;
|
|||
mod dynamic_function_mut;
|
||||
mod error;
|
||||
mod function;
|
||||
mod function_map;
|
||||
mod info;
|
||||
mod into_function;
|
||||
mod into_function_mut;
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
//! arguments and return type.
|
||||
|
||||
use crate::func::args::ArgInfo;
|
||||
use crate::func::FunctionInfo;
|
||||
use crate::func::{ArgList, FunctionInfo};
|
||||
use crate::Type;
|
||||
use core::borrow::Borrow;
|
||||
use core::fmt::{Debug, Formatter};
|
||||
|
@ -107,6 +107,24 @@ impl<T: Borrow<FunctionInfo>> From<T> for ArgumentSignature {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<&ArgList<'_>> for ArgumentSignature {
|
||||
fn from(args: &ArgList) -> Self {
|
||||
Self(
|
||||
args.iter()
|
||||
.map(|arg| {
|
||||
arg.value()
|
||||
.get_represented_type_info()
|
||||
.unwrap_or_else(|| {
|
||||
panic!("no `TypeInfo` found for argument: {:?}", arg);
|
||||
})
|
||||
.ty()
|
||||
})
|
||||
.copied()
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
Loading…
Reference in a new issue