From ca814580ac43117b6cfca41ef911fa96e09ae3a4 Mon Sep 17 00:00:00 2001 From: Matt Kleinschafer Date: Sun, 14 Jul 2024 19:09:25 +1000 Subject: [PATCH] Some work on scripting interop --- crates/scripting/src/callbacks.rs | 74 +++++++++++++++++++ crates/scripting/src/lang/javascript.rs | 36 ++++----- crates/scripting/src/lang/lua.rs | 8 +- crates/scripting/src/lib.rs | 21 ++---- .../scripting/src/{interop.rs => values.rs} | 10 +-- src/lib.rs | 2 + 6 files changed, 104 insertions(+), 47 deletions(-) create mode 100644 crates/scripting/src/callbacks.rs rename crates/scripting/src/{interop.rs => values.rs} (91%) diff --git a/crates/scripting/src/callbacks.rs b/crates/scripting/src/callbacks.rs new file mode 100644 index 00000000..25c90ba0 --- /dev/null +++ b/crates/scripting/src/callbacks.rs @@ -0,0 +1,74 @@ +use super::*; + +/// A callback that can be called from a script. +pub trait ScriptCallback { + /// Returns the number of arguments the callback expects. + fn argument_count(&self) -> usize; + + /// Calls the callback with the given arguments. + fn call(&self, args: &[ScriptValue]) -> Result; +} + +/// Implements the `ScriptCallback` trait for functions with up to 5 arguments. +macro_rules! impl_callback { + (@call $len:literal $self:ident $args:ident ) => { + $self() + }; + + (@call $len:literal $self:ident $args:ident $( $arg:ident ),* ) => { + { + let mut iter = $args.into_iter(); + + $self( + $($arg::from_script_value(iter.next().unwrap()),)* + ) + } + }; + + [ $( $len:literal : ( $( $arg:ident, )* ), )* ] => { + $( + impl< + $( $arg, )* + R, + F, + > ScriptCallback> for F + where + $( $arg: FromScriptValue, )* + R: ToScriptValue, + F: Fn( $( $arg, )* ) -> R, + { + fn argument_count(&self) -> usize { + $len + } + + fn call(&self, args: &[ScriptValue]) -> Result { + if args.len() != $len { + return Err(ScriptError::ExecutionError(format!( + "Invalid argument count: Expected {}, got {}", + self.argument_count(), + args.len() + ))); + } + + // recursive + let result = impl_callback!(@call $len self args $($arg),* ); + + Ok(result.to_script_value()) + } + } + )* + }; +} + +impl_callback![ + 0: (), + 1: (A1,), + 2: (A1, A2,), + 3: (A1, A2, A3,), + 4: (A1, A2, A3, A4,), + 5: (A1, A2, A3, A4, A5,), +]; diff --git a/crates/scripting/src/lang/javascript.rs b/crates/scripting/src/lang/javascript.rs index 95137ed6..f2346226 100644 --- a/crates/scripting/src/lang/javascript.rs +++ b/crates/scripting/src/lang/javascript.rs @@ -31,15 +31,11 @@ impl ScriptRuntime for JavascriptRuntime { self .context .eval(code) - .map(|it| R::from_script_value(it.to_script_value())) + .map(|it| R::from_script_value(&it.to_script_value())) .map_err(|it| ScriptError::ExecutionError(it.to_string())) } - fn add_callback( - &mut self, - _name: &str, - _callback: impl ScriptCallback + 'static, - ) -> Result<(), ScriptError> { + fn add_callback(&mut self, _name: &str, _callback: impl ScriptCallback + 'static) { todo!() } } @@ -61,21 +57,21 @@ impl ToScriptValue for JsValue { } impl FromScriptValue for JsValue { - fn from_script_value(value: ScriptValue) -> Self { - match value.into_variant() { + fn from_script_value(value: &ScriptValue) -> Self { + match value.as_variant() { Variant::Null => JsValue::Null, - Variant::Bool(value) => JsValue::Bool(value), - Variant::U8(value) => JsValue::Int(value as i32), - Variant::U16(value) => JsValue::Int(value as i32), - Variant::U32(value) => JsValue::Int(value as i32), - Variant::U64(value) => JsValue::Int(value as i32), - Variant::I8(value) => JsValue::Int(value as i32), - Variant::I16(value) => JsValue::Int(value as i32), - Variant::I32(value) => JsValue::Int(value), - Variant::I64(value) => JsValue::Int(value as i32), - Variant::F32(value) => JsValue::Float(value as f64), - Variant::F64(value) => JsValue::Float(value), - Variant::String(value) => JsValue::String(value), + Variant::Bool(value) => JsValue::Bool(*value), + Variant::U8(value) => JsValue::Int(*value as i32), + Variant::U16(value) => JsValue::Int(*value as i32), + Variant::U32(value) => JsValue::Int(*value as i32), + Variant::U64(value) => JsValue::Int(*value as i32), + Variant::I8(value) => JsValue::Int(*value as i32), + Variant::I16(value) => JsValue::Int(*value as i32), + Variant::I32(value) => JsValue::Int(*value), + Variant::I64(value) => JsValue::Int(*value as i32), + Variant::F32(value) => JsValue::Float(*value as f64), + Variant::F64(value) => JsValue::Float(*value), + Variant::String(value) => JsValue::String(value.clone()), Variant::StringName(value) => JsValue::String(value.to_string()), Variant::Vec2(value) => JsValue::Array(vec![JsValue::Float(value.x as f64), JsValue::Float(value.y as f64)]), Variant::Vec3(value) => JsValue::Array(vec![ diff --git a/crates/scripting/src/lang/lua.rs b/crates/scripting/src/lang/lua.rs index 5c2a6690..46295c4b 100644 --- a/crates/scripting/src/lang/lua.rs +++ b/crates/scripting/src/lang/lua.rs @@ -34,15 +34,11 @@ impl ScriptRuntime for LuaScriptRuntime { .lua .load(code) .eval() - .map(|it| R::from_script_value(it)) + .map(|it| R::from_script_value(&it)) .map_err(|it| ScriptError::ExecutionError(it.to_string())) } - fn add_callback( - &mut self, - _name: &str, - _callback: impl ScriptCallback + 'static, - ) -> Result<(), ScriptError> { + fn add_callback(&mut self, _name: &str, _callback: impl ScriptCallback + 'static) { todo!() } } diff --git a/crates/scripting/src/lib.rs b/crates/scripting/src/lib.rs index a8335c78..d18c29b7 100644 --- a/crates/scripting/src/lib.rs +++ b/crates/scripting/src/lib.rs @@ -1,10 +1,12 @@ //! Scripting engine for Surreal -pub use interop::*; +pub use callbacks::*; pub use lang::*; +pub use values::*; -mod interop; +mod callbacks; mod lang; +mod values; /// An error that can occur during script execution. #[derive(Debug)] @@ -15,15 +17,6 @@ pub enum ScriptError { ConversionError(String), } -/// A callback that can be called from a script. -pub trait ScriptCallback { - /// Returns the number of arguments the callback expects. - fn argument_count(&self) -> usize; - - /// Calls the callback with the given arguments. - fn call(&self, args: &[ScriptValue]) -> Result; -} - /// Represents a runtime that allows script execution. pub trait ScriptRuntime { /// Evaluates the given code and returns the result. @@ -33,9 +26,5 @@ pub trait ScriptRuntime { fn eval_as(&self, code: &str) -> Result; /// Adds a callback that can be called from scripts. - fn add_callback( - &mut self, - name: &str, - callback: impl ScriptCallback + 'static, - ) -> Result<(), ScriptError>; + fn add_callback(&mut self, name: &str, callback: impl ScriptCallback + 'static); } diff --git a/crates/scripting/src/interop.rs b/crates/scripting/src/values.rs similarity index 91% rename from crates/scripting/src/interop.rs rename to crates/scripting/src/values.rs index 46bdba6d..3483d031 100644 --- a/crates/scripting/src/interop.rs +++ b/crates/scripting/src/values.rs @@ -52,7 +52,7 @@ pub trait ToScriptValue { /// A type that can be converted from a [`ScriptValue`]. pub trait FromScriptValue { /// Converts a [`ScriptValue`] into the type. - fn from_script_value(value: ScriptValue) -> Self; + fn from_script_value(value: &ScriptValue) -> Self; } macro_rules! impl_script_value { @@ -66,9 +66,9 @@ macro_rules! impl_script_value { impl FromScriptValue for $type { #[inline] - fn from_script_value(value: ScriptValue) -> Self { - match value.0 { - Variant::$kind(value) => value, + fn from_script_value(value: &ScriptValue) -> Self { + match &value.0 { + Variant::$kind(value) => value.clone(), _ => panic!("ScriptValue is not convertible"), } } @@ -103,7 +103,7 @@ mod tests { #[test] fn test_script_value_conversion() { let value = true.to_script_value(); - let result = bool::from_script_value(value); + let result = bool::from_script_value(&value); assert_eq!(result, true); } diff --git a/src/lib.rs b/src/lib.rs index aa4e7e88..03cf8861 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,6 +22,8 @@ pub extern crate editor; pub extern crate graphics; #[cfg(feature = "input")] pub extern crate input; +#[cfg(feature = "scripting")] +pub extern crate scripting; pub mod backends { #[cfg(feature = "sdl")]