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),
#[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,
}

View file

@ -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<T> = Result<T, DocumentError>;
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<T> = Result<T, DocumentError>;
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<RwLock<WeakDom>>,
dom: WeakDom,
}
impl Document {
@ -96,11 +114,7 @@ impl Document {
pub fn from_bytes_auto(bytes: impl AsRef<[u8]>) -> DocumentResult<Self> {
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<Self> {
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<Vec<u8>> {
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<Instance> {
pub fn into_data_model_instance(mut self) -> DocumentResult<Instance> {
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<Vec<Instance>> {
pub fn into_instance_array(mut self) -> DocumentResult<Vec<Instance>> {
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<Instance>) -> DocumentResult<Self> {
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 {
/**
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<str>) -> 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.

View file

@ -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<Vec<(&'static str, LuaValue)>> {
}
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()?;
for (name, tab) in make_all_datatypes(lua)? {
exports.set(name, tab)?;