mirror of
https://github.com/lune-org/lune.git
synced 2024-12-12 13:00:37 +00:00
Simplify handling of internal weak dom, start adding instance tests
This commit is contained in:
parent
b8b39eb0b8
commit
7553e91dc6
8 changed files with 189 additions and 223 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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")]
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,9 +280,9 @@ 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
|
||||||
|
@ -284,21 +290,19 @@ impl Instance {
|
||||||
.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
|
|
||||||
.write()
|
|
||||||
.expect("Failed to get read access to source document");
|
|
||||||
|
|
||||||
let dom_target = parent
|
|
||||||
.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()
|
.try_write()
|
||||||
.expect("Failed to get write access to target document");
|
.expect("Failed to get write access to target document");
|
||||||
dom_source.transfer(self.dom_ref, &mut dom_target, target_ref)
|
|
||||||
}
|
dom.transfer_within(self.dom_ref, parent.dom_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,9 +408,9 @@ 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();
|
||||||
|
@ -460,9 +429,9 @@ impl Instance {
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
|
48
tests/roblox/instance/new.luau
Normal file
48
tests/roblox/instance/new.luau
Normal 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")
|
Loading…
Reference in a new issue