Initial implementation of datatypes module, first datatype impl

This commit is contained in:
Filip Tibell 2023-03-10 11:07:03 +01:00
parent 1129d32b7b
commit 501933e3f9
No known key found for this signature in database
6 changed files with 317 additions and 7 deletions

7
Cargo.lock generated
View file

@ -534,6 +534,12 @@ dependencies = [
"wasi",
]
[[package]]
name = "glam"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e4afd9ad95555081e109fe1d21f2a30c691b5f0919c67dfa690a2e1eb6bd51c"
[[package]]
name = "h2"
version = "0.3.16"
@ -855,6 +861,7 @@ dependencies = [
name = "lune-roblox"
version = "0.5.5"
dependencies = [
"glam",
"mlua",
"rbx_binary",
"rbx_dom_weak",

View file

@ -17,6 +17,7 @@ path = "src/lib.rs"
[dependencies]
mlua.workspace = true
glam = "0.23"
thiserror = "1.0"
rbx_binary = { git = "https://github.com/rojo-rbx/rbx-dom", rev = "ce4c5bf7b18c813417ad14cc37e5abe281dfb51a" }
@ -24,6 +25,3 @@ rbx_dom_weak = { git = "https://github.com/rojo-rbx/rbx-dom", rev = "ce4c5bf7b18
rbx_reflection = { git = "https://github.com/rojo-rbx/rbx-dom", rev = "ce4c5bf7b18c813417ad14cc37e5abe281dfb51a" }
rbx_reflection_database = { git = "https://github.com/rojo-rbx/rbx-dom", rev = "ce4c5bf7b18c813417ad14cc37e5abe281dfb51a" }
rbx_xml = { git = "https://github.com/rojo-rbx/rbx-dom", rev = "ce4c5bf7b18c813417ad14cc37e5abe281dfb51a" }
# TODO: Split lune lib out into something like lune-core so
# that we can use filesystem and async apis in this crate

View file

@ -0,0 +1,107 @@
use mlua::prelude::*;
pub(crate) use rbx_dom_weak::types::{Variant as RbxVariant, VariantType as RbxVariantType};
// NOTE: We create a new inner module scope here to make imports of datatypes more ergonomic
mod vector3;
pub mod types {
pub use super::vector3::Vector3;
}
// Trait definitions for conversion between rbx_dom_weak variant <-> datatype
#[allow(dead_code)]
pub(crate) enum RbxConversionError {
FromRbxVariant {
from: &'static str,
to: &'static str,
detail: Option<String>,
},
ToRbxVariant {
to: &'static str,
from: &'static str,
detail: Option<String>,
},
DesiredTypeMismatch {
actual: &'static str,
detail: Option<String>,
},
}
pub(crate) type RbxConversionResult<T> = Result<T, RbxConversionError>;
pub(crate) trait ToRbxVariant {
fn to_rbx_variant(
&self,
desired_type: Option<RbxVariantType>,
) -> RbxConversionResult<RbxVariant>;
}
pub(crate) trait FromRbxVariant: Sized {
fn from_rbx_variant(variant: &RbxVariant) -> RbxConversionResult<Self>;
}
pub(crate) trait DatatypeTable {
fn make_dt_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()>;
}
// NOTE: This implementation is .. not great, but it's the best we can
// do since we can't implement a trait like Display on a foreign type,
// and we are really only using it to make better error messages anyway
trait RbxVariantDisplayName {
fn display_name(&self) -> &'static str;
}
impl RbxVariantDisplayName for RbxVariantType {
fn display_name(&self) -> &'static str {
use RbxVariantType::*;
match self {
Axes => "Axes",
BinaryString => "BinaryString",
Bool => "Bool",
BrickColor => "BrickColor",
CFrame => "CFrame",
Color3 => "Color3",
Color3uint8 => "Color3uint8",
ColorSequence => "ColorSequence",
Content => "Content",
Enum => "Enum",
Faces => "Faces",
Float32 => "Float32",
Float64 => "Float64",
Int32 => "Int32",
Int64 => "Int64",
NumberRange => "NumberRange",
NumberSequence => "NumberSequence",
PhysicalProperties => "PhysicalProperties",
Ray => "Ray",
Rect => "Rect",
Ref => "Ref",
Region3 => "Region3",
Region3int16 => "Region3int16",
SharedString => "SharedString",
String => "String",
UDim => "UDim",
UDim2 => "UDim2",
Vector2 => "Vector2",
Vector2int16 => "Vector2int16",
Vector3 => "Vector3",
Vector3int16 => "Vector3int16",
OptionalCFrame => "OptionalCFrame",
_ => "?",
}
}
}
impl RbxVariantDisplayName for RbxVariant {
fn display_name(&self) -> &'static str {
self.ty().display_name()
}
}
// TODO: Implement tests for all datatypes in lua and run them here
// using the same mechanic we have to run tests in the main lib, these
// tests should also live next to other folders like fs, net, task, ..

View file

@ -0,0 +1,182 @@
use core::fmt;
use glam::Vec3A;
use mlua::prelude::*;
use rbx_dom_weak::types::Vector3 as RbxVector3;
use super::*;
/**
An implementation of the [Vector3](https://create.roblox.com/docs/reference/engine/datatypes/Vector3)
Roblox datatype, backed by [`glam::Vec3A`].
This implements all documented properties & methods of the Vector3
class as of March 2023, as well as the `new(x, y, z)` constructor.
Note that this does not use native Luau vectors to simplify implementation
and instead allow us to implement all abovementioned APIs accurately.
*/
#[derive(Debug, Clone, Copy)]
pub struct Vector3(pub Vec3A);
impl fmt::Display for Vector3 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}, {}, {}", self.0.x, self.0.y, self.0.z)
}
}
impl LuaUserData for Vector3 {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("Magnitude", |_, this| Ok(this.0.length()));
fields.add_field_method_get("Unit", |_, this| Ok(Vector3(this.0.normalize())));
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) {
// Methods
methods.add_method("Angle", |_, this, rhs: Vector3| {
Ok(this.0.angle_between(rhs.0))
});
methods.add_method("Cross", |_, this, rhs: Vector3| {
Ok(Vector3(this.0.cross(rhs.0)))
});
methods.add_method("Dot", |_, this, rhs: Vector3| Ok(this.0.dot(rhs.0)));
methods.add_method("FuzzyEq", |_, this, (rhs, epsilon): (Vector3, f32)| {
let eq_x = (rhs.0.x - this.0.x).abs() <= epsilon;
let eq_y = (rhs.0.y - this.0.y).abs() <= epsilon;
let eq_z = (rhs.0.z - this.0.z).abs() <= epsilon;
Ok(eq_x && eq_y && eq_z)
});
methods.add_method("Lerp", |_, this, (rhs, alpha): (Vector3, f32)| {
Ok(Vector3(this.0.lerp(rhs.0, alpha)))
});
methods.add_method("Max", |_, this, rhs: Vector3| {
Ok(Vector3(this.0.max(rhs.0)))
});
methods.add_method("Min", |_, this, rhs: Vector3| {
Ok(Vector3(this.0.min(rhs.0)))
});
// Metamethods - normal
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(this.to_string()));
methods.add_meta_method(LuaMetaMethod::Eq, |_, this, rhs: LuaValue| {
if let LuaValue::UserData(ud) = rhs {
if let Ok(vec) = ud.borrow::<Vector3>() {
Ok(this.0 == vec.0)
} else {
Ok(false)
}
} else {
Ok(false)
}
});
// Metamethods - math
methods.add_meta_method(LuaMetaMethod::Unm, |_, this, ()| Ok(Vector3(-this.0)));
methods.add_meta_method(LuaMetaMethod::Add, |_, this, rhs: Vector3| {
Ok(Vector3(this.0 + rhs.0))
});
methods.add_meta_method(LuaMetaMethod::Sub, |_, this, rhs: Vector3| {
Ok(Vector3(this.0 - rhs.0))
});
methods.add_meta_method(LuaMetaMethod::Mul, |_, this, rhs: LuaValue| {
match &rhs {
LuaValue::Number(n) => return Ok(Vector3(this.0 * Vec3A::splat(*n as f32))),
LuaValue::UserData(ud) => {
if let Ok(vec) = ud.borrow::<Vector3>() {
return Ok(Vector3(this.0 * vec.0));
}
}
_ => {}
};
Err(LuaError::FromLuaConversionError {
from: rhs.type_name(),
to: "Vector3",
message: Some(format!(
"Expected Vector3 or number, got {}",
rhs.type_name()
)),
})
});
methods.add_meta_method(LuaMetaMethod::Div, |_, this, rhs: LuaValue| {
match &rhs {
LuaValue::Number(n) => return Ok(Vector3(this.0 / Vec3A::splat(*n as f32))),
LuaValue::UserData(ud) => {
if let Ok(vec) = ud.borrow::<Vector3>() {
return Ok(Vector3(this.0 / vec.0));
}
}
_ => {}
};
Err(LuaError::FromLuaConversionError {
from: rhs.type_name(),
to: "Vector3",
message: Some(format!(
"Expected Vector3 or number, got {}",
rhs.type_name()
)),
})
});
}
}
impl DatatypeTable for Vector3 {
fn make_dt_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
// Constants
datatype_table.set("xAxis", Vector3(Vec3A::X))?;
datatype_table.set("yAxis", Vector3(Vec3A::Y))?;
datatype_table.set("zAxis", Vector3(Vec3A::Z))?;
datatype_table.set("zero", Vector3(Vec3A::ZERO))?;
datatype_table.set("one", Vector3(Vec3A::ONE))?;
// Constructors
datatype_table.set(
"new",
lua.create_function(|_, (x, y, z): (Option<f32>, Option<f32>, Option<f32>)| {
Ok(Vector3(Vec3A {
x: x.unwrap_or_default(),
y: y.unwrap_or_default(),
z: z.unwrap_or_default(),
}))
})?,
)
// FUTURE: Implement FromNormalId and FromAxis constructors?
}
}
impl FromRbxVariant for Vector3 {
fn from_rbx_variant(variant: &RbxVariant) -> RbxConversionResult<Self> {
if let RbxVariant::Vector3(v) = variant {
Ok(Vector3(Vec3A {
x: v.x,
y: v.y,
z: v.z,
}))
} else {
Err(RbxConversionError::FromRbxVariant {
from: variant.display_name(),
to: "Vector3",
detail: None,
})
}
}
}
impl ToRbxVariant for Vector3 {
fn to_rbx_variant(
&self,
desired_type: Option<RbxVariantType>,
) -> RbxConversionResult<RbxVariant> {
if matches!(desired_type, None | Some(RbxVariantType::Vector3)) {
Ok(RbxVariant::Vector3(RbxVector3 {
x: self.0.x,
y: self.0.y,
z: self.0.z,
}))
} else {
Err(RbxConversionError::DesiredTypeMismatch {
actual: RbxVariantType::Vector3.display_name(),
detail: None,
})
}
}
}

View file

@ -17,7 +17,7 @@ pub use kind::*;
pub type DocumentResult<T> = Result<T, DocumentError>;
/**
A wrapper for [`rbx_dom_weak::WeakDom`] that also takes care of
A container for [`rbx_dom_weak::WeakDom`] that also takes care of
reading and writing different kinds and formats of roblox files.
```rust ignore

View file

@ -1,11 +1,27 @@
use mlua::prelude::*;
mod instance;
pub mod datatypes;
pub mod document;
pub mod instance;
use datatypes::types::*;
use datatypes::DatatypeTable;
fn make_dt<F>(lua: &Lua, f: F) -> LuaResult<LuaTable>
where
F: Fn(&Lua, &LuaTable) -> LuaResult<()>,
{
let tab = lua.create_table()?;
f(lua, &tab)?;
tab.set_readonly(true);
Ok(tab)
}
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
let datatypes = vec![("Vector3", make_dt(lua, Vector3::make_dt_table)?)];
let exports = lua.create_table()?;
for (name, tab) in datatypes {
exports.set(name, tab)?;
}
Ok(exports)
}