Implement clone & destroy for roblox instances

This commit is contained in:
Filip Tibell 2023-03-19 14:21:02 +01:00
parent 69754bd830
commit c51548165a
No known key found for this signature in database

View file

@ -25,6 +25,8 @@ pub struct Instance {
pub(crate) dom: Arc<RwLock<WeakDom>>, pub(crate) dom: Arc<RwLock<WeakDom>>,
pub(crate) dom_ref: DomRef, pub(crate) dom_ref: DomRef,
pub(crate) class_name: String, pub(crate) class_name: String,
pub(crate) is_root: bool,
pub(crate) is_destroyed: bool,
} }
impl Instance { impl Instance {
@ -32,17 +34,16 @@ impl Instance {
Creates a new `Instance` from a document and dom object ref. Creates a new `Instance` from a document and dom object ref.
*/ */
pub fn new(dom: &Arc<RwLock<WeakDom>>, dom_ref: DomRef) -> Self { pub fn new(dom: &Arc<RwLock<WeakDom>>, dom_ref: DomRef) -> Self {
let class_name = dom let reader = dom.read().expect("Failed to get read access to document");
.read() let instance = reader
.expect("Failed to get read access to document")
.get_by_ref(dom_ref) .get_by_ref(dom_ref)
.expect("Failed to find instance in document") .expect("Failed to find instance in document");
.class
.clone();
Self { Self {
dom: Arc::clone(dom), dom: Arc::clone(dom),
dom_ref, dom_ref,
class_name, class_name: instance.class.clone(),
is_root: dom_ref == reader.root_ref(),
is_destroyed: false,
} }
} }
@ -69,6 +70,105 @@ impl Instance {
dom: Arc::clone(&dom_lua), dom: Arc::clone(&dom_lua),
dom_ref, dom_ref,
class_name: class_name.to_string(), class_name: class_name.to_string(),
is_root: false,
is_destroyed: false,
}
}
/**
Clones the instance and all of its descendants, and orphans it.
To then save the new instance it must be re-parented,
which matches the exact behavior of Roblox's instances.
*/
pub fn clone_instance(&self, lua: &Lua) -> Instance {
// NOTE: We create a new scope here to avoid deadlocking since
// our clone implementation must have exclusive write access
let parent_ref = {
self.dom
.read()
.expect("Failed to get read access to document")
.get_by_ref(self.dom_ref)
.expect("Failed to find instance in document")
.parent()
};
let new_ref = Self::clone_inner(lua, self.dom_ref, parent_ref);
let new_inst = Self::new(&self.dom, new_ref);
new_inst.set_parent_to_nil(lua);
new_inst
}
pub fn clone_inner(lua: &Lua, dom_ref: DomRef, parent_ref: DomRef) -> DomRef {
// NOTE: We create a new scope here to avoid deadlocking since
// our clone implementation must have exclusive write access
let (new_ref, child_refs) = {
let dom_lua = lua
.app_data_mut::<Arc<RwLock<WeakDom>>>()
.expect("Failed to find internal lua weak dom");
let mut dom = dom_lua
.try_write()
.expect("Failed to get write access to document");
let (new_class, new_name, new_props, child_refs) = {
let instance = dom
.get_by_ref(dom_ref)
.expect("Failed to find instance in document");
(
instance.class.to_string(),
instance.name.to_string(),
instance.properties.clone(),
instance.children().to_vec(),
)
};
let new_ref = dom.insert(
parent_ref,
DomInstanceBuilder::new(new_class)
.with_name(new_name)
.with_properties(new_props),
);
(new_ref, child_refs)
};
for child_ref in child_refs {
Self::clone_inner(lua, child_ref, new_ref);
}
new_ref
}
/**
Destroys the instance, unless it is the root instance, removing
it completely from the weak dom with no way of recovering it.
All member methods will throw errors when called from lua and panic
when called from rust after the instance has been destroyed.
Returns `true` if destroyed successfully, `false` if already destroyed.
*/
pub fn destroy(&mut self) -> bool {
if self.is_root || self.is_destroyed {
false
} else {
let mut dom = self
.dom
.try_write()
.expect("Failed to get write access to document");
dom.destroy(self.dom_ref);
self.is_destroyed = true;
true
}
}
fn ensure_not_destroyed(&self) -> LuaResult<()> {
if self.is_destroyed {
Err(LuaError::RuntimeError(format!(
"Tried to access destroyed instance '{}'",
self
)))
} else {
Ok(())
} }
} }
@ -79,28 +179,6 @@ impl Instance {
class_is_a(&self.class_name, class_name).unwrap_or(false) class_is_a(&self.class_name, class_name).unwrap_or(false)
} }
/**
Checks if the instance has been destroyed.
*/
pub fn is_destroyed(&self) -> bool {
self.dom
.read()
.expect("Failed to get read access to document")
.get_by_ref(self.dom_ref)
.is_none()
}
/**
Checks if the instance is the root instance.
*/
pub fn is_root(&self) -> bool {
self.dom
.read()
.expect("Failed to get read access to document")
.root_ref()
== self.dom_ref
}
/** /**
Gets the name of the instance, if it exists. Gets the name of the instance, if it exists.
*/ */
@ -319,6 +397,8 @@ impl LuaUserData for Instance {
4. No valid property or instance found, throw error 4. No valid property or instance found, throw error
*/ */
methods.add_meta_method(LuaMetaMethod::Index, |lua, this, prop_name: String| { methods.add_meta_method(LuaMetaMethod::Index, |lua, this, prop_name: String| {
this.ensure_not_destroyed()?;
match prop_name.as_str() { match prop_name.as_str() {
"ClassName" => return this.class_name.clone().to_lua(lua), "ClassName" => return this.class_name.clone().to_lua(lua),
"Name" => { "Name" => {
@ -355,6 +435,8 @@ impl LuaUserData for Instance {
methods.add_meta_method_mut( methods.add_meta_method_mut(
LuaMetaMethod::NewIndex, LuaMetaMethod::NewIndex,
|lua, this, (prop_name, prop_value): (String, LuaValue)| { |lua, this, (prop_name, prop_value): (String, LuaValue)| {
this.ensure_not_destroyed()?;
match prop_name.as_str() { match prop_name.as_str() {
"ClassName" => { "ClassName" => {
return Err(LuaError::RuntimeError( return Err(LuaError::RuntimeError(
@ -417,19 +499,21 @@ impl LuaUserData for Instance {
Currently implemented: Currently implemented:
* Clone
* Destroy
* FindFirstAncestor * FindFirstAncestor
* FindFirstAncestorOfClass * FindFirstAncestorOfClass
* FindFirstAncestorWhichIsA * FindFirstAncestorWhichIsA
* FindFirstChild * FindFirstChild
* FindFirstChildOfClass * FindFirstChildOfClass
* FindFirstChildWhichIsA * FindFirstChildWhichIsA
* IsAncestorOf * IsAncestorOf
* IsDescendantOf * IsDescendantOf
Not yet implemented, but planned: Not yet implemented, but planned:
* Clone
* Destroy
* FindFirstDescendant * FindFirstDescendant
* GetChildren * GetChildren
* GetDescendants * GetDescendants
@ -438,12 +522,22 @@ impl LuaUserData for Instance {
* GetAttributes * GetAttributes
* SetAttribute * SetAttribute
*/ */
methods.add_method("Clone", |lua, this, ()| {
this.ensure_not_destroyed()?;
this.clone_instance(lua).to_lua(lua)
});
methods.add_method_mut("Destroy", |_, this, ()| {
this.destroy();
Ok(())
});
methods.add_method("FindFirstAncestor", |lua, this, name: String| { methods.add_method("FindFirstAncestor", |lua, this, name: String| {
this.ensure_not_destroyed()?;
this.find_ancestor(|child| child.name == name).to_lua(lua) this.find_ancestor(|child| child.name == name).to_lua(lua)
}); });
methods.add_method( methods.add_method(
"FindFirstAncestorOfClass", "FindFirstAncestorOfClass",
|lua, this, class_name: String| { |lua, this, class_name: String| {
this.ensure_not_destroyed()?;
this.find_ancestor(|child| child.class == class_name) this.find_ancestor(|child| child.class == class_name)
.to_lua(lua) .to_lua(lua)
}, },
@ -451,27 +545,33 @@ impl LuaUserData for Instance {
methods.add_method( methods.add_method(
"FindFirstAncestorWhichIsA", "FindFirstAncestorWhichIsA",
|lua, this, class_name: String| { |lua, this, class_name: String| {
this.ensure_not_destroyed()?;
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))
.to_lua(lua) .to_lua(lua)
}, },
); );
methods.add_method("FindFirstChild", |lua, this, name: String| { methods.add_method("FindFirstChild", |lua, this, name: String| {
this.ensure_not_destroyed()?;
this.find_child(|child| child.name == name).to_lua(lua) this.find_child(|child| child.name == name).to_lua(lua)
}); });
methods.add_method("FindFirstChildOfClass", |lua, this, class_name: String| { methods.add_method("FindFirstChildOfClass", |lua, this, class_name: String| {
this.ensure_not_destroyed()?;
this.find_child(|child| child.class == class_name) this.find_child(|child| child.class == class_name)
.to_lua(lua) .to_lua(lua)
}); });
methods.add_method("FindFirstChildWhichIsA", |lua, this, class_name: String| { methods.add_method("FindFirstChildWhichIsA", |lua, this, class_name: String| {
this.ensure_not_destroyed()?;
this.find_child(|child| class_is_a(&child.class, &class_name).unwrap_or(false)) this.find_child(|child| class_is_a(&child.class, &class_name).unwrap_or(false))
.to_lua(lua) .to_lua(lua)
}); });
methods.add_method("IsAncestorOf", |_, this, instance: Instance| { methods.add_method("IsAncestorOf", |_, this, instance: Instance| {
this.ensure_not_destroyed()?;
Ok(instance Ok(instance
.find_ancestor(|ancestor| ancestor.referent() == this.dom_ref) .find_ancestor(|ancestor| ancestor.referent() == this.dom_ref)
.is_some()) .is_some())
}); });
methods.add_method("IsDescendantOf", |_, this, instance: Instance| { methods.add_method("IsDescendantOf", |_, this, instance: Instance| {
this.ensure_not_destroyed()?;
Ok(this Ok(this
.find_ancestor(|ancestor| ancestor.referent() == instance.dom_ref) .find_ancestor(|ancestor| ancestor.referent() == instance.dom_ref)
.is_some()) .is_some())