From 178f7b41ab07212cb89d6f0b43a3f099ca2ec03d Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Wed, 22 Mar 2023 10:14:05 +0100 Subject: [PATCH] Fully implement document methods for converting to/from instances --- packages/lib-roblox/src/document/error.rs | 6 +- packages/lib-roblox/src/document/mod.rs | 114 +++++++++++++++------- packages/lib-roblox/src/instance/mod.rs | 45 ++++++++- packages/lib-roblox/src/lib.rs | 9 -- 4 files changed, 125 insertions(+), 49 deletions(-) diff --git a/packages/lib-roblox/src/document/error.rs b/packages/lib-roblox/src/document/error.rs index 89b7bda..67178cd 100644 --- a/packages/lib-roblox/src/document/error.rs +++ b/packages/lib-roblox/src/document/error.rs @@ -13,11 +13,11 @@ pub enum DocumentError { WriteError(String), #[error("Failed to convert into a DataModel - the given document is not a place")] IntoDataModelInvalidArgs, - #[error("Failed to convert into array of Instances - the given document is a place")] + #[error("Failed to convert into array of Instances - the given document is a model")] IntoInstanceArrayInvalidArgs, - #[error("Failed to convert into a document - the given instance is not a DataModel")] + #[error("Failed to convert into a place - the given instance is not a DataModel")] FromDataModelInvalidArgs, - #[error("Failed to convert into a document - a given instances is a DataModel")] + #[error("Failed to convert into a model - a given instance is a DataModel")] FromInstanceArrayInvalidArgs, } diff --git a/packages/lib-roblox/src/document/mod.rs b/packages/lib-roblox/src/document/mod.rs index 86d17fb..81e7248 100644 --- a/packages/lib-roblox/src/document/mod.rs +++ b/packages/lib-roblox/src/document/mod.rs @@ -1,6 +1,4 @@ -use std::sync::{Arc, RwLock}; - -use rbx_dom_weak::WeakDom; +use rbx_dom_weak::{InstanceBuilder as DomInstanceBuilder, WeakDom}; use rbx_xml::{ DecodeOptions as XmlDecodeOptions, DecodePropertyBehavior as XmlDecodePropertyBehavior, EncodeOptions as XmlEncodeOptions, EncodePropertyBehavior as XmlEncodePropertyBehavior, @@ -22,6 +20,10 @@ pub type DocumentResult = Result; A container for [`rbx_dom_weak::WeakDom`] that also takes care of reading and writing different kinds and formats of roblox files. + --- + + ### Code Sample #1 + ```rust ignore // Reading a document from a file @@ -37,12 +39,28 @@ pub type DocumentResult = Result; std::fs::write(&file_path, document.to_bytes()?)?; ``` + + --- + + ### Code Sample #2 + + ```rust ignore + // Converting a Document to a DataModel or model child instances + let data_model = document.into_data_model_instance()?; + + let model_children = document.into_instance_array()?; + + // Converting a DataModel or model child instances into a Document + let place_doc = Document::from_data_model_instance(data_model)?; + + let model_doc = Document::from_instance_array(model_children)?; + ``` */ -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct Document { kind: DocumentKind, format: DocumentFormat, - dom: Arc>, + dom: WeakDom, } impl Document { @@ -96,11 +114,7 @@ impl Document { pub fn from_bytes_auto(bytes: impl AsRef<[u8]>) -> DocumentResult { let (format, dom) = Self::from_bytes_inner(bytes)?; let kind = DocumentKind::from_weak_dom(&dom).ok_or(DocumentError::UnknownKind)?; - Ok(Self { - kind, - format, - dom: Arc::new(RwLock::new(dom)), - }) + Ok(Self { kind, format, dom }) } /** @@ -108,17 +122,10 @@ impl Document { This will automatically handle and detect if the document should be decoded using a roblox binary or roblox xml format. - - Note that passing [`DocumentKind`] enum values other than [`DocumentKind::Place`] and - [`DocumentKind::Model`] is possible but should only be done within the `lune-roblox` crate. */ pub fn from_bytes(bytes: impl AsRef<[u8]>, kind: DocumentKind) -> DocumentResult { let (format, dom) = Self::from_bytes_inner(bytes)?; - Ok(Self { - kind, - format, - dom: Arc::new(RwLock::new(dom)), - }) + Ok(Self { kind, format, dom }) } /** @@ -138,15 +145,16 @@ impl Document { be written to a file or sent over the network. */ pub fn to_bytes_with_format(&self, format: DocumentFormat) -> DocumentResult> { - let dom = self.dom.try_read().expect("Failed to lock dom"); let mut bytes = Vec::new(); match format { - DocumentFormat::Binary => rbx_binary::to_writer(&mut bytes, &dom, &[dom.root_ref()]) - .map_err(|err| DocumentError::WriteError(err.to_string())), + DocumentFormat::Binary => { + rbx_binary::to_writer(&mut bytes, &self.dom, &[self.dom.root_ref()]) + .map_err(|err| DocumentError::WriteError(err.to_string())) + } DocumentFormat::Xml => { let xml_options = XmlEncodeOptions::new() .property_behavior(XmlEncodePropertyBehavior::WriteUnknown); - rbx_xml::to_writer(&mut bytes, &dom, &[dom.root_ref()], xml_options) + rbx_xml::to_writer(&mut bytes, &self.dom, &[self.dom.root_ref()], xml_options) .map_err(|err| DocumentError::WriteError(err.to_string())) } }?; @@ -175,33 +183,53 @@ impl Document { } /** - Creates a DataModel instance out of this document. + Creates a DataModel instance out of this place document. Will error if the document is not a place. */ - pub fn into_data_model_instance(self) -> DocumentResult { + pub fn into_data_model_instance(mut self) -> DocumentResult { if self.kind != DocumentKind::Place { return Err(DocumentError::IntoDataModelInvalidArgs); } - todo!() + let dom_root = self.dom.root_ref(); + + let data_model_ref = self + .dom + .insert(dom_root, DomInstanceBuilder::new("DataModel")); + let data_model_child_refs = self.dom.root().children().to_vec(); + + for child_ref in data_model_child_refs { + if child_ref != data_model_ref { + self.dom.transfer_within(child_ref, data_model_ref); + } + } + + Ok(Instance::from_external_dom(&mut self.dom, data_model_ref)) } /** - Creates an array of instances out of this document. + Creates an array of instances out of this model document. Will error if the document is not a model. */ - pub fn into_instance_array(self) -> DocumentResult> { + pub fn into_instance_array(mut self) -> DocumentResult> { if self.kind != DocumentKind::Model { return Err(DocumentError::IntoInstanceArrayInvalidArgs); } - todo!() + let dom_child_refs = self.dom.root().children().to_vec(); + + let root_child_instances = dom_child_refs + .into_iter() + .map(|child_ref| Instance::from_external_dom(&mut self.dom, child_ref)) + .collect(); + + Ok(root_child_instances) } /** - Creates a Document out of a DataModel instance. + Creates a place document out of a DataModel instance. Will error if the instance is not a DataModel. */ @@ -210,13 +238,23 @@ impl Document { return Err(DocumentError::FromDataModelInvalidArgs); } - todo!() + let mut dom = WeakDom::new(DomInstanceBuilder::new("ROOT")); + + for data_model_child in instance.get_children() { + data_model_child.into_external_dom(&mut dom); + } + + Ok(Self { + kind: DocumentKind::Place, + format: DocumentFormat::default(), + dom, + }) } /** - Creates an array of instances out of this document. + Creates a model document out of an array of instances. - Will error if the document is not a model. + Will error if any of the instances is a DataModel. */ pub fn from_instance_array(instances: Vec) -> DocumentResult { for instance in &instances { @@ -225,6 +263,16 @@ impl Document { } } - todo!() + let mut dom = WeakDom::new(DomInstanceBuilder::new("ROOT")); + + for instance in instances { + instance.into_external_dom(&mut dom); + } + + Ok(Self { + kind: DocumentKind::Model, + format: DocumentFormat::default(), + dom, + }) } } diff --git a/packages/lib-roblox/src/instance/mod.rs b/packages/lib-roblox/src/instance/mod.rs index c9d6ea8..499b306 100644 --- a/packages/lib-roblox/src/instance/mod.rs +++ b/packages/lib-roblox/src/instance/mod.rs @@ -29,7 +29,9 @@ pub struct Instance { impl Instance { /** - Creates a new `Instance` from a document and dom object ref. + Creates a new `Instance` from an existing dom object ref. + + Panics if the instance does not exist in the internal dom. */ fn new(dom_ref: DomRef) -> Self { let reader = INTERNAL_DOM @@ -50,9 +52,7 @@ impl Instance { /** Creates a new orphaned `Instance` with a given class name. - An orphaned instance does not belong to any particular document and - 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. + An orphaned instance is an instance at the root of a weak dom. */ fn new_orphaned(class_name: impl AsRef) -> Self { let mut dom = INTERNAL_DOM @@ -73,6 +73,43 @@ impl Instance { } } + /** + Creates a new orphaned `Instance` by transferring + it from an external weak dom to the internal one. + + An orphaned instance is an instance at the root of a weak dom. + */ + pub fn from_external_dom(external_dom: &mut WeakDom, external_dom_ref: DomRef) -> Self { + { + let mut dom = INTERNAL_DOM + .try_write() + .expect("Failed to get write access to document"); + let dom_root = dom.root_ref(); + + external_dom.transfer(external_dom_ref, &mut dom, dom_root); + } + + Self::new(external_dom_ref) + } + + /** + Transfers an instance to an external weak dom. + + This will place the instance at the root of the weak dom and return its referent. + */ + pub fn into_external_dom(self, external_dom: &mut WeakDom) -> DomRef { + let mut dom = INTERNAL_DOM + .try_write() + .expect("Failed to get write access to document"); + + let internal_dom_ref = self.dom_ref; + let external_root_ref = external_dom.root_ref(); + + dom.transfer(internal_dom_ref, external_dom, external_root_ref); + + internal_dom_ref + } + /** Clones the instance and all of its descendants, and orphans it. diff --git a/packages/lib-roblox/src/lib.rs b/packages/lib-roblox/src/lib.rs index bb30f72..dffb2f6 100644 --- a/packages/lib-roblox/src/lib.rs +++ b/packages/lib-roblox/src/lib.rs @@ -1,7 +1,4 @@ -use std::sync::{Arc, RwLock}; - use mlua::prelude::*; -use rbx_dom_weak::{InstanceBuilder as DomInstanceBuilder, WeakDom}; use crate::instance::Instance; @@ -56,12 +53,6 @@ fn make_all_datatypes(lua: &Lua) -> LuaResult> { } pub fn module(lua: &Lua) -> LuaResult { - // Create an internal weak dom that will be used - // for any instance that does not yet have a parent - let internal_root = DomInstanceBuilder::new("<<>>"); - let internal_dom = Arc::new(RwLock::new(WeakDom::new(internal_root))); - lua.set_app_data(internal_dom); - // Create all datatypes and singletons and export them let exports = lua.create_table()?; for (name, tab) in make_all_datatypes(lua)? { exports.set(name, tab)?;