From 75188d3e3504fa3d94b76174a5e60d1ab899d423 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Thu, 16 Mar 2023 09:24:56 +0100 Subject: [PATCH] Implement Faces roblox datatype --- .../lib-roblox/src/datatypes/conversion.rs | 7 +- packages/lib-roblox/src/datatypes/shared.rs | 21 ++- .../lib-roblox/src/datatypes/types/axes.rs | 33 ++--- .../lib-roblox/src/datatypes/types/faces.rs | 133 ++++++++++++++++++ .../lib-roblox/src/datatypes/types/mod.rs | 3 + packages/lib-roblox/src/lib.rs | 1 + tests/roblox/datatypes/Faces.luau | 53 +++++++ 7 files changed, 223 insertions(+), 28 deletions(-) create mode 100644 packages/lib-roblox/src/datatypes/types/faces.rs create mode 100644 tests/roblox/datatypes/Faces.luau diff --git a/packages/lib-roblox/src/datatypes/conversion.rs b/packages/lib-roblox/src/datatypes/conversion.rs index 2c2c1e9..7a250b1 100644 --- a/packages/lib-roblox/src/datatypes/conversion.rs +++ b/packages/lib-roblox/src/datatypes/conversion.rs @@ -122,7 +122,6 @@ impl<'lua> RbxVariantToLua<'lua> for LuaAnyUserData<'lua> { Ok(match variant.clone() { // Not yet implemented datatypes // Rbx::CFrame(_) => todo!(), - // Rbx::Faces(_) => todo!(), // Rbx::NumberRange(_) => todo!(), // Rbx::NumberSequence(_) => todo!(), // Rbx::OptionalCFrame(_) => todo!(), @@ -132,7 +131,8 @@ impl<'lua> RbxVariantToLua<'lua> for LuaAnyUserData<'lua> { // Rbx::Region3(_) => todo!(), // Rbx::Region3int16(_) => todo!(), - Rbx::Axes(value) => lua.create_userdata(Axes::from(value))?, + Rbx::Axes(value) => lua.create_userdata(Axes::from(value))?, + Rbx::Faces(value) => lua.create_userdata(Faces::from(value))?, Rbx::BrickColor(value) => lua.create_userdata(BrickColor::from(value))?, Rbx::Color3(value) => lua.create_userdata(Color3::from(value))?, @@ -169,7 +169,8 @@ impl<'lua> LuaToRbxVariant<'lua> for LuaAnyUserData<'lua> { use rbx_dom_weak::types as rbx; let f = match variant_type { - RbxVariantType::Axes => convert::, + RbxVariantType::Axes => convert::, + RbxVariantType::Faces => convert::, RbxVariantType::BrickColor => convert::, RbxVariantType::Color3 => convert::, diff --git a/packages/lib-roblox/src/datatypes/shared.rs b/packages/lib-roblox/src/datatypes/shared.rs index 5ec63b7..033f203 100644 --- a/packages/lib-roblox/src/datatypes/shared.rs +++ b/packages/lib-roblox/src/datatypes/shared.rs @@ -1,7 +1,26 @@ -use std::{any::type_name, ops}; +use std::{any::type_name, cell::RefCell, fmt, ops}; use mlua::prelude::*; +// Utility functions + +type ListWriter = dyn Fn(&mut fmt::Formatter<'_>, bool, &str) -> fmt::Result; +pub(super) fn make_list_writer() -> Box { + let first = RefCell::new(true); + Box::new(move |f, flag, literal| { + if flag { + if first.take() { + write!(f, "{}", literal)?; + } else { + write!(f, ", {}", literal)?; + } + } + Ok::<_, fmt::Error>(()) + }) +} + +// Userdata metamethod implementations + pub(super) fn userdata_impl_to_string(_: &Lua, datatype: &D, _: ()) -> LuaResult where D: LuaUserData + ToString + 'static, diff --git a/packages/lib-roblox/src/datatypes/types/axes.rs b/packages/lib-roblox/src/datatypes/types/axes.rs index 3c7ce1a..111c8d9 100644 --- a/packages/lib-roblox/src/datatypes/types/axes.rs +++ b/packages/lib-roblox/src/datatypes/types/axes.rs @@ -88,26 +88,10 @@ impl LuaUserData for Axes { impl fmt::Display for Axes { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut got_value = false; - if self.x { - write!(f, "X")?; - got_value = true; - } - if self.y { - if got_value { - write!(f, ", Y")?; - } else { - write!(f, "Y")?; - got_value = true; - } - } - if self.z { - if got_value { - write!(f, ", Z")?; - } else { - write!(f, "Z")?; - } - } + let write = make_list_writer(); + write(f, self.x, "X")?; + write(f, self.y, "Y")?; + write(f, self.z, "Z")?; Ok(()) } } @@ -115,10 +99,11 @@ impl fmt::Display for Axes { impl From for Axes { fn from(v: RbxAxes) -> Self { let bits = v.bits(); - let x = (bits & 1) == 1; - let y = ((bits >> 1) & 1) == 1; - let z = ((bits >> 2) & 1) == 1; - Self { x, y, z } + Self { + x: (bits & 1) == 1, + y: ((bits >> 1) & 1) == 1, + z: ((bits >> 2) & 1) == 1, + } } } diff --git a/packages/lib-roblox/src/datatypes/types/faces.rs b/packages/lib-roblox/src/datatypes/types/faces.rs new file mode 100644 index 0000000..d4a8542 --- /dev/null +++ b/packages/lib-roblox/src/datatypes/types/faces.rs @@ -0,0 +1,133 @@ +use core::fmt; + +use mlua::prelude::*; +use rbx_dom_weak::types::Faces as RbxFaces; + +use super::{super::*, EnumItem}; + +/** + An implementation of the [Faces](https://create.roblox.com/docs/reference/engine/datatypes/Faces) Roblox datatype. + + This implements all documented properties, methods & constructors of the Faces class as of March 2023. +*/ +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Faces { + pub(crate) right: bool, + pub(crate) top: bool, + pub(crate) back: bool, + pub(crate) left: bool, + pub(crate) bottom: bool, + pub(crate) front: bool, +} + +impl Faces { + pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> { + datatype_table.set( + "new", + lua.create_function(|_, args: LuaMultiValue| { + let mut right = false; + let mut top = false; + let mut back = false; + let mut left = false; + let mut bottom = false; + let mut front = false; + let mut check = |e: &EnumItem| { + if e.parent.desc.name == "NormalId" { + match &e.name { + name if name == "Right" => right = true, + name if name == "Top" => top = true, + name if name == "Back" => back = true, + name if name == "Left" => left = true, + name if name == "Bottom" => bottom = true, + name if name == "Front" => front = true, + _ => {} + } + } + }; + for (index, arg) in args.into_iter().enumerate() { + if let LuaValue::UserData(u) = arg { + if let Ok(e) = u.borrow::() { + check(&e); + } else { + return Err(LuaError::RuntimeError(format!( + "Expected argument #{} to be an EnumItem, got userdata", + index + ))); + } + } else { + return Err(LuaError::RuntimeError(format!( + "Expected argument #{} to be an EnumItem, got {}", + index, + arg.type_name() + ))); + } + } + Ok(Faces { + right, + top, + back, + left, + bottom, + front, + }) + })?, + )?; + Ok(()) + } +} + +impl LuaUserData for Faces { + fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("Right", |_, this| Ok(this.right)); + fields.add_field_method_get("Top", |_, this| Ok(this.top)); + fields.add_field_method_get("Back", |_, this| Ok(this.back)); + fields.add_field_method_get("Left", |_, this| Ok(this.left)); + fields.add_field_method_get("Bottom", |_, this| Ok(this.bottom)); + fields.add_field_method_get("Front", |_, this| Ok(this.front)); + } + + 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); + } +} + +impl fmt::Display for Faces { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let write = make_list_writer(); + write(f, self.right, "Right")?; + write(f, self.top, "Top")?; + write(f, self.back, "Back")?; + write(f, self.left, "Left")?; + write(f, self.bottom, "Bottom")?; + write(f, self.front, "Front")?; + Ok(()) + } +} + +impl From for Faces { + fn from(v: RbxFaces) -> Self { + let bits = v.bits(); + Self { + right: (bits & 1) == 1, + top: ((bits >> 1) & 1) == 1, + back: ((bits >> 2) & 1) == 1, + left: ((bits >> 3) & 1) == 1, + bottom: ((bits >> 4) & 1) == 1, + front: ((bits >> 5) & 1) == 1, + } + } +} + +impl From for RbxFaces { + fn from(v: Faces) -> Self { + let mut bits = 0; + bits += v.right as u8; + bits += (v.top as u8) << 1; + bits += (v.back as u8) << 2; + bits += (v.left as u8) << 3; + bits += (v.bottom as u8) << 4; + bits += (v.front as u8) << 5; + RbxFaces::from_bits(bits).expect("Invalid bits") + } +} diff --git a/packages/lib-roblox/src/datatypes/types/mod.rs b/packages/lib-roblox/src/datatypes/types/mod.rs index b29d498..de7bfab 100644 --- a/packages/lib-roblox/src/datatypes/types/mod.rs +++ b/packages/lib-roblox/src/datatypes/types/mod.rs @@ -6,6 +6,7 @@ mod color_sequence_keypoint; mod r#enum; mod r#enum_item; mod r#enums; +mod faces; mod udim; mod udim2; mod vector2; @@ -18,6 +19,7 @@ pub use brick_color::BrickColor; pub use color3::Color3; pub use color_sequence::ColorSequence; pub use color_sequence_keypoint::ColorSequenceKeypoint; +pub use faces::Faces; pub use r#enum::Enum; pub use r#enum_item::EnumItem; pub use r#enums::Enums; @@ -76,6 +78,7 @@ mod tests { color_sequence: "datatypes/ColorSequence", color_sequence_keypoint: "datatypes/ColorSequenceKeypoint", r#enum: "datatypes/Enum", + faces: "datatypes/Faces", udim: "datatypes/UDim", udim2: "datatypes/UDim2", vector2: "datatypes/Vector2", diff --git a/packages/lib-roblox/src/lib.rs b/packages/lib-roblox/src/lib.rs index 519574d..128aa90 100644 --- a/packages/lib-roblox/src/lib.rs +++ b/packages/lib-roblox/src/lib.rs @@ -24,6 +24,7 @@ fn make_all_datatypes(lua: &Lua) -> LuaResult> { ("Color3", make_dt(lua, Color3::make_table)?), ("ColorSequence", make_dt(lua, ColorSequence::make_table)?), ("ColorSequenceKeypoint", make_dt(lua, ColorSequenceKeypoint::make_table)?), + ("Faces", make_dt(lua, Faces::make_table)?), ("UDim", make_dt(lua, UDim::make_table)?), ("UDim2", make_dt(lua, UDim2::make_table)?), ("Vector2", make_dt(lua, Vector2::make_table)?), diff --git a/tests/roblox/datatypes/Faces.luau b/tests/roblox/datatypes/Faces.luau new file mode 100644 index 0000000..b953f4b --- /dev/null +++ b/tests/roblox/datatypes/Faces.luau @@ -0,0 +1,53 @@ +-- HACK: Make luau happy, with the mlua rust +-- crate all globals are also present in _G +local Faces = _G.Faces +local Enum = _G.Enum + +-- Constructors & properties + +Faces.new() +Faces.new(Enum.NormalId.Top) +Faces.new(Enum.NormalId.Left, Enum.NormalId.Top) + +assert(not pcall(function() + return Faces.new(false) +end)) +assert(not pcall(function() + return Faces.new({}) +end)) +assert(not pcall(function() + return Faces.new(newproxy(true)) +end)) + +assert(Faces.new().Left == false) +assert(Faces.new().Right == false) +assert(Faces.new().Top == false) +assert(Faces.new().Bottom == false) +assert(Faces.new().Front == false) +assert(Faces.new().Back == false) + +assert(Faces.new(Enum.NormalId.Left).Left == true) +assert(Faces.new(Enum.NormalId.Right).Right == true) + +local f = Faces.new( + Enum.NormalId.Left, + Enum.NormalId.Right, + Enum.NormalId.Top, + Enum.NormalId.Bottom, + Enum.NormalId.Back +) +assert(f.Left == true) +assert(f.Right == true) +assert(f.Top == true) +assert(f.Bottom == true) +assert(f.Front == false) +assert(f.Back == true) + +-- Ops + +assert(not pcall(function() + return Faces.new() + Faces.new() +end)) +assert(not pcall(function() + return Faces.new() / Faces.new() +end))