Implement attributes & instance ref properties for roblox lib

This commit is contained in:
Filip Tibell 2023-03-25 10:48:14 +01:00
parent 4023521f95
commit 22ab18026b
No known key found for this signature in database
4 changed files with 358 additions and 176 deletions

View file

@ -25,17 +25,14 @@ Currently implemented APIs:
- [`FindFirstChildOfClass`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstChildOfClass) - [`FindFirstChildOfClass`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstChildOfClass)
- [`FindFirstChildWhichIsA`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstChildWhichIsA) - [`FindFirstChildWhichIsA`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstChildWhichIsA)
- [`FindFirstDescendant`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstDescendant) - [`FindFirstDescendant`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstDescendant)
- [`GetAttribute`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetAttribute)
- [`GetAttributes`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetAttributes)
- [`GetChildren`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetChildren) - [`GetChildren`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetChildren)
- [`GetDescendants`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetDescendants) - [`GetDescendants`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetDescendants)
- [`GetFullName`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetFullName) - [`GetFullName`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetFullName)
- [`IsA`](https://create.roblox.com/docs/reference/engine/classes/Instance#IsA) - [`IsA`](https://create.roblox.com/docs/reference/engine/classes/Instance#IsA)
- [`IsAncestorOf`](https://create.roblox.com/docs/reference/engine/classes/Instance#IsAncestorOf) - [`IsAncestorOf`](https://create.roblox.com/docs/reference/engine/classes/Instance#IsAncestorOf)
- [`IsDescendantOf`](https://create.roblox.com/docs/reference/engine/classes/Instance#IsDescendantOf) - [`IsDescendantOf`](https://create.roblox.com/docs/reference/engine/classes/Instance#IsDescendantOf)
Not yet implemented, but planned:
- [`GetAttribute`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetAttribute)
- [`GetAttributes`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetAttributes)
- [`SetAttribute`](https://create.roblox.com/docs/reference/engine/classes/Instance#SetAttribute) - [`SetAttribute`](https://create.roblox.com/docs/reference/engine/classes/Instance#SetAttribute)
### `DataModel` ### `DataModel`

View file

@ -2,19 +2,28 @@ use mlua::prelude::*;
use rbx_dom_weak::types::{Variant as DomValue, VariantType as DomType}; use rbx_dom_weak::types::{Variant as DomValue, VariantType as DomType};
use crate::datatypes::extension::DomValueExt; use crate::{datatypes::extension::DomValueExt, instance::Instance};
use super::*; use super::*;
pub(crate) trait LuaToDomValue<'lua> { pub(crate) trait LuaToDomValue<'lua> {
/**
Converts a lua value into a weak dom value.
If a `variant_type` is given the conversion will be more strict
and also more accurate, it should be given whenever possible.
*/
fn lua_to_dom_value( fn lua_to_dom_value(
&self, &self,
lua: &'lua Lua, lua: &'lua Lua,
variant_type: DomType, variant_type: Option<DomType>,
) -> DomConversionResult<DomValue>; ) -> DomConversionResult<DomValue>;
} }
pub(crate) trait DomValueToLua<'lua>: Sized { pub(crate) trait DomValueToLua<'lua>: Sized {
/**
Converts a weak dom value into a lua value.
*/
fn dom_value_to_lua(lua: &'lua Lua, variant: &DomValue) -> DomConversionResult<Self>; fn dom_value_to_lua(lua: &'lua Lua, variant: &DomValue) -> DomConversionResult<Self>;
} }
@ -52,9 +61,9 @@ impl<'lua> DomValueToLua<'lua> for LuaValue<'lua> {
Ok(LuaValue::String(lua.create_string(&encoded)?)) Ok(LuaValue::String(lua.create_string(&encoded)?))
} }
// NOTE: We need this special case here to handle default (nil) // NOTE: Some values are either optional or default and we should handle
// physical properties since our PhysicalProperties datatype // that properly here since the userdata conversion above will always fail
// implementation does not handle default at all, only custom DomValue::OptionalCFrame(None) => Ok(LuaValue::Nil),
DomValue::PhysicalProperties(dom::PhysicalProperties::Default) => Ok(LuaValue::Nil), DomValue::PhysicalProperties(dom::PhysicalProperties::Default) => Ok(LuaValue::Nil),
_ => Err(e), _ => Err(e),
@ -67,48 +76,65 @@ impl<'lua> LuaToDomValue<'lua> for LuaValue<'lua> {
fn lua_to_dom_value( fn lua_to_dom_value(
&self, &self,
lua: &'lua Lua, lua: &'lua Lua,
variant_type: DomType, variant_type: Option<DomType>,
) -> DomConversionResult<DomValue> { ) -> DomConversionResult<DomValue> {
use base64::engine::general_purpose::STANDARD_NO_PAD; use base64::engine::general_purpose::STANDARD_NO_PAD;
use base64::engine::Engine as _; use base64::engine::Engine as _;
use rbx_dom_weak::types as dom; use rbx_dom_weak::types as dom;
match (self, variant_type) { if let Some(variant_type) = variant_type {
(LuaValue::Boolean(b), DomType::Bool) => Ok(DomValue::Bool(*b)), match (self, variant_type) {
(LuaValue::Boolean(b), DomType::Bool) => Ok(DomValue::Bool(*b)),
(LuaValue::Integer(i), DomType::Int64) => Ok(DomValue::Int64(*i as i64)), (LuaValue::Integer(i), DomType::Int64) => Ok(DomValue::Int64(*i as i64)),
(LuaValue::Integer(i), DomType::Int32) => Ok(DomValue::Int32(*i)), (LuaValue::Integer(i), DomType::Int32) => Ok(DomValue::Int32(*i)),
(LuaValue::Integer(i), DomType::Float64) => Ok(DomValue::Float64(*i as f64)), (LuaValue::Integer(i), DomType::Float64) => Ok(DomValue::Float64(*i as f64)),
(LuaValue::Integer(i), DomType::Float32) => Ok(DomValue::Float32(*i as f32)), (LuaValue::Integer(i), DomType::Float32) => Ok(DomValue::Float32(*i as f32)),
(LuaValue::Number(n), DomType::Int64) => Ok(DomValue::Int64(*n as i64)), (LuaValue::Number(n), DomType::Int64) => Ok(DomValue::Int64(*n as i64)),
(LuaValue::Number(n), DomType::Int32) => Ok(DomValue::Int32(*n as i32)), (LuaValue::Number(n), DomType::Int32) => Ok(DomValue::Int32(*n as i32)),
(LuaValue::Number(n), DomType::Float64) => Ok(DomValue::Float64(*n)), (LuaValue::Number(n), DomType::Float64) => Ok(DomValue::Float64(*n)),
(LuaValue::Number(n), DomType::Float32) => Ok(DomValue::Float32(*n as f32)), (LuaValue::Number(n), DomType::Float32) => Ok(DomValue::Float32(*n as f32)),
(LuaValue::String(s), DomType::String) => Ok(DomValue::String(s.to_str()?.to_string())), (LuaValue::String(s), DomType::String) => {
(LuaValue::String(s), DomType::Content) => { Ok(DomValue::String(s.to_str()?.to_string()))
Ok(DomValue::Content(s.to_str()?.to_string().into())) }
(LuaValue::String(s), DomType::Content) => {
Ok(DomValue::Content(s.to_str()?.to_string().into()))
}
(LuaValue::String(s), DomType::BinaryString) => {
Ok(DomValue::BinaryString(STANDARD_NO_PAD.decode(s)?.into()))
}
// NOTE: Some values are either optional or default and we
// should handle that here before trying to convert as userdata
(LuaValue::Nil, DomType::OptionalCFrame) => Ok(DomValue::OptionalCFrame(None)),
(LuaValue::Nil, DomType::PhysicalProperties) => Ok(DomValue::PhysicalProperties(
dom::PhysicalProperties::Default,
)),
(LuaValue::UserData(u), d) => u.lua_to_dom_value(lua, Some(d)),
(v, d) => Err(DomConversionError::ToDomValue {
to: d.variant_name(),
from: v.type_name(),
detail: None,
}),
} }
(LuaValue::String(s), DomType::BinaryString) => { } else {
Ok(DomValue::BinaryString(STANDARD_NO_PAD.decode(s)?.into())) match self {
LuaValue::Boolean(b) => Ok(DomValue::Bool(*b)),
LuaValue::Integer(i) => Ok(DomValue::Int32(*i)),
LuaValue::Number(n) => Ok(DomValue::Float64(*n)),
LuaValue::String(s) => Ok(DomValue::String(s.to_str()?.to_string())),
LuaValue::UserData(u) => u.lua_to_dom_value(lua, None),
v => Err(DomConversionError::ToDomValue {
to: "unknown",
from: v.type_name(),
detail: None,
}),
} }
// NOTE: We need this special case here to handle default (nil)
// physical properties since our PhysicalProperties datatype
// implementation does not handle default at all, only custom
(LuaValue::Nil, DomType::PhysicalProperties) => Ok(DomValue::PhysicalProperties(
dom::PhysicalProperties::Default,
)),
(LuaValue::UserData(u), d) => u.lua_to_dom_value(lua, d),
(v, d) => Err(DomConversionError::ToDomValue {
to: d.variant_name(),
from: v.type_name(),
detail: None,
}),
} }
} }
} }
@ -123,6 +149,32 @@ impl<'lua> LuaToDomValue<'lua> for LuaValue<'lua> {
*/ */
macro_rules! dom_to_userdata {
($lua:expr, $value:ident => $to_type:ty) => {
Ok($lua.create_userdata(Into::<$to_type>::into($value.clone()))?)
};
}
macro_rules! userdata_to_dom {
($userdata:ident as $from_type:ty => $to_type:ty) => {
match $userdata.borrow::<$from_type>() {
Ok(value) => Ok(From::<$to_type>::from(value.clone().into())),
Err(error) => match error {
LuaError::UserDataTypeMismatch => Err(DomConversionError::ToDomValue {
to: stringify!($to_type),
from: "userdata",
detail: Some("Type mismatch".to_string()),
}),
e => Err(DomConversionError::ToDomValue {
to: stringify!($to_type),
from: "userdata",
detail: Some(format!("Internal error: {e}")),
}),
},
}
};
}
impl<'lua> DomValueToLua<'lua> for LuaAnyUserData<'lua> { impl<'lua> DomValueToLua<'lua> for LuaAnyUserData<'lua> {
#[rustfmt::skip] #[rustfmt::skip]
fn dom_value_to_lua(lua: &'lua Lua, variant: &DomValue) -> DomConversionResult<Self> { fn dom_value_to_lua(lua: &'lua Lua, variant: &DomValue) -> DomConversionResult<Self> {
@ -130,64 +182,44 @@ impl<'lua> DomValueToLua<'lua> for LuaAnyUserData<'lua> {
use rbx_dom_weak::types as dom; use rbx_dom_weak::types as dom;
/* match variant {
NOTES: DomValue::Axes(value) => dom_to_userdata!(lua, value => Axes),
DomValue::BrickColor(value) => dom_to_userdata!(lua, value => BrickColor),
1. Enum is intentionally left out here, it has a custom DomValue::CFrame(value) => dom_to_userdata!(lua, value => CFrame),
conversion going from instance property > datatype instead, DomValue::Color3(value) => dom_to_userdata!(lua, value => Color3),
check `EnumItem::from_instance_property` for specifics DomValue::Color3uint8(value) => dom_to_userdata!(lua, value => Color3),
DomValue::ColorSequence(value) => dom_to_userdata!(lua, value => ColorSequence),
2. PhysicalProperties can only be converted if they are custom DomValue::Faces(value) => dom_to_userdata!(lua, value => Faces),
physical properties, since default physical properties values DomValue::Font(value) => dom_to_userdata!(lua, value => Font),
depend on what other related properties an instance might have DomValue::NumberRange(value) => dom_to_userdata!(lua, value => NumberRange),
DomValue::NumberSequence(value) => dom_to_userdata!(lua, value => NumberSequence),
*/ DomValue::Ray(value) => dom_to_userdata!(lua, value => Ray),
Ok(match variant.clone() { DomValue::Rect(value) => dom_to_userdata!(lua, value => Rect),
DomValue::Axes(value) => lua.create_userdata(Axes::from(value))?, DomValue::Ref(value) => dom_to_userdata!(lua, value => Instance),
DomValue::Faces(value) => lua.create_userdata(Faces::from(value))?, DomValue::Region3(value) => dom_to_userdata!(lua, value => Region3),
DomValue::Region3int16(value) => dom_to_userdata!(lua, value => Region3int16),
DomValue::CFrame(value) => lua.create_userdata(CFrame::from(value))?, DomValue::UDim(value) => dom_to_userdata!(lua, value => UDim),
DomValue::UDim2(value) => dom_to_userdata!(lua, value => UDim2),
DomValue::BrickColor(value) => lua.create_userdata(BrickColor::from(value))?, DomValue::Vector2(value) => dom_to_userdata!(lua, value => Vector2),
DomValue::Color3(value) => lua.create_userdata(Color3::from(value))?, DomValue::Vector2int16(value) => dom_to_userdata!(lua, value => Vector2int16),
DomValue::Color3uint8(value) => lua.create_userdata(Color3::from(value))?, DomValue::Vector3(value) => dom_to_userdata!(lua, value => Vector3),
DomValue::ColorSequence(value) => lua.create_userdata(ColorSequence::from(value))?, DomValue::Vector3int16(value) => dom_to_userdata!(lua, value => Vector3int16),
DomValue::Font(value) => lua.create_userdata(Font::from(value))?,
DomValue::NumberRange(value) => lua.create_userdata(NumberRange::from(value))?,
DomValue::NumberSequence(value) => lua.create_userdata(NumberSequence::from(value))?,
DomValue::Ray(value) => lua.create_userdata(Ray::from(value))?,
DomValue::Rect(value) => lua.create_userdata(Rect::from(value))?,
DomValue::UDim(value) => lua.create_userdata(UDim::from(value))?,
DomValue::UDim2(value) => lua.create_userdata(UDim2::from(value))?,
DomValue::Region3(value) => lua.create_userdata(Region3::from(value))?,
DomValue::Region3int16(value) => lua.create_userdata(Region3int16::from(value))?,
DomValue::Vector2(value) => lua.create_userdata(Vector2::from(value))?,
DomValue::Vector2int16(value) => lua.create_userdata(Vector2int16::from(value))?,
DomValue::Vector3(value) => lua.create_userdata(Vector3::from(value))?,
DomValue::Vector3int16(value) => lua.create_userdata(Vector3int16::from(value))?,
DomValue::OptionalCFrame(value) => match value {
Some(value) => lua.create_userdata(CFrame::from(value))?,
None => lua.create_userdata(CFrame::IDENTITY)?
},
// NOTE: The none and default variants of these types are handled in
// DomValueToLua for the LuaValue type instead, allowing for nil/default
DomValue::OptionalCFrame(Some(value)) => dom_to_userdata!(lua, value => CFrame),
DomValue::PhysicalProperties(dom::PhysicalProperties::Custom(value)) => { DomValue::PhysicalProperties(dom::PhysicalProperties::Custom(value)) => {
lua.create_userdata(PhysicalProperties::from(value))? dom_to_userdata!(lua, value => PhysicalProperties)
}, },
v => { v => {
return Err(DomConversionError::FromDomValue { Err(DomConversionError::FromDomValue {
from: v.variant_name(), from: v.variant_name(),
to: "userdata", to: "userdata",
detail: Some("Type not supported".to_string()), detail: Some("Type not supported".to_string()),
}) })
} }
}) }
} }
} }
@ -196,96 +228,105 @@ impl<'lua> LuaToDomValue<'lua> for LuaAnyUserData<'lua> {
fn lua_to_dom_value( fn lua_to_dom_value(
&self, &self,
_: &'lua Lua, _: &'lua Lua,
variant_type: DomType, variant_type: Option<DomType>,
) -> DomConversionResult<DomValue> { ) -> DomConversionResult<DomValue> {
use super::types::*; use super::types::*;
use rbx_dom_weak::types as dom; use rbx_dom_weak::types as dom;
let f = match variant_type { if let Some(variant_type) = variant_type {
DomType::Axes => convert::<Axes, dom::Axes>, /*
DomType::Faces => convert::<Faces, dom::Faces>, Strict target type, use it to skip checking the actual
type of the userdata and try to just do a pure conversion
*/
match variant_type {
DomType::Axes => userdata_to_dom!(self as Axes => dom::Axes),
DomType::BrickColor => userdata_to_dom!(self as BrickColor => dom::BrickColor),
DomType::CFrame => userdata_to_dom!(self as CFrame => dom::CFrame),
DomType::Color3 => userdata_to_dom!(self as Color3 => dom::Color3),
DomType::Color3uint8 => userdata_to_dom!(self as Color3 => dom::Color3uint8),
DomType::ColorSequence => userdata_to_dom!(self as ColorSequence => dom::ColorSequence),
DomType::Enum => userdata_to_dom!(self as EnumItem => dom::Enum),
DomType::Faces => userdata_to_dom!(self as Faces => dom::Faces),
DomType::Font => userdata_to_dom!(self as Font => dom::Font),
DomType::NumberRange => userdata_to_dom!(self as NumberRange => dom::NumberRange),
DomType::NumberSequence => userdata_to_dom!(self as NumberSequence => dom::NumberSequence),
DomType::Ray => userdata_to_dom!(self as Ray => dom::Ray),
DomType::Rect => userdata_to_dom!(self as Rect => dom::Rect),
DomType::Ref => userdata_to_dom!(self as Instance => dom::Ref),
DomType::Region3 => userdata_to_dom!(self as Region3 => dom::Region3),
DomType::Region3int16 => userdata_to_dom!(self as Region3int16 => dom::Region3int16),
DomType::UDim => userdata_to_dom!(self as UDim => dom::UDim),
DomType::UDim2 => userdata_to_dom!(self as UDim2 => dom::UDim2),
DomType::Vector2 => userdata_to_dom!(self as Vector2 => dom::Vector2),
DomType::Vector2int16 => userdata_to_dom!(self as Vector2int16 => dom::Vector2int16),
DomType::Vector3 => userdata_to_dom!(self as Vector3 => dom::Vector3),
DomType::Vector3int16 => userdata_to_dom!(self as Vector3int16 => dom::Vector3int16),
DomType::CFrame => convert::<CFrame, dom::CFrame>, // NOTE: The none and default variants of these types are handled in
// LuaToDomValue for the LuaValue type instead, allowing for nil/default
DomType::OptionalCFrame => {
return match self.borrow::<CFrame>() {
Err(_) => unreachable!("Invalid use of conversion method, should be using LuaValue"),
Ok(value) => Ok(DomValue::OptionalCFrame(Some(dom::CFrame::from(*value)))),
}
}
DomType::PhysicalProperties => {
return match self.borrow::<PhysicalProperties>() {
Err(_) => unreachable!("Invalid use of conversion method, should be using LuaValue"),
Ok(value) => {
let props = dom::CustomPhysicalProperties::from(*value);
let custom = dom::PhysicalProperties::Custom(props);
Ok(DomValue::PhysicalProperties(custom))
}
}
}
DomType::BrickColor => convert::<BrickColor, dom::BrickColor>, ty => {
DomType::Color3 => convert::<Color3, dom::Color3>, return Err(DomConversionError::ToDomValue {
DomType::Color3uint8 => convert::<Color3, dom::Color3uint8>, to: ty.variant_name(),
DomType::ColorSequence => convert::<ColorSequence, dom::ColorSequence>, from: "userdata",
detail: Some("Type not supported".to_string()),
})
}
}
} else {
/*
Non-strict target type, here we need to do manual typechecks
on the userdata to see what we should be converting it into
DomType::Enum => convert::<EnumItem, dom::Enum>, This is used for example for attributes, where the wanted
type is not known by the dom and instead determined by the user
*/
match self {
value if value.is::<Axes>() => userdata_to_dom!(value as Axes => dom::Axes),
value if value.is::<BrickColor>() => userdata_to_dom!(value as BrickColor => dom::BrickColor),
value if value.is::<CFrame>() => userdata_to_dom!(value as CFrame => dom::CFrame),
value if value.is::<Color3>() => userdata_to_dom!(value as Color3 => dom::Color3),
value if value.is::<ColorSequence>() => userdata_to_dom!(value as ColorSequence => dom::ColorSequence),
value if value.is::<Enum>() => userdata_to_dom!(value as EnumItem => dom::Enum),
value if value.is::<Faces>() => userdata_to_dom!(value as Faces => dom::Faces),
value if value.is::<Font>() => userdata_to_dom!(value as Font => dom::Font),
value if value.is::<NumberRange>() => userdata_to_dom!(value as NumberRange => dom::NumberRange),
value if value.is::<NumberSequence>() => userdata_to_dom!(value as NumberSequence => dom::NumberSequence),
value if value.is::<Ray>() => userdata_to_dom!(value as Ray => dom::Ray),
value if value.is::<Rect>() => userdata_to_dom!(value as Rect => dom::Rect),
value if value.is::<Instance>() => userdata_to_dom!(value as Instance => dom::Ref),
value if value.is::<Region3>() => userdata_to_dom!(value as Region3 => dom::Region3),
value if value.is::<Region3int16>() => userdata_to_dom!(value as Region3int16 => dom::Region3int16),
value if value.is::<UDim>() => userdata_to_dom!(value as UDim => dom::UDim),
value if value.is::<UDim2>() => userdata_to_dom!(value as UDim2 => dom::UDim2),
value if value.is::<Vector2>() => userdata_to_dom!(value as Vector2 => dom::Vector2),
value if value.is::<Vector2int16>() => userdata_to_dom!(value as Vector2int16 => dom::Vector2int16),
value if value.is::<Vector3>() => userdata_to_dom!(value as Vector3 => dom::Vector3),
value if value.is::<Vector3int16>() => userdata_to_dom!(value as Vector3int16 => dom::Vector3int16),
DomType::Font => convert::<Font, dom::Font>, _ => Err(DomConversionError::ToDomValue {
to: "unknown",
DomType::NumberRange => convert::<NumberRange, dom::NumberRange>, from: "userdata",
DomType::NumberSequence => convert::<NumberSequence, dom::NumberSequence>, detail: Some("Type not supported".to_string()),
})
DomType::Rect => convert::<Rect, dom::Rect>, }
DomType::UDim => convert::<UDim, dom::UDim>, }
DomType::UDim2 => convert::<UDim2, dom::UDim2>,
DomType::Ray => convert::<Ray, dom::Ray>,
DomType::Region3 => convert::<Region3, dom::Region3>,
DomType::Region3int16 => convert::<Region3int16, dom::Region3int16>,
DomType::Vector2 => convert::<Vector2, dom::Vector2>,
DomType::Vector2int16 => convert::<Vector2int16, dom::Vector2int16>,
DomType::Vector3 => convert::<Vector3, dom::Vector3>,
DomType::Vector3int16 => convert::<Vector3int16, dom::Vector3int16>,
DomType::OptionalCFrame => return match self.borrow::<CFrame>() {
Ok(value) => Ok(DomValue::OptionalCFrame(Some(dom::CFrame::from(*value)))),
Err(e) => Err(lua_userdata_error_to_conversion_error(variant_type, e)),
},
DomType::PhysicalProperties => return match self.borrow::<PhysicalProperties>() {
Ok(value) => {
let props = dom::CustomPhysicalProperties::from(*value);
let custom = dom::PhysicalProperties::Custom(props);
Ok(DomValue::PhysicalProperties(custom))
},
Err(e) => Err(lua_userdata_error_to_conversion_error(variant_type, e)),
},
_ => return Err(DomConversionError::ToDomValue {
to: variant_type.variant_name(),
from: "userdata",
detail: Some("Type not supported".to_string()),
}),
};
f(self, variant_type)
}
}
fn convert<TypeFrom, TypeTo>(
userdata: &LuaAnyUserData,
variant_type: DomType,
) -> DomConversionResult<DomValue>
where
TypeFrom: LuaUserData + Clone + 'static,
TypeTo: From<TypeFrom> + Into<DomValue>,
{
match userdata.borrow::<TypeFrom>() {
Ok(value) => Ok(TypeTo::from(value.clone()).into()),
Err(e) => Err(lua_userdata_error_to_conversion_error(variant_type, e)),
}
}
fn lua_userdata_error_to_conversion_error(
variant_type: DomType,
error: LuaError,
) -> DomConversionError {
match error {
LuaError::UserDataTypeMismatch => DomConversionError::ToDomValue {
to: variant_type.variant_name(),
from: "userdata",
detail: Some("Type mismatch".to_string()),
},
e => DomConversionError::ToDomValue {
to: variant_type.variant_name(),
from: "userdata",
detail: Some(format!("Internal error: {e}")),
},
} }
} }

