Simplify handling of internal weak dom, start adding instance tests

This commit is contained in:
Filip Tibell 2023-03-22 09:39:26 +01:00
parent b8b39eb0b8
commit 7553e91dc6
No known key found for this signature in database
8 changed files with 189 additions and 223 deletions

1
Cargo.lock generated
View file

@ -865,6 +865,7 @@ dependencies = [
"anyhow", "anyhow",
"base64 0.21.0", "base64 0.21.0",
"glam", "glam",
"lazy_static",
"mlua", "mlua",
"rand", "rand",
"rbx_binary", "rbx_binary",

View file

@ -16,6 +16,7 @@ path = "src/lib.rs"
[dependencies] [dependencies]
mlua.workspace = true mlua.workspace = true
lazy_static.workspace = true
base64 = "0.21" base64 = "0.21"
glam = "0.23" glam = "0.23"

View file

@ -3,8 +3,6 @@ use thiserror::Error;
#[derive(Debug, Clone, Error)] #[derive(Debug, Clone, Error)]
pub enum DocumentError { pub enum DocumentError {
#[error("Attempted to read or write internal root document")]
InternalRootReadWrite,
#[error("Unknown document kind")] #[error("Unknown document kind")]
UnknownKind, UnknownKind,
#[error("Unknown document format")] #[error("Unknown document format")]

View file

@ -15,7 +15,6 @@ use std::path::Path;
*/ */
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum DocumentFormat { pub enum DocumentFormat {
InternalRoot,
Binary, Binary,
Xml, Xml,
} }

View file

@ -16,7 +16,6 @@ use crate::shared::instance::class_is_a_service;
*/ */
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum DocumentKind { pub enum DocumentKind {
InternalRoot,
Place, Place,
Model, Model,
} }

View file

@ -1,6 +1,6 @@
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use rbx_dom_weak::{InstanceBuilder as DomInstanceBuilder, WeakDom}; use rbx_dom_weak::WeakDom;
use rbx_xml::{ use rbx_xml::{
DecodeOptions as XmlDecodeOptions, DecodePropertyBehavior as XmlDecodePropertyBehavior, DecodeOptions as XmlDecodeOptions, DecodePropertyBehavior as XmlDecodePropertyBehavior,
EncodeOptions as XmlEncodeOptions, EncodePropertyBehavior as XmlEncodePropertyBehavior, EncodeOptions as XmlEncodeOptions, EncodePropertyBehavior as XmlEncodePropertyBehavior,
@ -56,22 +56,14 @@ impl Document {
| Place | Xml | `rbxlx` | | Place | Xml | `rbxlx` |
| Model | Binary | `rbxm` | | Model | Binary | `rbxm` |
| Model | Xml | `rbxmx` | | Model | Xml | `rbxmx` |
| ? | ? | None |
The last entry here signifies any kind of internal document kind
or format variant, which should not be used outside of this crate.
As such, if it is known that no internal specifier is being
passed here, the return value can be safely unwrapped.
*/ */
#[rustfmt::skip] #[rustfmt::skip]
pub fn canonical_extension(kind: DocumentKind, format: DocumentFormat) -> Option<&'static str> { pub fn canonical_extension(kind: DocumentKind, format: DocumentFormat) -> &'static str {
match (kind, format) { match (kind, format) {
(DocumentKind::Place, DocumentFormat::Binary) => Some("rbxl"), (DocumentKind::Place, DocumentFormat::Binary) => "rbxl",
(DocumentKind::Place, DocumentFormat::Xml) => Some("rbxlx"), (DocumentKind::Place, DocumentFormat::Xml) => "rbxlx",
(DocumentKind::Model, DocumentFormat::Binary) => Some("rbxm"), (DocumentKind::Model, DocumentFormat::Binary) => "rbxm",
(DocumentKind::Model, DocumentFormat::Xml) => Some("rbxmx"), (DocumentKind::Model, DocumentFormat::Xml) => "rbxmx",
_ => None,
} }
} }
@ -79,7 +71,6 @@ impl Document {
let bytes = bytes.as_ref(); let bytes = bytes.as_ref();
let format = DocumentFormat::from_bytes(bytes).ok_or(DocumentError::UnknownFormat)?; let format = DocumentFormat::from_bytes(bytes).ok_or(DocumentError::UnknownFormat)?;
let dom = match format { let dom = match format {
DocumentFormat::InternalRoot => Err(DocumentError::InternalRootReadWrite),
DocumentFormat::Binary => rbx_binary::from_reader(bytes) DocumentFormat::Binary => rbx_binary::from_reader(bytes)
.map_err(|err| DocumentError::ReadError(err.to_string())), .map_err(|err| DocumentError::ReadError(err.to_string())),
DocumentFormat::Xml => { DocumentFormat::Xml => {
@ -150,7 +141,6 @@ impl Document {
let dom = self.dom.try_read().expect("Failed to lock dom"); let dom = self.dom.try_read().expect("Failed to lock dom");
let mut bytes = Vec::new(); let mut bytes = Vec::new();
match format { match format {
DocumentFormat::InternalRoot => Err(DocumentError::InternalRootReadWrite),
DocumentFormat::Binary => rbx_binary::to_writer(&mut bytes, &dom, &[dom.root_ref()]) DocumentFormat::Binary => rbx_binary::to_writer(&mut bytes, &dom, &[dom.root_ref()])
.map_err(|err| DocumentError::WriteError(err.to_string())), .map_err(|err| DocumentError::WriteError(err.to_string())),
DocumentFormat::Xml => { DocumentFormat::Xml => {
@ -179,14 +169,8 @@ impl Document {
/** /**
Gets the file extension for this document. Gets the file extension for this document.
Note that this will return `None` for an internal root
document, otherwise it will always return `Some`.
As such, if it is known that no internal root document is
being used here, the return value can be safely unwrapped.
*/ */
pub fn extension(&self) -> Option<&'static str> { pub fn extension(&self) -> &'static str {
Self::canonical_extension(self.kind, self.format) Self::canonical_extension(self.kind, self.format)
} }
@ -200,25 +184,7 @@ impl Document {
return Err(DocumentError::IntoDataModelInvalidArgs); return Err(DocumentError::IntoDataModelInvalidArgs);
} }
// NOTE: We create a new scope here to avoid deadlocking, todo!()
// creating a new instance will try to get the dom rwlock
let data_model_ref = {
let mut dom_handle = self.dom.write().unwrap();
let dom_root = dom_handle.root_ref();
let data_model_ref = dom_handle.insert(dom_root, DomInstanceBuilder::new("DataModel"));
let data_model_child_refs = dom_handle.root().children().to_vec();
for child_ref in data_model_child_refs {
if child_ref != data_model_ref {
dom_handle.transfer_within(child_ref, data_model_ref);
}
}
data_model_ref
};
Ok(Instance::new(&self.dom, data_model_ref))
} }
/** /**
@ -231,19 +197,7 @@ impl Document {
return Err(DocumentError::IntoInstanceArrayInvalidArgs); return Err(DocumentError::IntoInstanceArrayInvalidArgs);
} }
// NOTE: We create a new scope here to avoid deadlocking, todo!()
// creating a new instance will try to get the dom rwlock
let root_child_refs = {
let dom_handle = self.dom.read().unwrap();
dom_handle.root().children().to_vec()
};
let root_child_instances = root_child_refs
.into_iter()
.map(|child_ref| Instance::new(&self.dom, child_ref))
.collect();
Ok(root_child_instances)
} }
/** /**
@ -252,7 +206,7 @@ impl Document {
Will error if the instance is not a DataModel. Will error if the instance is not a DataModel.
*/ */
pub fn from_data_model_instance(instance: Instance) -> DocumentResult<Self> { pub fn from_data_model_instance(instance: Instance) -> DocumentResult<Self> {
if instance.class_name != "DataModel" { if instance.get_class_name() != "DataModel" {
return Err(DocumentError::FromDataModelInvalidArgs); return Err(DocumentError::FromDataModelInvalidArgs);
} }
@ -266,7 +220,7 @@ impl Document {
*/ */
pub fn from_instance_array(instances: Vec<Instance>) -> DocumentResult<Self> { pub fn from_instance_array(instances: Vec<Instance>) -> DocumentResult<Self> {
for instance in &instances { for instance in &instances {
if instance.class_name == "DataModel" { if instance.get_class_name() == "DataModel" {
return Err(DocumentError::FromInstanceArrayInvalidArgs); return Err(DocumentError::FromInstanceArrayInvalidArgs);
} }
} }

View file

@ -1,8 +1,4 @@
use std::{ use std::{collections::VecDeque, fmt, sync::RwLock};
collections::VecDeque,
fmt,
sync::{Arc, RwLock},
};
use mlua::prelude::*; use mlua::prelude::*;
use rbx_dom_weak::{ use rbx_dom_weak::{
@ -19,26 +15,32 @@ use crate::{
shared::instance::{class_exists, class_is_a, find_property_info}, shared::instance::{class_exists, class_is_a, find_property_info},
}; };
lazy_static::lazy_static! {
static ref INTERNAL_DOM: RwLock<WeakDom> =
RwLock::new(WeakDom::new(DomInstanceBuilder::new("ROOT")));
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Instance { pub struct Instance {
pub(crate) dom: Arc<RwLock<WeakDom>>, dom_ref: DomRef,
pub(crate) dom_ref: DomRef, class_name: String,
pub(crate) class_name: String, is_root: bool,
pub(crate) is_root: bool,
} }
impl Instance { 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 { fn new(dom_ref: DomRef) -> Self {
let reader = dom.read().expect("Failed to get read access to document"); let reader = INTERNAL_DOM
.try_read()
.expect("Failed to get read access to document");
let instance = reader let instance = reader
.get_by_ref(dom_ref) .get_by_ref(dom_ref)
.expect("Failed to find instance in document"); .expect("Failed to find instance in document");
Self { Self {
dom: Arc::clone(dom),
dom_ref, dom_ref,
class_name: instance.class.clone(), class_name: instance.class.clone(),
is_root: dom_ref == reader.root_ref(), is_root: dom_ref == reader.root_ref(),
@ -52,20 +54,19 @@ impl Instance {
is instead part of the internal weak dom for orphaned lua instances, is instead part of the internal weak dom for orphaned lua instances,
it can however be re-parented to a "real" document and weak dom. it can however be re-parented to a "real" document and weak dom.
*/ */
pub fn new_orphaned(lua: &Lua, class_name: impl AsRef<str>) -> Self { fn new_orphaned(class_name: impl AsRef<str>) -> Self {
let dom_lua = lua let mut dom = INTERNAL_DOM
.app_data_mut::<Arc<RwLock<WeakDom>>>() .try_write()
.expect("Failed to find internal lua weak dom");
let mut dom = dom_lua
.write()
.expect("Failed to get write access to document"); .expect("Failed to get write access to document");
let class_name = class_name.as_ref(); let class_name = class_name.as_ref();
let instance = DomInstanceBuilder::new(class_name.to_string());
let dom_root = dom.root_ref(); let dom_root = dom.root_ref();
let dom_ref = dom.insert(dom_root, DomInstanceBuilder::new(class_name.to_string())); let dom_ref = dom.insert(dom_root, instance);
Self { Self {
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_root: false,
@ -82,33 +83,30 @@ impl Instance {
* [`Clone`](https://create.roblox.com/docs/reference/engine/classes/Instance#Clone) * [`Clone`](https://create.roblox.com/docs/reference/engine/classes/Instance#Clone)
on the Roblox Developer Hub on the Roblox Developer Hub
*/ */
pub fn clone_instance(&self, lua: &Lua) -> Instance { pub fn clone_instance(&self) -> Instance {
// 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 parent_ref = { let parent_ref = {
self.dom INTERNAL_DOM
.read() .try_read()
.expect("Failed to get read access to document") .expect("Failed to get read access to document")
.get_by_ref(self.dom_ref) .get_by_ref(self.dom_ref)
.expect("Failed to find instance in document") .expect("Failed to find instance in document")
.parent() .parent()
}; };
let new_ref = Self::clone_inner(lua, self.dom_ref, parent_ref); let new_ref = Self::clone_inner(self.dom_ref, parent_ref);
let new_inst = Self::new(&self.dom, new_ref); let new_inst = Self::new(new_ref);
new_inst.set_parent_to_nil(lua); new_inst.set_parent_to_nil();
new_inst new_inst
} }
pub fn clone_inner(lua: &Lua, dom_ref: DomRef, parent_ref: DomRef) -> DomRef { pub fn clone_inner(dom_ref: DomRef, parent_ref: 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) = {
let dom_lua = lua let mut dom = INTERNAL_DOM
.app_data_mut::<Arc<RwLock<WeakDom>>>()
.expect("Failed to find internal lua weak dom");
let mut dom = dom_lua
.try_write() .try_write()
.expect("Failed to get write access to document"); .expect("Failed to get write access to document");
@ -135,7 +133,7 @@ impl Instance {
}; };
for child_ref in child_refs { for child_ref in child_refs {
Self::clone_inner(lua, child_ref, new_ref); Self::clone_inner(child_ref, new_ref);
} }
new_ref new_ref
@ -158,8 +156,7 @@ impl Instance {
if self.is_root || self.is_destroyed() { if self.is_root || self.is_destroyed() {
false false
} else { } else {
let mut dom = self let mut dom = INTERNAL_DOM
.dom
.try_write() .try_write()
.expect("Failed to get write access to document"); .expect("Failed to get write access to document");
@ -183,9 +180,8 @@ impl Instance {
// NOTE: This property can not be cached since instance references // NOTE: This property can not be cached since instance references
// other than this one may have destroyed this one, and we don't // other than this one may have destroyed this one, and we don't
// keep track of all current instance reference structs // keep track of all current instance reference structs
let dom = self let dom = INTERNAL_DOM
.dom .try_read()
.read()
.expect("Failed to get write access to document"); .expect("Failed to get write access to document");
dom.get_by_ref(self.dom_ref).is_none() dom.get_by_ref(self.dom_ref).is_none()
} }
@ -199,8 +195,7 @@ impl Instance {
on the Roblox Developer Hub on the Roblox Developer Hub
*/ */
pub fn clear_all_children(&mut self) { pub fn clear_all_children(&mut self) {
let mut dom = self let mut dom = INTERNAL_DOM
.dom
.try_write() .try_write()
.expect("Failed to get write access to document"); .expect("Failed to get write access to document");
@ -225,6 +220,19 @@ impl Instance {
class_is_a(&self.class_name, class_name).unwrap_or(false) class_is_a(&self.class_name, class_name).unwrap_or(false)
} }
/**
Gets the class name of the instance.
This will return the correct class name even if the instance has been destroyed.
### See Also
* [`ClassName`](https://create.roblox.com/docs/reference/engine/classes/Instance#ClassName)
on the Roblox Developer Hub
*/
pub fn get_class_name(&self) -> &str {
self.class_name.as_str()
}
/** /**
Gets the name of the instance, if it exists. Gets the name of the instance, if it exists.
@ -233,9 +241,8 @@ impl Instance {
on the Roblox Developer Hub on the Roblox Developer Hub
*/ */
pub fn get_name(&self) -> String { pub fn get_name(&self) -> String {
let dom = self let dom = INTERNAL_DOM
.dom .try_read()
.read()
.expect("Failed to get read access to document"); .expect("Failed to get read access to document");
dom.get_by_ref(self.dom_ref) dom.get_by_ref(self.dom_ref)
@ -252,9 +259,8 @@ impl Instance {
on the Roblox Developer Hub on the Roblox Developer Hub
*/ */
pub fn set_name(&self, name: impl Into<String>) { pub fn set_name(&self, name: impl Into<String>) {
let mut dom = self let mut dom = INTERNAL_DOM
.dom .try_write()
.write()
.expect("Failed to get write access to document"); .expect("Failed to get write access to document");
dom.get_by_ref_mut(self.dom_ref) dom.get_by_ref_mut(self.dom_ref)
@ -274,31 +280,29 @@ impl Instance {
return None; return None;
} }
let dom = self let (nil_parent_ref, parent_ref) = {
.dom let dom = INTERNAL_DOM
.read() .try_read()
.expect("Failed to get read access to document"); .expect("Failed to get read access to document");
let parent_ref = dom let parent_ref = dom
.get_by_ref(self.dom_ref) .get_by_ref(self.dom_ref)
.expect("Failed to find instance in document") .expect("Failed to find instance in document")
.parent(); .parent();
if parent_ref == dom.root_ref() { (dom.root_ref(), parent_ref)
};
if parent_ref == nil_parent_ref {
None None
} else { } else {
Some(Self::new(&self.dom, parent_ref)) Some(Self::new(parent_ref))
} }
} }
/** /**
Sets the parent of the instance, if it exists. Sets the parent of the instance, if it exists.
Note that this can transfer between different weak doms,
and assumes that separate doms always have unique root referents.
If doms do not have unique root referents then this operation may panic.
### See Also ### See Also
* [`Parent`](https://create.roblox.com/docs/reference/engine/classes/Instance#Parent) * [`Parent`](https://create.roblox.com/docs/reference/engine/classes/Instance#Parent)
on the Roblox Developer Hub on the Roblox Developer Hub
@ -308,75 +312,42 @@ impl Instance {
panic!("Root instance can not be reparented") panic!("Root instance can not be reparented")
} }
let mut dom_source = self let mut dom = INTERNAL_DOM
.dom .try_write()
.write() .expect("Failed to get write access to target document");
.expect("Failed to get read access to source document");
let dom_target = parent dom.transfer_within(self.dom_ref, parent.dom_ref);
.dom
.read()
.expect("Failed to get read access to target document");
let target_ref = dom_target
.get_by_ref(parent.dom_ref)
.expect("Failed to find instance in target document")
.parent();
if dom_source.root_ref() == dom_target.root_ref() {
dom_source.transfer_within(self.dom_ref, target_ref);
} else {
// NOTE: We must drop the previous dom_target read handle here first so
// that we can get exclusive write access for transferring across doms
drop(dom_target);
let mut dom_target = parent
.dom
.try_write()
.expect("Failed to get write access to target document");
dom_source.transfer(self.dom_ref, &mut dom_target, target_ref)
}
} }
/** /**
Sets the parent of the instance, if it exists, to nil, making it orphaned. Sets the parent of the instance, if it exists, to nil, making it orphaned.
An orphaned instance does not belong to any particular document and An orphaned instance is an instance at the root of a weak dom.
is instead part of the internal weak dom for orphaned lua instances,
it can however be re-parented to a "real" document and weak dom.
### See Also ### See Also
* [`Parent`](https://create.roblox.com/docs/reference/engine/classes/Instance#Parent) * [`Parent`](https://create.roblox.com/docs/reference/engine/classes/Instance#Parent)
on the Roblox Developer Hub on the Roblox Developer Hub
*/ */
pub fn set_parent_to_nil(&self, lua: &Lua) { pub fn set_parent_to_nil(&self) {
if self.is_root { if self.is_root {
panic!("Root instance can not be reparented") panic!("Root instance can not be reparented")
} }
let mut dom_source = self let mut dom = INTERNAL_DOM
.dom .try_write()
.write()
.expect("Failed to get read access to source document");
let dom_lua = lua
.app_data_mut::<Arc<RwLock<WeakDom>>>()
.expect("Failed to find internal lua weak dom");
let mut dom_target = dom_lua
.write()
.expect("Failed to get write access to target document"); .expect("Failed to get write access to target document");
let target_ref = dom_target.root_ref(); let nil_parent_ref = dom.root_ref();
dom_source.transfer(self.dom_ref, &mut dom_target, target_ref)
dom.transfer_within(self.dom_ref, nil_parent_ref);
} }
/** /**
Gets a property for the instance, if it exists. Gets a property for the instance, if it exists.
*/ */
pub fn get_property(&self, name: impl AsRef<str>) -> Option<DomValue> { pub fn get_property(&self, name: impl AsRef<str>) -> Option<DomValue> {
self.dom INTERNAL_DOM
.read() .try_read()
.expect("Failed to get read access to document") .expect("Failed to get read access to document")
.get_by_ref(self.dom_ref) .get_by_ref(self.dom_ref)
.expect("Failed to find instance in document") .expect("Failed to find instance in document")
@ -392,8 +363,8 @@ impl Instance {
property does not actually exist for the instance class. property does not actually exist for the instance class.
*/ */
pub fn set_property(&self, name: impl AsRef<str>, value: DomValue) { pub fn set_property(&self, name: impl AsRef<str>, value: DomValue) {
self.dom INTERNAL_DOM
.write() .try_write()
.expect("Failed to get read access to document") .expect("Failed to get read access to document")
.get_by_ref_mut(self.dom_ref) .get_by_ref_mut(self.dom_ref)
.expect("Failed to find instance in document") .expect("Failed to find instance in document")
@ -412,20 +383,18 @@ impl Instance {
on the Roblox Developer Hub on the Roblox Developer Hub
*/ */
pub fn get_children(&self) -> Vec<Instance> { pub fn get_children(&self) -> Vec<Instance> {
let dom = self let children = {
.dom let dom = INTERNAL_DOM
.read() .try_read()
.expect("Failed to get read access to document"); .expect("Failed to get read access to document");
let children = dom dom.get_by_ref(self.dom_ref)
.get_by_ref(self.dom_ref) .expect("Failed to find instance in document")
.expect("Failed to find instance in document") .children()
.children(); .to_vec()
};
children children.into_iter().map(Self::new).collect()
.iter()
.map(|child_ref| Self::new(&self.dom, *child_ref))
.collect()
} }
/** /**
@ -439,30 +408,30 @@ impl Instance {
on the Roblox Developer Hub on the Roblox Developer Hub
*/ */
pub fn get_descendants(&self) -> Vec<Instance> { pub fn get_descendants(&self) -> Vec<Instance> {
let dom = self let descendants = {
.dom let dom = INTERNAL_DOM
.read() .try_read()
.expect("Failed to get read access to document"); .expect("Failed to get read access to document");
let mut descendants = Vec::new(); let mut descendants = Vec::new();
let mut queue = VecDeque::from_iter( let mut queue = VecDeque::from_iter(
dom.get_by_ref(self.dom_ref) dom.get_by_ref(self.dom_ref)
.expect("Failed to find instance in document") .expect("Failed to find instance in document")
.children(), .children(),
); );
while let Some(queue_ref) = queue.pop_front() { while let Some(queue_ref) = queue.pop_front() {
descendants.push(*queue_ref); descendants.push(*queue_ref);
let queue_inst = dom.get_by_ref(*queue_ref).unwrap(); let queue_inst = dom.get_by_ref(*queue_ref).unwrap();
for queue_ref_inner in queue_inst.children().iter().rev() { for queue_ref_inner in queue_inst.children().iter().rev() {
queue.push_front(queue_ref_inner); queue.push_front(queue_ref_inner);
}
} }
}
descendants descendants
.iter() };
.map(|child_ref| Self::new(&self.dom, *child_ref))
.collect() descendants.into_iter().map(Self::new).collect()
} }
/** /**
@ -478,9 +447,8 @@ impl Instance {
on the Roblox Developer Hub on the Roblox Developer Hub
*/ */
pub fn get_full_name(&self) -> String { pub fn get_full_name(&self) -> String {
let dom = self let dom = INTERNAL_DOM
.dom .try_read()
.read()
.expect("Failed to get read access to document"); .expect("Failed to get read access to document");
let dom_root = dom.root_ref(); let dom_root = dom.root_ref();
@ -512,20 +480,20 @@ impl Instance {
where where
F: Fn(&DomInstance) -> bool, F: Fn(&DomInstance) -> bool,
{ {
let dom = self let dom = INTERNAL_DOM
.dom .try_read()
.read()
.expect("Failed to get read access to document"); .expect("Failed to get read access to document");
let children = dom let children = dom
.get_by_ref(self.dom_ref) .get_by_ref(self.dom_ref)
.expect("Failed to find instance in document") .expect("Failed to find instance in document")
.children(); .children()
.to_vec();
children.iter().find_map(|child_ref| { children.into_iter().find_map(|child_ref| {
if let Some(child_inst) = dom.get_by_ref(*child_ref) { if let Some(child_inst) = dom.get_by_ref(child_ref) {
if predicate(child_inst) { if predicate(child_inst) {
Some(Self::new(&self.dom, *child_ref)) Some(Self::new(child_ref))
} else { } else {
None None
} }
@ -547,9 +515,8 @@ impl Instance {
where where
F: Fn(&DomInstance) -> bool, F: Fn(&DomInstance) -> bool,
{ {
let dom = self let dom = INTERNAL_DOM
.dom .try_read()
.read()
.expect("Failed to get read access to document"); .expect("Failed to get read access to document");
let mut ancestor_ref = dom let mut ancestor_ref = dom
@ -559,7 +526,7 @@ impl Instance {
while let Some(ancestor) = dom.get_by_ref(ancestor_ref) { while let Some(ancestor) = dom.get_by_ref(ancestor_ref) {
if predicate(ancestor) { if predicate(ancestor) {
return Some(Self::new(&self.dom, ancestor_ref)); return Some(Self::new(ancestor_ref));
} else { } else {
ancestor_ref = ancestor.parent(); ancestor_ref = ancestor.parent();
} }
@ -580,9 +547,8 @@ impl Instance {
where where
F: Fn(&DomInstance) -> bool, F: Fn(&DomInstance) -> bool,
{ {
let dom = self let dom = INTERNAL_DOM
.dom .try_read()
.read()
.expect("Failed to get read access to document"); .expect("Failed to get read access to document");
let mut queue = VecDeque::from_iter( let mut queue = VecDeque::from_iter(
@ -596,7 +562,7 @@ impl Instance {
.and_then(|queue_ref| dom.get_by_ref(*queue_ref)) .and_then(|queue_ref| dom.get_by_ref(*queue_ref))
{ {
if predicate(queue_item) { if predicate(queue_item) {
return Some(Self::new(&self.dom, queue_item.referent())); return Some(Self::new(queue_item.referent()));
} else { } else {
queue.extend(queue_item.children()) queue.extend(queue_item.children())
} }
@ -612,7 +578,7 @@ impl Instance {
"new", "new",
lua.create_function(|lua, class_name: String| { lua.create_function(|lua, class_name: String| {
if class_exists(&class_name) { if class_exists(&class_name) {
Instance::new_orphaned(lua, class_name).to_lua(lua) Instance::new_orphaned(class_name).to_lua(lua)
} else { } else {
Err(LuaError::RuntimeError(format!( Err(LuaError::RuntimeError(format!(
"'{}' is not a valid class name", "'{}' is not a valid class name",
@ -642,7 +608,7 @@ impl LuaUserData for Instance {
this.ensure_not_destroyed()?; 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.get_class_name().to_lua(lua),
"Name" => { "Name" => {
return this.get_name().to_lua(lua); return this.get_name().to_lua(lua);
} }
@ -733,7 +699,7 @@ impl LuaUserData for Instance {
type Parent = Option<Instance>; type Parent = Option<Instance>;
match Parent::from_lua(prop_value, lua)? { match Parent::from_lua(prop_value, lua)? {
Some(parent) => this.set_parent(parent), Some(parent) => this.set_parent(parent),
None => this.set_parent_to_nil(lua), None => this.set_parent_to_nil(),
} }
return Ok(()); return Ok(());
} }
@ -815,7 +781,7 @@ impl LuaUserData for Instance {
*/ */
methods.add_method("Clone", |lua, this, ()| { methods.add_method("Clone", |lua, this, ()| {
this.ensure_not_destroyed()?; this.ensure_not_destroyed()?;
this.clone_instance(lua).to_lua(lua) this.clone_instance().to_lua(lua)
}); });
methods.add_method_mut("Destroy", |_, this, ()| { methods.add_method_mut("Destroy", |_, this, ()| {
this.destroy(); this.destroy();

View file

@ -0,0 +1,48 @@
local roblox = require("@lune/roblox") :: any
local Instance = roblox.Instance
-- Should not allow creating unknown classes
assert(not pcall(function()
Instance.new("asdf")
end))
-- Should be case sensitive
assert(not pcall(function()
Instance.new("part")
end))
-- Should allow "not creatable" tagged classes to be created
Instance.new("BasePart")
-- Should have correct classnames
assert(Instance.new("Part").ClassName == "Part")
assert(Instance.new("Folder").ClassName == "Folder")
assert(Instance.new("ReplicatedStorage").ClassName == "ReplicatedStorage")
-- Should have initial names that are the same as the class name
assert(Instance.new("Part").Name == "Part")
assert(Instance.new("Folder").Name == "Folder")
assert(Instance.new("ReplicatedStorage").Name == "ReplicatedStorage")
-- Parent should be nil until parented
local folder = Instance.new("Folder")
local model = Instance.new("Model")
assert(folder.Parent == nil)
assert(model.Parent == nil)
-- Parenting and indexing should work
model.Parent = folder
assert(model.Parent == folder)
assert(folder.Model == model)
-- Parenting to nil should work
model.Parent = nil
assert(model.Parent == nil)
-- Name should be able to be set, and should not be nillable
model.Name = "MyCoolModel"
assert(model.Name == "MyCoolModel")
assert(not pcall(function()
model.Name = nil
end))
assert(model.Name == "MyCoolModel")