Implement Faces roblox datatype

This commit is contained in:
Filip Tibell 2023-03-16 09:24:56 +01:00
parent cfc9299165
commit 75188d3e35
No known key found for this signature in database
7 changed files with 223 additions and 28 deletions

View file

@ -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::<Axes, rbx::Axes>,
RbxVariantType::Axes => convert::<Axes, rbx::Axes>,
RbxVariantType::Faces => convert::<Faces, rbx::Faces>,
RbxVariantType::BrickColor => convert::<BrickColor, rbx::BrickColor>,
RbxVariantType::Color3 => convert::<Color3, rbx::Color3>,

View file

@ -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<ListWriter> {
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<D>(_: &Lua, datatype: &D, _: ()) -> LuaResult<String>
where
D: LuaUserData + ToString + 'static,

View file

@ -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<RbxAxes> 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,
}
}
}

View file

@ -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::<EnumItem>() {
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<RbxFaces> 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<Faces> 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")
}
}

View file

@ -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",

View file

@ -24,6 +24,7 @@ fn make_all_datatypes(lua: &Lua) -> LuaResult<Vec<(&'static str, LuaValue)>> {
("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)?),

View file

@ -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))