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)
- [`FindFirstChildWhichIsA`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstChildWhichIsA)
- [`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)
- [`GetDescendants`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetDescendants)
- [`GetFullName`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetFullName)
- [`IsA`](https://create.roblox.com/docs/reference/engine/classes/Instance#IsA)
- [`IsAncestorOf`](https://create.roblox.com/docs/reference/engine/classes/Instance#IsAncestorOf)
- [`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)
### `DataModel`

View file

@ -2,19 +2,28 @@ use mlua::prelude::*;
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::*;
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(
&self,
lua: &'lua Lua,
variant_type: DomType,
variant_type: Option<DomType>,
) -> DomConversionResult<DomValue>;
}
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>;
}
@ -52,9 +61,9 @@ impl<'lua> DomValueToLua<'lua> for LuaValue<'lua> {
Ok(LuaValue::String(lua.create_string(&encoded)?))
}
// 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
// NOTE: Some values are either optional or default and we should handle
// that properly here since the userdata conversion above will always fail
DomValue::OptionalCFrame(None) => Ok(LuaValue::Nil),
DomValue::PhysicalProperties(dom::PhysicalProperties::Default) => Ok(LuaValue::Nil),
_ => Err(e),
@ -67,48 +76,65 @@ impl<'lua> LuaToDomValue<'lua> for LuaValue<'lua> {
fn lua_to_dom_value(
&self,
lua: &'lua Lua,
variant_type: DomType,
variant_type: Option<DomType>,
) -> DomConversionResult<DomValue> {
use base64::engine::general_purpose::STANDARD_NO_PAD;
use base64::engine::Engine as _;
use rbx_dom_weak::types as dom;
match (self, variant_type) {
(LuaValue::Boolean(b), DomType::Bool) => Ok(DomValue::Bool(*b)),
if let Some(variant_type) = variant_type {
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::Int32) => Ok(DomValue::Int32(*i)),
(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::Int64) => Ok(DomValue::Int64(*i as i64)),
(LuaValue::Integer(i), DomType::Int32) => Ok(DomValue::Int32(*i)),
(LuaValue::Integer(i), DomType::Float64) => Ok(DomValue::Float64(*i as f64)),
(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::Int32) => Ok(DomValue::Int32(*n as i32)),
(LuaValue::Number(n), DomType::Float64) => Ok(DomValue::Float64(*n)),
(LuaValue::Number(n), DomType::Float32) => Ok(DomValue::Float32(*n as f32)),
(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::Float64) => Ok(DomValue::Float64(*n)),
(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::Content) => {
Ok(DomValue::Content(s.to_str()?.to_string().into()))
(LuaValue::String(s), DomType::String) => {
Ok(DomValue::String(s.to_str()?.to_string()))
}
(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) => {
Ok(DomValue::BinaryString(STANDARD_NO_PAD.decode(s)?.into()))
} else {
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> {
#[rustfmt::skip]
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;
/*
NOTES:
1. Enum is intentionally left out here, it has a custom
conversion going from instance property > datatype instead,
check `EnumItem::from_instance_property` for specifics
2. PhysicalProperties can only be converted if they are custom
physical properties, since default physical properties values
depend on what other related properties an instance might have
*/
Ok(match variant.clone() {
DomValue::Axes(value) => lua.create_userdata(Axes::from(value))?,
DomValue::Faces(value) => lua.create_userdata(Faces::from(value))?,
DomValue::CFrame(value) => lua.create_userdata(CFrame::from(value))?,
DomValue::BrickColor(value) => lua.create_userdata(BrickColor::from(value))?,
DomValue::Color3(value) => lua.create_userdata(Color3::from(value))?,
DomValue::Color3uint8(value) => lua.create_userdata(Color3::from(value))?,
DomValue::ColorSequence(value) => lua.create_userdata(ColorSequence::from(value))?,
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)?
},
match variant {
DomValue::Axes(value) => dom_to_userdata!(lua, value => Axes),
DomValue::BrickColor(value) => dom_to_userdata!(lua, value => BrickColor),
DomValue::CFrame(value) => dom_to_userdata!(lua, value => CFrame),
DomValue::Color3(value) => dom_to_userdata!(lua, value => Color3),
DomValue::Color3uint8(value) => dom_to_userdata!(lua, value => Color3),
DomValue::ColorSequence(value) => dom_to_userdata!(lua, value => ColorSequence),
DomValue::Faces(value) => dom_to_userdata!(lua, value => Faces),
DomValue::Font(value) => dom_to_userdata!(lua, value => Font),
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),
DomValue::Rect(value) => dom_to_userdata!(lua, value => Rect),
DomValue::Ref(value) => dom_to_userdata!(lua, value => Instance),
DomValue::Region3(value) => dom_to_userdata!(lua, value => Region3),
DomValue::Region3int16(value) => dom_to_userdata!(lua, value => Region3int16),
DomValue::UDim(value) => dom_to_userdata!(lua, value => UDim),
DomValue::UDim2(value) => dom_to_userdata!(lua, value => UDim2),
DomValue::Vector2(value) => dom_to_userdata!(lua, value => Vector2),
DomValue::Vector2int16(value) => dom_to_userdata!(lua, value => Vector2int16),
DomValue::Vector3(value) => dom_to_userdata!(lua, value => Vector3),
DomValue::Vector3int16(value) => dom_to_userdata!(lua, value => Vector3int16),
// 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)) => {
lua.create_userdata(PhysicalProperties::from(value))?
dom_to_userdata!(lua, value => PhysicalProperties)
},
v => {
return Err(DomConversionError::FromDomValue {
Err(DomConversionError::FromDomValue {
from: v.variant_name(),
to: "userdata",
detail: Some("Type not supported".to_string()),
})
}
})
}
}
}
@ -196,96 +228,105 @@ impl<'lua> LuaToDomValue<'lua> for LuaAnyUserData<'lua> {
fn lua_to_dom_value(
&self,
_: &'lua Lua,
variant_type: DomType,
variant_type: Option<DomType>,
) -> DomConversionResult<DomValue> {
use super::types::*;
use rbx_dom_weak::types as dom;
let f = match variant_type {
DomType::Axes => convert::<Axes, dom::Axes>,
DomType::Faces => convert::<Faces, dom::Faces>,
if let Some(variant_type) = variant_type {
/*
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>,
DomType::Color3 => convert::<Color3, dom::Color3>,
DomType::Color3uint8 => convert::<Color3, dom::Color3uint8>,
DomType::ColorSequence => convert::<ColorSequence, dom::ColorSequence>,
ty => {
return Err(DomConversionError::ToDomValue {
to: ty.variant_name(),
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>,
DomType::NumberRange => convert::<NumberRange, dom::NumberRange>,
DomType::NumberSequence => convert::<NumberSequence, dom::NumberSequence>,
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}")),
},
_ => Err(DomConversionError::ToDomValue {
to: "unknown",
from: "userdata",
detail: Some("Type not supported".to_string()),
})
}
}
}
}

View file

@ -1,6 +1,6 @@
use super::*;
pub(super) trait DomValueExt {
pub(crate) trait DomValueExt {
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 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,
};
use crate::{
datatypes::{
conversion::{DomValueToLua, LuaToDomValue},
extension::DomValueExt,
types::EnumItem,
userdata_impl_eq, userdata_impl_to_string,
},
@ -141,6 +146,10 @@ impl Instance {
.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_inst = Self::new(new_ref);
@ -391,6 +400,87 @@ impl Instance {
.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`.
@ -629,6 +719,11 @@ impl LuaUserData for Instance {
"Parent" => {
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",
prop_name
)))
} else {
} else {
Err(LuaError::RuntimeError(format!(
"Failed to get property '{}' - malformed property info",
prop_name
@ -720,6 +815,13 @@ impl LuaUserData for Instance {
this.set_parent(parent);
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),
}
} 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) => {
this.set_property(prop_name, dom_value);
Ok(())
@ -846,6 +948,36 @@ impl LuaUserData for Instance {
.find_ancestor(|ancestor| ancestor.referent() == instance.dom_ref)
.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
// methods that are restricted to specific classnames / base classes
data_model::add_methods(methods);
@ -863,3 +995,15 @@ impl PartialEq for Instance {
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)
}
}