From 201f38d44e7ef7745f30ce015f34a0477bc15ffd Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Wed, 22 Mar 2023 13:29:39 +0100 Subject: [PATCH] Implement tests for getting & setting roblox instance properties Also fix some related bugs --- packages/lib-roblox/src/instance/mod.rs | 11 +++- packages/lib-roblox/src/shared/instance.rs | 66 ++++++++++++++-------- tests/roblox/instance/properties.luau | 42 ++++++++++++++ 3 files changed, 91 insertions(+), 28 deletions(-) create mode 100644 tests/roblox/instance/properties.luau diff --git a/packages/lib-roblox/src/instance/mod.rs b/packages/lib-roblox/src/instance/mod.rs index b05867a..aa1e993 100644 --- a/packages/lib-roblox/src/instance/mod.rs +++ b/packages/lib-roblox/src/instance/mod.rs @@ -663,7 +663,12 @@ impl LuaUserData for Instance { .to_lua(lua) } else if let Some(prop_default) = info.value_default { Ok(LuaValue::dom_value_to_lua(lua, prop_default)?) - } else { + } else if info.value_type.is_some() { + Err(LuaError::RuntimeError(format!( + "Failed to get property '{}' - missing default value", + prop_name + ))) + } else { Err(LuaError::RuntimeError(format!( "Failed to get property '{}' - malformed property info", prop_name @@ -730,13 +735,13 @@ impl LuaUserData for Instance { if let Some(enum_name) = info.enum_name { match EnumItem::from_lua(prop_value, lua) { - Ok(given_enum) if given_enum.name == enum_name => { + Ok(given_enum) if given_enum.parent.desc.name == enum_name => { this.set_property(prop_name, DomValue::Enum(given_enum.into())); Ok(()) } Ok(given_enum) => Err(LuaError::RuntimeError(format!( "Failed to set property '{}' - expected Enum.{}, got Enum.{}", - prop_name, enum_name, given_enum.name + prop_name, enum_name, given_enum.parent.desc.name ))), Err(e) => Err(e), } diff --git a/packages/lib-roblox/src/shared/instance.rs b/packages/lib-roblox/src/shared/instance.rs index d833dda..6dcd8dc 100644 --- a/packages/lib-roblox/src/shared/instance.rs +++ b/packages/lib-roblox/src/shared/instance.rs @@ -13,6 +13,9 @@ pub(crate) struct PropertyInfo { /** Finds the info of a property of the given class. + This will also check superclasses if the property + was not directly found for the given class. + Returns `None` if the class or property does not exist. */ pub(crate) fn find_property_info( @@ -20,35 +23,48 @@ pub(crate) fn find_property_info( property_name: impl AsRef, ) -> Option { let db = rbx_reflection_database::get(); - let class = db.classes.get(instance_class.as_ref())?; + let instance_class = instance_class.as_ref(); let property_name = property_name.as_ref(); - let prop_definition = class.properties.get(property_name)?; - let prop_default = class.default_properties.get(property_name); - match &prop_definition.data_type { - DataType::Enum(enum_name) => Some(PropertyInfo { - enum_name: Some(Cow::Borrowed(enum_name)), - enum_default: prop_default.and_then(|default| match default { - DomValue::Enum(enum_default) => Some(enum_default.to_u32()), - _ => None, - }), - value_type: None, - value_default: None, - }), - DataType::Value(value_type) => Some(PropertyInfo { - enum_name: None, - enum_default: None, - value_type: Some(*value_type), - value_default: prop_default, - }), - _ => Some(PropertyInfo { - enum_name: None, - enum_default: None, - value_type: None, - value_default: None, - }), + let mut current_class = Cow::Borrowed(instance_class); + while let Some(class) = db.classes.get(current_class.as_ref()) { + if let Some(prop_definition) = class.properties.get(property_name) { + // We found a property, we should map it to a property + // info containing name/type and default property value + let prop_default = class.default_properties.get(property_name); + return Some(match &prop_definition.data_type { + DataType::Enum(enum_name) => PropertyInfo { + enum_name: Some(Cow::Borrowed(enum_name)), + enum_default: prop_default.and_then(|default| match default { + DomValue::Enum(enum_default) => Some(enum_default.to_u32()), + _ => None, + }), + value_type: None, + value_default: None, + }, + DataType::Value(value_type) => PropertyInfo { + enum_name: None, + enum_default: None, + value_type: Some(*value_type), + value_default: prop_default, + }, + _ => PropertyInfo { + enum_name: None, + enum_default: None, + value_type: None, + value_default: None, + }, + }); + } else if let Some(sup) = &class.superclass { + // No property found, we should look at the superclass + current_class = Cow::Borrowed(sup) + } else { + break; + } } + + None } /** diff --git a/tests/roblox/instance/properties.luau b/tests/roblox/instance/properties.luau new file mode 100644 index 0000000..d4e36cb --- /dev/null +++ b/tests/roblox/instance/properties.luau @@ -0,0 +1,42 @@ +local roblox = require("@lune/roblox") :: any +local BrickColor = roblox.BrickColor +local Instance = roblox.Instance +local Vector3 = roblox.Vector3 +local CFrame = roblox.CFrame +local Enum = roblox.Enum + +local part = Instance.new("Part") + +-- Primitive type properties should work (note that these are inherited from BasePart) + +part.Anchored = true +part.CanCollide = true +part.CanQuery = false + +assert(part.Anchored == true) +assert(part.CanCollide == true) +assert(part.CanQuery == false) + +-- More complex types like Vector3 should work + +part.Size = Vector3.one +part.CFrame = CFrame.identity +part.BrickColor = BrickColor.Red() + +assert(part.Size == Vector3.one) +assert(part.CFrame == CFrame.identity) +assert(part.BrickColor == BrickColor.Red()) + +-- Enums should work (note that these are specific to Part and not on BasePart) + +part.Shape = Enum.PartType.Ball + +assert(part.Shape == Enum.PartType.Ball) + +-- Properties that don't exist for a class should error + +local meshPart = Instance.new("MeshPart") + +assert(not pcall(function() + meshPart.Shape = Enum.PartType.Ball +end))