From 5656c384bfe35ce6eeb5120af74fed56b6059f8b Mon Sep 17 00:00:00 2001 From: makspll Date: Mon, 27 Jan 2025 22:51:01 +0000 Subject: [PATCH] add function introspection methods --- crates/bevy_mod_scripting_core/Cargo.toml | 1 + .../src/bindings/function/arg_info.rs | 64 ----- .../src/bindings/function/arg_meta.rs | 95 ++++++++ .../src/bindings/function/from.rs | 24 ++ .../src/bindings/function/into.rs | 12 +- .../src/bindings/function/into_ref.rs | 9 +- .../src/bindings/function/mod.rs | 134 ++++++++++- .../src/bindings/function/namespace.rs | 17 +- .../src/bindings/function/script_function.rs | 221 ++++++------------ .../bindings/function/type_dependencies.rs | 107 +++++++++ .../src/bindings/pretty_print.rs | 90 ++++++- .../src/bindings/script_value.rs | 11 +- .../src/bindings/world.rs | 34 ++- .../src/docgen/info.rs | 180 ++++++++++++++ .../bevy_mod_scripting_core/src/docgen/mod.rs | 1 + crates/bevy_mod_scripting_core/src/lib.rs | 1 + .../bevy_mod_scripting_functions/src/core.rs | 10 + .../src/bindings/script_value.rs | 13 +- .../contains_reflect_reference_functions.lua | 22 ++ .../src/bindings/script_value.rs | 70 +----- .../contains_reflect_reference_functions.rhai | 14 ++ 21 files changed, 843 insertions(+), 287 deletions(-) delete mode 100644 crates/bevy_mod_scripting_core/src/bindings/function/arg_info.rs create mode 100644 crates/bevy_mod_scripting_core/src/bindings/function/arg_meta.rs create mode 100644 crates/bevy_mod_scripting_core/src/bindings/function/type_dependencies.rs create mode 100644 crates/bevy_mod_scripting_core/src/docgen/info.rs create mode 100644 crates/bevy_mod_scripting_core/src/docgen/mod.rs create mode 100644 crates/languages/bevy_mod_scripting_lua/tests/data/functions/contains_reflect_reference_functions.lua create mode 100644 crates/languages/bevy_mod_scripting_rhai/tests/data/functions/contains_reflect_reference_functions.rhai diff --git a/crates/bevy_mod_scripting_core/Cargo.toml b/crates/bevy_mod_scripting_core/Cargo.toml index faba9e751e..be04972629 100644 --- a/crates/bevy_mod_scripting_core/Cargo.toml +++ b/crates/bevy_mod_scripting_core/Cargo.toml @@ -39,6 +39,7 @@ parking_lot = "0.12.1" dashmap = "6" smallvec = "1.11" itertools = "0.13" +derivative = "2.2" [dev-dependencies] test_utils = { workspace = true } diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/arg_info.rs b/crates/bevy_mod_scripting_core/src/bindings/function/arg_info.rs deleted file mode 100644 index a7bb28a90b..0000000000 --- a/crates/bevy_mod_scripting_core/src/bindings/function/arg_info.rs +++ /dev/null @@ -1,64 +0,0 @@ -//! Trait implementations to help with function dispatch. - -use std::{ffi::OsString, path::PathBuf}; - -use crate::bindings::{script_value::ScriptValue, ReflectReference}; - -use super::{ - from::{FromScript, Mut, Ref, Val}, - into::IntoScript, - script_function::{DynamicScriptFunction, DynamicScriptFunctionMut, GetInnerTypeDependencies}, -}; - -/// Marker trait for types that can be used as arguments to a script function. -pub trait ScriptArgument: ArgInfo + FromScript + GetInnerTypeDependencies {} -impl ScriptArgument for T {} - -/// Marker trait for types that can be used as return values from a script function. -pub trait ScriptReturn: IntoScript + GetInnerTypeDependencies {} - -/// Describes an argument to a script function. Provides necessary information for the function to handle dispatch. -pub trait ArgInfo { - fn default_value() -> Option { - None - } -} - -impl ArgInfo for ScriptValue {} - -impl ArgInfo for () { - fn default_value() -> Option { - Some(ScriptValue::Unit) - } -} - -macro_rules! impl_arg_info { - ($($ty:ty),*) => { - $( - impl ArgInfo for $ty {} - )* - }; -} - -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 ArgInfo for Val {} -impl ArgInfo for Ref<'_, T> {} -impl ArgInfo for Mut<'_, T> {} - -impl ArgInfo for Option { - fn default_value() -> Option { - Some(ScriptValue::Unit) - } -} - -impl ArgInfo for Vec {} -impl ArgInfo for [T; N] {} - -impl_arg_info!(DynamicScriptFunction, DynamicScriptFunctionMut); 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 new file mode 100644 index 0000000000..a5cee5abe8 --- /dev/null +++ b/crates/bevy_mod_scripting_core/src/bindings/function/arg_meta.rs @@ -0,0 +1,95 @@ +//! Trait implementations to help with function dispatch. + +use std::{ffi::OsString, path::PathBuf}; + +use crate::bindings::{script_value::ScriptValue, ReflectReference}; + +use super::{ + from::{FromScript, Mut, Ref, Val}, + into::IntoScript, + script_function::{DynamicScriptFunction, DynamicScriptFunctionMut, FunctionCallContext}, + type_dependencies::GetTypeDependencies, +}; + +/// Marker trait for types that can be used as arguments to a script function. +pub trait ScriptArgument: ArgMeta + FromScript + GetTypeDependencies {} +impl ScriptArgument 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 {} + +/// Describes an argument to a script function. Provides necessary information for the function to handle dispatch. +pub trait ArgMeta { + fn default_value() -> Option { + None + } +} + +impl ArgMeta for ScriptValue {} + +macro_rules! impl_arg_info { + ($($ty:ty),*) => { + $( + impl ArgMeta for $ty {} + )* + }; +} + +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 ArgMeta for Val {} +impl ArgMeta for Ref<'_, T> {} +impl ArgMeta for Mut<'_, T> {} + +impl ArgMeta for Option { + fn default_value() -> Option { + Some(ScriptValue::Unit) + } +} + +impl ArgMeta for Vec {} +impl ArgMeta for [T; N] {} + +impl ArgMeta for std::collections::HashMap {} + +impl_arg_info!(DynamicScriptFunction, DynamicScriptFunctionMut); + +impl ArgMeta for () { + fn default_value() -> Option { + Some(ScriptValue::Unit) + } +} +impl ArgMeta for (T,) {} +impl ArgMeta for (T1, T2) {} +impl ArgMeta for (T1, T2, T3) {} +impl ArgMeta for (T1, T2, T3, T4) {} +impl ArgMeta for (T1, T2, T3, T4, T5) {} +impl ArgMeta for (T1, T2, T3, T4, T5, T6) {} +impl ArgMeta for (T1, T2, T3, T4, T5, T6, T7) {} +impl ArgMeta for (T1, T2, T3, T4, T5, T6, T7, T8) {} +impl ArgMeta for (T1, T2, T3, T4, T5, T6, T7, T8, T9) {} +impl ArgMeta + for (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) +{ +} +impl ArgMeta + for (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) +{ +} +impl ArgMeta + for (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) +{ +} +impl ArgMeta + for (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) +{ +} + +impl ArgMeta for FunctionCallContext {} diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/from.rs b/crates/bevy_mod_scripting_core/src/bindings/function/from.rs index 2cf79ce2ad..254beed275 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/from.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/from.rs @@ -421,3 +421,27 @@ impl FromScript for DynamicScriptFunction { } } } + +impl FromScript for std::collections::HashMap +where + V: FromScript + 'static, + for<'w> V::This<'w>: Into, +{ + type This<'w> = Self; + + fn from_script(value: ScriptValue, world: WorldGuard) -> Result { + match value { + ScriptValue::Map(map) => { + let mut hashmap = std::collections::HashMap::new(); + for (key, value) in map { + hashmap.insert(key, V::from_script(value, world.clone())?.into()); + } + Ok(hashmap) + } + _ => Err(InteropError::value_mismatch( + std::any::TypeId::of::>(), + value, + )), + } + } +} diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/into.rs b/crates/bevy_mod_scripting_core/src/bindings/function/into.rs index 4ec5852198..fac522c515 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/into.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/into.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, ffi::OsString, path::PathBuf}; +use std::{borrow::Cow, collections::HashMap, ffi::OsString, path::PathBuf}; use bevy::reflect::Reflect; @@ -153,6 +153,16 @@ impl IntoScript for [T; N] { } } +impl IntoScript for HashMap { + fn into_script(self, world: WorldGuard) -> Result { + let mut map = HashMap::new(); + for (key, value) in self { + map.insert(key, value.into_script(world.clone())?); + } + Ok(ScriptValue::Map(map)) + } +} + impl IntoScript for InteropError { fn into_script(self, _world: WorldGuard) -> Result { Ok(ScriptValue::Error(self)) diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/into_ref.rs b/crates/bevy_mod_scripting_core/src/bindings/function/into_ref.rs index f95635a55a..a9358da882 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/into_ref.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/into_ref.rs @@ -1,4 +1,4 @@ -use std::{ffi::OsString, path::PathBuf}; +use std::{borrow::Cow, ffi::OsString, path::PathBuf}; use bevy::reflect::{Access, PartialReflect}; @@ -90,6 +90,13 @@ fn into_script_ref( to : bool => return downcast_into_value!(r, bool).into_script(world), tp : char => return downcast_into_value!(r, char).into_script(world), tq : String => return downcast_into_value!(r, String).clone().into_script(world), + tcs: Cow<'static, str> => match r.try_downcast_ref::>() { + Some(cow) => return Ok(ScriptValue::String(cow.clone())), + None => return Err(InteropError::type_mismatch( + std::any::TypeId::of::>(), + r.get_represented_type_info().map(|i| i.type_id()), + )), + }, tr : PathBuf => return downcast_into_value!(r, PathBuf).clone().into_script(world), ts : OsString=> return downcast_into_value!(r, OsString).clone().into_script(world), tn : () => return Ok(ScriptValue::Unit) 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 50f53baa52..5046157a0f 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/mod.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/mod.rs @@ -1,7 +1,139 @@ -pub mod arg_info; +pub mod arg_meta; pub mod from; pub mod from_ref; pub mod into; pub mod into_ref; pub mod namespace; pub mod script_function; +pub mod type_dependencies; + +#[cfg(test)] +#[allow(dead_code)] +mod test { + use bevy::reflect::{FromReflect, GetTypeRegistration, Typed}; + + use crate::{ + bindings::function::from::{Ref, Val}, + error::InteropError, + }; + + use super::arg_meta::{ScriptArgument, ScriptReturn}; + + fn test_is_valid_return() {} + fn test_is_valid_arg() {} + fn test_is_valid_arg_and_return() {} + + #[test] + fn primitives_are_valid_args() { + 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_is_valid_arg_and_return::(); + 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_is_valid_arg_and_return::(); + 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_is_valid_arg_and_return::(); + } + + #[test] + fn strings_are_valid_args() { + 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] + fn composites_are_valid_args() { + fn test_val() + where + T: ScriptArgument + ScriptReturn, + T: GetTypeRegistration + FromReflect, + { + test_is_valid_arg_and_return::>(); + } + + fn test_ref() + where + T: ScriptArgument, + T: GetTypeRegistration + FromReflect + Typed, + { + test_is_valid_arg::>(); + } + + fn test_mut() + where + T: ScriptArgument, + T: GetTypeRegistration + FromReflect + Typed, + { + test_is_valid_arg::>(); + } + + test_is_valid_return::(); + + fn test_array() + where + T: ScriptArgument + ScriptReturn, + T: GetTypeRegistration + FromReflect + Typed, + for<'a> T::This<'a>: Into, + { + test_is_valid_arg_and_return::<[T; N]>(); + } + + fn test_tuple() + where + T: ScriptArgument + ScriptReturn, + T: GetTypeRegistration + FromReflect + Typed, + for<'a> T::This<'a>: Into, + { + test_is_valid_arg_and_return::<()>(); + test_is_valid_return::<(T,)>(); + test_is_valid_return::<(T, T)>(); + test_is_valid_return::<(T, T, T, T, T, T, T, T, T, T)>(); + } + + fn test_option() + where + T: ScriptArgument + ScriptReturn, + T: GetTypeRegistration + FromReflect + Typed, + for<'a> T::This<'a>: Into, + { + test_is_valid_arg_and_return::>(); + } + + fn test_vec() + where + T: ScriptArgument + ScriptReturn, + T: GetTypeRegistration + FromReflect + Typed, + for<'a> T::This<'a>: Into, + { + test_is_valid_arg_and_return::>(); + } + + fn test_hashmap() + where + V: ScriptArgument + ScriptReturn, + V: GetTypeRegistration + FromReflect + Typed, + for<'a> V::This<'a>: Into, + { + test_is_valid_arg_and_return::>(); + } + } + + #[test] + fn test_dynamic_functions() { + test_is_valid_arg_and_return::< + crate::bindings::function::script_function::DynamicScriptFunction, + >(); + test_is_valid_arg_and_return::< + crate::bindings::function::script_function::DynamicScriptFunctionMut, + >(); + } +} diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/namespace.rs b/crates/bevy_mod_scripting_core/src/bindings/function/namespace.rs index 66288ffa00..44eaf649f9 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/namespace.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/namespace.rs @@ -1,12 +1,17 @@ -use crate::bindings::function::script_function::{ - AppScriptFunctionRegistry, DynamicScriptFunction, GetFunctionTypeDependencies, ScriptFunction, +use crate::{ + bindings::function::script_function::{ + AppScriptFunctionRegistry, DynamicScriptFunction, ScriptFunction, + }, + docgen::info::GetFunctionInfo, }; use bevy::{ prelude::{AppTypeRegistry, World}, - reflect::GetTypeRegistration, + reflect::{GetTypeRegistration, Reflect}, }; use std::{any::TypeId, borrow::Cow, marker::PhantomData}; +use super::type_dependencies::GetFunctionTypeDependencies; + pub trait RegisterNamespacedFunction { fn register_namespaced_function(&mut self, name: N, function: F) where @@ -52,7 +57,7 @@ pub trait GetNamespacedFunction { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Reflect)] pub enum Namespace { /// The function is registered in the global namespace, i.e. with no namespace. /// In practice functions in this namespace should be callable directly by their name, i.e. `my_function()` @@ -128,10 +133,10 @@ impl<'a, S: IntoNamespace> NamespaceBuilder<'a, S> { } } - pub fn register(&mut self, name: N, function: F) -> &mut Self + pub fn register<'env, N, F, M>(&mut self, name: N, function: F) -> &mut Self where N: Into>, - F: ScriptFunction<'static, M> + GetFunctionTypeDependencies, + F: ScriptFunction<'env, M> + GetFunctionTypeDependencies + GetFunctionInfo, { { { 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 e0155bbe1d..52d6e4bab9 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 @@ -1,16 +1,14 @@ use super::{from::FromScript, into::IntoScript, namespace::Namespace}; -use crate::bindings::function::arg_info::ArgInfo; +use crate::bindings::function::arg_meta::ArgMeta; +use crate::docgen::info::{FunctionInfo, GetFunctionInfo}; use crate::{ - bindings::{ - function::from::{Mut, Ref, Val}, - ReflectReference, ThreadWorldContainer, WorldContainer, WorldGuard, - }, + bindings::{ThreadWorldContainer, WorldContainer, WorldGuard}, error::InteropError, ScriptValue, }; use bevy::{ prelude::{Reflect, Resource}, - reflect::{func::FunctionError, FromReflect, GetTypeRegistration, TypeRegistry, Typed}, + reflect::func::FunctionError, }; use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::borrow::Cow; @@ -28,91 +26,13 @@ pub trait ScriptFunction<'env, Marker> { } #[diagnostic::on_unimplemented( - message = "Only functions with all arguments impplementing FromScript and return values supporting IntoScript are supported. Registering functions also requires they implement GetInnerTypeDependencies", + message = "Only functions with all arguments impplementing FromScript and return values supporting IntoScript are supported. Registering functions also requires they implement GetTypeDependencies", note = "If you're trying to return a non-primitive type, you might need to use Val Ref or Mut wrappers" )] pub trait ScriptFunctionMut<'env, Marker> { fn into_dynamic_script_function_mut(self) -> DynamicScriptFunctionMut; } -/// Functionally identical to [`GetTypeRegistration`] but without the 'static bound -pub trait GetInnerTypeDependencies { - fn register_type_dependencies(registry: &mut TypeRegistry); -} - -#[macro_export] -macro_rules! no_type_dependencies { - ($($path:path),*) => { - $( - impl $crate::bindings::function::script_function::GetInnerTypeDependencies for $path { - fn register_type_dependencies(_registry: &mut bevy::reflect::TypeRegistry) {} - } - )* - }; -} - -#[macro_export] -macro_rules! self_type_dependency_only { - ($($path:ty),*) => { - $( - impl $crate::bindings::function::script_function::GetInnerTypeDependencies for $path { - fn register_type_dependencies(registry: &mut bevy::reflect::TypeRegistry) { - registry.register::<$path>(); - } - } - )* - }; -} - -macro_rules! recursive_type_dependencies { - ($( ($path:ty where $($bound:ident : $($bound_val:path);*),* $(,,const $const:ident : $const_ty:ty)? $(=> with $self_:ident)?) ),* ) => { - $( - impl<$($bound : $($bound_val +)*),* , $(const $const : $const_ty )?> GetInnerTypeDependencies for $path { - fn register_type_dependencies(registry: &mut TypeRegistry) { - $( - registry.register::<$bound>(); - )* - $( - registry.register::<$self_>(); - )? - } - } - )* - }; -} - -macro_rules! register_tuple_dependencies { - ($($ty:ident),*) => { - impl<$($ty: GetTypeRegistration + Typed),*> GetInnerTypeDependencies for ($($ty,)*) { - fn register_type_dependencies(registry: &mut TypeRegistry) { - $( - registry.register::<$ty>(); - )* - } - } - }; -} - -no_type_dependencies!(InteropError); -no_type_dependencies!(WorldGuard<'static>); -self_type_dependency_only!(FunctionCallContext, ReflectReference); - -recursive_type_dependencies!( - (Val where T: GetTypeRegistration), - (Ref<'_, T> where T: GetTypeRegistration), - (Mut<'_, T> where T: GetTypeRegistration), - (Result where T: GetTypeRegistration), - ([T; N] where T: GetTypeRegistration;Typed,, const N: usize => with Self), - (Option where T: GetTypeRegistration;FromReflect;Typed => with Self), - (Vec where T: GetTypeRegistration;FromReflect;Typed => with Self), - (HashMap where K: GetTypeRegistration;FromReflect;Typed;Hash;Eq, V: GetTypeRegistration;FromReflect;Typed => with Self) -); - -bevy::utils::all_tuples!(register_tuple_dependencies, 1, 14, T); -pub trait GetFunctionTypeDependencies { - fn register_type_dependencies(registry: &mut TypeRegistry); -} - /// The caller context when calling a script function. /// Functions can choose to react to caller preferences such as converting 1-indexed numbers to 0-indexed numbers #[derive(Clone, Copy, Debug, Reflect, Default)] @@ -128,29 +48,11 @@ impl FunctionCallContext { } /// Tries to access the world, returning an error if the world is not available - pub fn world(&self) -> Result, InteropError> { + pub fn world<'l>(&self) -> Result, InteropError> { ThreadWorldContainer.try_get_world() } } -#[derive(Clone, Debug, PartialEq, Default)] -pub struct FunctionInfo { - pub name: Cow<'static, str>, - pub namespace: Namespace, -} - -impl FunctionInfo { - /// The name of the function - pub fn name(&self) -> &Cow<'static, str> { - &self.name - } - - /// If the function is namespaced to a specific type, this will return the type id of that type - pub fn namespace(&self) -> Namespace { - self.namespace - } -} - /// The Script Function equivalent for dynamic functions. Currently unused #[derive(Clone, Reflect)] #[reflect(opaque)] @@ -204,7 +106,7 @@ impl DynamicScriptFunction { match return_val { ScriptValue::Error(e) => Err(InteropError::function_interop_error( self.name(), - self.info.namespace(), + self.info.namespace, e, )), v => Ok(v), @@ -215,6 +117,10 @@ impl DynamicScriptFunction { &self.info.name } + pub fn with_info(self, info: FunctionInfo) -> Self { + Self { info, ..self } + } + pub fn with_name>>(self, name: N) -> Self { Self { info: FunctionInfo { @@ -252,7 +158,7 @@ impl DynamicScriptFunctionMut { match return_val { ScriptValue::Error(e) => Err(InteropError::function_interop_error( self.name(), - self.info.namespace(), + self.info.namespace, e, )), v => Ok(v), @@ -262,6 +168,10 @@ impl DynamicScriptFunctionMut { &self.info.name } + pub fn with_info(self, info: FunctionInfo) -> Self { + Self { info, ..self } + } + pub fn with_name>>(self, name: N) -> Self { Self { info: FunctionInfo { @@ -372,15 +282,30 @@ impl ScriptFunctionRegistry { /// the new function will be registered as an overload of the function. /// /// If you want to overwrite an existing function, use [`ScriptFunctionRegistry::overwrite`] - pub fn register( + pub fn register<'env, F, M>( &mut self, namespace: Namespace, name: impl Into>, func: F, ) where - F: ScriptFunction<'static, M>, + F: ScriptFunction<'env, M> + GetFunctionInfo, { - self.register_overload(namespace, name, func, false); + self.register_overload(namespace, name, func, false, None::<&'static str>); + } + + /// Equivalent to [`ScriptFunctionRegistry::register`] but with the ability to provide documentation for the function. + /// + /// The docstring will be added to the function's metadata and can be accessed at runtime. + pub fn register_documented( + &mut self, + namespace: Namespace, + name: impl Into>, + func: F, + docs: &'static str, + ) where + F: ScriptFunction<'static, M> + GetFunctionInfo, + { + self.register_overload(namespace, name, func, false, Some(docs)); } /// Overwrite a function with the given name. If the function does not exist, it will be registered as a new function. @@ -390,9 +315,21 @@ impl ScriptFunctionRegistry { name: impl Into>, func: F, ) where - F: ScriptFunction<'static, M>, + F: ScriptFunction<'static, M> + GetFunctionInfo, { - self.register_overload(namespace, name, func, true); + self.register_overload(namespace, name, func, true, None::<&'static str>); + } + + pub fn overwrite_documented( + &mut self, + namespace: Namespace, + name: impl Into>, + func: F, + docs: &'static str, + ) where + F: ScriptFunction<'static, M> + GetFunctionInfo, + { + self.register_overload(namespace, name, func, true, Some(docs)); } /// Remove a function from the registry if it exists. Returns the removed function if it was found. @@ -423,23 +360,26 @@ impl ScriptFunctionRegistry { Ok(overloads) } - fn register_overload( + fn register_overload<'env, F, M>( &mut self, namespace: Namespace, name: impl Into>, func: F, overwrite: bool, + docs: Option>>, ) where - F: ScriptFunction<'static, M>, + F: ScriptFunction<'env, M> + GetFunctionInfo, { // always start with non-suffixed registration // TODO: we do alot of string work, can we make this all more efficient? let name: Cow<'static, str> = name.into(); if overwrite || !self.contains(namespace, name.clone()) { - let func = func - .into_dynamic_script_function() - .with_name(name.clone()) - .with_namespace(namespace); + let info = func.get_function_info(name.clone(), namespace); + let info = match docs { + Some(docs) => info.with_docs(docs.into()), + None => info, + }; + let func = func.into_dynamic_script_function().with_info(info); self.functions.insert(FunctionKey { name, namespace }, func); return; } @@ -508,6 +448,15 @@ impl ScriptFunctionRegistry { self.functions.iter() } + pub fn iter_namespace( + &self, + namespace: Namespace, + ) -> impl Iterator { + self.functions + .iter() + .filter(move |(key, _)| key.namespace == namespace) + } + /// Insert a function into the registry with the given key, this will not perform any overloading logic. /// Do not use unless you really need to. pub fn raw_insert( @@ -564,8 +513,7 @@ macro_rules! impl_script_function { #[allow(non_snake_case)] impl< 'env, - 'w : 'static, - $( $param: FromScript + ArgInfo,)* + $( $param: FromScript + ArgMeta,)* O, F > $trait_type<'env, @@ -573,8 +521,8 @@ macro_rules! impl_script_function { > for F where O: IntoScript, - F: $fn_type( $($contextty,)? $($param ),* ) -> $res + Send + Sync + 'static , - $( $param::This<'w>: Into<$param>,)* + F: $fn_type( $($contextty,)? $($param ),* ) -> $res + Send + Sync + 'static, + $( $param::This<'env>: Into<$param>,)* { #[allow(unused_mut,unused_variables)] fn $trait_fn_name(mut self) -> $dynamic_type { @@ -630,24 +578,7 @@ macro_rules! impl_script_function { }; } -macro_rules! impl_script_function_type_dependencies{ - ($( $param:ident ),* ) => { - impl GetFunctionTypeDependencies O> for F - where F: Fn( $( $param ),* ) -> O - { - fn register_type_dependencies(registry: &mut TypeRegistry) { - $( - $param::register_type_dependencies(registry); - )* - - O::register_type_dependencies(registry); - } - } - }; -} - bevy::utils::all_tuples!(impl_script_function, 0, 13, T); -bevy::utils::all_tuples!(impl_script_function_type_dependencies, 0, 13, T); #[cfg(test)] mod test { @@ -672,8 +603,8 @@ mod test { .get_function(namespace, "test") .expect("Failed to get function"); - assert_eq!(function.info.name(), "test"); - assert_eq!(function.info.namespace(), namespace); + assert_eq!(function.info.name, "test"); + assert_eq!(function.info.namespace, namespace); } #[test] @@ -729,8 +660,8 @@ mod test { .get_function(namespace, "test") .expect("Failed to get function"); - assert_eq!(first_function.info.name(), "test"); - assert_eq!(first_function.info.namespace(), namespace); + assert_eq!(first_function.info.name, "test"); + assert_eq!(first_function.info.namespace, namespace); let all_functions = registry .iter_overloads(namespace, "test") @@ -738,8 +669,8 @@ mod test { .collect::>(); assert_eq!(all_functions.len(), 2); - assert_eq!(all_functions[0].info.name(), "test"); - assert_eq!(all_functions[1].info.name(), "test-1"); + assert_eq!(all_functions[0].info.name, "test"); + assert_eq!(all_functions[1].info.name, "test-1"); } #[test] @@ -757,7 +688,7 @@ mod test { .collect::>(); assert_eq!(all_functions.len(), 1); - assert_eq!(all_functions[0].info.name(), "test"); + assert_eq!(all_functions[0].info.name, "test"); } #[test] diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/type_dependencies.rs b/crates/bevy_mod_scripting_core/src/bindings/function/type_dependencies.rs new file mode 100644 index 0000000000..2d6596d864 --- /dev/null +++ b/crates/bevy_mod_scripting_core/src/bindings/function/type_dependencies.rs @@ -0,0 +1,107 @@ +use super::{ + from::{Mut, Ref, Val}, + script_function::FunctionCallContext, +}; +use crate::{ + bindings::{ReflectReference, WorldGuard}, + error::InteropError, +}; +use bevy::reflect::{FromReflect, GetTypeRegistration, TypeRegistry, Typed}; +use std::collections::HashMap; +use std::hash::Hash; + +/// Functionally identical to [`GetTypeRegistration`] but without the 'static bound +pub trait GetTypeDependencies { + fn register_type_dependencies(registry: &mut TypeRegistry); +} + +#[macro_export] +macro_rules! no_type_dependencies { + ($($path:path),*) => { + $( + impl $crate::bindings::function::type_dependencies::GetTypeDependencies for $path { + fn register_type_dependencies(_registry: &mut bevy::reflect::TypeRegistry) {} + } + )* + }; +} + +#[macro_export] +macro_rules! self_type_dependency_only { + ($($path:ty),*) => { + $( + impl $crate::bindings::function::type_dependencies::GetTypeDependencies for $path { + fn register_type_dependencies(registry: &mut bevy::reflect::TypeRegistry) { + registry.register::<$path>(); + } + } + )* + }; +} + +macro_rules! recursive_type_dependencies { + ($( ($path:ty where $($bound:ident : $($bound_val:path);*),* $(,,const $const:ident : $const_ty:ty)? $(=> with $self_:ident)?) ),* ) => { + $( + impl<$($bound : $($bound_val +)*),* , $(const $const : $const_ty )?> GetTypeDependencies for $path { + fn register_type_dependencies(registry: &mut TypeRegistry) { + $( + registry.register::<$bound>(); + )* + $( + registry.register::<$self_>(); + )? + } + } + )* + }; +} + +macro_rules! register_tuple_dependencies { + ($($ty:ident),*) => { + impl<$($ty: GetTypeRegistration + Typed),*> GetTypeDependencies for ($($ty,)*) { + fn register_type_dependencies(registry: &mut TypeRegistry) { + $( + registry.register::<$ty>(); + )* + } + } + }; +} + +no_type_dependencies!(InteropError); +no_type_dependencies!(WorldGuard<'static>); +self_type_dependency_only!(FunctionCallContext, ReflectReference); + +recursive_type_dependencies!( + (Val where T: GetTypeRegistration), + (Ref<'_, T> where T: GetTypeRegistration), + (Mut<'_, T> where T: GetTypeRegistration), + (Result where T: GetTypeRegistration), + ([T; N] where T: GetTypeRegistration;Typed,, const N: usize => with Self), + (Option where T: GetTypeRegistration;FromReflect;Typed => with Self), + (Vec where T: GetTypeRegistration;FromReflect;Typed => with Self), + (HashMap where K: GetTypeRegistration;FromReflect;Typed;Hash;Eq, V: GetTypeRegistration;FromReflect;Typed => with Self) +); + +bevy::utils::all_tuples!(register_tuple_dependencies, 1, 14, T); +pub trait GetFunctionTypeDependencies { + fn register_type_dependencies(registry: &mut TypeRegistry); +} + +macro_rules! impl_script_function_type_dependencies{ + ($( $param:ident ),* ) => { + impl GetFunctionTypeDependencies O> for F + where F: Fn( $( $param ),* ) -> O + { + fn register_type_dependencies(registry: &mut TypeRegistry) { + $( + $param::register_type_dependencies(registry); + )* + + O::register_type_dependencies(registry); + } + } + }; +} + +bevy::utils::all_tuples!(impl_script_function_type_dependencies, 0, 13, T); diff --git a/crates/bevy_mod_scripting_core/src/bindings/pretty_print.rs b/crates/bevy_mod_scripting_core/src/bindings/pretty_print.rs index ffb1645346..800e48f2bb 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/pretty_print.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/pretty_print.rs @@ -154,6 +154,16 @@ impl ReflectReferencePrinter { id if id == TypeId::of::() => downcast_case!(v, output, i16), id if id == TypeId::of::() => downcast_case!(v, output, i8), id if id == TypeId::of::() => downcast_case!(v, output, String), + id if id == TypeId::of::() => { + downcast_case!(v, output, std::path::PathBuf) + } + id if id == TypeId::of::() => { + downcast_case!(v, output, std::ffi::OsString) + } + id if id == TypeId::of::>() => { + downcast_case!(v, output, Cow) + } + id if id == TypeId::of::() => downcast_case!(v, output, char), id if id == TypeId::of::() => downcast_case!(v, output, bool), _ => { output.push_str( @@ -465,7 +475,8 @@ impl DisplayWithWorld for ScriptValue { ScriptValue::Float(f) => f.to_string(), ScriptValue::String(cow) => cow.to_string(), ScriptValue::Error(script_error) => script_error.display_with_world(world), - ScriptValue::List(vec) => vec.display_without_world(), + ScriptValue::List(vec) => vec.display_with_world(world), + ScriptValue::Map(hash_map) => hash_map.display_with_world(world), } } @@ -494,6 +505,7 @@ impl DisplayWithWorld for ScriptValue { format!("Function({})", dynamic_script_function.name()) } ScriptValue::Error(interop_error) => interop_error.display_without_world(), + ScriptValue::Map(hash_map) => hash_map.display_without_world(), } } } @@ -539,6 +551,69 @@ impl DisplayWithWorld for Vec { } } +impl DisplayWithWorld for String { + fn display_with_world(&self, _world: WorldGuard) -> String { + self.to_string() + } + + fn display_value_with_world(&self, _world: WorldGuard) -> String { + self.to_string() + } + + fn display_without_world(&self) -> String { + self.to_string() + } +} + +impl DisplayWithWorld + for std::collections::HashMap +{ + fn display_with_world(&self, world: WorldGuard) -> String { + let mut string = String::new(); + BracketType::Curly.surrounded(&mut string, |string| { + for (i, (k, v)) in self.iter().enumerate() { + string.push_str(&k.display_with_world(world.clone())); + string.push_str(": "); + string.push_str(&v.display_with_world(world.clone())); + if i != self.len() - 1 { + string.push_str(", "); + } + } + }); + string + } + + fn display_value_with_world(&self, world: WorldGuard) -> String { + let mut string = String::new(); + BracketType::Curly.surrounded(&mut string, |string| { + for (i, (k, v)) in self.iter().enumerate() { + string.push_str(&k.display_value_with_world(world.clone())); + string.push_str(": "); + string.push_str(&v.display_value_with_world(world.clone())); + if i != self.len() - 1 { + string.push_str(", "); + } + } + }); + string + } + + fn display_without_world(&self) -> String { + let mut string = String::new(); + BracketType::Curly.surrounded(&mut string, |string| { + for (i, (k, v)) in self.iter().enumerate() { + string.push_str(&k.display_without_world()); + string.push_str(": "); + string.push_str(&v.display_without_world()); + if i != self.len() - 1 { + string.push_str(", "); + } + } + }); + string + } +} + #[cfg(test)] mod test { use bevy::prelude::AppTypeRegistry; @@ -652,4 +727,17 @@ mod test { format!("", type_id) ); } + + #[test] + fn test_hashmap() { + let mut world = setup_world(); + let world = WorldGuard::new(&mut world); + + let mut map = std::collections::HashMap::new(); + map.insert("hello".to_owned(), ScriptValue::Bool(true)); + + assert_eq!(map.display_with_world(world.clone()), "{hello: true}"); + + assert_eq!(map.display_value_with_world(world.clone()), "{hello: true}"); + } } diff --git a/crates/bevy_mod_scripting_core/src/bindings/script_value.rs b/crates/bevy_mod_scripting_core/src/bindings/script_value.rs index 993980141b..b623274305 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/script_value.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/script_value.rs @@ -1,4 +1,4 @@ -use std::borrow::Cow; +use std::{borrow::Cow, collections::HashMap}; use bevy::reflect::{OffsetAccess, ParsedPath, Reflect}; @@ -26,6 +26,8 @@ pub enum ScriptValue { String(Cow<'static, str>), /// Represents a list of other things passed by value List(Vec), + /// Represents a map of other things passed by value + Map(HashMap), /// Represents a reference to a value. Reference(ReflectReference), /// A dynamic script function possibly storing state. Preffer using the [`ScriptValue::Function`] variant instead if possible. @@ -57,6 +59,7 @@ impl ScriptValue { ScriptValue::FunctionMut(_) => "FunctionMut".to_owned(), ScriptValue::Function(_) => "Function".to_owned(), ScriptValue::Error(_) => "Error".to_owned(), + ScriptValue::Map(_) => "Map".to_owned(), } } } @@ -139,6 +142,12 @@ impl, E: Into> From> for ScriptV } } +impl From> for ScriptValue { + fn from(value: HashMap) -> Self { + ScriptValue::Map(value) + } +} + impl TryFrom for ParsedPath { type Error = InteropError; fn try_from(value: ScriptValue) -> Result { diff --git a/crates/bevy_mod_scripting_core/src/bindings/world.rs b/crates/bevy_mod_scripting_core/src/bindings/world.rs index a80c2e61b1..d3d983331b 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/world.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/world.rs @@ -62,6 +62,14 @@ pub(crate) struct WorldAccessGuardInner<'w> { function_registry: AppScriptFunctionRegistry, } +impl WorldAccessGuard<'static> { + /// Shortens the lifetime of the guard to the given lifetime. + pub(crate) fn shorten_lifetime<'w>(self) -> WorldGuard<'w> { + // Safety: todo + unsafe { std::mem::transmute(self) } + } +} + impl<'w> WorldAccessGuard<'w> { /// Safely allows access to the world for the duration of the closure via a static [`WorldAccessGuard`]. /// @@ -365,6 +373,24 @@ impl<'w> WorldAccessGuard<'w> { Err(name) } + /// Iterates over all available functions on the type id's namespace + those available on any reference if any exist. + pub fn get_functions_on_type( + &self, + type_id: TypeId, + ) -> Vec<(Cow<'static, str>, DynamicScriptFunction)> { + let registry = self.script_function_registry(); + let registry = registry.read(); + + registry + .iter_namespace(Namespace::OnType(type_id)) + .chain( + registry + .iter_namespace(Namespace::OnType(std::any::TypeId::of::())), + ) + .map(|(key, func)| (key.name.clone(), func.clone())) + .collect() + } + /// checks if a given entity exists and is valid pub fn is_valid_entity(&self, entity: Entity) -> Result { let cell = self.as_unsafe_world_cell()?; @@ -791,7 +817,7 @@ pub trait WorldContainer { fn set_world(&mut self, world: WorldGuard<'static>) -> Result<(), Self::Error>; /// Tries to get the world from the container - fn try_get_world(&self) -> Result, Self::Error>; + fn try_get_world<'l>(&self) -> Result, Self::Error>; } /// A world container that stores the world in a thread local @@ -811,7 +837,9 @@ impl WorldContainer for ThreadWorldContainer { Ok(()) } - fn try_get_world(&self) -> Result, Self::Error> { - WORLD_CALLBACK_ACCESS.with(|w| w.borrow().clone().ok_or_else(InteropError::missing_world)) + fn try_get_world<'l>(&self) -> Result, Self::Error> { + WORLD_CALLBACK_ACCESS + .with(|w| w.borrow().clone().ok_or_else(InteropError::missing_world)) + .map(|v| v.shorten_lifetime()) } } diff --git a/crates/bevy_mod_scripting_core/src/docgen/info.rs b/crates/bevy_mod_scripting_core/src/docgen/info.rs new file mode 100644 index 0000000000..5cafa6f771 --- /dev/null +++ b/crates/bevy_mod_scripting_core/src/docgen/info.rs @@ -0,0 +1,180 @@ +use bevy::reflect::Reflect; + +use crate::bindings::function::arg_meta::ArgMeta; +use crate::bindings::function::namespace::Namespace; +use std::{any::TypeId, borrow::Cow}; + +/// for things you can call and provide some introspection capability. +pub trait GetFunctionInfo { + fn get_function_info(&self, name: Cow<'static, str>, namespace: Namespace) -> FunctionInfo; +} + +#[derive(Debug, Clone, PartialEq, Reflect)] +pub struct FunctionInfo { + pub name: Cow<'static, str>, + pub namespace: Namespace, + pub arg_info: Vec, + pub return_info: FunctionReturnInfo, + pub docs: Option>, +} + +impl Default for FunctionInfo { + fn default() -> Self { + Self::new() + } +} + +impl FunctionInfo { + pub fn new() -> Self { + Self { + name: Cow::Borrowed(""), + namespace: Namespace::Global, + arg_info: Vec::new(), + return_info: FunctionReturnInfo::new(), + docs: None, + } + } + + pub fn new_for(name: Cow<'static, str>, namespace: Namespace) -> Self { + Self { + name, + namespace, + arg_info: Vec::new(), + return_info: FunctionReturnInfo::new(), + docs: None, + } + } + + 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, + }); + self + } + + pub fn add_return(mut self, return_info: FunctionReturnInfo) -> Self { + self.return_info = return_info; + self + } + + pub fn with_docs(mut self, docs: impl Into>) -> Self { + self.docs = Some(docs.into()); + self + } +} + +#[derive(Debug, Clone, PartialEq, Reflect)] +pub struct FunctionArgInfo { + pub name: Option>, + pub arg_index: usize, + pub type_id: TypeId, + pub docs: Option>, +} + +impl FunctionArgInfo { + pub fn new(arg_index: usize, type_id: TypeId) -> Self { + Self { + name: None, + arg_index, + type_id, + docs: None, + } + } + + pub fn with_name(mut self, name: Cow<'static, str>) -> Self { + self.name = Some(name); + self + } + + pub fn with_docs(mut self, docs: Cow<'static, str>) -> Self { + self.docs = Some(docs); + self + } +} + +#[derive(Debug, Clone, PartialEq, Reflect)] +pub struct FunctionReturnInfo { + pub type_id: TypeId, +} + +impl Default for FunctionReturnInfo { + fn default() -> Self { + Self::new() + } +} + +impl FunctionReturnInfo { + pub fn new() -> Self { + Self { + type_id: TypeId::of::<()>(), + } + } + + pub fn new_for() -> Self { + Self { + type_id: TypeId::of::(), + } + } +} + +macro_rules! impl_documentable { + ($( $param:ident ),*) => { + impl<$($param,)* F, O> GetFunctionInfo O> for F + where + F: Fn($($param),*) -> O, + $($param: ArgMeta + 'static,)* + O: 'static + { + fn get_function_info(&self, name: Cow<'static, str>, namespace: Namespace) -> FunctionInfo { + #[allow(unused_mut)] + let mut info = FunctionInfo::new_for(name, namespace); + $( + info = info.add_arg::<$param>(None); + )* + info.add_return(FunctionReturnInfo::new_for::()) + } + } + }; +} + +bevy::utils::all_tuples!(impl_documentable, 0, 13, T); + +#[cfg(test)] +mod test { + use crate::bindings::function::from::{Mut, Ref, Val}; + + use super::*; + + #[test] + fn test_get_function_info() { + fn test_fn(a: i32, b: f32) -> f64 { + (a as f64) + (b as f64) + } + + let info = test_fn.get_function_info(Cow::Borrowed("test_fn"), Namespace::Global); + 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.arg_info[0].type_id, TypeId::of::()); + assert_eq!(info.arg_info[1].type_id, TypeId::of::()); + } + + #[test] + fn test_get_function_info_references() { + let fn_ = |_: Ref, _: Mut| -> Val { Val::new(0.0) }; + + let info = fn_.get_function_info(Cow::Borrowed("test_fn"), Namespace::Global); + 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.arg_info[0].type_id, TypeId::of::>()); + assert_eq!(info.arg_info[1].type_id, TypeId::of::>()); + } +} diff --git a/crates/bevy_mod_scripting_core/src/docgen/mod.rs b/crates/bevy_mod_scripting_core/src/docgen/mod.rs new file mode 100644 index 0000000000..4b8757fcc7 --- /dev/null +++ b/crates/bevy_mod_scripting_core/src/docgen/mod.rs @@ -0,0 +1 @@ +pub mod info; diff --git a/crates/bevy_mod_scripting_core/src/lib.rs b/crates/bevy_mod_scripting_core/src/lib.rs index 26b9ff3c46..da540286bf 100644 --- a/crates/bevy_mod_scripting_core/src/lib.rs +++ b/crates/bevy_mod_scripting_core/src/lib.rs @@ -23,6 +23,7 @@ pub mod asset; pub mod bindings; pub mod commands; pub mod context; +pub mod docgen; pub mod error; pub mod event; pub mod extractors; diff --git a/crates/bevy_mod_scripting_functions/src/core.rs b/crates/bevy_mod_scripting_functions/src/core.rs index 8f62ba392e..751c52838e 100644 --- a/crates/bevy_mod_scripting_functions/src/core.rs +++ b/crates/bevy_mod_scripting_functions/src/core.rs @@ -378,6 +378,16 @@ pub fn register_reflect_reference_functions( }; Ok(iter_function.into_dynamic_script_function_mut()) + }) + .register("functions", |ctxt: FunctionCallContext, s: ReflectReference| { + let world = ctxt.world()?; + let type_id = s.tail_type_id(world.clone())?.or_fake_id(); + let functions = world.get_functions_on_type(type_id) + .into_iter() + .map(|(_,v)| Val::new(v.info)) + .collect::>(); + // convert to info + Ok(functions) }); Ok(()) diff --git a/crates/languages/bevy_mod_scripting_lua/src/bindings/script_value.rs b/crates/languages/bevy_mod_scripting_lua/src/bindings/script_value.rs index 6f464b0896..bddced885f 100644 --- a/crates/languages/bevy_mod_scripting_lua/src/bindings/script_value.rs +++ b/crates/languages/bevy_mod_scripting_lua/src/bindings/script_value.rs @@ -3,7 +3,10 @@ use bevy_mod_scripting_core::bindings::{ function::script_function::FunctionCallContext, script_value::ScriptValue, }; use mlua::{FromLua, IntoLua, Value, Variadic}; -use std::ops::{Deref, DerefMut}; +use std::{ + collections::HashMap, + ops::{Deref, DerefMut}, +}; #[derive(Debug, Clone)] pub struct LuaScriptValue(pub ScriptValue); @@ -65,7 +68,6 @@ impl FromLua for LuaScriptValue { })?; ScriptValue::Reference(ud.clone().into()) } - // Value::Error(error) => todo!(), _ => { return Err(mlua::Error::FromLuaConversionError { from: value.type_name(), @@ -119,6 +121,13 @@ impl IntoLua for LuaScriptValue { )?; Value::Table(table) } + ScriptValue::Map(map) => { + let hashmap: HashMap = map + .into_iter() + .map(|(k, v)| Ok((k, LuaScriptValue::from(v).into_lua(lua)?))) + .collect::>()?; + hashmap.into_lua(lua)? + } }) } } diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/functions/contains_reflect_reference_functions.lua b/crates/languages/bevy_mod_scripting_lua/tests/data/functions/contains_reflect_reference_functions.lua new file mode 100644 index 0000000000..73554fd73c --- /dev/null +++ b/crates/languages/bevy_mod_scripting_lua/tests/data/functions/contains_reflect_reference_functions.lua @@ -0,0 +1,22 @@ +function contains(table, element) + for _, value in pairs(table) do + if value == element then + return true + end + end + return false +end + +local Resource = world.get_type_by_name("TestResource") +local resource = world.get_resource(Resource) + +local functions = resource:functions() +assert(#functions > 0, "functions should not be empty") + +local available_names = {} + +for _, function_ref in pairs(functions) do + table.insert(available_names, function_ref.name) +end + +assert(contains(available_names, "display_ref"), "functions should contain display_ref, but got: " .. table.concat(available_names, ", ")) \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/src/bindings/script_value.rs b/crates/languages/bevy_mod_scripting_rhai/src/bindings/script_value.rs index 0ec05baf8a..3992e00e72 100644 --- a/crates/languages/bevy_mod_scripting_rhai/src/bindings/script_value.rs +++ b/crates/languages/bevy_mod_scripting_rhai/src/bindings/script_value.rs @@ -5,7 +5,7 @@ use bevy_mod_scripting_core::{ }, error::InteropError, }; -use rhai::{Dynamic, EvalAltResult, FnPtr, NativeCallContext}; +use rhai::{Dynamic, EvalAltResult, FnPtr, Map, NativeCallContext}; use std::str::FromStr; use super::reference::RhaiReflectReference; @@ -14,67 +14,6 @@ pub const RHAI_CALLER_CONTEXT: FunctionCallContext = FunctionCallContext { convert_to_0_indexed: false, }; -// impl PluginFunc for FuncWrapper { -// fn call( -// &self, -// _context: Option, -// _args: &mut [&mut Dynamic], -// ) -> rhai::plugin::RhaiResult { -// // let convert_args = _args -// // .iter_mut() -// // .map(|arg| ScriptValue::from_dynamic(arg.clone())) -// // .collect::, _>>()?; - -// // let out = self.0.call( -// // rhai_caller_context(self.0.info.namespace()), -// // WorldCallbackAccess::from_guard(ThreadWorldContainer.get_world()), -// // convert_args, -// // ); - -// // out.into_dynamic() -// todo!() -// } - -// fn is_method_call(&self) -> bool { -// // TODO: is this correct? do we care if it's a method call? -// false -// } - -// fn has_context(&self) -> bool { -// false -// } -// } - -// impl PluginFunc for FuncMutWrapper { -// fn call( -// &self, -// _context: Option, -// _args: &mut [&mut Dynamic], -// ) -> rhai::plugin::RhaiResult { -// // let convert_args = _args -// // .iter_mut() -// // .map(|arg| ScriptValue::from_dynamic(arg.clone())) -// // .collect::, _>>()?; - -// // let out = self.0.call( -// // rhai_caller_context(self.0.info.namespace()), -// // WorldCallbackAccess::from_guard(ThreadWorldContainer.get_world()), -// // convert_args, -// // ); - -// // out.into_dynamic() -// todo!() -// } - -// fn is_method_call(&self) -> bool { -// false -// } - -// fn has_context(&self) -> bool { -// false -// } -// } - /// A function curried with one argument, i.e. the receiver pub struct FunctionWithReceiver { pub function: DynamicScriptFunction, @@ -137,6 +76,13 @@ impl IntoDynamic for ScriptValue { .map(|v| v.into_dynamic()) .collect::, _>>()?, ), + ScriptValue::Map(map) => { + let rhai_map: Map = map + .into_iter() + .map(|(k, v)| Ok((k.into(), v.into_dynamic()?))) + .collect::>>()?; + Dynamic::from_map(rhai_map) + } ScriptValue::Reference(reflect_reference) => { Dynamic::from(RhaiReflectReference(reflect_reference)) } diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/functions/contains_reflect_reference_functions.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/functions/contains_reflect_reference_functions.rhai new file mode 100644 index 0000000000..f83b980c99 --- /dev/null +++ b/crates/languages/bevy_mod_scripting_rhai/tests/data/functions/contains_reflect_reference_functions.rhai @@ -0,0 +1,14 @@ + +let Resource = world.get_type_by_name.call("TestResource"); +let resource = world.get_resource.call(Resource); + +let functions = resource.functions.call(); +assert(functions.len() > 0, "functions should not be empty"); + +let available_names = []; + +for function_ref in functions { + available_names.push(function_ref.name); +} + +assert("display_ref" in available_names, "functions should contain display_ref, but got: " + available_names); \ No newline at end of file