diff --git a/src/roblox/document/mod.rs b/src/roblox/document/mod.rs index ae76be3..4f5cf00 100644 --- a/src/roblox/document/mod.rs +++ b/src/roblox/document/mod.rs @@ -1,4 +1,4 @@ -use rbx_dom_weak::{InstanceBuilder as DomInstanceBuilder, WeakDom}; +use rbx_dom_weak::{types::Ref as DomRef, InstanceBuilder as DomInstanceBuilder, WeakDom}; use rbx_xml::{ DecodeOptions as XmlDecodeOptions, DecodePropertyBehavior as XmlDecodePropertyBehavior, EncodeOptions as XmlEncodeOptions, EncodePropertyBehavior as XmlEncodePropertyBehavior, @@ -247,11 +247,13 @@ impl Document { } let mut dom = WeakDom::new(DomInstanceBuilder::new("ROOT")); + let children: Vec = i + .get_children() + .iter() + .map(|instance| instance.dom_ref) + .collect(); - for data_model_child in i.get_children() { - data_model_child.clone_into_external_dom(&mut dom); - } - + Instance::clone_multiple_into_external_dom(&children, &mut dom); postprocess_dom_for_place(&mut dom); Ok(Self { @@ -274,11 +276,9 @@ impl Document { } let mut dom = WeakDom::new(DomInstanceBuilder::new("ROOT")); + let instances: Vec = v.iter().map(|instance| instance.dom_ref).collect(); - for instance in v { - instance.clone_into_external_dom(&mut dom); - } - + Instance::clone_multiple_into_external_dom(&instances, &mut dom); postprocess_dom_for_model(&mut dom); Ok(Self { diff --git a/src/roblox/instance/mod.rs b/src/roblox/instance/mod.rs index 2b124f8..79d39f2 100644 --- a/src/roblox/instance/mod.rs +++ b/src/roblox/instance/mod.rs @@ -144,6 +144,21 @@ impl Instance { cloned } + pub fn clone_multiple_into_external_dom( + referents: &[DomRef], + external_dom: &mut WeakDom, + ) -> Vec { + let dom = INTERNAL_DOM.lock().expect("Failed to lock document"); + + let cloned = dom.clone_multiple_into_external(referents, external_dom); + + for referent in cloned.iter() { + external_dom.transfer_within(*referent, external_dom.root_ref()); + } + + cloned + } + /** Clones the instance and all of its descendants, and orphans it. diff --git a/tests/roblox/files/serializeModel.luau b/tests/roblox/files/serializeModel.luau index 3bebc1c..a33ca61 100644 --- a/tests/roblox/files/serializeModel.luau +++ b/tests/roblox/files/serializeModel.luau @@ -2,31 +2,53 @@ local fs = require("@lune/fs") local roblox = require("@lune/roblox") local Instance = roblox.Instance -local instances = { - Instance.new("Model"), - Instance.new("Part"), -} +-- Smoke tests +do + local instances = { + Instance.new("Model"), + Instance.new("Part"), + } -local modelAsBinary = roblox.serializeModel(instances) -local modelAsXml = roblox.serializeModel(instances, true) + local modelAsBinary = roblox.serializeModel(instances) + local modelAsXml = roblox.serializeModel(instances, true) -fs.writeFile("bin/temp-model.rbxm", modelAsBinary) -fs.writeFile("bin/temp-model.rbxmx", modelAsXml) + fs.writeFile("bin/temp-model.rbxm", modelAsBinary) + fs.writeFile("bin/temp-model.rbxmx", modelAsXml) -local savedFileBinary = fs.readFile("bin/temp-model.rbxm") -local savedFileXml = fs.readFile("bin/temp-model.rbxmx") + local savedFileBinary = fs.readFile("bin/temp-model.rbxm") + local savedFileXml = fs.readFile("bin/temp-model.rbxmx") -local savedBinary = roblox.deserializeModel(savedFileBinary) -local savedXml = roblox.deserializeModel(savedFileXml) + local savedBinary = roblox.deserializeModel(savedFileBinary) + local savedXml = roblox.deserializeModel(savedFileXml) -assert(savedBinary[1].Name ~= "ROOT") -assert(savedXml[1].Name ~= "ROOT") + assert(savedBinary[1].Name ~= "ROOT") + assert(savedXml[1].Name ~= "ROOT") -assert(savedBinary[1].Name ~= "DataModel") -assert(savedXml[1].Name ~= "DataModel") + assert(savedBinary[1].Name ~= "DataModel") + assert(savedXml[1].Name ~= "DataModel") -assert(savedBinary[1].ClassName == "Model") -assert(savedBinary[2].ClassName == "Part") + assert(savedBinary[1].ClassName == "Model") + assert(savedBinary[2].ClassName == "Part") -assert(savedXml[1].ClassName == "Model") -assert(savedXml[2].ClassName == "Part") + assert(savedXml[1].ClassName == "Model") + assert(savedXml[2].ClassName == "Part") +end + +-- Ensure Ref properties are preserved across descendants of multi-root model siblings +do + local part = Instance.new("Part") + + local particleEmitter = Instance.new("ParticleEmitter") + particleEmitter.Parent = part + + local folder = Instance.new("Folder") + + local objectValue = Instance.new("ObjectValue") :: any + objectValue.Value = particleEmitter + objectValue.Parent = folder + + local serialized = roblox.serializeModel({ part, folder }) + local deserialized = roblox.deserializeModel(serialized) :: any + + assert(deserialized[2].ObjectValue.Value == deserialized[1].ParticleEmitter) +end diff --git a/tests/roblox/files/serializePlace.luau b/tests/roblox/files/serializePlace.luau index 5dfdfbe..8a55c53 100644 --- a/tests/roblox/files/serializePlace.luau +++ b/tests/roblox/files/serializePlace.luau @@ -2,30 +2,55 @@ local fs = require("@lune/fs") local roblox = require("@lune/roblox") local Instance = roblox.Instance -local game = Instance.new("DataModel") +-- Smoke tests +do + local game = Instance.new("DataModel") -local workspace = game:GetService("Workspace") + local workspace = game:GetService("Workspace") -local model = Instance.new("Model") -local part = Instance.new("Part") + local model = Instance.new("Model") + local part = Instance.new("Part") -part.Parent = model -model.Parent = workspace + part.Parent = model + model.Parent = workspace -local placeAsBinary = roblox.serializePlace(game) -local placeAsXml = roblox.serializePlace(game, true) + local placeAsBinary = roblox.serializePlace(game) + local placeAsXml = roblox.serializePlace(game, true) -fs.writeFile("bin/temp-place.rbxl", placeAsBinary) -fs.writeFile("bin/temp-place.rbxlx", placeAsXml) + fs.writeFile("bin/temp-place.rbxl", placeAsBinary) + fs.writeFile("bin/temp-place.rbxlx", placeAsXml) -local savedFileBinary = fs.readFile("bin/temp-place.rbxl") -local savedFileXml = fs.readFile("bin/temp-place.rbxlx") + local savedFileBinary = fs.readFile("bin/temp-place.rbxl") + local savedFileXml = fs.readFile("bin/temp-place.rbxlx") -local savedBinary = roblox.deserializePlace(savedFileBinary) -local savedXml = roblox.deserializePlace(savedFileXml) + local savedBinary = roblox.deserializePlace(savedFileBinary) + local savedXml = roblox.deserializePlace(savedFileXml) -assert(savedBinary.Name ~= "ROOT") -assert(savedXml.Name ~= "ROOT") + assert(savedBinary.Name ~= "ROOT") + assert(savedXml.Name ~= "ROOT") -assert(savedBinary.ClassName == "DataModel") -assert(savedXml.ClassName == "DataModel") + assert(savedBinary.ClassName == "DataModel") + assert(savedXml.ClassName == "DataModel") +end + +-- Ensure Ref properties are preserved across services +do + local game = Instance.new("DataModel") + local ReplicatedStorage = Instance.new("ReplicatedStorage") + local Workspace = Instance.new("Workspace") + + Workspace.Parent = game + ReplicatedStorage.Parent = game + + local part = Instance.new("Part") + part.Parent = ReplicatedStorage + + local objectValue = Instance.new("ObjectValue") :: any + objectValue.Value = part + objectValue.Parent = Workspace + + local serialized = roblox.serializePlace(game) + local deserialized = roblox.deserializePlace(serialized) :: any + + assert(deserialized.Workspace.ObjectValue.Value == deserialized.ReplicatedStorage.Part) +end