View file

@ -1,6 +1,6 @@
use super::*; use super::*;
pub(super) trait DomValueExt { pub(crate) trait DomValueExt {
fn variant_name(&self) -> &'static str; fn variant_name(&self) -> &'static str;
} }

View file

@ -1,14 +1,19 @@
use std::{collections::VecDeque, fmt, sync::RwLock}; use std::{
collections::{BTreeMap, VecDeque},
fmt,
sync::RwLock,
};
use mlua::prelude::*; use mlua::prelude::*;
use rbx_dom_weak::{ use rbx_dom_weak::{
types::{Ref as DomRef, Variant as DomValue}, types::{Ref as DomRef, Variant as DomValue, VariantType as DomType},
Instance as DomInstance, InstanceBuilder as DomInstanceBuilder, WeakDom, Instance as DomInstance, InstanceBuilder as DomInstanceBuilder, WeakDom,
}; };
use crate::{ use crate::{
datatypes::{ datatypes::{
conversion::{DomValueToLua, LuaToDomValue}, conversion::{DomValueToLua, LuaToDomValue},
extension::DomValueExt,
types::EnumItem, types::EnumItem,
userdata_impl_eq, userdata_impl_to_string, userdata_impl_eq, userdata_impl_to_string,
}, },
@ -141,6 +146,10 @@ impl Instance {
.parent() .parent()
}; };
// TODO: We should keep track of a map from old ref -> new ref
// for each instance so that we can then transform properties
// that are instance refs into ones pointing at the new instances
let new_ref = Self::clone_inner(self.dom_ref, parent_ref); let new_ref = Self::clone_inner(self.dom_ref, parent_ref);
let new_inst = Self::new(new_ref); let new_inst = Self::new(new_ref);
@ -391,6 +400,87 @@ impl Instance {
.insert(name.as_ref().to_string(), value); .insert(name.as_ref().to_string(), value);
} }
fn ensure_valid_attribute_value(&self, value: &DomValue) -> LuaResult<()> {
let is_valid = matches!(
value.ty(),
DomType::Bool
| DomType::BrickColor
| DomType::CFrame
| DomType::Color3
| DomType::ColorSequence
| DomType::Float32
| DomType::Float64
| DomType::Int32
| DomType::Int64
| DomType::NumberRange
| DomType::NumberSequence
| DomType::Rect
| DomType::String
| DomType::UDim
| DomType::UDim2
| DomType::Vector2
| DomType::Vector3
| DomType::Font
);
if is_valid {
Ok(())
} else {
Err(LuaError::RuntimeError(format!(
"'{}' is not a valid attribute type",
value.ty().variant_name()
)))
}
}
/**
Gets an attribute for the instance, if it exists.
*/
pub fn get_attribute(&self, name: impl AsRef<str>) -> Option<DomValue> {
let dom = INTERNAL_DOM
.try_read()
.expect("Failed to get read access to document");
let inst = dom
.get_by_ref(self.dom_ref)
.expect("Failed to find instance in document");
if let Some(DomValue::Attributes(attributes)) = inst.properties.get("Attributes") {
attributes.get(name.as_ref()).cloned()
} else {
None
}
}
/**
Gets all known attributes for the instance.
*/
pub fn get_attributes(&self) -> BTreeMap<String, DomValue> {
let dom = INTERNAL_DOM
.try_read()
.expect("Failed to get read access to document");
let inst = dom
.get_by_ref(self.dom_ref)
.expect("Failed to find instance in document");
if let Some(DomValue::Attributes(attributes)) = inst.properties.get("Attributes") {
attributes.clone().into_iter().collect()
} else {
BTreeMap::new()
}
}
/**
Sets an attribute for the instance.
*/
pub fn set_attribute(&self, name: impl AsRef<str>, value: DomValue) {
let mut dom = INTERNAL_DOM
.try_write()
.expect("Failed to get read access to document");
let inst = dom
.get_by_ref_mut(self.dom_ref)
.expect("Failed to find instance in document");
if let Some(DomValue::Attributes(attributes)) = inst.properties.get_mut("Attributes") {
attributes.insert(name.as_ref().to_string(), value);
}
}
/** /**
Gets all of the current children of this `Instance`. Gets all of the current children of this `Instance`.
@ -629,6 +719,11 @@ impl LuaUserData for Instance {
"Parent" => { "Parent" => {
return this.get_parent().to_lua(lua); return this.get_parent().to_lua(lua);
} }
// These are stored as properties in Rojo but are actually not, so we block them
"Attributes" | "Tags" => return Err(LuaError::RuntimeError(format!(
"{} is not a valid member of {}",
prop_name, this
))),
_ => {} _ => {}
} }
@ -668,7 +763,7 @@ impl LuaUserData for Instance {
"Failed to get property '{}' - missing default value", "Failed to get property '{}' - missing default value",
prop_name prop_name
))) )))
} else { } else {
Err(LuaError::RuntimeError(format!( Err(LuaError::RuntimeError(format!(
"Failed to get property '{}' - malformed property info", "Failed to get property '{}' - malformed property info",
prop_name prop_name
@ -720,6 +815,13 @@ impl LuaUserData for Instance {
this.set_parent(parent); this.set_parent(parent);
return Ok(()); return Ok(());
} }
// These are stored as properties in Rojo but are actually not, so we block them
"Attributes" | "Tags" => {
return Err(LuaError::RuntimeError(format!(
"{} is not a valid member of {}",
prop_name, this
)))
}
_ => {} _ => {}
} }
@ -746,7 +848,7 @@ impl LuaUserData for Instance {
Err(e) => Err(e), Err(e) => Err(e),
} }
} else if let Some(dom_type) = info.value_type { } else if let Some(dom_type) = info.value_type {
match prop_value.lua_to_dom_value(lua, dom_type) { match prop_value.lua_to_dom_value(lua, Some(dom_type)) {
Ok(dom_value) => { Ok(dom_value) => {
this.set_property(prop_name, dom_value); this.set_property(prop_name, dom_value);
Ok(()) Ok(())
@ -846,6 +948,36 @@ impl LuaUserData for Instance {
.find_ancestor(|ancestor| ancestor.referent() == instance.dom_ref) .find_ancestor(|ancestor| ancestor.referent() == instance.dom_ref)
.is_some()) .is_some())
}); });
methods.add_method("GetAttribute", |lua, this, name: String| {
this.ensure_not_destroyed()?;
match this.get_attribute(name) {
Some(attribute) => Ok(LuaValue::dom_value_to_lua(lua, &attribute)?),
None => Ok(LuaValue::Nil),
}
});
methods.add_method("GetAttributes", |lua, this, ()| {
this.ensure_not_destroyed()?;
let attributes = this.get_attributes();
let tab = lua.create_table_with_capacity(0, attributes.len() as i32)?;
for (key, value) in attributes.into_iter() {
tab.set(key, LuaValue::dom_value_to_lua(lua, &value)?)?;
}
Ok(tab)
});
methods.add_method(
"SetAttribute",
|lua, this, (attribute_name, lua_value): (String, LuaValue)| {
this.ensure_not_destroyed()?;
match lua_value.lua_to_dom_value(lua, None) {
Ok(dom_value) => {
this.ensure_valid_attribute_value(&dom_value)?;
this.set_attribute(attribute_name, dom_value);
Ok(())
}
Err(e) => Err(e.into()),
}
},
);
// Here we add inheritance-like behavior for instances by creating // Here we add inheritance-like behavior for instances by creating
// methods that are restricted to specific classnames / base classes // methods that are restricted to specific classnames / base classes
data_model::add_methods(methods); data_model::add_methods(methods);
@ -863,3 +995,15 @@ impl PartialEq for Instance {
self.dom_ref == other.dom_ref self.dom_ref == other.dom_ref
} }
} }
impl From<Instance> for DomRef {
fn from(value: Instance) -> Self {
value.dom_ref
}
}
impl From<DomRef> for Instance {
fn from(value: DomRef) -> Self {
Instance::new(value)
}
}