mirror of
https://github.com/lune-org/lune.git
synced 2024-12-12 13:00:37 +00:00
Implement clone & destroy for roblox instances
This commit is contained in:
parent
69754bd830
commit
c51548165a
1 changed files with 131 additions and 31 deletions
|
@ -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())
|
||||||
|
|
Loading…
Reference in a new issue