diff --git a/crates/bevy_mod_scripting_core/Cargo.toml b/crates/bevy_mod_scripting_core/Cargo.toml index 64c699caf4..faba9e751e 100644 --- a/crates/bevy_mod_scripting_core/Cargo.toml +++ b/crates/bevy_mod_scripting_core/Cargo.toml @@ -26,7 +26,7 @@ rhai_impls = ["rhai"] [dependencies] mlua = { version = "0.10", default-features = false, optional = true } -rhai = { git = "https://github.com/rhaiscript/rhai", rev = "4ead53eb40f4a18d6f827609041ef1c742f04799", default-features = false, features = [ +rhai = { version = "1.21", default-features = false, features = [ "sync", ], optional = 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 new file mode 100644 index 0000000000..a7bb28a90b --- /dev/null +++ b/crates/bevy_mod_scripting_core/src/bindings/function/arg_info.rs @@ -0,0 +1,64 @@ +//! 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/mod.rs b/crates/bevy_mod_scripting_core/src/bindings/function/mod.rs index b5d1a3251d..50f53baa52 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/mod.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/mod.rs @@ -1,3 +1,4 @@ +pub mod arg_info; pub mod from; pub mod from_ref; pub mod into; 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 1a48857581..e0155bbe1d 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,4 +1,5 @@ use super::{from::FromScript, into::IntoScript, namespace::Namespace}; +use crate::bindings::function::arg_info::ArgInfo; use crate::{ bindings::{ function::from::{Mut, Ref, Val}, @@ -9,20 +10,17 @@ use crate::{ }; use bevy::{ prelude::{Reflect, Resource}, - reflect::{ - func::{args::GetOwnership, FunctionError}, - FromReflect, GetTypeRegistration, TypePath, TypeRegistry, Typed, - }, + reflect::{func::FunctionError, FromReflect, GetTypeRegistration, TypeRegistry, Typed}, }; use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::borrow::Cow; -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; use std::hash::Hash; use std::ops::{Deref, DerefMut}; use std::sync::Arc; #[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 = "This function does not fulfil the requirements to be a script callable function. All arguments must implement the ScriptArgument trait and all return values must implement the ScriptReturn trait", note = "If you're trying to return a non-primitive type, you might need to use Val Ref or Mut wrappers" )] pub trait ScriptFunction<'env, Marker> { @@ -159,7 +157,9 @@ impl FunctionInfo { pub struct DynamicScriptFunction { pub info: FunctionInfo, // TODO: info about the function, this is hard right now because of non 'static lifetimes in wrappers, we can't use TypePath etc - func: Arc) -> ScriptValue + Send + Sync + 'static>, + func: Arc< + dyn Fn(FunctionCallContext, VecDeque) -> ScriptValue + Send + Sync + 'static, + >, } impl PartialEq for DynamicScriptFunction { @@ -175,7 +175,10 @@ pub struct DynamicScriptFunctionMut { func: Arc< RwLock< // I'd rather consume an option or something instead of having the RWLock but I just wanna get this release out - dyn FnMut(FunctionCallContext, Vec) -> ScriptValue + Send + Sync + 'static, + dyn FnMut(FunctionCallContext, VecDeque) -> ScriptValue + + Send + + Sync + + 'static, >, >, } @@ -195,7 +198,7 @@ impl DynamicScriptFunction { args: I, context: FunctionCallContext, ) -> Result { - let args = args.into_iter().collect::>(); + let args = args.into_iter().collect::>(); // should we be inlining call errors into the return value? let return_val = (self.func)(context, args); match return_val { @@ -242,7 +245,7 @@ impl DynamicScriptFunctionMut { args: I, context: FunctionCallContext, ) -> Result { - let args = args.into_iter().collect::>(); + let args = args.into_iter().collect::>(); // should we be inlining call errors into the return value? let mut write = self.func.write(); let return_val = (write)(context, args); @@ -298,7 +301,7 @@ impl std::fmt::Debug for DynamicScriptFunctionMut { impl From for DynamicScriptFunction where - F: Fn(FunctionCallContext, Vec) -> ScriptValue + Send + Sync + 'static, + F: Fn(FunctionCallContext, VecDeque) -> ScriptValue + Send + Sync + 'static, { fn from(fn_: F) -> Self { DynamicScriptFunction { @@ -311,7 +314,7 @@ where impl From for DynamicScriptFunctionMut where - F: FnMut(FunctionCallContext, Vec) -> ScriptValue + Send + Sync + 'static, + F: FnMut(FunctionCallContext, VecDeque) -> ScriptValue + Send + Sync + 'static, { fn from(fn_: F) -> Self { DynamicScriptFunctionMut { @@ -562,39 +565,50 @@ macro_rules! impl_script_function { impl< 'env, 'w : 'static, - $( $param: FromScript, )* + $( $param: FromScript + ArgInfo,)* O, F > $trait_type<'env, fn( $($contextty,)? $($param ),* ) -> $res > for F where - O: IntoScript + TypePath + GetOwnership, + O: IntoScript, F: $fn_type( $($contextty,)? $($param ),* ) -> $res + Send + Sync + 'static , $( $param::This<'w>: Into<$param>,)* { #[allow(unused_mut,unused_variables)] fn $trait_fn_name(mut self) -> $dynamic_type { - let func = (move |caller_context: FunctionCallContext, args: Vec | { + let func = (move |caller_context: FunctionCallContext, mut args: VecDeque | { let res: Result = (|| { + let received_args_len = args.len(); let expected_arg_count = count!($($param )*); - if args.len() < expected_arg_count { - return Err(InteropError::function_call_error(FunctionError::ArgCountMismatch{ - expected: expected_arg_count, - received: args.len() - })); - } + $( let $context = caller_context; )? let world = caller_context.world()?; world.begin_access_scope()?; + let mut current_arg = 0; + + $( + current_arg += 1; + let $param = args.pop_front(); + let $param = match $param { + Some($param) => $param, + None => { + if let Some(default) = <$param>::default_value() { + default + } else { + return Err(InteropError::function_call_error(FunctionError::ArgCountMismatch{ + expected: expected_arg_count, + received: received_args_len + })); + } + } + }; + let $param = <$param>::from_script($param, world.clone()) + .map_err(|e| InteropError::function_arg_conversion_error(current_arg.to_string(), e))?; + )* + let ret = { - let mut current_arg = 0; - let mut arg_iter = args.into_iter(); - $( - current_arg += 1; - let $param = <$param>::from_script(arg_iter.next().expect("invariant"), world.clone()) - .map_err(|e| InteropError::function_arg_conversion_error(current_arg.to_string(), e))?; - )* let out = self( $( $context,)? $( $param.into(), )* ); $( let $out = out?; @@ -638,10 +652,20 @@ bevy::utils::all_tuples!(impl_script_function_type_dependencies, 0, 13, T); #[cfg(test)] mod test { use super::*; + + fn with_local_world(f: F) { + let mut world = bevy::prelude::World::default(); + WorldGuard::with_static_guard(&mut world, |world| { + ThreadWorldContainer.set_world(world).unwrap(); + f() + }); + } + #[test] fn test_register_script_function() { let mut registry = ScriptFunctionRegistry::default(); let fn_ = |a: usize, b: usize| a + b; + let namespace = Namespace::Global; registry.register(namespace, "test", fn_); let function = registry @@ -652,6 +676,46 @@ mod test { assert_eq!(function.info.namespace(), namespace); } + #[test] + fn test_optional_argument_not_required() { + let fn_ = |a: usize, b: Option| a + b.unwrap_or(0); + let script_function = fn_.into_dynamic_script_function(); + + with_local_world(|| { + let out = script_function + .call(vec![ScriptValue::from(1)], FunctionCallContext::default()) + .unwrap(); + + assert_eq!(out, ScriptValue::from(1)); + }); + } + + #[test] + fn test_invalid_amount_of_args_errors_nicely() { + let fn_ = |a: usize, b: usize| a + b; + let script_function = fn_.into_dynamic_script_function().with_name("my_fn"); + + with_local_world(|| { + let out = + script_function.call(vec![ScriptValue::from(1)], FunctionCallContext::default()); + + assert!(out.is_err()); + assert_eq!( + out.unwrap_err().into_inner().unwrap(), + InteropError::function_interop_error( + "my_fn", + Namespace::Global, + InteropError::function_call_error(FunctionError::ArgCountMismatch { + expected: 2, + received: 1 + }) + ) + .into_inner() + .unwrap() + ); + }); + } + #[test] fn test_overloaded_script_function() { let mut registry = ScriptFunctionRegistry::default(); diff --git a/crates/bevy_mod_scripting_core/src/bindings/world.rs b/crates/bevy_mod_scripting_core/src/bindings/world.rs index 0b9cea8d42..a80c2e61b1 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/world.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/world.rs @@ -322,7 +322,8 @@ impl<'w> WorldAccessGuard<'w> { } /// Safey modify or insert a component by claiming and releasing global access. - pub fn with_or_insert_component_mut(&self, + pub fn with_or_insert_component_mut( + &self, entity: Entity, f: F, ) -> Result diff --git a/crates/bevy_mod_scripting_core/src/error.rs b/crates/bevy_mod_scripting_core/src/error.rs index b18496055d..0449ba5d91 100644 --- a/crates/bevy_mod_scripting_core/src/error.rs +++ b/crates/bevy_mod_scripting_core/src/error.rs @@ -624,9 +624,194 @@ pub enum InteropErrorInner { }, } +/// For test purposes impl PartialEq for InteropErrorInner { fn eq(&self, _other: &Self) -> bool { - false + match (self, _other) { + (InteropErrorInner::StaleWorldAccess, InteropErrorInner::StaleWorldAccess) => true, + (InteropErrorInner::MissingWorld, InteropErrorInner::MissingWorld) => true, + ( + InteropErrorInner::UnregisteredBase { base: a }, + InteropErrorInner::UnregisteredBase { base: b }, + ) => a == b, + ( + InteropErrorInner::MissingTypeData { + type_id: a, + type_data: b, + }, + InteropErrorInner::MissingTypeData { + type_id: c, + type_data: d, + }, + ) => a == c && b == d, + ( + InteropErrorInner::FailedFromReflect { + type_id: a, + reason: b, + }, + InteropErrorInner::FailedFromReflect { + type_id: c, + reason: d, + }, + ) => a == c && b == d, + ( + InteropErrorInner::CannotClaimAccess { + base: a, + context: b, + location: c, + }, + InteropErrorInner::CannotClaimAccess { + base: d, + context: e, + location: f, + }, + ) => a == d && b == e && c == f, + ( + InteropErrorInner::ImpossibleConversion { into: a }, + InteropErrorInner::ImpossibleConversion { into: b }, + ) => a == b, + ( + InteropErrorInner::BetterConversionExists { context: a }, + InteropErrorInner::BetterConversionExists { context: b }, + ) => a == b, + ( + InteropErrorInner::TypeMismatch { + expected: a, + got: b, + }, + InteropErrorInner::TypeMismatch { + expected: c, + got: d, + }, + ) => a == c && b == d, + ( + InteropErrorInner::StringTypeMismatch { + expected: a, + got: b, + }, + InteropErrorInner::StringTypeMismatch { + expected: c, + got: d, + }, + ) => a == c && b == d, + ( + InteropErrorInner::ValueMismatch { + expected: a, + got: b, + }, + InteropErrorInner::ValueMismatch { + expected: c, + got: d, + }, + ) => a == c && b == d, + ( + InteropErrorInner::LengthMismatch { + expected: a, + got: b, + }, + InteropErrorInner::LengthMismatch { + expected: c, + got: d, + }, + ) => a == c && b == d, + ( + InteropErrorInner::CouldNotDowncast { from: a, to: b }, + InteropErrorInner::CouldNotDowncast { from: c, to: d }, + ) => a == c && b == d, + ( + InteropErrorInner::GarbageCollectedAllocation { reference: a }, + InteropErrorInner::GarbageCollectedAllocation { reference: b }, + ) => a == b, + ( + InteropErrorInner::ReflectionPathError { + error: a, + reference: b, + }, + InteropErrorInner::ReflectionPathError { + error: c, + reference: d, + }, + ) => a == c && b == d, + ( + InteropErrorInner::UnsupportedOperation { + base: a, + value: _b, + operation: c, + }, + InteropErrorInner::UnsupportedOperation { + base: d, + value: _e, + operation: f, + }, + ) => a == d && c == f, + ( + InteropErrorInner::InvalidIndex { + value: a, + reason: b, + }, + InteropErrorInner::InvalidIndex { + value: c, + reason: d, + }, + ) => a == c && b == d, + ( + InteropErrorInner::MissingEntity { entity: a }, + InteropErrorInner::MissingEntity { entity: b }, + ) => a == b, + ( + InteropErrorInner::InvalidComponent { component_id: a }, + InteropErrorInner::InvalidComponent { component_id: b }, + ) => a == b, + ( + InteropErrorInner::FunctionCallError { inner: a }, + InteropErrorInner::FunctionCallError { inner: b }, + ) => a == b, + ( + InteropErrorInner::MissingFunctionError { + on: a, + function_name: _b, + }, + InteropErrorInner::MissingFunctionError { + on: c, + function_name: _d, + }, + ) => a == c, + ( + InteropErrorInner::FunctionInteropError { + function_name: a, + on: b, + error: c, + }, + InteropErrorInner::FunctionInteropError { + function_name: d, + on: e, + error: f, + }, + ) => a == d && b == e && c == f, + ( + InteropErrorInner::FunctionArgConversionError { + argument: a, + error: b, + }, + InteropErrorInner::FunctionArgConversionError { + argument: c, + error: d, + }, + ) => a == c && b == d, + ( + InteropErrorInner::OtherError { error: a }, + InteropErrorInner::OtherError { error: b }, + ) => a.to_string() == b.to_string(), + ( + InteropErrorInner::UnregisteredComponentOrResourceType { type_name: a }, + InteropErrorInner::UnregisteredComponentOrResourceType { type_name: b }, + ) => a == b, + ( + InteropErrorInner::Invariant { message: a }, + InteropErrorInner::Invariant { message: b }, + ) => a == b, + _ => false, + } } } diff --git a/crates/bevy_mod_scripting_core/src/handler.rs b/crates/bevy_mod_scripting_core/src/handler.rs index 650aaed64d..12e75a3377 100644 --- a/crates/bevy_mod_scripting_core/src/handler.rs +++ b/crates/bevy_mod_scripting_core/src/handler.rs @@ -16,7 +16,7 @@ use bevy::{ system::{Resource, SystemState}, world::World, }, - log::{trace, trace_once}, + log::trace_once, prelude::{EventReader, Events, Query, Ref}, }; diff --git a/crates/languages/bevy_mod_scripting_rhai/Cargo.toml b/crates/languages/bevy_mod_scripting_rhai/Cargo.toml index 273bfcd387..2fa76db512 100644 --- a/crates/languages/bevy_mod_scripting_rhai/Cargo.toml +++ b/crates/languages/bevy_mod_scripting_rhai/Cargo.toml @@ -17,7 +17,7 @@ path = "src/lib.rs" [dependencies] bevy = { workspace = true, default-features = false } -rhai = { git = "https://github.com/rhaiscript/rhai", rev = "4ead53eb40f4a18d6f827609041ef1c742f04799" } +rhai = { version = "1.21" } bevy_mod_scripting_core = { workspace = true, features = ["rhai_impls"] } bevy_mod_scripting_functions = { workspace = true, features = [ ], default-features = false } 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 5ea9038351..0ec05baf8a 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 @@ -89,6 +89,7 @@ impl FunctionWithReceiver { impl IntoDynamic for FunctionWithReceiver { fn into_dynamic(self) -> Result> { + #[allow(deprecated)] Ok(Dynamic::from(FnPtr::from_fn( self.function.name().to_string(), move |_ctxt: NativeCallContext, args: &mut [&mut Dynamic]| { @@ -139,6 +140,7 @@ impl IntoDynamic for ScriptValue { ScriptValue::Reference(reflect_reference) => { Dynamic::from(RhaiReflectReference(reflect_reference)) } + #[allow(deprecated)] ScriptValue::FunctionMut(func) => Dynamic::from(FnPtr::from_fn( func.name().to_string(), move |_ctxt: NativeCallContext, args: &mut [&mut Dynamic]| { @@ -152,6 +154,7 @@ impl IntoDynamic for ScriptValue { out.into_dynamic() }, )?), + #[allow(deprecated)] ScriptValue::Function(func) => Dynamic::from(FnPtr::from_fn( func.name().to_string(), move |_ctxt: NativeCallContext, args: &mut [&mut Dynamic]| {