Fully implement document methods for converting to/from instances

This commit is contained in:
Filip Tibell 2023-03-22 10:14:05 +01:00
parent 7553e91dc6
commit 178f7b41ab
No known key found for this signature in database
4 changed files with 125 additions and 49 deletions

View file

@ -13,11 +13,11 @@ pub enum DocumentError {
WriteError(String), WriteError(String),
#[error("Failed to convert into a DataModel - the given document is not a place")] #[error("Failed to convert into a DataModel - the given document is not a place")]
IntoDataModelInvalidArgs, 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, 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, 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, FromInstanceArrayInvalidArgs,
} }

View file

@ -1,6 +1,4 @@
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,
@ -22,6 +20,10 @@ pub type DocumentResult<T> = Result<T, DocumentError>;
A container for [`rbx_dom_weak::WeakDom`] that also takes care of A container for [`rbx_dom_weak::WeakDom`] that also takes care of
reading and writing different kinds and formats of roblox files. reading and writing different kinds and formats of roblox files.
---
### Code Sample #1
```rust ignore ```rust ignore
// Reading a document from a file // Reading a document from a file
@ -37,12 +39,28 @@ pub type DocumentResult<T> = Result<T, DocumentError>;
std::fs::write(&file_path, document.to_bytes()?)?; 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 { pub struct Document {
kind: DocumentKind, kind: DocumentKind,
format: DocumentFormat, format: DocumentFormat,
dom: Arc<RwLock<WeakDom>>, dom: WeakDom,
} }
impl Document { impl Document {
@ -96,11 +114,7 @@ impl Document {
pub fn from_bytes_auto(bytes: impl AsRef<[u8]>) -> DocumentResult<Self> { pub fn from_bytes_auto(bytes: impl AsRef<[u8]>) -> DocumentResult<Self> {
let (format, dom) = Self::from_bytes_inner(bytes)?; let (format, dom) = Self::from_bytes_inner(bytes)?;
let kind = DocumentKind::from_weak_dom(&dom).ok_or(DocumentError::UnknownKind)?; let kind = DocumentKind::from_weak_dom(&dom).ok_or(DocumentError::UnknownKind)?;
Ok(Self { Ok(Self { kind, format, dom })
kind,
format,
dom: Arc::new(RwLock::new(dom)),
})
} }
/** /**
@ -108,17 +122,10 @@ impl Document {
This will automatically handle and detect if the document This will automatically handle and detect if the document
should be decoded using a roblox binary or roblox xml format. 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<Self> { pub fn from_bytes(bytes: impl AsRef<[u8]>, kind: DocumentKind) -> DocumentResult<Self> {
let (format, dom) = Self::from_bytes_inner(bytes)?; let (format, dom) = Self::from_bytes_inner(bytes)?;
Ok(Self { Ok(Self { kind, format, dom })
kind,
format,
dom: Arc::new(RwLock::new(dom)),
})
} }
/** /**
@ -138,15 +145,16 @@ impl Document {
be written to a file or sent over the network. be written to a file or sent over the network.
*/ */
pub fn to_bytes_with_format(&self, format: DocumentFormat) -> DocumentResult<Vec<u8>> { pub fn to_bytes_with_format(&self, format: DocumentFormat) -> DocumentResult<Vec<u8>> {
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::Binary => rbx_binary::to_writer(&mut bytes, &dom, &[dom.root_ref()]) DocumentFormat::Binary => {
.map_err(|err| DocumentError::WriteError(err.to_string())), rbx_binary::to_writer(&mut bytes, &self.dom, &[self.dom.root_ref()])
.map_err(|err| DocumentError::WriteError(err.to_string()))
}
DocumentFormat::Xml => { DocumentFormat::Xml => {
let xml_options = XmlEncodeOptions::new() let xml_options = XmlEncodeOptions::new()
.property_behavior(XmlEncodePropertyBehavior::WriteUnknown); .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())) .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. Will error if the document is not a place.
*/ */
pub fn into_data_model_instance(self) -> DocumentResult<Instance> { pub fn into_data_model_instance(mut self) -> DocumentResult<Instance> {
if self.kind != DocumentKind::Place { if self.kind != DocumentKind::Place {
return Err(DocumentError::IntoDataModelInvalidArgs); 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. Will error if the document is not a model.
*/ */
pub fn into_instance_array(self) -> DocumentResult<Vec<Instance>> { pub fn into_instance_array(mut self) -> DocumentResult<Vec<Instance>> {
if self.kind != DocumentKind::Model { if self.kind != DocumentKind::Model {
return Err(DocumentError::IntoInstanceArrayInvalidArgs); 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. Will error if the instance is not a DataModel.
*/ */
@ -210,13 +238,23 @@ impl Document {
return Err(DocumentError::FromDataModelInvalidArgs); 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<Instance>) -> DocumentResult<Self> { pub fn from_instance_array(instances: Vec<Instance>) -> DocumentResult<Self> {
for instance in &instances { 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,
})
} }
} }

View file

@ -29,7 +29,9 @@ pub struct Instance {
impl 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 { fn new(dom_ref: DomRef) -> Self {
let reader = INTERNAL_DOM let reader = INTERNAL_DOM
@ -50,9 +52,7 @@ impl Instance {
/** /**
Creates a new orphaned `Instance` with a given class name. Creates a new orphaned `Instance` with a given class name.
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.
*/ */
fn new_orphaned(class_name: impl AsRef<str>) -> Self { fn new_orphaned(class_name: impl AsRef<str>) -> Self {
let mut dom = INTERNAL_DOM 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. Clones the instance and all of its descendants, and orphans it.

View file

@ -1,7 +1,4 @@
use std::sync::{Arc, RwLock};
use mlua::prelude::*; use mlua::prelude::*;
use rbx_dom_weak::{InstanceBuilder as DomInstanceBuilder, WeakDom};
use crate::instance::Instance; use crate::instance::Instance;
@ -56,12 +53,6 @@ fn make_all_datatypes(lua: &Lua) -> LuaResult<Vec<(&'static str, LuaValue)>> {
} }
pub fn module(lua: &Lua) -> LuaResult<LuaTable> { pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
// Create an internal weak dom that will be used
// for any instance that does not yet have a parent
let internal_root = DomInstanceBuilder::new("<<<ROOT>>>");
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()?; let exports = lua.create_table()?;
for (name, tab) in make_all_datatypes(lua)? { for (name, tab) in make_all_datatypes(lua)? {
exports.set(name, tab)?; exports.set(name, tab)?;