diff --git a/Cargo.lock b/Cargo.lock index 8544491..0e9fe0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,6 +28,19 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if 1.0.0", + "getrandom 0.2.15", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -2258,10 +2271,11 @@ dependencies = [ [[package]] name = "rbx_binary" -version = "0.7.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b85057e8ff75a1ce99248200c4b3c7b481a3d52f921f1053ecd67921dcc7930" +checksum = "9573fee5e073d7b303f475c285197fdc8179468de66ca60ee115a58fbac99296" dependencies = [ + "ahash", "log", "lz4", "profiling", @@ -2269,6 +2283,7 @@ dependencies = [ "rbx_reflection", "rbx_reflection_database", "thiserror", + "zstd", ] [[package]] @@ -2288,19 +2303,21 @@ dependencies = [ [[package]] name = "rbx_dom_weak" -version = "2.9.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcd2a17d09e46af0805f8b311a926402172b97e8d9388745c9adf8f448901841" +checksum = "04425cf6e9376e5486f4fb35906c120d1b1b45618a490318cf563fab1fa230a9" dependencies = [ + "ahash", "rbx_types", "serde", + "ustr", ] [[package]] name = "rbx_reflection" -version = "4.7.0" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8118ac6021d700e8debe324af6b40ecfd2cef270a00247849dbdfeebb0802677" +checksum = "1b6d0d62baa613556b058a5f94a53b01cf0ccde0ea327ce03056e335b982e77e" dependencies = [ "rbx_types", "serde", @@ -2309,9 +2326,9 @@ dependencies = [ [[package]] name = "rbx_reflection_database" -version = "0.2.12+roblox-638" +version = "1.0.1+roblox-666" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e29381d675420e841f8c02db5755cbb2545ed3e13f56c539546dc58702b512a" +checksum = "1ea11f26cfddc57a136781baed2e68eda5a3aa0735152d1562d65b1909c8d65d" dependencies = [ "lazy_static", "rbx_reflection", @@ -2321,9 +2338,9 @@ dependencies = [ [[package]] name = "rbx_types" -version = "1.10.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e30f49b2a3bb667e4074ba73c2dfb8ca0873f610b448ccf318a240acfdec6c73" +checksum = "78e4fdde46493def107e5f923d82e813dec9b3eef52c2f75fbad3a716023eda2" dependencies = [ "base64 0.13.1", "bitflags 1.3.2", @@ -2336,10 +2353,11 @@ dependencies = [ [[package]] name = "rbx_xml" -version = "0.13.5" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b14b3027bc9ccd82e2fc854c8bcd25ed58318e570c355bf2cf63df9cdbd5ba8" +checksum = "bb623833c31cc43bbdaeb32f5e91db8ecd63fc46e438d0d268baf9e61539cf1c" dependencies = [ + "ahash", "base64 0.13.1", "log", "rbx_dom_weak", @@ -3483,6 +3501,19 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +[[package]] +name = "ustr" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18b19e258aa08450f93369cf56dd78063586adf19e92a75b338a800f799a0208" +dependencies = [ + "ahash", + "byteorder 1.5.0", + "lazy_static", + "parking_lot", + "serde", +] + [[package]] name = "utf-8" version = "0.7.6" diff --git a/crates/lune-roblox/Cargo.toml b/crates/lune-roblox/Cargo.toml index 73ca05a..97bcf8d 100644 --- a/crates/lune-roblox/Cargo.toml +++ b/crates/lune-roblox/Cargo.toml @@ -20,10 +20,10 @@ rand = "0.8" thiserror = "1.0" once_cell = "1.17" -rbx_binary = "0.7.7" -rbx_dom_weak = "2.9.0" -rbx_reflection = "4.7.0" -rbx_reflection_database = "0.2.12" -rbx_xml = "0.13.5" +rbx_binary = "1.0.0" +rbx_dom_weak = "3.0.0" +rbx_reflection = "5.0.0" +rbx_reflection_database = "1.0.0" +rbx_xml = "1.0.0" lune-utils = { version = "0.1.3", path = "../lune-utils" } diff --git a/crates/lune-roblox/src/datatypes/attributes.rs b/crates/lune-roblox/src/datatypes/attributes.rs index 7d5bd26..f7b0fc4 100644 --- a/crates/lune-roblox/src/datatypes/attributes.rs +++ b/crates/lune-roblox/src/datatypes/attributes.rs @@ -47,6 +47,7 @@ pub fn ensure_valid_attribute_value(value: &DomValue) -> LuaResult<()> { | DomType::CFrame | DomType::Color3 | DomType::ColorSequence + | DomType::EnumItem | DomType::Float32 | DomType::Float64 | DomType::Font diff --git a/crates/lune-roblox/src/datatypes/conversion.rs b/crates/lune-roblox/src/datatypes/conversion.rs index f38ecc5..201ab00 100644 --- a/crates/lune-roblox/src/datatypes/conversion.rs +++ b/crates/lune-roblox/src/datatypes/conversion.rs @@ -51,7 +51,7 @@ impl<'lua> DomValueToLua<'lua> for LuaValue<'lua> { DomValue::Float32(n) => Ok(LuaValue::Number(*n as f64)), DomValue::String(s) => Ok(LuaValue::String(lua.create_string(s)?)), DomValue::BinaryString(s) => Ok(LuaValue::String(lua.create_string(s)?)), - DomValue::Content(s) => Ok(LuaValue::String( + DomValue::ContentId(s) => Ok(LuaValue::String( lua.create_string(AsRef::::as_ref(s))?, )), @@ -104,8 +104,8 @@ impl<'lua> LuaToDomValue<'lua> for LuaValue<'lua> { (LuaValue::String(s), DomType::BinaryString) => { Ok(DomValue::BinaryString(s.as_ref().into())) } - (LuaValue::String(s), DomType::Content) => { - Ok(DomValue::Content(s.to_str()?.to_string().into())) + (LuaValue::String(s), DomType::ContentId) => { + Ok(DomValue::ContentId(s.to_str()?.to_string().into())) } // NOTE: Some values are either optional or default and we @@ -200,6 +200,8 @@ impl<'lua> DomValueToLua<'lua> for LuaAnyUserData<'lua> { 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::Content(value) => dom_to_userdata!(lua, value => Content), + DomValue::EnumItem(value) => dom_to_userdata!(lua, value => EnumItem), 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), @@ -256,7 +258,8 @@ impl<'lua> LuaToDomValue<'lua> for LuaAnyUserData<'lua> { 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::Content => userdata_to_dom!(self as Content => dom::Content), + DomType::EnumItem => userdata_to_dom!(self as EnumItem => dom::EnumItem), 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), @@ -314,7 +317,7 @@ impl<'lua> LuaToDomValue<'lua> for LuaAnyUserData<'lua> { value if value.is::() => userdata_to_dom!(value as CFrame => dom::CFrame), value if value.is::() => userdata_to_dom!(value as Color3 => dom::Color3), value if value.is::() => userdata_to_dom!(value as ColorSequence => dom::ColorSequence), - value if value.is::() => userdata_to_dom!(value as EnumItem => dom::Enum), + value if value.is::() => userdata_to_dom!(value as EnumItem => dom::EnumItem), value if value.is::() => userdata_to_dom!(value as Faces => dom::Faces), value if value.is::() => userdata_to_dom!(value as Font => dom::Font), value if value.is::() => userdata_to_dom!(value as Instance => dom::Ref), diff --git a/crates/lune-roblox/src/datatypes/extension.rs b/crates/lune-roblox/src/datatypes/extension.rs index 6f83242..2ad1125 100644 --- a/crates/lune-roblox/src/datatypes/extension.rs +++ b/crates/lune-roblox/src/datatypes/extension.rs @@ -19,7 +19,9 @@ impl DomValueExt for DomType { Color3uint8 => "Color3uint8", ColorSequence => "ColorSequence", Content => "Content", + ContentId => "ContentId", Enum => "Enum", + EnumItem => "EnumItem", Faces => "Faces", Float32 => "Float32", Float64 => "Float64", diff --git a/crates/lune-roblox/src/datatypes/types/content.rs b/crates/lune-roblox/src/datatypes/types/content.rs new file mode 100644 index 0000000..226ea92 --- /dev/null +++ b/crates/lune-roblox/src/datatypes/types/content.rs @@ -0,0 +1,120 @@ +use core::fmt; + +use mlua::prelude::*; +use rbx_dom_weak::types::{Content as DomContent, ContentType}; + +use lune_utils::TableBuilder; + +use crate::{exports::LuaExportsTable, instance::Instance}; + +use super::{super::*, EnumItem}; + +/** + An implementation of the [Content](https://create.roblox.com/docs/reference/engine/datatypes/Content) Roblox datatype. + + This implements all documented properties, methods & constructors of the Content type as of April 2025. +*/ +#[derive(Debug, Clone, PartialEq)] +pub struct Content(ContentType); + +impl LuaExportsTable<'_> for Content { + const EXPORT_NAME: &'static str = "Content"; + + fn create_exports_table(lua: &'_ Lua) -> LuaResult> { + let from_uri = |_, uri: String| Ok(Self(ContentType::Uri(uri))); + + let from_object = |_, obj: LuaUserDataRef| { + let database = rbx_reflection_database::get(); + let instance_descriptor = database + .classes + .get("Instance") + .expect("the reflection database should always have Instance in it"); + let param_descriptor = database.classes.get(obj.get_class_name()).expect( + "you should not be able to construct an Instance that is not known to Lune", + ); + if database.has_superclass(param_descriptor, instance_descriptor) { + Err(LuaError::runtime("the provided object is a descendant class of 'Instance', expected one that was only an 'Object'")) + } else { + Ok(Content(ContentType::Object(obj.dom_ref))) + } + }; + + TableBuilder::new(lua)? + .with_value("none", Content(ContentType::None))? + .with_function("fromUri", from_uri)? + .with_function("fromObject", from_object)? + .build_readonly() + } +} + +impl LuaUserData for Content { + fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("SourceType", |_, this| { + let variant_name = match &this.0 { + ContentType::None => "None", + ContentType::Uri(_) => "Uri", + ContentType::Object(_) => "Object", + other => { + return Err(LuaError::runtime(format!( + "cannot get SourceType: unknown ContentType variant '{other:?}'" + ))) + } + }; + Ok(EnumItem::from_enum_name_and_name( + "ContentSourceType", + variant_name, + )) + }); + fields.add_field_method_get("Uri", |_, this| { + if let ContentType::Uri(uri) = &this.0 { + Ok(Some(uri.to_owned())) + } else { + Ok(None) + } + }); + fields.add_field_method_get("Object", |_, this| { + if let ContentType::Object(referent) = &this.0 { + Ok(Instance::new_opt(*referent)) + } else { + Ok(None) + } + }); + } + + 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 Content { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Regardless of the actual content of the Content, Roblox just emits + // `Content` when casting it to a string. We do not do that. + write!(f, "Content(")?; + match &self.0 { + ContentType::None => write!(f, "None")?, + ContentType::Uri(uri) => write!(f, "Uri={uri}")?, + ContentType::Object(_) => write!(f, "Object")?, + other => write!(f, "UnknownType({other:?})")?, + } + write!(f, ")") + } +} + +impl From for Content { + fn from(value: DomContent) -> Self { + Self(value.value().clone()) + } +} + +impl From for DomContent { + fn from(value: Content) -> Self { + match value.0 { + ContentType::None => Self::none(), + ContentType::Uri(uri) => Self::from_uri(uri), + ContentType::Object(referent) => Self::from_referent(referent), + other => unimplemented!("unknown variant of ContentType: {other:?}"), + } + } +} diff --git a/crates/lune-roblox/src/datatypes/types/enum_item.rs b/crates/lune-roblox/src/datatypes/types/enum_item.rs index 7089ccc..c403f55 100644 --- a/crates/lune-roblox/src/datatypes/types/enum_item.rs +++ b/crates/lune-roblox/src/datatypes/types/enum_item.rs @@ -1,7 +1,7 @@ use core::fmt; use mlua::prelude::*; -use rbx_dom_weak::types::Enum as DomEnum; +use rbx_dom_weak::types::EnumItem as DomEnumItem; use super::{super::*, Enum}; @@ -100,8 +100,18 @@ impl PartialEq for EnumItem { } } -impl From for DomEnum { +impl From for DomEnumItem { fn from(v: EnumItem) -> Self { - DomEnum::from_u32(v.value) + DomEnumItem { + ty: v.parent.desc.name.to_string(), + value: v.value, + } + } +} + +impl From for EnumItem { + fn from(value: DomEnumItem) -> Self { + EnumItem::from_enum_name_and_value(value.ty, value.value) + .expect("cannot convert rbx_type::EnumItem with unknown type into EnumItem") } } diff --git a/crates/lune-roblox/src/datatypes/types/mod.rs b/crates/lune-roblox/src/datatypes/types/mod.rs index 394b210..abc07a7 100644 --- a/crates/lune-roblox/src/datatypes/types/mod.rs +++ b/crates/lune-roblox/src/datatypes/types/mod.rs @@ -4,6 +4,7 @@ mod cframe; mod color3; mod color_sequence; mod color_sequence_keypoint; +mod content; mod r#enum; mod r#enum_item; mod r#enums; @@ -30,6 +31,7 @@ pub use cframe::CFrame; pub use color3::Color3; pub use color_sequence::ColorSequence; pub use color_sequence_keypoint::ColorSequenceKeypoint; +pub use content::Content; pub use faces::Faces; pub use font::Font; pub use number_range::NumberRange; diff --git a/crates/lune-roblox/src/document/kind.rs b/crates/lune-roblox/src/document/kind.rs index eee19ba..2ef637d 100644 --- a/crates/lune-roblox/src/document/kind.rs +++ b/crates/lune-roblox/src/document/kind.rs @@ -65,7 +65,7 @@ impl DocumentKind { for child_ref in dom.root().children() { if let Some(child_inst) = dom.get_by_ref(*child_ref) { has_top_level_child = true; - if class_is_a_service(&child_inst.class).unwrap_or(false) { + if class_is_a_service(child_inst.class).unwrap_or(false) { has_top_level_service = true; break; } diff --git a/crates/lune-roblox/src/document/postprocessing.rs b/crates/lune-roblox/src/document/postprocessing.rs index 28ec7ad..927e8c2 100644 --- a/crates/lune-roblox/src/document/postprocessing.rs +++ b/crates/lune-roblox/src/document/postprocessing.rs @@ -1,6 +1,6 @@ use rbx_dom_weak::{ types::{Ref as DomRef, VariantType as DomType}, - Instance as DomInstance, WeakDom, + ustr, Instance as DomInstance, WeakDom, }; use crate::shared::instance::class_is_a; @@ -18,8 +18,8 @@ pub fn postprocess_dom_for_model(dom: &mut WeakDom) { remove_matching_prop(inst, DomType::UniqueId, "HistoryId"); // Similar story with ScriptGuid - this is used // in the studio-only cloud script drafts feature - if class_is_a(&inst.class, "LuaSourceContainer").unwrap_or(false) { - inst.properties.remove("ScriptGuid"); + if class_is_a(inst.class, "LuaSourceContainer").unwrap_or(false) { + inst.properties.remove(&ustr("ScriptGuid")); } }); } @@ -41,6 +41,7 @@ where } fn remove_matching_prop(inst: &mut DomInstance, ty: DomType, name: &'static str) { + let name = &ustr(name); if inst.properties.get(name).is_some_and(|u| u.ty() == ty) { inst.properties.remove(name); } diff --git a/crates/lune-roblox/src/instance/base.rs b/crates/lune-roblox/src/instance/base.rs index 4b737d3..1a10096 100644 --- a/crates/lune-roblox/src/instance/base.rs +++ b/crates/lune-roblox/src/instance/base.rs @@ -71,7 +71,7 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) { "FindFirstAncestorWhichIsA", |lua, this, class_name: String| { ensure_not_destroyed(this)?; - this.find_ancestor(|child| class_is_a(&child.class, &class_name).unwrap_or(false)) + this.find_ancestor(|child| class_is_a(child.class, &class_name).unwrap_or(false)) .into_lua(lua) }, ); @@ -104,7 +104,7 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) { |lua, this, (class_name, recursive): (String, Option)| { ensure_not_destroyed(this)?; let predicate = - |child: &DomInstance| class_is_a(&child.class, &class_name).unwrap_or(false); + |child: &DomInstance| class_is_a(child.class, &class_name).unwrap_or(false); if matches!(recursive, Some(true)) { this.find_descendant(predicate).into_lua(lua) } else { @@ -113,7 +113,7 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) { }, ); m.add_method("IsA", |_, this, class_name: String| { - Ok(class_is_a(&this.class_name, class_name).unwrap_or(false)) + Ok(class_is_a(this.class_name, class_name).unwrap_or(false)) }); m.add_method( "IsAncestorOf", @@ -230,7 +230,7 @@ fn instance_property_get<'lua>( return this.get_name().into_lua(lua); } - if let Some(info) = find_property_info(&this.class_name, &prop_name) { + if let Some(info) = find_property_info(this.class_name, &prop_name) { if let Some(prop) = this.get_property(&prop_name) { if let DomValue::Enum(enum_value) = prop { let enum_name = info.enum_name.ok_or_else(|| { @@ -275,7 +275,7 @@ fn instance_property_get<'lua>( } else if let Some(inst) = this.find_child(|inst| inst.name == prop_name) { Ok(LuaValue::UserData(lua.create_userdata(inst)?)) } else if let Some(getter) = InstanceRegistry::find_property_getter(lua, this, &prop_name) { - getter.call(this.clone()) + getter.call(*this) } else if let Some(method) = InstanceRegistry::find_method(lua, this, &prop_name) { Ok(LuaValue::Function(method)) } else { @@ -321,17 +321,17 @@ fn instance_property_set<'lua>( } type Parent<'lua> = Option>; let parent = Parent::from_lua(prop_value, lua)?; - this.set_parent(parent.map(|p| p.clone())); + this.set_parent(parent.map(|p| *p)); return Ok(()); } _ => {} } - if let Some(info) = find_property_info(&this.class_name, &prop_name) { + if let Some(info) = find_property_info(this.class_name, &prop_name) { if let Some(enum_name) = info.enum_name { match LuaUserDataRef::::from_lua(prop_value, lua) { Ok(given_enum) if given_enum.parent.desc.name == enum_name => { - this.set_property(prop_name, DomValue::Enum((*given_enum).clone().into())); + this.set_property(prop_name, DomValue::EnumItem((*given_enum).clone().into())); Ok(()) } Ok(given_enum) => Err(LuaError::RuntimeError(format!( @@ -354,7 +354,7 @@ fn instance_property_set<'lua>( ))) } } else if let Some(setter) = InstanceRegistry::find_property_setter(lua, this, &prop_name) { - setter.call((this.clone(), prop_value)) + setter.call((*this, prop_value)) } else { Err(LuaError::RuntimeError(format!( "{prop_name} is not a valid member of {this}", diff --git a/crates/lune-roblox/src/instance/data_model.rs b/crates/lune-roblox/src/instance/data_model.rs index 80003c1..6aebb8c 100644 --- a/crates/lune-roblox/src/instance/data_model.rs +++ b/crates/lune-roblox/src/instance/data_model.rs @@ -48,7 +48,7 @@ fn data_model_get_service(_: &Lua, this: &Instance, service_name: String) -> Lua Ok(service) } else { let service = Instance::new_orphaned(service_name); - service.set_parent(Some(this.clone())); + service.set_parent(Some(*this)); Ok(service) } } diff --git a/crates/lune-roblox/src/instance/mod.rs b/crates/lune-roblox/src/instance/mod.rs index 120a514..59bf929 100644 --- a/crates/lune-roblox/src/instance/mod.rs +++ b/crates/lune-roblox/src/instance/mod.rs @@ -11,7 +11,7 @@ use mlua::prelude::*; use once_cell::sync::Lazy; use rbx_dom_weak::{ types::{Attributes as DomAttributes, Ref as DomRef, Variant as DomValue}, - Instance as DomInstance, InstanceBuilder as DomInstanceBuilder, WeakDom, + ustr, Instance as DomInstance, InstanceBuilder as DomInstanceBuilder, Ustr, WeakDom, }; use lune_utils::TableBuilder; @@ -34,10 +34,10 @@ const PROPERTY_NAME_TAGS: &str = "Tags"; static INTERNAL_DOM: Lazy> = Lazy::new(|| Mutex::new(WeakDom::new(DomInstanceBuilder::new("ROOT")))); -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub struct Instance { pub(crate) dom_ref: DomRef, - pub(crate) class_name: String, + pub(crate) class_name: Ustr, } impl Instance { @@ -75,7 +75,7 @@ impl Instance { Some(Self { dom_ref, - class_name: instance.class.clone(), + class_name: instance.class, }) } else { None @@ -96,14 +96,14 @@ impl Instance { let class_name = class_name.as_ref(); - let instance = DomInstanceBuilder::new(class_name.to_string()); + let instance = DomInstanceBuilder::new(class_name); let dom_root = dom.root_ref(); let dom_ref = dom.insert(dom_root, instance); Self { dom_ref, - class_name: class_name.to_string(), + class_name: ustr(class_name), } } @@ -244,7 +244,7 @@ impl Instance { on the Roblox Developer Hub */ pub fn is_a(&self, class_name: impl AsRef) -> bool { - class_is_a(&self.class_name, class_name).unwrap_or(false) + class_is_a(self.class_name, class_name).unwrap_or(false) } /** @@ -341,7 +341,7 @@ impl Instance { .get_by_ref(self.dom_ref) .expect("Failed to find instance in document") .properties - .get(name.as_ref()) + .get(&ustr(name.as_ref())) .cloned() } @@ -358,7 +358,7 @@ impl Instance { .get_by_ref_mut(self.dom_ref) .expect("Failed to find instance in document") .properties - .insert(name.as_ref().to_string(), value); + .insert(ustr(name.as_ref()), value); } /** @@ -374,7 +374,7 @@ impl Instance { .get_by_ref(self.dom_ref) .expect("Failed to find instance in document"); if let Some(DomValue::Attributes(attributes)) = - inst.properties.get(PROPERTY_NAME_ATTRIBUTES) + inst.properties.get(&ustr(PROPERTY_NAME_ATTRIBUTES)) { attributes.get(name.as_ref()).cloned() } else { @@ -395,7 +395,7 @@ impl Instance { .get_by_ref(self.dom_ref) .expect("Failed to find instance in document"); if let Some(DomValue::Attributes(attributes)) = - inst.properties.get(PROPERTY_NAME_ATTRIBUTES) + inst.properties.get(&ustr(PROPERTY_NAME_ATTRIBUTES)) { attributes.clone().into_iter().collect() } else { @@ -422,14 +422,14 @@ impl Instance { value => value, }; if let Some(DomValue::Attributes(attributes)) = - inst.properties.get_mut(PROPERTY_NAME_ATTRIBUTES) + inst.properties.get_mut(&ustr(PROPERTY_NAME_ATTRIBUTES)) { attributes.insert(name.as_ref().to_string(), value); } else { let mut attributes = DomAttributes::new(); attributes.insert(name.as_ref().to_string(), value); inst.properties.insert( - PROPERTY_NAME_ATTRIBUTES.to_string(), + ustr(PROPERTY_NAME_ATTRIBUTES), DomValue::Attributes(attributes), ); } @@ -449,11 +449,11 @@ impl Instance { .get_by_ref_mut(self.dom_ref) .expect("Failed to find instance in document"); if let Some(DomValue::Attributes(attributes)) = - inst.properties.get_mut(PROPERTY_NAME_ATTRIBUTES) + inst.properties.get_mut(&ustr(PROPERTY_NAME_ATTRIBUTES)) { attributes.remove(name.as_ref()); if attributes.is_empty() { - inst.properties.remove(PROPERTY_NAME_ATTRIBUTES); + inst.properties.remove(&ustr(PROPERTY_NAME_ATTRIBUTES)); } } } @@ -470,11 +470,11 @@ impl Instance { let inst = dom .get_by_ref_mut(self.dom_ref) .expect("Failed to find instance in document"); - if let Some(DomValue::Tags(tags)) = inst.properties.get_mut(PROPERTY_NAME_TAGS) { + if let Some(DomValue::Tags(tags)) = inst.properties.get_mut(&ustr(PROPERTY_NAME_TAGS)) { tags.push(name.as_ref()); } else { inst.properties.insert( - PROPERTY_NAME_TAGS.to_string(), + ustr(PROPERTY_NAME_TAGS), DomValue::Tags(vec![name.as_ref().to_string()].into()), ); } @@ -492,7 +492,7 @@ impl Instance { let inst = dom .get_by_ref(self.dom_ref) .expect("Failed to find instance in document"); - if let Some(DomValue::Tags(tags)) = inst.properties.get(PROPERTY_NAME_TAGS) { + if let Some(DomValue::Tags(tags)) = inst.properties.get(&ustr(PROPERTY_NAME_TAGS)) { tags.iter().map(ToString::to_string).collect() } else { Vec::new() @@ -511,7 +511,7 @@ impl Instance { let inst = dom .get_by_ref(self.dom_ref) .expect("Failed to find instance in document"); - if let Some(DomValue::Tags(tags)) = inst.properties.get(PROPERTY_NAME_TAGS) { + if let Some(DomValue::Tags(tags)) = inst.properties.get(&ustr(PROPERTY_NAME_TAGS)) { let name = name.as_ref(); tags.iter().any(|tag| tag == name) } else { @@ -531,14 +531,12 @@ impl Instance { let inst = dom .get_by_ref_mut(self.dom_ref) .expect("Failed to find instance in document"); - if let Some(DomValue::Tags(tags)) = inst.properties.get_mut(PROPERTY_NAME_TAGS) { + if let Some(DomValue::Tags(tags)) = inst.properties.get_mut(&ustr(PROPERTY_NAME_TAGS)) { let name = name.as_ref(); let mut new_tags = tags.iter().map(ToString::to_string).collect::>(); new_tags.retain(|tag| tag != name); - inst.properties.insert( - PROPERTY_NAME_TAGS.to_string(), - DomValue::Tags(new_tags.into()), - ); + inst.properties + .insert(ustr(PROPERTY_NAME_TAGS), DomValue::Tags(new_tags.into())); } } diff --git a/crates/lune-roblox/src/lib.rs b/crates/lune-roblox/src/lib.rs index 878a2f2..eefa73b 100644 --- a/crates/lune-roblox/src/lib.rs +++ b/crates/lune-roblox/src/lib.rs @@ -25,6 +25,7 @@ fn create_all_exports(lua: &Lua) -> LuaResult> { export::(lua)?, export::(lua)?, export::(lua)?, + export::(lua)?, export::(lua)?, export::(lua)?, export::(lua)?, diff --git a/crates/lune-roblox/src/shared/classes.rs b/crates/lune-roblox/src/shared/classes.rs index e87759b..d1e2c69 100644 --- a/crates/lune-roblox/src/shared/classes.rs +++ b/crates/lune-roblox/src/shared/classes.rs @@ -122,7 +122,7 @@ pub(crate) fn get_or_create_property_ref_instance( Ok(inst) } else { let inst = Instance::new_orphaned(class_name); - inst.set_parent(Some(this.clone())); + inst.set_parent(Some(*this)); this.set_property(prop_name, DomValue::Ref(inst.dom_ref)); Ok(inst) } diff --git a/crates/lune-std-roblox/src/lib.rs b/crates/lune-std-roblox/src/lib.rs index ec574b7..c208d9c 100644 --- a/crates/lune-std-roblox/src/lib.rs +++ b/crates/lune-std-roblox/src/lib.rs @@ -77,7 +77,7 @@ async fn serialize_place<'lua>( lua: &'lua Lua, (data_model, as_xml): (LuaUserDataRef<'lua, Instance>, Option), ) -> LuaResult> { - let data_model = (*data_model).clone(); + let data_model = *data_model; let fut = lua.spawn_blocking(move || { let doc = Document::from_data_model_instance(data_model)?; let bytes = doc.to_bytes_with_format(match as_xml { @@ -94,7 +94,7 @@ async fn serialize_model<'lua>( lua: &'lua Lua, (instances, as_xml): (Vec>, Option), ) -> LuaResult> { - let instances = instances.iter().map(|i| (*i).clone()).collect(); + let instances = instances.iter().map(|i| **i).collect(); let fut = lua.spawn_blocking(move || { let doc = Document::from_instance_array(instances)?; let bytes = doc.to_bytes_with_format(match as_xml { diff --git a/crates/lune/src/tests.rs b/crates/lune/src/tests.rs index f9fb9e3..2a75717 100644 --- a/crates/lune/src/tests.rs +++ b/crates/lune/src/tests.rs @@ -169,6 +169,7 @@ create_tests! { roblox_datatype_color3: "roblox/datatypes/Color3", roblox_datatype_color_sequence: "roblox/datatypes/ColorSequence", roblox_datatype_color_sequence_keypoint: "roblox/datatypes/ColorSequenceKeypoint", + roblox_datatype_content: "roblox/datatypes/Content", roblox_datatype_enum: "roblox/datatypes/Enum", roblox_datatype_faces: "roblox/datatypes/Faces", roblox_datatype_font: "roblox/datatypes/Font", diff --git a/tests/roblox/datatypes/Content.luau b/tests/roblox/datatypes/Content.luau new file mode 100644 index 0000000..2ca90d6 --- /dev/null +++ b/tests/roblox/datatypes/Content.luau @@ -0,0 +1,62 @@ +local roblox = require("@lune/roblox") :: any +local Content = roblox.Content +local Instance = roblox.Instance +local Enum = roblox.Enum + +assert(Content.none, "Content.none did not exist") +assert( + Content.none.SourceType == Enum.ContentSourceType.None, + "Content.none's SourceType was wrong" +) +assert(Content.none.Uri == nil, "Content.none's Uri field was wrong") +assert(Content.none.Object == nil, "Content.none's Object field was wrong") + +local uri = Content.fromUri("test uri") +assert(uri.SourceType == Enum.ContentSourceType.Uri, "URI Content's SourceType was wrong") +assert(uri.Uri == "test uri", "URI Content's Uri field was wrong") +assert(uri.Object == nil, "URI Content's Object field was wrong") + +assert(not pcall(Content.fromUri), "Content.fromUri accepted no argument") +assert(not pcall(Content.fromUri, false), "Content.fromUri accepted a boolean argument") +assert(not pcall(Content.fromUri, Enum), "Content.fromUri accepted a UserData as an argument") +assert( + not pcall(Content.fromUri, buffer.create(0)), + "Content.fromUri accepted a buffer as an argument" +) + +-- It feels weird that this is allowed because `EditableImage` is very much +-- not an Instance. But what can you do? +local target = Instance.new("EditableImage") +local object = Content.fromObject(target) +assert(object.SourceType == Enum.ContentSourceType.Object, "Object Content's SourceType was wrong") +assert(object.Uri == nil, "Object Content's Uri field was wrong") +assert(object.Object == target, "Object Content's Object field was wrong") + +assert(not pcall(Content.fromObject), "Content.fromObject accepted no argument") +assert(not pcall(Content.fromObject, false), "Content.fromObject accepted a boolean argument") +assert( + not pcall(Content.fromObject, Enum), + "Content.fromObject accepted a non-Instance/Object UserData as an argument" +) +assert( + not pcall(Content.fromObject, buffer.create(0)), + "Content.fromObject accepted a buffer as an argument" +) + +assert( + not pcall(Content.fromObject, Instance.new("Folder")), + "Content.fromObject accepted an Instance as an argument" +) + +assert( + tostring(Content.none) == "Content(None)", + `expected tostring(Content.none) to be Content(None), it was actually {Content.none}` +) +assert( + tostring(uri) == "Content(Uri=test uri)", + `expected tostring(URI Content) to be Content(Uri=...), it was actually {uri}` +) +assert( + tostring(object) == "Content(Object)", + `expected tostring(Object Content) to be Content(Object), it was actually {object}` +) diff --git a/tests/roblox/instance/attributes.luau b/tests/roblox/instance/attributes.luau index 4c0a62f..c62c879 100644 --- a/tests/roblox/instance/attributes.luau +++ b/tests/roblox/instance/attributes.luau @@ -16,6 +16,7 @@ local UDim2 = roblox.UDim2 local Vector2 = roblox.Vector2 local Vector3 = roblox.Vector3 local Instance = roblox.Instance +local Enum = roblox.Enum local modelFile = fs.readFile("tests/roblox/rbx-test-files/models/attributes/binary.rbxm") local model = roblox.deserializeModel(modelFile)[1] @@ -114,3 +115,12 @@ model.Parent = game local placeFile = roblox.serializePlace(game) fs.writeDir("bin/roblox") fs.writeFile("bin/roblox/attributes.rbxl", placeFile) + +local enum_attr = Instance.new("Folder") +enum_attr:SetAttribute("Foo", Enum.NormalId.Front) +assert(enum_attr:GetAttribute("Foo") == Enum.NormalId.Front) + +local enum_attr_ser = roblox.serializeModel({ enum_attr }) +local enum_attr_de = roblox.deserializeModel(enum_attr_ser) + +assert(enum_attr_de[1]:GetAttribute("Foo") == Enum.NormalId.Front) diff --git a/tests/roblox/instance/properties.luau b/tests/roblox/instance/properties.luau index 40709a7..576b15b 100644 --- a/tests/roblox/instance/properties.luau +++ b/tests/roblox/instance/properties.luau @@ -33,6 +33,16 @@ part.Shape = Enum.PartType.Ball assert(part.Shape == Enum.PartType.Ball) +-- Enums should roundtrip through serde without problem + +local decal = Instance.new("Decal") +decal.Face = Enum.NormalId.Top + +local decal_ser = roblox.serializeModel({ decal }) +local decal_de = roblox.deserializeModel(decal_ser) + +assert(decal_de[1].Face == Enum.NormalId.Top) + -- Properties that don't exist for a class should error local meshPart = Instance.new("MeshPart")