diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/arg_meta.rs b/crates/bevy_mod_scripting_core/src/bindings/function/arg_meta.rs index a8ca08cee4..5fdec4ade0 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/arg_meta.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/arg_meta.rs @@ -2,7 +2,10 @@ use std::{ffi::OsString, path::PathBuf}; -use crate::bindings::{script_value::ScriptValue, ReflectReference}; +use crate::{ + bindings::{script_value::ScriptValue, ReflectReference}, + docgen::typed_through::TypedThrough, +}; use super::{ from::{FromScript, Mut, Ref, Val}, @@ -15,10 +18,18 @@ use super::{ pub trait ScriptArgument: ArgMeta + FromScript + GetTypeDependencies {} impl ScriptArgument for T {} +/// Marker trait for types that can be used as arguments to a script function. And contain type information. +pub trait TypedScriptArgument: TypedThrough + ScriptArgument {} +impl TypedScriptArgument for T {} + /// Marker trait for types that can be used as return values from a script function. pub trait ScriptReturn: IntoScript + GetTypeDependencies {} impl ScriptReturn for T {} +/// Marker trait for types that can be used as return values from a script function. And contain type information. +pub trait TypedScriptReturn: TypedThrough + ScriptReturn {} +impl TypedScriptReturn for T {} + /// Describes an argument to a script function. Provides necessary information for the function to handle dispatch. pub trait ArgMeta { /// The default value for the argument. Used when the argument is not provided. @@ -37,13 +48,29 @@ macro_rules! impl_arg_info { }; } -impl_arg_info!(bool, i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, f32, f64, usize, isize); - -impl_arg_info!(String, PathBuf, OsString); - -impl_arg_info!(char); - -impl_arg_info!(ReflectReference); +impl_arg_info!( + bool, + i8, + i16, + i32, + i64, + i128, + u8, + u16, + u32, + u64, + u128, + f32, + f64, + usize, + isize, + String, + PathBuf, + OsString, + char, + ReflectReference, + &'static str +); impl ArgMeta for Val {} impl ArgMeta for Ref<'_, T> {} diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/mod.rs b/crates/bevy_mod_scripting_core/src/bindings/function/mod.rs index 1f4eb9106a..6c5e08e121 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/mod.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/mod.rs @@ -16,15 +16,18 @@ mod test { use bevy_mod_scripting_derive::script_bindings; use crate::{ - bindings::function::{ - from::{Ref, Val}, - namespace::IntoNamespace, - script_function::AppScriptFunctionRegistry, + bindings::{ + function::{ + from::{Ref, Val}, + namespace::IntoNamespace, + script_function::AppScriptFunctionRegistry, + }, + script_value::ScriptValue, }, - error::InteropError, + docgen::typed_through::TypedThrough, }; - use super::arg_meta::{ScriptArgument, ScriptReturn}; + use super::arg_meta::{ScriptArgument, ScriptReturn, TypedScriptArgument, TypedScriptReturn}; #[test] fn test_macro_generates_correct_registrator_function() { @@ -66,14 +69,14 @@ mod test { assert_eq!(test_fn.info.arg_info[1].name, Some("_arg1".into())); assert_eq!( - test_fn.info.return_info.type_id, + test_fn.info.return_info.as_ref().unwrap().type_id, std::any::TypeId::of::<()>() ); } - fn test_is_valid_return() {} - fn test_is_valid_arg() {} - fn test_is_valid_arg_and_return() {} + fn test_is_valid_return() {} + fn test_is_valid_arg() {} + fn test_is_valid_arg_and_return() {} #[test] fn primitives_are_valid_args() { @@ -92,6 +95,7 @@ mod test { test_is_valid_arg_and_return::(); test_is_valid_arg_and_return::(); test_is_valid_arg_and_return::(); + test_is_valid_arg_and_return::(); } #[test] @@ -100,6 +104,7 @@ mod test { test_is_valid_arg_and_return::(); test_is_valid_arg_and_return::(); test_is_valid_arg_and_return::(); + test_is_valid_return::<&'static str>(); } #[test] @@ -107,7 +112,7 @@ mod test { fn test_val() where T: ScriptArgument + ScriptReturn, - T: GetTypeRegistration + FromReflect, + T: GetTypeRegistration + FromReflect + Typed, { test_is_valid_arg_and_return::>(); } @@ -128,12 +133,10 @@ mod test { test_is_valid_arg::>(); } - test_is_valid_return::(); - fn test_array() where T: ScriptArgument + ScriptReturn, - T: GetTypeRegistration + FromReflect + Typed, + T: GetTypeRegistration + FromReflect + TypedThrough + Typed, for<'a> T::This<'a>: Into, { test_is_valid_arg_and_return::<[T; N]>(); @@ -142,7 +145,7 @@ mod test { fn test_tuple() where T: ScriptArgument + ScriptReturn, - T: GetTypeRegistration + FromReflect + Typed, + T: GetTypeRegistration + FromReflect + TypedThrough + Typed, for<'a> T::This<'a>: Into, { test_is_valid_arg_and_return::<()>(); @@ -154,7 +157,7 @@ mod test { fn test_option() where T: ScriptArgument + ScriptReturn, - T: GetTypeRegistration + FromReflect + Typed, + T: GetTypeRegistration + FromReflect + Typed + TypedThrough, for<'a> T::This<'a>: Into, { test_is_valid_arg_and_return::>(); @@ -163,7 +166,7 @@ mod test { fn test_vec() where T: ScriptArgument + ScriptReturn, - T: GetTypeRegistration + FromReflect + Typed, + T: GetTypeRegistration + FromReflect + Typed + TypedThrough, for<'a> T::This<'a>: Into, { test_is_valid_arg_and_return::>(); @@ -172,7 +175,7 @@ mod test { fn test_hashmap() where V: ScriptArgument + ScriptReturn, - V: GetTypeRegistration + FromReflect + Typed, + V: GetTypeRegistration + FromReflect + Typed + TypedThrough, for<'a> V::This<'a>: Into, { test_is_valid_arg_and_return::>(); diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs b/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs index c2b1dd559c..4c9e42d61b 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs @@ -73,12 +73,6 @@ pub struct DynamicScriptFunction { >, } -impl PartialEq for DynamicScriptFunction { - fn eq(&self, other: &Self) -> bool { - self.info == other.info - } -} - #[derive(Clone, Reflect)] #[reflect(opaque)] /// A dynamic mutable script function. @@ -96,12 +90,6 @@ pub struct DynamicScriptFunctionMut { >, } -impl PartialEq for DynamicScriptFunctionMut { - fn eq(&self, other: &Self) -> bool { - self.info == other.info - } -} - impl DynamicScriptFunction { /// Call the function with the given arguments and caller context. /// @@ -215,6 +203,18 @@ impl DynamicScriptFunctionMut { } } +impl PartialEq for DynamicScriptFunction { + fn eq(&self, other: &Self) -> bool { + std::ptr::addr_eq(self as *const Self, other as *const Self) + } +} + +impl PartialEq for DynamicScriptFunctionMut { + fn eq(&self, other: &Self) -> bool { + std::ptr::addr_eq(self as *const Self, other as *const Self) + } +} + impl std::fmt::Debug for DynamicScriptFunction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("DynamicScriptFunction") diff --git a/crates/bevy_mod_scripting_core/src/docgen/info.rs b/crates/bevy_mod_scripting_core/src/docgen/info.rs index 82f4ffaea1..b22b460770 100644 --- a/crates/bevy_mod_scripting_core/src/docgen/info.rs +++ b/crates/bevy_mod_scripting_core/src/docgen/info.rs @@ -1,18 +1,19 @@ //! Information about functions and their arguments. -use bevy::reflect::Reflect; - use crate::bindings::function::arg_meta::ArgMeta; use crate::bindings::function::namespace::Namespace; +use bevy::reflect::Reflect; use std::{any::TypeId, borrow::Cow}; +use super::typed_through::{ThroughTypeInfo, TypedThrough}; + /// for things you can call and provide some introspection capability. pub trait GetFunctionInfo { /// Get the function info for the function. fn get_function_info(&self, name: Cow<'static, str>, namespace: Namespace) -> FunctionInfo; } -#[derive(Debug, Clone, PartialEq, Reflect)] +#[derive(Debug, Clone, Reflect)] /// Information about a function. pub struct FunctionInfo { /// The name of the function. @@ -22,7 +23,7 @@ pub struct FunctionInfo { /// Information about the arguments of the function. pub arg_info: Vec, /// Information about the return value of the function. - pub return_info: FunctionReturnInfo, + pub return_info: Option, /// Documentation for the function. pub docs: Option>, } @@ -40,7 +41,7 @@ impl FunctionInfo { name: Cow::Borrowed(""), namespace: Namespace::Global, arg_info: Vec::new(), - return_info: FunctionReturnInfo::new(), + return_info: None, docs: None, } } @@ -51,25 +52,24 @@ impl FunctionInfo { name, namespace, arg_info: Vec::new(), - return_info: FunctionReturnInfo::new(), + return_info: None, docs: None, } } /// Add an argument to the function info. - pub fn add_arg(mut self, name: Option>) -> Self { - self.arg_info.push(FunctionArgInfo { - name, - arg_index: self.arg_info.len(), - type_id: TypeId::of::(), - docs: None, - }); + pub fn add_arg( + mut self, + name: Option>, + ) -> Self { + self.arg_info + .push(FunctionArgInfo::for_type::(name, self.arg_info.len())); self } /// Add a return value to the function info. - pub fn add_return(mut self, return_info: FunctionReturnInfo) -> Self { - self.return_info = return_info; + pub fn add_return(mut self) -> Self { + self.return_info = Some(FunctionReturnInfo::new_for::()); self } @@ -94,7 +94,7 @@ impl FunctionInfo { } } -#[derive(Debug, Clone, PartialEq, Reflect)] +#[derive(Debug, Clone, Reflect)] /// Information about a function argument. pub struct FunctionArgInfo { /// The name of the argument. @@ -103,31 +103,29 @@ pub struct FunctionArgInfo { pub arg_index: usize, /// The type of the argument. pub type_id: TypeId, - /// Documentation for the argument. - pub docs: Option>, + /// The type information of the argument. + #[reflect(ignore)] + pub type_info: Option, } impl FunctionArgInfo { - /// Create a new function argument info with default values. - pub fn new(arg_index: usize, type_id: TypeId) -> Self { - Self { - name: None, - arg_index, - type_id, - docs: None, - } - } - /// Create a new function argument info with a name. pub fn with_name(mut self, name: Cow<'static, str>) -> Self { self.name = Some(name); self } - /// Add documentation to the function argument info. - pub fn with_docs(mut self, docs: Cow<'static, str>) -> Self { - self.docs = Some(docs); - self + /// Create a new function argument info for a specific type. + pub fn for_type( + name: Option>>, + arg_index: usize, + ) -> Self { + Self { + name: name.map(Into::into), + arg_index, + type_id: TypeId::of::(), + type_info: Some(T::through_type_info()), + } } } @@ -138,20 +136,7 @@ pub struct FunctionReturnInfo { pub type_id: TypeId, } -impl Default for FunctionReturnInfo { - fn default() -> Self { - Self::new() - } -} - impl FunctionReturnInfo { - /// Create a new function return info with default values. - pub fn new() -> Self { - Self { - type_id: TypeId::of::<()>(), - } - } - /// Create a new function return info for a specific type. pub fn new_for() -> Self { Self { @@ -165,8 +150,8 @@ macro_rules! impl_documentable { impl<$($param,)* F, O> GetFunctionInfo O> for F where F: Fn($($param),*) -> O, - $($param: ArgMeta + 'static,)* - O: 'static + $($param: ArgMeta + TypedThrough + 'static,)* + O: TypedThrough + 'static { fn get_function_info(&self, name: Cow<'static, str>, namespace: Namespace) -> FunctionInfo { #[allow(unused_mut)] @@ -174,7 +159,7 @@ macro_rules! impl_documentable { $( info = info.add_arg::<$param>(None); )* - info.add_return(FunctionReturnInfo::new_for::()) + info.add_return::() } } }; @@ -198,10 +183,24 @@ mod test { assert_eq!(info.name, "test_fn"); assert_eq!(info.namespace, Namespace::Global); assert_eq!(info.arg_info.len(), 2); - assert_eq!(info.return_info.type_id, TypeId::of::()); + assert_eq!(info.return_info.unwrap().type_id, TypeId::of::()); assert_eq!(info.arg_info[0].type_id, TypeId::of::()); assert_eq!(info.arg_info[1].type_id, TypeId::of::()); + + match info.arg_info[0].type_info.as_ref().unwrap() { + ThroughTypeInfo::TypeInfo(type_info) => { + assert_eq!(type_info.type_id(), TypeId::of::()); + } + _ => panic!("Expected TypeInfo"), + } + + match info.arg_info[1].type_info.as_ref().unwrap() { + ThroughTypeInfo::TypeInfo(type_info) => { + assert_eq!(type_info.type_id(), TypeId::of::()); + } + _ => panic!("Expected TypeInfo"), + } } #[test] @@ -212,9 +211,35 @@ mod test { assert_eq!(info.name, "test_fn"); assert_eq!(info.namespace, Namespace::Global); assert_eq!(info.arg_info.len(), 2); - assert_eq!(info.return_info.type_id, TypeId::of::>()); + assert_eq!(info.return_info.unwrap().type_id, TypeId::of::>()); assert_eq!(info.arg_info[0].type_id, TypeId::of::>()); assert_eq!(info.arg_info[1].type_id, TypeId::of::>()); + + match info.arg_info[0].type_info.as_ref().unwrap() { + ThroughTypeInfo::UntypedWrapper { + through_type, + wrapper_type_id, + wrapper_name, + } => { + assert_eq!(through_type.type_id(), TypeId::of::()); + assert_eq!(*wrapper_type_id, TypeId::of::>()); + assert_eq!(*wrapper_name, "Ref"); + } + _ => panic!("Expected UntypedWrapper"), + } + + match info.arg_info[1].type_info.as_ref().unwrap() { + ThroughTypeInfo::UntypedWrapper { + through_type, + wrapper_type_id, + wrapper_name, + } => { + assert_eq!(through_type.type_id(), TypeId::of::()); + assert_eq!(*wrapper_type_id, TypeId::of::>()); + assert_eq!(*wrapper_name, "Mut"); + } + _ => panic!("Expected UntypedWrapper"), + } } } diff --git a/crates/bevy_mod_scripting_core/src/docgen/mod.rs b/crates/bevy_mod_scripting_core/src/docgen/mod.rs index 38cc7be64f..e74e969692 100644 --- a/crates/bevy_mod_scripting_core/src/docgen/mod.rs +++ b/crates/bevy_mod_scripting_core/src/docgen/mod.rs @@ -1,2 +1,3 @@ //! Documentation generation for scripting languages. pub mod info; +pub mod typed_through; diff --git a/crates/bevy_mod_scripting_core/src/docgen/typed_through.rs b/crates/bevy_mod_scripting_core/src/docgen/typed_through.rs new file mode 100644 index 0000000000..3efb4f1155 --- /dev/null +++ b/crates/bevy_mod_scripting_core/src/docgen/typed_through.rs @@ -0,0 +1,262 @@ +//! Defines a set of traits which destruture [`bevy::reflect::TypeInfo`] and implement a light weight wrapper around it, to allow types +//! which normally can't implement [`bevy::reflect::Typed`] to be used in a reflection context. + +use std::{any::TypeId, ffi::OsString, path::PathBuf}; + +use bevy::reflect::{TypeInfo, Typed}; + +use crate::{ + bindings::{ + function::{ + from::{Mut, Ref, Val}, + script_function::{ + DynamicScriptFunction, DynamicScriptFunctionMut, FunctionCallContext, + }, + }, + script_value::ScriptValue, + ReflectReference, + }, + error::InteropError, +}; + +/// All Through types follow one rule: +/// - A through type can not contain a nested through type. It must always contain a fully typed inner type. +/// +/// This means that: +/// - `Ref>` is not allowed, but `Ref` is. +/// +/// i.e. `Ref`, `Mut` and `Val` wrappers are `leaf` types, and can not contain other `leaf` types. +/// +/// This is to keep the implementations of this trait simple, and to ultimately depend on the `TypeInfo` trait for the actual type information. +#[derive(Debug, Clone)] +pub enum ThroughTypeInfo { + /// A wrapper around a typed type, which itself is not a `Typed` type. + UntypedWrapper { + /// The type information of the inner type. + through_type: &'static TypeInfo, + /// The type id of the wrapper type. + wrapper_type_id: TypeId, + /// The name of the wrapper type. + wrapper_name: &'static str, + }, + /// A wrapper around a through typed type, which itself is also a `Typed` type. + TypedWrapper(TypedWrapperKind), + /// an actual type info + TypeInfo(&'static TypeInfo), +} + +/// The kind of typed wrapper. +#[derive(Debug, Clone)] +pub enum TypedWrapperKind { + /// Wraps a `Vec` of a through typed type. + Vec(Box), + /// Wraps a `HashMap` of a through typed type. + HashMap(Box, Box), + /// Wraps a `Result` of a through typed type. + Array(Box, usize), + /// Wraps an `Option` of a through typed type. + Option(Box), + /// Wraps a `Result` of a through typed type. + InteropResult(Box), + /// Wraps a tuple of through typed types. + Tuple(Vec), +} + +/// A trait for types that can be converted to a [`ThroughTypeInfo`]. +pub trait TypedThrough { + /// Get the [`ThroughTypeInfo`] for the type. + fn through_type_info() -> ThroughTypeInfo; +} + +impl TypedThrough for Ref<'_, T> { + fn through_type_info() -> ThroughTypeInfo { + ThroughTypeInfo::UntypedWrapper { + through_type: T::type_info(), + wrapper_type_id: TypeId::of::>(), + wrapper_name: "Ref", + } + } +} + +impl TypedThrough for Mut<'_, T> { + fn through_type_info() -> ThroughTypeInfo { + ThroughTypeInfo::UntypedWrapper { + through_type: T::type_info(), + wrapper_type_id: TypeId::of::>(), + wrapper_name: "Mut", + } + } +} + +impl TypedThrough for Val { + fn through_type_info() -> ThroughTypeInfo { + ThroughTypeInfo::UntypedWrapper { + through_type: T::type_info(), + wrapper_type_id: TypeId::of::>(), + wrapper_name: "Val", + } + } +} + +impl TypedThrough for Vec { + fn through_type_info() -> ThroughTypeInfo { + ThroughTypeInfo::TypedWrapper(TypedWrapperKind::Vec(Box::new(T::through_type_info()))) + } +} + +impl TypedThrough for std::collections::HashMap { + fn through_type_info() -> ThroughTypeInfo { + ThroughTypeInfo::TypedWrapper(TypedWrapperKind::HashMap( + Box::new(K::through_type_info()), + Box::new(V::through_type_info()), + )) + } +} + +impl TypedThrough for Result { + fn through_type_info() -> ThroughTypeInfo { + ThroughTypeInfo::TypedWrapper(TypedWrapperKind::InteropResult(Box::new( + T::through_type_info(), + ))) + } +} + +impl TypedThrough for [T; N] { + fn through_type_info() -> ThroughTypeInfo { + ThroughTypeInfo::TypedWrapper(TypedWrapperKind::Array(Box::new(T::through_type_info()), N)) + } +} + +impl TypedThrough for Option { + fn through_type_info() -> ThroughTypeInfo { + ThroughTypeInfo::TypedWrapper(TypedWrapperKind::Option(Box::new(T::through_type_info()))) + } +} + +macro_rules! impl_through_typed { + ($($ty:ty),*) => { + $( + impl $crate::docgen::typed_through::TypedThrough for $ty { + fn through_type_info() -> $crate::docgen::typed_through::ThroughTypeInfo { + $crate::docgen::typed_through::ThroughTypeInfo::TypeInfo(<$ty as bevy::reflect::Typed>::type_info()) + } + } + )* + }; +} + +impl_through_typed!( + FunctionCallContext, + ReflectReference, + DynamicScriptFunctionMut, + DynamicScriptFunction, + ScriptValue, + bool, + i8, + i16, + i32, + i64, + i128, + u8, + u16, + u32, + u64, + u128, + f32, + f64, + usize, + isize, + String, + PathBuf, + OsString, + char, + &'static str +); + +macro_rules! impl_through_typed_tuple { + ($($ty:ident),*) => { + impl<$($ty: TypedThrough),*> TypedThrough for ($($ty,)*) { + fn through_type_info() -> ThroughTypeInfo { + ThroughTypeInfo::TypedWrapper(TypedWrapperKind::Tuple(vec![$($ty::through_type_info()),*])) + } + } + }; +} + +bevy::utils::all_tuples!(impl_through_typed_tuple, 0, 13, T); + +#[cfg(test)] +mod test { + use super::*; + + fn assert_type_info_is_through() { + let type_info = T::type_info(); + let through_type_info = T::through_type_info(); + + match through_type_info { + ThroughTypeInfo::TypeInfo(info) => { + assert_eq!(info.type_id(), type_info.type_id()); + assert_eq!(info.type_path(), type_info.type_path()); + } + _ => panic!("Expected ThroughTypeInfo::TypeInfo"), + } + } + + #[test] + fn test_typed_through_primitives() { + assert_type_info_is_through::(); + assert_type_info_is_through::(); + assert_type_info_is_through::(); + assert_type_info_is_through::(); + assert_type_info_is_through::(); + assert_type_info_is_through::(); + assert_type_info_is_through::(); + assert_type_info_is_through::(); + assert_type_info_is_through::(); + assert_type_info_is_through::(); + assert_type_info_is_through::(); + assert_type_info_is_through::(); + assert_type_info_is_through::(); + assert_type_info_is_through::(); + assert_type_info_is_through::(); + assert_type_info_is_through::(); + assert_type_info_is_through::(); + assert_type_info_is_through::(); + assert_type_info_is_through::(); + assert_type_info_is_through::(); + assert_type_info_is_through::<&'static str>(); + } + + #[test] + fn test_typed_wrapper_outer_variant_matches() { + assert!(matches!( + Vec::::through_type_info(), + ThroughTypeInfo::TypedWrapper(TypedWrapperKind::Vec(..)) + )); + + assert!(matches!( + std::collections::HashMap::::through_type_info(), + ThroughTypeInfo::TypedWrapper(TypedWrapperKind::HashMap(..)) + )); + + assert!(matches!( + Result::::through_type_info(), + ThroughTypeInfo::TypedWrapper(TypedWrapperKind::InteropResult(..)) + )); + + assert!(matches!( + <[i32; 3]>::through_type_info(), + ThroughTypeInfo::TypedWrapper(TypedWrapperKind::Array(..)) + )); + + assert!(matches!( + Option::::through_type_info(), + ThroughTypeInfo::TypedWrapper(TypedWrapperKind::Option(..)) + )); + + assert!(matches!( + <(i32, f32)>::through_type_info(), + ThroughTypeInfo::TypedWrapper(TypedWrapperKind::Tuple(..)) + )); + } +}