diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0e10429..3ac7aa8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,7 +9,7 @@ jobs: matrix: os: [ubuntu-22.04, macos-latest, windows-latest] rust: [stable] - lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit] + lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] include: - os: ubuntu-22.04 target: x86_64-unknown-linux-gnu @@ -104,7 +104,7 @@ jobs: matrix: os: [ubuntu-22.04, macos-latest, windows-latest] rust: [stable, nightly] - lua: [lua54, lua53, lua52, lua51, luajit, luajit52, luau, luau-jit] + lua: [lua54, lua53, lua52, lua51, luajit, luajit52, luau, luau-jit, luau-vector4] include: - os: ubuntu-22.04 target: x86_64-unknown-linux-gnu @@ -140,7 +140,7 @@ jobs: matrix: os: [ubuntu-22.04] rust: [nightly] - lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit] + lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] include: - os: ubuntu-22.04 target: x86_64-unknown-linux-gnu @@ -222,7 +222,7 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit] + lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable diff --git a/Cargo.toml b/Cargo.toml index 6c903dc..8767f98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ luajit = ["ffi/luajit"] luajit52 = ["luajit", "ffi/luajit52"] luau = ["ffi/luau"] luau-jit = ["luau", "ffi/luau-codegen"] +luau-vector4 = ["luau", "ffi/luau-vector4"] vendored = ["ffi/vendored"] module = ["mlua_derive", "ffi/module"] async = ["futures-util"] diff --git a/README.md b/README.md index abbc2ba..c9fd581 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ Below is a list of the available feature flags. By default `mlua` does not enabl * `luajit52`: activate [LuaJIT] support with partial compatibility with Lua 5.2 * `luau`: activate [Luau] support (auto vendored mode) * `luau-jit`: activate [Luau] support with experimental jit backend. This is unstable feature and not recommended to use. +* `luau-vector4`: activate [Luau] support with 4-dimensional vector. * `vendored`: build static Lua(JIT) library from sources during `mlua` compilation using [lua-src] or [luajit-src] crates * `module`: enable module mode (building loadable `cdylib` library for Lua) * `async`: enable async/await support (any executor can be used, eg. [tokio] or [async-std]) diff --git a/mlua-sys/Cargo.toml b/mlua-sys/Cargo.toml index 9dca3a1..ceabe97 100644 --- a/mlua-sys/Cargo.toml +++ b/mlua-sys/Cargo.toml @@ -27,6 +27,7 @@ luajit = [] luajit52 = ["luajit"] luau = ["luau0-src"] luau-codegen = ["luau"] +luau-vector4 = ["luau"] vendored = ["lua-src", "luajit-src"] module = [] @@ -38,4 +39,4 @@ cfg-if = "1.0" pkg-config = "0.3.17" lua-src = { version = ">= 546.0.0, < 550.0.0", optional = true } luajit-src = { version = ">= 210.4.0, < 220.0.0", optional = true } -luau0-src = { version = "0.5.8", optional = true } +luau0-src = { version = "0.5.10", optional = true } diff --git a/mlua-sys/build/find_vendored.rs b/mlua-sys/build/find_vendored.rs index 13940dc..0da3cee 100644 --- a/mlua-sys/build/find_vendored.rs +++ b/mlua-sys/build/find_vendored.rs @@ -23,6 +23,7 @@ pub fn probe_lua() -> Option { #[cfg(feature = "luau")] let artifacts = luau0_src::Build::new() .enable_codegen(cfg!(feature = "luau-codegen")) + .set_vector_size(if cfg!(feature = "luau-vector4") { 4 } else { 3 }) .build(); artifacts.print_cargo_metadata(); diff --git a/mlua-sys/src/luau/lua.rs b/mlua-sys/src/luau/lua.rs index 12c2e08..d9c10ab 100644 --- a/mlua-sys/src/luau/lua.rs +++ b/mlua-sys/src/luau/lua.rs @@ -156,7 +156,10 @@ extern "C" { pub fn lua_pushnumber(L: *mut lua_State, n: lua_Number); pub fn lua_pushinteger(L: *mut lua_State, n: lua_Integer); pub fn lua_pushunsigned(L: *mut lua_State, n: lua_Unsigned); + #[cfg(not(feature = "luau-vector4"))] pub fn lua_pushvector(L: *mut lua_State, x: c_float, y: c_float, z: c_float); + #[cfg(feature = "luau-vector4")] + pub fn lua_pushvector(L: *mut lua_State, x: c_float, y: c_float, z: c_float, w: c_float); #[link_name = "lua_pushlstring"] pub fn lua_pushlstring_(L: *mut lua_State, s: *const c_char, l: usize); #[link_name = "lua_pushstring"] diff --git a/src/conversion.rs b/src/conversion.rs index ebd6343..dd77e5d 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -292,6 +292,29 @@ impl<'lua> FromLua<'lua> for LightUserData { } } +#[cfg(feature = "luau")] +impl<'lua> IntoLua<'lua> for crate::types::Vector { + #[inline] + fn into_lua(self, _: &'lua Lua) -> Result> { + Ok(Value::Vector(self)) + } +} + +#[cfg(feature = "luau")] +impl<'lua> FromLua<'lua> for crate::types::Vector { + #[inline] + fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result { + match value { + Value::Vector(v) => Ok(v), + _ => Err(Error::FromLuaConversionError { + from: value.type_name(), + to: "vector", + message: None, + }), + } + } +} + impl<'lua> IntoLua<'lua> for StdString { #[inline] fn into_lua(self, lua: &'lua Lua) -> Result> { @@ -561,16 +584,17 @@ where fn from_lua(value: Value<'lua>, _lua: &'lua Lua) -> Result { match value { #[cfg(feature = "luau")] - Value::Vector(x, y, z) if N == 3 => Ok(mlua_expect!( - vec![ - T::from_lua(Value::Number(x as _), _lua)?, - T::from_lua(Value::Number(y as _), _lua)?, - T::from_lua(Value::Number(z as _), _lua)?, - ] - .try_into() - .map_err(|_| ()), - "cannot convert vector to array" - )), + #[rustfmt::skip] + Value::Vector(v) if N == crate::types::Vector::SIZE => unsafe { + use std::{mem, ptr}; + let mut arr: [mem::MaybeUninit; N] = mem::MaybeUninit::uninit().assume_init(); + ptr::write(arr[0].as_mut_ptr() , T::from_lua(Value::Number(v.x() as _), _lua)?); + ptr::write(arr[1].as_mut_ptr(), T::from_lua(Value::Number(v.y() as _), _lua)?); + ptr::write(arr[2].as_mut_ptr(), T::from_lua(Value::Number(v.z() as _), _lua)?); + #[cfg(feature = "luau-vector4")] + ptr::write(arr[3].as_mut_ptr(), T::from_lua(Value::Number(v.w() as _), _lua)?); + Ok(mem::transmute_copy(&arr)) + }, Value::Table(table) => { let vec = table.sequence_values().collect::>>()?; vec.try_into() @@ -614,12 +638,6 @@ impl<'lua, T: FromLua<'lua>> FromLua<'lua> for Vec { #[inline] fn from_lua(value: Value<'lua>, _lua: &'lua Lua) -> Result { match value { - #[cfg(feature = "luau")] - Value::Vector(x, y, z) => Ok(vec![ - T::from_lua(Value::Number(x as _), _lua)?, - T::from_lua(Value::Number(y as _), _lua)?, - T::from_lua(Value::Number(z as _), _lua)?, - ]), Value::Table(table) => table.sequence_values().collect(), _ => Err(Error::FromLuaConversionError { from: value.type_name(), diff --git a/src/lib.rs b/src/lib.rs index d27aace..023bd21 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -130,7 +130,11 @@ pub use crate::hook::HookTriggers; #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] -pub use crate::{chunk::Compiler, function::CoverageInfo, types::VmState}; +pub use crate::{ + chunk::Compiler, + function::CoverageInfo, + types::{Vector, VmState}, +}; #[cfg(feature = "async")] pub use crate::thread::AsyncThread; diff --git a/src/lua.rs b/src/lua.rs index 17d041c..d860c69 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -50,7 +50,10 @@ use crate::{hook::HookTriggers, types::HookCallback}; #[cfg(feature = "luau")] use crate::types::InterruptCallback; #[cfg(any(feature = "luau", doc))] -use crate::{chunk::Compiler, types::VmState}; +use crate::{ + chunk::Compiler, + types::{Vector, VmState}, +}; #[cfg(feature = "async")] use { @@ -2295,8 +2298,11 @@ impl Lua { } #[cfg(feature = "luau")] - Value::Vector(x, y, z) => { - ffi::lua_pushvector(state, x, y, z); + Value::Vector(v) => { + #[cfg(not(feature = "luau-vector4"))] + ffi::lua_pushvector(state, v.x(), v.y(), v.z()); + #[cfg(feature = "luau-vector4")] + ffi::lua_pushvector(state, v.x(), v.y(), v.z(), v.w()); } Value::String(s) => { @@ -2379,7 +2385,10 @@ impl Lua { ffi::LUA_TVECTOR => { let v = ffi::lua_tovector(state, -1); mlua_debug_assert!(!v.is_null(), "vector is null"); - let vec = Value::Vector(*v, *v.add(1), *v.add(2)); + #[cfg(not(feature = "luau-vector4"))] + let vec = Value::Vector(Vector([*v, *v.add(1), *v.add(2)])); + #[cfg(feature = "luau-vector4")] + let vec = Value::Vector(Vector([*v, *v.add(1), *v.add(2), *v.add(3)])); ffi::lua_pop(state, 1); vec } diff --git a/src/luau.rs b/src/luau.rs index f64e3a3..3a9f379 100644 --- a/src/luau.rs +++ b/src/luau.rs @@ -126,6 +126,12 @@ unsafe extern "C" fn lua_vector(state: *mut ffi::lua_State) -> c_int { let x = ffi::luaL_checknumber(state, 1) as c_float; let y = ffi::luaL_checknumber(state, 2) as c_float; let z = ffi::luaL_checknumber(state, 3) as c_float; + #[cfg(feature = "luau-vector4")] + let w = ffi::luaL_checknumber(state, 4) as c_float; + + #[cfg(not(feature = "luau-vector4"))] ffi::lua_pushvector(state, x, y, z); + #[cfg(feature = "luau-vector4")] + ffi::lua_pushvector(state, x, y, z, w); 1 } diff --git a/src/prelude.rs b/src/prelude.rs index f5af8e2..956de61 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -23,7 +23,7 @@ pub use crate::HookTriggers as LuaHookTriggers; #[cfg(feature = "luau")] #[doc(no_inline)] -pub use crate::{CoverageInfo as LuaCoverageInfo, VmState as LuaVmState}; +pub use crate::{CoverageInfo as LuaCoverageInfo, Vector as LuaVector, VmState as LuaVmState}; #[cfg(feature = "async")] #[doc(no_inline)] diff --git a/src/serde/de.rs b/src/serde/de.rs index aab303d..19c37cb 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -124,7 +124,7 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> { #[allow(clippy::useless_conversion)] Value::Number(n) => visitor.visit_f64(n.into()), #[cfg(feature = "luau")] - Value::Vector(_, _, _) => self.deserialize_seq(visitor), + Value::Vector(_) => self.deserialize_seq(visitor), Value::String(s) => match s.to_str() { Ok(s) => visitor.visit_str(s), Err(_) => visitor.visit_bytes(s.as_bytes()), @@ -223,9 +223,9 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> { { match self.value { #[cfg(feature = "luau")] - Value::Vector(x, y, z) => { + Value::Vector(vec) => { let mut deserializer = VecDeserializer { - vec: [x, y, z], + vec, next: 0, options: self.options, visited: self.visited, @@ -412,7 +412,7 @@ impl<'lua, 'de> de::SeqAccess<'de> for SeqDeserializer<'lua> { #[cfg(feature = "luau")] struct VecDeserializer { - vec: [f32; 3], + vec: crate::types::Vector, next: usize, options: Options, visited: Rc>>, @@ -426,7 +426,7 @@ impl<'de> de::SeqAccess<'de> for VecDeserializer { where T: de::DeserializeSeed<'de>, { - match self.vec.get(self.next) { + match self.vec.0.get(self.next) { Some(&n) => { self.next += 1; let visited = Rc::clone(&self.visited); @@ -439,7 +439,7 @@ impl<'de> de::SeqAccess<'de> for VecDeserializer { } fn size_hint(&self) -> Option { - Some(3) + Some(crate::types::Vector::SIZE) } } diff --git a/src/serde/ser.rs b/src/serde/ser.rs index 9d4bbe5..9b4a3aa 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -7,8 +7,6 @@ use crate::error::{Error, Result}; use crate::lua::Lua; use crate::string::String; use crate::table::Table; -use crate::types::Integer; -use crate::util::{check_stack, StackGuard}; use crate::value::{IntoLua, Value}; /// A struct for serializing Rust values into Lua values. @@ -120,9 +118,9 @@ impl<'lua> ser::Serializer for Serializer<'lua> { // Associated types for keeping track of additional state while serializing // compound data structures like sequences and maps. - type SerializeSeq = SerializeVec<'lua>; - type SerializeTuple = SerializeVec<'lua>; - type SerializeTupleStruct = SerializeVec<'lua>; + type SerializeSeq = SerializeSeq<'lua>; + type SerializeTuple = SerializeSeq<'lua>; + type SerializeTupleStruct = SerializeSeq<'lua>; type SerializeTupleVariant = SerializeTupleVariant<'lua>; type SerializeMap = SerializeMap<'lua>; type SerializeStruct = SerializeMap<'lua>; @@ -240,8 +238,7 @@ impl<'lua> ser::Serializer for Serializer<'lua> { if self.options.set_array_metatable { table.set_metatable(Some(self.lua.array_metatable())); } - let options = self.options; - Ok(SerializeVec { table, options }) + Ok(SerializeSeq::new(table, self.options)) } #[inline] @@ -252,9 +249,14 @@ impl<'lua> ser::Serializer for Serializer<'lua> { #[inline] fn serialize_tuple_struct( self, - _name: &'static str, + name: &'static str, len: usize, ) -> Result { + #[cfg(feature = "luau")] + if name == "Vector" && len == crate::types::Vector::SIZE { + return Ok(SerializeSeq::new_vector(self.lua, self.options)); + } + _ = name; self.serialize_seq(Some(len)) } @@ -305,12 +307,40 @@ impl<'lua> ser::Serializer for Serializer<'lua> { } #[doc(hidden)] -pub struct SerializeVec<'lua> { - table: Table<'lua>, +pub struct SerializeSeq<'lua> { + lua: &'lua Lua, + #[cfg(feature = "luau")] + vector: Option, + table: Option>, + next: usize, options: Options, } -impl<'lua> ser::SerializeSeq for SerializeVec<'lua> { +impl<'lua> SerializeSeq<'lua> { + const fn new(table: Table<'lua>, options: Options) -> Self { + Self { + lua: table.0.lua, + #[cfg(feature = "luau")] + vector: None, + table: Some(table), + next: 0, + options, + } + } + + #[cfg(feature = "luau")] + const fn new_vector(lua: &'lua Lua, options: Options) -> Self { + Self { + lua, + vector: Some(crate::types::Vector::zero()), + table: None, + next: 0, + options, + } + } +} + +impl<'lua> ser::SerializeSeq for SerializeSeq<'lua> { type Ok = Value<'lua>; type Error = Error; @@ -318,35 +348,19 @@ impl<'lua> ser::SerializeSeq for SerializeVec<'lua> { where T: Serialize + ?Sized, { - let lua = self.table.0.lua; - let state = lua.state(); - let value = lua.to_value_with(value, self.options)?; - unsafe { - let _sg = StackGuard::new(state); - check_stack(state, 4)?; - - lua.push_ref(&self.table.0); - lua.push_value(value)?; - if lua.unlikely_memory_error() { - let len = ffi::lua_rawlen(state, -2) as Integer; - ffi::lua_rawseti(state, -2, len + 1); - ffi::lua_pop(state, 1); - Ok(()) - } else { - protect_lua!(state, 2, 0, fn(state) { - let len = ffi::lua_rawlen(state, -2) as Integer; - ffi::lua_rawseti(state, -2, len + 1); - }) - } - } + let value = self.lua.to_value_with(value, self.options)?; + let table = self.table.as_ref().unwrap(); + table.raw_seti(self.next + 1, value)?; + self.next += 1; + Ok(()) } fn end(self) -> Result> { - Ok(Value::Table(self.table)) + Ok(Value::Table(self.table.unwrap())) } } -impl<'lua> ser::SerializeTuple for SerializeVec<'lua> { +impl<'lua> ser::SerializeTuple for SerializeSeq<'lua> { type Ok = Value<'lua>; type Error = Error; @@ -362,7 +376,7 @@ impl<'lua> ser::SerializeTuple for SerializeVec<'lua> { } } -impl<'lua> ser::SerializeTupleStruct for SerializeVec<'lua> { +impl<'lua> ser::SerializeTupleStruct for SerializeSeq<'lua> { type Ok = Value<'lua>; type Error = Error; @@ -370,10 +384,22 @@ impl<'lua> ser::SerializeTupleStruct for SerializeVec<'lua> { where T: Serialize + ?Sized, { + #[cfg(feature = "luau")] + if let Some(vector) = self.vector.as_mut() { + let value = self.lua.to_value_with(value, self.options)?; + let value = self.lua.unpack(value)?; + vector.0[self.next] = value; + self.next += 1; + return Ok(()); + } ser::SerializeSeq::serialize_element(self, value) } fn end(self) -> Result> { + #[cfg(feature = "luau")] + if let Some(vector) = self.vector { + return Ok(Value::Vector(vector)); + } ser::SerializeSeq::end(self) } } @@ -394,9 +420,7 @@ impl<'lua> ser::SerializeTupleVariant for SerializeTupleVariant<'lua> { T: Serialize + ?Sized, { let lua = self.table.0.lua; - let idx = self.table.raw_len() + 1; - self.table - .raw_insert(idx, lua.to_value_with(value, self.options)?) + self.table.raw_push(lua.to_value_with(value, self.options)?) } fn end(self) -> Result> { diff --git a/src/table.rs b/src/table.rs index e87ecc9..0f6da68 100644 --- a/src/table.rs +++ b/src/table.rs @@ -720,6 +720,32 @@ impl<'lua> Table<'lua> { } } + /// Sets element value at position `idx` without invoking metamethods. + #[allow(dead_code)] + pub(crate) fn raw_seti>(&self, idx: usize, value: V) -> Result<()> { + #[cfg(feature = "luau")] + self.check_readonly_write()?; + + let lua = self.0.lua; + let state = lua.state(); + let value = value.into_lua(lua)?; + + unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 5)?; + + lua.push_ref(&self.0); + lua.push_value(value)?; + + if lua.unlikely_memory_error() { + ffi::lua_rawseti(state, -2, idx as _); + } else { + protect_lua!(state, 2, 0, |state| ffi::lua_rawseti(state, -2, idx as _))?; + } + Ok(()) + } + } + #[cfg(feature = "serialize")] pub(crate) fn is_array(&self) -> bool { let lua = self.0.lua; @@ -810,7 +836,7 @@ where lua.push_ref(&self.0); - let len = ffi::lua_rawlen(state, -1) as usize; + let len = ffi::lua_rawlen(state, -1); for i in 0..len { ffi::lua_rawgeti(state, -1, (i + 1) as _); let val = lua.pop_value(); diff --git a/src/types.rs b/src/types.rs index 2465fa9..a272249 100644 --- a/src/types.rs +++ b/src/types.rs @@ -26,6 +26,9 @@ use crate::value::MultiValue; #[cfg(feature = "unstable")] use {crate::lua::LuaInner, std::marker::PhantomData}; +#[cfg(all(feature = "luau", feature = "serialize"))] +use serde::ser::{Serialize, SerializeTupleStruct, Serializer}; + /// Type of Lua integer numbers. pub type Integer = ffi::lua_Integer; /// Type of Lua floating point numbers. @@ -91,6 +94,92 @@ pub trait MaybeSend {} #[cfg(not(feature = "send"))] impl MaybeSend for T {} +/// A Luau vector type. +/// +/// By default vectors are 3-dimensional, but can be 4-dimensional +/// if the `luau-vector4` feature is enabled. +#[cfg(any(feature = "luau", doc))] +#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] +#[derive(Debug, Default, Clone, Copy, PartialEq)] +pub struct Vector(pub(crate) [f32; Self::SIZE]); + +#[cfg(feature = "luau")] +impl fmt::Display for Vector { + #[rustfmt::skip] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + #[cfg(not(feature = "luau-vector4"))] + return write!(f, "vector({}, {}, {})", self.x(), self.y(), self.z()); + #[cfg(feature = "luau-vector4")] + return write!(f, "vector({}, {}, {}, {})", self.x(), self.y(), self.z(), self.w()); + } +} + +#[cfg(feature = "luau")] +impl Vector { + pub(crate) const SIZE: usize = if cfg!(feature = "luau-vector4") { 4 } else { 3 }; + + /// Creates a new vector. + #[cfg(not(feature = "luau-vector4"))] + pub const fn new(x: f32, y: f32, z: f32) -> Self { + Self([x, y, z]) + } + + /// Creates a new vector. + #[cfg(feature = "luau-vector4")] + pub const fn new(x: f32, y: f32, z: f32, w: f32) -> Self { + Self([x, y, z, w]) + } + + /// Creates a new vector with all components set to `0.0`. + #[doc(hidden)] + pub const fn zero() -> Self { + Self([0.0; Self::SIZE]) + } + + /// Returns 1st component of the vector. + pub const fn x(&self) -> f32 { + self.0[0] + } + + /// Returns 2nd component of the vector. + pub const fn y(&self) -> f32 { + self.0[1] + } + + /// Returns 3rd component of the vector. + pub const fn z(&self) -> f32 { + self.0[2] + } + + /// Returns 4th component of the vector. + #[cfg(any(feature = "luau-vector4", doc))] + #[cfg_attr(docsrs, doc(cfg(feature = "luau-vector4")))] + pub const fn w(&self) -> f32 { + self.0[3] + } +} + +#[cfg(all(feature = "luau", feature = "serialize"))] +impl Serialize for Vector { + fn serialize(&self, serializer: S) -> StdResult { + let mut ts = serializer.serialize_tuple_struct("Vector", Self::SIZE)?; + ts.serialize_field(&self.x())?; + ts.serialize_field(&self.y())?; + ts.serialize_field(&self.z())?; + #[cfg(feature = "luau-vector4")] + ts.serialize_field(&self.w())?; + ts.end() + } +} + +#[cfg(feature = "luau")] +impl PartialEq<[f32; Self::SIZE]> for Vector { + #[inline] + fn eq(&self, other: &[f32; Self::SIZE]) -> bool { + self.0 == *other + } +} + pub(crate) struct DestructedUserdata; /// An auto generated key into the Lua registry. diff --git a/src/value.rs b/src/value.rs index 3f17456..f363eb5 100644 --- a/src/value.rs +++ b/src/value.rs @@ -44,7 +44,7 @@ pub enum Value<'lua> { /// A Luau vector. #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] - Vector(f32, f32, f32), + Vector(crate::types::Vector), /// An interned string, managed by Lua. /// /// Unlike Rust strings, Lua strings may not be valid UTF-8. @@ -79,7 +79,7 @@ impl<'lua> Value<'lua> { Value::Integer(_) => "integer", Value::Number(_) => "number", #[cfg(feature = "luau")] - Value::Vector(_, _, _) => "vector", + Value::Vector(_) => "vector", Value::String(_) => "string", Value::Table(_) => "table", Value::Function(_) => "function", @@ -143,7 +143,7 @@ impl<'lua> Value<'lua> { Value::Integer(i) => Ok(i.to_string()), Value::Number(n) => Ok(n.to_string()), #[cfg(feature = "luau")] - Value::Vector(x, y, z) => Ok(format!("vector({x}, {y}, {z})")), + Value::Vector(v) => Ok(v.to_string()), Value::String(s) => Ok(s.to_str()?.to_string()), Value::Table(Table(r)) | Value::Function(Function(r)) @@ -218,7 +218,7 @@ impl<'lua> Value<'lua> { Value::Integer(i) => write!(fmt, "{i}"), Value::Number(n) => write!(fmt, "{n}"), #[cfg(feature = "luau")] - Value::Vector(x, y, z) => write!(fmt, "vector({x}, {y}, {z})"), + Value::Vector(v) => write!(fmt, "{v}"), Value::String(s) => write!(fmt, "{s:?}"), Value::Table(t) if recursive && !visited.contains(&t.to_pointer()) => { t.fmt_pretty(fmt, ident, visited) @@ -249,7 +249,7 @@ impl fmt::Debug for Value<'_> { Value::Integer(i) => write!(fmt, "Integer({i})"), Value::Number(n) => write!(fmt, "Number({n})"), #[cfg(feature = "luau")] - Value::Vector(x, y, z) => write!(fmt, "Vector({x}, {y}, {z})"), + Value::Vector(v) => write!(fmt, "{v:?}"), Value::String(s) => write!(fmt, "String({s:?})"), Value::Table(t) => write!(fmt, "{t:?}"), Value::Function(f) => write!(fmt, "{f:?}"), @@ -271,7 +271,7 @@ impl<'lua> PartialEq for Value<'lua> { (Value::Number(a), Value::Integer(b)) => *a == *b as Number, (Value::Number(a), Value::Number(b)) => *a == *b, #[cfg(feature = "luau")] - (Value::Vector(x1, y1, z1), Value::Vector(x2, y2, z2)) => (x1, y1, z1) == (x2, y2, z2), + (Value::Vector(v1), Value::Vector(v2)) => v1 == v2, (Value::String(a), Value::String(b)) => a == b, (Value::Table(a), Value::Table(b)) => a == b, (Value::Function(a), Value::Function(b)) => a == b, @@ -303,7 +303,7 @@ impl<'lua> Serialize for Value<'lua> { .serialize_i64((*i).try_into().expect("cannot convert Lua Integer to i64")), Value::Number(n) => serializer.serialize_f64(*n), #[cfg(feature = "luau")] - Value::Vector(x, y, z) => (x, y, z).serialize(serializer), + Value::Vector(v) => v.serialize(serializer), Value::String(s) => s.serialize(serializer), Value::Table(t) => t.serialize(serializer), Value::UserData(ud) => ud.serialize(serializer), diff --git a/tests/luau.rs b/tests/luau.rs index 33fa335..28e2ece 100644 --- a/tests/luau.rs +++ b/tests/luau.rs @@ -7,7 +7,9 @@ use std::panic::{catch_unwind, AssertUnwindSafe}; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; -use mlua::{Compiler, CoverageInfo, Error, Lua, Result, Table, ThreadStatus, Value, VmState}; +use mlua::{ + Compiler, CoverageInfo, Error, Lua, Result, Table, ThreadStatus, Value, Vector, VmState, +}; #[test] fn test_version() -> Result<()> { @@ -50,13 +52,18 @@ fn test_require() -> Result<()> { .exec() } +#[cfg(not(feature = "luau-vector4"))] #[test] fn test_vectors() -> Result<()> { let lua = Lua::new(); - let v: [f32; 3] = lua.load("vector(1, 2, 3) + vector(3, 2, 1)").eval()?; + let v: Vector = lua.load("vector(1, 2, 3) + vector(3, 2, 1)").eval()?; assert_eq!(v, [4.0, 4.0, 4.0]); + // Test conversion into Rust array + let v: [f64; 3] = lua.load("vector(1, 2, 3)").eval()?; + assert!(v == [1.0, 2.0, 3.0]); + // Test vector methods lua.load( r#" @@ -83,6 +90,46 @@ fn test_vectors() -> Result<()> { Ok(()) } +#[cfg(feature = "luau-vector4")] +#[test] +fn test_vectors() -> Result<()> { + let lua = Lua::new(); + + let v: Vector = lua.load("vector(1, 2, 3, 4) + vector(4, 3, 2, 1)").eval()?; + assert_eq!(v, [5.0, 5.0, 5.0, 5.0]); + + // Test conversion into Rust array + let v: [f64; 4] = lua.load("vector(1, 2, 3, 4)").eval()?; + assert!(v == [1.0, 2.0, 3.0, 4.0]); + + // Test vector methods + lua.load( + r#" + local v = vector(1, 2, 3, 4) + assert(v.x == 1) + assert(v.y == 2) + assert(v.z == 3) + assert(v.w == 4) + "#, + ) + .exec()?; + + // Test vector methods (fastcall) + lua.load( + r#" + local v = vector(1, 2, 3, 4) + assert(v.x == 1) + assert(v.y == 2) + assert(v.z == 3) + assert(v.w == 4) + "#, + ) + .set_compiler(Compiler::new().set_vector_ctor(Some("vector".to_string()))) + .exec()?; + + Ok(()) +} + #[test] fn test_readonly_table() -> Result<()> { let lua = Lua::new(); @@ -254,8 +301,8 @@ fn test_coverage() -> Result<()> { let f = lua .load( - r#"local v = vector(1, 2, 3) - assert(v.x == 1 and v.y == 2 and v.z == 3) + r#"local s = "abc" + assert(#s == 3) function abc(i) if i < 5 then diff --git a/tests/serde.rs b/tests/serde.rs index c092c43..660c83c 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -145,7 +145,7 @@ fn test_serialize_failure() -> Result<(), Box> { Ok(()) } -#[cfg(feature = "luau")] +#[cfg(all(feature = "luau", not(feature = "luau-vector4")))] #[test] fn test_serialize_vector() -> Result<(), Box> { let lua = Lua::new(); @@ -153,7 +153,7 @@ fn test_serialize_vector() -> Result<(), Box> { let globals = lua.globals(); globals.set( "vector", - lua.create_function(|_, (x, y, z)| Ok(Value::Vector(x, y, z)))?, + lua.create_function(|_, (x, y, z)| Ok(mlua::Vector::new(x, y, z)))?, )?; let val = lua.load("{_vector = vector(1, 2, 3)}").eval::()?; @@ -168,6 +168,29 @@ fn test_serialize_vector() -> Result<(), Box> { Ok(()) } +#[cfg(feature = "luau-vector4")] +#[test] +fn test_serialize_vector() -> Result<(), Box> { + let lua = Lua::new(); + + let globals = lua.globals(); + globals.set( + "vector", + lua.create_function(|_, (x, y, z, w)| Ok(mlua::Vector::new(x, y, z, w)))?, + )?; + + let val = lua.load("{_vector = vector(1, 2, 3, 4)}").eval::()?; + let json = serde_json::json!({ + "_vector": [1.0, 2.0, 3.0, 4.0], + }); + assert_eq!(serde_json::to_value(&val)?, json); + + let expected_json = lua.from_value::(val)?; + assert_eq!(expected_json, json); + + Ok(()) +} + #[test] fn test_to_value_struct() -> LuaResult<()> { let lua = Lua::new(); diff --git a/tests/value.rs b/tests/value.rs index 1fc1e66..13d1ef7 100644 --- a/tests/value.rs +++ b/tests/value.rs @@ -101,11 +101,16 @@ fn test_value_to_string() -> Result<()> { ); assert_eq!(Value::Integer(1).to_string()?, "1"); assert_eq!(Value::Number(34.59).to_string()?, "34.59"); - #[cfg(feature = "luau")] + #[cfg(all(feature = "luau", not(feature = "luau-vector4")))] assert_eq!( - Value::Vector(10.0, 11.1, 12.2).to_string()?, + Value::Vector(mlua::Vector::new(10.0, 11.1, 12.2)).to_string()?, "vector(10, 11.1, 12.2)" ); + #[cfg(feature = "luau-vector4")] + assert_eq!( + Value::Vector(mlua::Vector::new(10.0, 11.1, 12.2, 13.3)).to_string()?, + "vector(10, 11.1, 12.2, 13.3)" + ); assert_eq!( Value::String(lua.create_string("hello")?).to_string()?, "hello"