diff --git a/packages/lib-roblox/src/datatypes/conversion.rs b/packages/lib-roblox/src/datatypes/conversion.rs index d8946f7..45d86f1 100644 --- a/packages/lib-roblox/src/datatypes/conversion.rs +++ b/packages/lib-roblox/src/datatypes/conversion.rs @@ -133,6 +133,8 @@ impl<'lua> FromRbxVariantLua<'lua> for LuaAnyUserData<'lua> { use RbxVariant as Rbx; use super::types::*; match variant { + Rbx::UDim(_) => Ok(lua.create_userdata(UDim::from_rbx_variant(variant)?)?), + Rbx::UDim2(_) => Ok(lua.create_userdata(UDim2::from_rbx_variant(variant)?)?), Rbx::Vector2(_) => Ok(lua.create_userdata(Vector2::from_rbx_variant(variant)?)?), Rbx::Vector2int16(_) => Ok(lua.create_userdata(Vector2int16::from_rbx_variant(variant)?)?), Rbx::Vector3(_) => Ok(lua.create_userdata(Vector3::from_rbx_variant(variant)?)?), @@ -154,8 +156,6 @@ impl<'lua> FromRbxVariantLua<'lua> for LuaAnyUserData<'lua> { // Rbx::Rect(_) => todo!(), // Rbx::Region3(_) => todo!(), // Rbx::Region3int16(_) => todo!(), - // Rbx::UDim(_) => todo!(), - // Rbx::UDim2(_) => todo!(), v => Err(DatatypeConversionError::FromRbxVariant { from: v.variant_name(), to: "LuaValue", @@ -171,7 +171,11 @@ impl<'lua> ToRbxVariant for LuaAnyUserData<'lua> { desired_type: Option, ) -> DatatypeConversionResult { use super::types::*; - if let Ok(v2) = self.borrow::() { + if let Ok(u) = self.borrow::() { + u.to_rbx_variant(desired_type) + } else if let Ok(u2) = self.borrow::() { + u2.to_rbx_variant(desired_type) + } else if let Ok(v2) = self.borrow::() { v2.to_rbx_variant(desired_type) } else if let Ok(v2i) = self.borrow::() { v2i.to_rbx_variant(desired_type) diff --git a/packages/lib-roblox/src/datatypes/types/mod.rs b/packages/lib-roblox/src/datatypes/types/mod.rs index 0baf644..7561e03 100644 --- a/packages/lib-roblox/src/datatypes/types/mod.rs +++ b/packages/lib-roblox/src/datatypes/types/mod.rs @@ -1,8 +1,12 @@ +mod udim; +mod udim2; mod vector2; mod vector2int16; mod vector3; mod vector3int16; +pub use udim::UDim; +pub use udim2::UDim2; pub use vector2::Vector2; pub use vector2int16::Vector2int16; pub use vector3::Vector3; diff --git a/packages/lib-roblox/src/datatypes/types/udim.rs b/packages/lib-roblox/src/datatypes/types/udim.rs new file mode 100644 index 0000000..0bd79f3 --- /dev/null +++ b/packages/lib-roblox/src/datatypes/types/udim.rs @@ -0,0 +1,141 @@ +use core::fmt; +use std::ops; + +use mlua::prelude::*; +use rbx_dom_weak::types::UDim as RbxUDim; + +use super::super::*; + +/** + An implementation of the [UDim](https://create.roblox.com/docs/reference/engine/datatypes/UDim) Roblox datatype. + + This implements all documented properties, methods & constructors of the UDim class as of March 2023. +*/ +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct UDim { + pub(crate) scale: f32, + pub(crate) offset: i32, +} + +impl UDim { + pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> { + datatype_table.set( + "new", + lua.create_function(|_, (scale, offset): (Option, Option)| { + Ok(UDim { + scale: scale.unwrap_or_default(), + offset: offset.unwrap_or_default(), + }) + })?, + ) + } +} + +impl Default for UDim { + fn default() -> Self { + Self { + scale: 0f32, + offset: 0, + } + } +} + +impl fmt::Display for UDim { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}, {}", self.scale, self.offset) + } +} + +impl ops::Neg for UDim { + type Output = Self; + fn neg(self) -> Self::Output { + UDim { + scale: -self.scale, + offset: -self.offset, + } + } +} + +impl ops::Add for UDim { + type Output = Self; + fn add(self, rhs: Self) -> Self::Output { + UDim { + scale: self.scale + rhs.scale, + offset: self.offset + rhs.offset, + } + } +} + +impl ops::Sub for UDim { + type Output = Self; + fn sub(self, rhs: Self) -> Self::Output { + UDim { + scale: self.scale - rhs.scale, + offset: self.offset - rhs.offset, + } + } +} + +impl LuaUserData for UDim { + fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("Scale", |_, this| Ok(this.scale)); + fields.add_field_method_get("Offset", |_, this| Ok(this.offset)); + } + + fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq); + methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string); + methods.add_meta_method(LuaMetaMethod::Unm, |_, this, ()| Ok(-*this)); + methods.add_meta_method(LuaMetaMethod::Add, |_, this, rhs: UDim| Ok(*this + rhs)); + methods.add_meta_method(LuaMetaMethod::Sub, |_, this, rhs: UDim| Ok(*this - rhs)); + } +} + +impl From<&RbxUDim> for UDim { + fn from(v: &RbxUDim) -> Self { + UDim { + scale: v.scale, + offset: v.offset, + } + } +} + +impl From<&UDim> for RbxUDim { + fn from(v: &UDim) -> Self { + RbxUDim { + scale: v.scale, + offset: v.offset, + } + } +} + +impl FromRbxVariant for UDim { + fn from_rbx_variant(variant: &RbxVariant) -> DatatypeConversionResult { + if let RbxVariant::UDim(u) = variant { + Ok(u.into()) + } else { + Err(DatatypeConversionError::FromRbxVariant { + from: variant.variant_name(), + to: "UDim", + detail: None, + }) + } + } +} + +impl ToRbxVariant for UDim { + fn to_rbx_variant( + &self, + desired_type: Option, + ) -> DatatypeConversionResult { + if matches!(desired_type, None | Some(RbxVariantType::UDim)) { + Ok(RbxVariant::UDim(self.into())) + } else { + Err(DatatypeConversionError::ToRbxVariant { + to: desired_type.map(|d| d.variant_name()).unwrap_or("?"), + from: "UDim", + detail: None, + }) + } + } +} diff --git a/packages/lib-roblox/src/datatypes/types/udim2.rs b/packages/lib-roblox/src/datatypes/types/udim2.rs new file mode 100644 index 0000000..d2231f1 --- /dev/null +++ b/packages/lib-roblox/src/datatypes/types/udim2.rs @@ -0,0 +1,205 @@ +use core::fmt; +use std::ops; + +use glam::Vec2; +use mlua::prelude::*; +use rbx_dom_weak::types::UDim2 as RbxUDim2; + +use super::{super::*, UDim}; + +/** + An implementation of the [UDim2](https://create.roblox.com/docs/reference/engine/datatypes/UDim2) Roblox datatype. + + This implements all documented properties, methods & constructors of the UDim2 class as of March 2023. +*/ +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct UDim2 { + pub(crate) x: UDim, + pub(crate) y: UDim, +} + +impl UDim2 { + pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> { + datatype_table.set( + "fromScale", + lua.create_function(|_, (x, y): (Option, Option)| { + Ok(UDim2 { + x: UDim { + scale: x.unwrap_or_default(), + offset: 0, + }, + y: UDim { + scale: y.unwrap_or_default(), + offset: 0, + }, + }) + })?, + )?; + datatype_table.set( + "fromOffset", + lua.create_function(|_, (x, y): (Option, Option)| { + Ok(UDim2 { + x: UDim { + scale: 0f32, + offset: x.unwrap_or_default(), + }, + y: UDim { + scale: 0f32, + offset: y.unwrap_or_default(), + }, + }) + })?, + )?; + type ArgsUDims = (Option, Option); + type ArgsNums = (Option, Option, Option, Option); + datatype_table.set( + "new", + lua.create_function(|lua, args: LuaMultiValue| { + if let Ok((x, y)) = ArgsUDims::from_lua_multi(args.clone(), lua) { + Ok(UDim2 { + x: x.unwrap_or_default(), + y: y.unwrap_or_default(), + }) + } else if let Ok((sx, ox, sy, oy)) = ArgsNums::from_lua_multi(args, lua) { + Ok(UDim2 { + x: UDim { + scale: sx.unwrap_or_default(), + offset: ox.unwrap_or_default(), + }, + y: UDim { + scale: sy.unwrap_or_default(), + offset: oy.unwrap_or_default(), + }, + }) + } else { + // TODO: Better error message here using arg types + Err(LuaError::RuntimeError( + "Invalid arguments to constructor".to_string(), + )) + } + })?, + ) + } +} + +impl fmt::Display for UDim2 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}, {}", self.x, self.y) + } +} + +impl ops::Neg for UDim2 { + type Output = Self; + fn neg(self) -> Self::Output { + UDim2 { + x: -self.x, + y: -self.y, + } + } +} + +impl ops::Add for UDim2 { + type Output = Self; + fn add(self, rhs: Self) -> Self::Output { + UDim2 { + x: self.x + rhs.x, + y: self.y + rhs.y, + } + } +} + +impl ops::Sub for UDim2 { + type Output = Self; + fn sub(self, rhs: Self) -> Self::Output { + UDim2 { + x: self.x - rhs.x, + y: self.y - rhs.y, + } + } +} + +impl LuaUserData for UDim2 { + fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("X", |_, this| Ok(this.x)); + fields.add_field_method_get("Y", |_, this| Ok(this.y)); + fields.add_field_method_get("Width", |_, this| Ok(this.x)); + fields.add_field_method_get("Height", |_, this| Ok(this.y)); + } + + fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { + // Methods + methods.add_method("Lerp", |_, this, (rhs, alpha): (UDim2, f32)| { + let this_vec_x = Vec2::new(this.x.scale, this.x.offset as f32); + let this_vec_y = Vec2::new(this.y.scale, this.y.offset as f32); + let rhs_vec_x = Vec2::new(rhs.x.scale, rhs.x.offset as f32); + let rhs_vec_y = Vec2::new(rhs.y.scale, rhs.y.offset as f32); + let result_x = this_vec_x.lerp(rhs_vec_x, alpha); + let result_y = this_vec_y.lerp(rhs_vec_y, alpha); + Ok(UDim2 { + x: UDim { + scale: result_x.x, + offset: result_x.y.clamp(i32::MIN as f32, i32::MAX as f32).round() as i32, + }, + y: UDim { + scale: result_y.x, + offset: result_y.y.clamp(i32::MIN as f32, i32::MAX as f32).round() as i32, + }, + }) + }); + // Metamethods + methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq); + methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string); + methods.add_meta_method(LuaMetaMethod::Unm, |_, this, ()| Ok(-*this)); + methods.add_meta_method(LuaMetaMethod::Add, |_, this, rhs: UDim2| Ok(*this + rhs)); + methods.add_meta_method(LuaMetaMethod::Sub, |_, this, rhs: UDim2| Ok(*this - rhs)); + } +} + +impl From<&RbxUDim2> for UDim2 { + fn from(v: &RbxUDim2) -> Self { + UDim2 { + x: (&v.x).into(), + y: (&v.y).into(), + } + } +} + +impl From<&UDim2> for RbxUDim2 { + fn from(v: &UDim2) -> Self { + RbxUDim2 { + x: (&v.x).into(), + y: (&v.y).into(), + } + } +} + +impl FromRbxVariant for UDim2 { + fn from_rbx_variant(variant: &RbxVariant) -> DatatypeConversionResult { + if let RbxVariant::UDim2(u) = variant { + Ok(u.into()) + } else { + Err(DatatypeConversionError::FromRbxVariant { + from: variant.variant_name(), + to: "UDim2", + detail: None, + }) + } + } +} + +impl ToRbxVariant for UDim2 { + fn to_rbx_variant( + &self, + desired_type: Option, + ) -> DatatypeConversionResult { + if matches!(desired_type, None | Some(RbxVariantType::UDim2)) { + Ok(RbxVariant::UDim2(self.into())) + } else { + Err(DatatypeConversionError::ToRbxVariant { + to: desired_type.map(|d| d.variant_name()).unwrap_or("?"), + from: "UDim2", + detail: None, + }) + } + } +} diff --git a/packages/lib-roblox/src/datatypes/types/vector3int16.rs b/packages/lib-roblox/src/datatypes/types/vector3int16.rs index f934e4e..c3279ed 100644 --- a/packages/lib-roblox/src/datatypes/types/vector3int16.rs +++ b/packages/lib-roblox/src/datatypes/types/vector3int16.rs @@ -41,6 +41,7 @@ impl LuaUserData for Vector3int16 { fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { fields.add_field_method_get("X", |_, this| Ok(this.0.x)); fields.add_field_method_get("Y", |_, this| Ok(this.0.y)); + fields.add_field_method_get("Z", |_, this| Ok(this.0.z)); } fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { diff --git a/packages/lib-roblox/src/lib.rs b/packages/lib-roblox/src/lib.rs index 5240a01..8b389d6 100644 --- a/packages/lib-roblox/src/lib.rs +++ b/packages/lib-roblox/src/lib.rs @@ -7,8 +7,6 @@ pub mod instance; #[cfg(test)] mod tests; -use datatypes::types::*; - fn make_dt(lua: &Lua, f: F) -> LuaResult where F: Fn(&Lua, &LuaTable) -> LuaResult<()>, @@ -21,7 +19,10 @@ where #[rustfmt::skip] fn make_all_datatypes(lua: &Lua) -> LuaResult> { + use datatypes::types::*; Ok(vec![ + ("UDim", make_dt(lua, UDim::make_table)?), + ("UDim2", make_dt(lua, UDim2::make_table)?), ("Vector2", make_dt(lua, Vector2::make_table)?), ("Vector2int16", make_dt(lua, Vector2int16::make_table)?), ("Vector3", make_dt(lua, Vector3::make_table)?), diff --git a/packages/lib-roblox/src/tests.rs b/packages/lib-roblox/src/tests.rs index cb581c0..ab1b6d0 100644 --- a/packages/lib-roblox/src/tests.rs +++ b/packages/lib-roblox/src/tests.rs @@ -38,6 +38,8 @@ macro_rules! create_tests { } create_tests! { + datatypes_udim: "datatypes/UDim", + datatypes_udim2: "datatypes/UDim2", datatypes_vector2: "datatypes/Vector2", datatypes_vector2int16: "datatypes/Vector2int16", datatypes_vector3: "datatypes/Vector3", diff --git a/tests/roblox/datatypes/UDim.luau b/tests/roblox/datatypes/UDim.luau new file mode 100644 index 0000000..0dec1d1 --- /dev/null +++ b/tests/roblox/datatypes/UDim.luau @@ -0,0 +1,28 @@ +-- HACK: Make luau happy, with the mlua rust +-- crate all globals are also present in _G +local UDim = _G.UDim + +-- Constructors & properties + +UDim.new() +UDim.new(0) +UDim.new(0, 0) +UDim.new(0 / 0, 0) + +assert(not pcall(function() + return UDim.new(false) +end)) +assert(not pcall(function() + return UDim.new("", "") +end)) +assert(not pcall(function() + return UDim.new(newproxy(true)) +end)) + +assert(UDim.new(1, 2).Scale == 1) +assert(UDim.new(1, 2).Offset == 2) + +-- Ops + +assert(UDim.new(2, 4) + UDim.new(1, 1) == UDim.new(3, 5)) +assert(UDim.new(2, 4) - UDim.new(1, 1) == UDim.new(1, 3)) diff --git a/tests/roblox/datatypes/UDim2.luau b/tests/roblox/datatypes/UDim2.luau new file mode 100644 index 0000000..40c8445 --- /dev/null +++ b/tests/roblox/datatypes/UDim2.luau @@ -0,0 +1,48 @@ +-- HACK: Make luau happy, with the mlua rust +-- crate all globals are also present in _G +local UDim = _G.UDim +local UDim2 = _G.UDim2 + +-- Constructors & properties + +UDim2.new() +UDim2.new(0) +UDim2.new(0, 0) +UDim2.new(0, 0, 0) +UDim2.new(0, 0, 0, 0) +UDim2.new(0 / 0, 0, 0 / 0, 0) + +assert(not pcall(function() + return UDim2.new(false) +end)) +assert(not pcall(function() + return UDim2.new("", "") +end)) +assert(not pcall(function() + return UDim2.new(newproxy(true)) +end)) + +UDim2.fromScale() +UDim2.fromScale(0) +UDim2.fromScale(0, 0) + +UDim2.fromOffset() +UDim2.fromOffset(0) +UDim2.fromOffset(0, 0) + +assert(UDim2.fromScale(1, 1).X == UDim.new(1, 0)) +assert(UDim2.fromScale(1, 1).Y == UDim.new(1, 0)) +assert(UDim2.fromScale(1, 1).Width == UDim.new(1, 0)) +assert(UDim2.fromScale(1, 1).Height == UDim.new(1, 0)) + +assert(UDim2.fromOffset(1, 1).X == UDim.new(0, 1)) +assert(UDim2.fromOffset(1, 1).Y == UDim.new(0, 1)) +assert(UDim2.fromOffset(1, 1).Width == UDim.new(0, 1)) +assert(UDim2.fromOffset(1, 1).Height == UDim.new(0, 1)) + +-- Ops + +assert(UDim2.new(2, 4, 6, 8) + UDim2.new(1, 1, 1, 1) == UDim2.new(3, 5, 7, 9)) +assert(UDim2.new(2, 4, 6, 8) - UDim2.new(1, 1, 1, 1) == UDim2.new(1, 3, 5, 7)) + +-- TODO: Methods diff --git a/tests/roblox/datatypes/Vector2.luau b/tests/roblox/datatypes/Vector2.luau index 033412d..0db21ef 100644 --- a/tests/roblox/datatypes/Vector2.luau +++ b/tests/roblox/datatypes/Vector2.luau @@ -2,7 +2,7 @@ -- crate all globals are also present in _G local Vector2 = _G.Vector2 --- Constructors +-- Constructors & properties Vector2.new() Vector2.new(0) @@ -19,6 +19,9 @@ assert(not pcall(function() return Vector2.new(newproxy(true)) end)) +assert(Vector2.new(1, 2).X == 1) +assert(Vector2.new(1, 2).Y == 2) + -- Constants assert(Vector2.one == Vector2.new(1, 1)) diff --git a/tests/roblox/datatypes/Vector2int16.luau b/tests/roblox/datatypes/Vector2int16.luau index 663f44a..ed4284d 100644 --- a/tests/roblox/datatypes/Vector2int16.luau +++ b/tests/roblox/datatypes/Vector2int16.luau @@ -2,7 +2,7 @@ -- crate all globals are also present in _G local Vector2int16 = _G.Vector2int16 --- Constructors +-- Constructors & properties Vector2int16.new() Vector2int16.new(0) @@ -21,6 +21,9 @@ assert(not pcall(function() return Vector2int16.new(newproxy(true)) end)) +assert(Vector2int16.new(1, 2).X == 1) +assert(Vector2int16.new(1, 2).Y == 2) + -- Ops assert(Vector2int16.new(2, 4) + Vector2int16.new(1, 1) == Vector2int16.new(3, 5)) diff --git a/tests/roblox/datatypes/Vector3.luau b/tests/roblox/datatypes/Vector3.luau index 71aff32..f3f8ed8 100644 --- a/tests/roblox/datatypes/Vector3.luau +++ b/tests/roblox/datatypes/Vector3.luau @@ -2,7 +2,7 @@ -- crate all globals are also present in _G local Vector3 = _G.Vector3 --- Constructors +-- Constructors & properties Vector3.new() Vector3.new(0) @@ -21,6 +21,10 @@ assert(not pcall(function() return Vector3.new(newproxy(true)) end)) +assert(Vector3.new(1, 2, 3).X == 1) +assert(Vector3.new(1, 2, 3).Y == 2) +assert(Vector3.new(1, 2, 3).Z == 3) + -- Constants assert(Vector3.one == Vector3.new(1, 1, 1)) diff --git a/tests/roblox/datatypes/Vector3int16.luau b/tests/roblox/datatypes/Vector3int16.luau index 66400b9..3341ad6 100644 --- a/tests/roblox/datatypes/Vector3int16.luau +++ b/tests/roblox/datatypes/Vector3int16.luau @@ -2,7 +2,7 @@ -- crate all globals are also present in _G local Vector3int16 = _G.Vector3int16 --- Constructors +-- Constructors & properties Vector3int16.new() Vector3int16.new(0) @@ -22,6 +22,10 @@ assert(not pcall(function() return Vector3int16.new(newproxy(true)) end)) +assert(Vector3int16.new(1, 2, 3).X == 1) +assert(Vector3int16.new(1, 2, 3).Y == 2) +assert(Vector3int16.new(1, 2, 3).Z == 3) + -- Ops assert(Vector3int16.new(2, 4, 8) + Vector3int16.new(1, 1, 1) == Vector3int16.new(3, 5, 9))