mirror of
https://github.com/lune-org/lune.git
synced 2025-01-06 11:29:11 +00:00
Implement proper cloning of instance reference properties for instance cloning
This commit is contained in:
parent
1bbb6a9fd7
commit
580ed6414c
2 changed files with 63 additions and 7 deletions
|
@ -1,5 +1,5 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, VecDeque},
|
collections::{BTreeMap, HashMap, VecDeque},
|
||||||
fmt,
|
fmt,
|
||||||
sync::RwLock,
|
sync::RwLock,
|
||||||
};
|
};
|
||||||
|
@ -146,18 +146,44 @@ impl Instance {
|
||||||
.parent()
|
.parent()
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: We should keep track of a map from old ref -> new ref
|
// Keep track of a map from old ref -> new ref for each
|
||||||
// for each instance so that we can then transform properties
|
// instance so that we can then transform properties that
|
||||||
// that are instance refs into ones pointing at the new instances
|
// are instance refs into ones pointing at the new instances
|
||||||
|
let mut reference_map = HashMap::new();
|
||||||
|
|
||||||
let new_ref = Self::clone_inner(self.dom_ref, parent_ref);
|
let new_ref = Self::clone_inner(self.dom_ref, parent_ref, &mut reference_map);
|
||||||
let new_inst = Self::new(new_ref);
|
let new_inst = Self::new(new_ref);
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut dom = INTERNAL_DOM
|
||||||
|
.try_write()
|
||||||
|
.expect("Failed to get write access to document");
|
||||||
|
let new_refs = reference_map.values().clone().collect::<Vec<_>>();
|
||||||
|
for new_ref in new_refs {
|
||||||
|
let new_inst = dom
|
||||||
|
.get_by_ref_mut(*new_ref)
|
||||||
|
.expect("Failed to find cloned instance in document");
|
||||||
|
for prop_value in new_inst.properties.values_mut() {
|
||||||
|
if let DomValue::Ref(prop_ref) = prop_value {
|
||||||
|
// NOTE: It is possible to get None here if the ref points to
|
||||||
|
// something outside of the newly cloned instance hierarchy
|
||||||
|
if let Some(new) = reference_map.get(prop_ref) {
|
||||||
|
*prop_value = DomValue::Ref(*new);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
new_inst.set_parent(None);
|
new_inst.set_parent(None);
|
||||||
new_inst
|
new_inst
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clone_inner(dom_ref: DomRef, parent_ref: DomRef) -> DomRef {
|
pub fn clone_inner(
|
||||||
|
dom_ref: DomRef,
|
||||||
|
parent_ref: DomRef,
|
||||||
|
reference_map: &mut HashMap<DomRef, DomRef>,
|
||||||
|
) -> DomRef {
|
||||||
// NOTE: We create a new scope here to avoid deadlocking since
|
// NOTE: We create a new scope here to avoid deadlocking since
|
||||||
// our clone implementation must have exclusive write access
|
// our clone implementation must have exclusive write access
|
||||||
let (new_ref, child_refs) = {
|
let (new_ref, child_refs) = {
|
||||||
|
@ -184,11 +210,13 @@ impl Instance {
|
||||||
.with_properties(new_props),
|
.with_properties(new_props),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
reference_map.insert(dom_ref, new_ref);
|
||||||
|
|
||||||
(new_ref, child_refs)
|
(new_ref, child_refs)
|
||||||
};
|
};
|
||||||
|
|
||||||
for child_ref in child_refs {
|
for child_ref in child_refs {
|
||||||
Self::clone_inner(child_ref, new_ref);
|
Self::clone_inner(child_ref, new_ref, reference_map);
|
||||||
}
|
}
|
||||||
|
|
||||||
new_ref
|
new_ref
|
||||||
|
|
28
tests/roblox/instance/methods/Clone.luau
Normal file
28
tests/roblox/instance/methods/Clone.luau
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
local roblox = require("@lune/roblox") :: any
|
||||||
|
local Instance = roblox.Instance
|
||||||
|
|
||||||
|
local root = Instance.new("Model")
|
||||||
|
local child = Instance.new("Part")
|
||||||
|
local objValue1 = Instance.new("ObjectValue")
|
||||||
|
local objValue2 = Instance.new("ObjectValue")
|
||||||
|
|
||||||
|
objValue1.Name = "ObjectValue1"
|
||||||
|
objValue2.Name = "ObjectValue2"
|
||||||
|
objValue1.Value = root
|
||||||
|
objValue2.Value = child
|
||||||
|
objValue1.Parent = child
|
||||||
|
objValue2.Parent = child
|
||||||
|
child.Parent = root
|
||||||
|
|
||||||
|
local clonedChild = child:Clone()
|
||||||
|
assert(clonedChild ~= child)
|
||||||
|
assert(clonedChild.Parent == nil)
|
||||||
|
|
||||||
|
local clonedObjValue1 = clonedChild[objValue1.Name]
|
||||||
|
local clonedObjValue2 = clonedChild[objValue2.Name]
|
||||||
|
|
||||||
|
assert(clonedObjValue1 ~= objValue1)
|
||||||
|
assert(clonedObjValue2 ~= objValue2)
|
||||||
|
|
||||||
|
assert(clonedObjValue1.Value == root, "ObjectValue1.Value should still point to original root")
|
||||||
|
assert(clonedObjValue2.Value == clonedChild, "ObjectValue2.Value should point to cloned child")
|
Loading…
Reference in a new issue