mirror of
https://github.com/CompeyDev/lune-packaging.git
synced 2025-01-09 12:19:09 +00:00
Implement lua APIs for reading & writing roblox files
This commit is contained in:
parent
531c579f2d
commit
b8b39eb0b8
5 changed files with 185 additions and 11 deletions
|
@ -1,6 +0,0 @@
|
|||
[env]
|
||||
# We only use these blocking::unblock threads for prompting
|
||||
# for interactive input using stdin, the default amount of
|
||||
# threads is 500 which is unnecessarily high, we probably
|
||||
# only need one thread here but lets do 10 just in case
|
||||
BLOCKING_MAX_THREADS = "10"
|
|
@ -1,3 +1,4 @@
|
|||
use mlua::prelude::*;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Clone, Error)]
|
||||
|
@ -12,4 +13,18 @@ pub enum DocumentError {
|
|||
ReadError(String),
|
||||
#[error("Failed to write document to buffer")]
|
||||
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")]
|
||||
IntoInstanceArrayInvalidArgs,
|
||||
#[error("Failed to convert into a document - the given instance is not a DataModel")]
|
||||
FromDataModelInvalidArgs,
|
||||
#[error("Failed to convert into a document - a given instances is a DataModel")]
|
||||
FromInstanceArrayInvalidArgs,
|
||||
}
|
||||
|
||||
impl From<DocumentError> for LuaError {
|
||||
fn from(value: DocumentError) -> Self {
|
||||
Self::RuntimeError(value.to_string())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,6 +73,12 @@ impl DocumentFormat {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for DocumentFormat {
|
||||
fn default() -> Self {
|
||||
Self::Binary
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::PathBuf;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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,
|
||||
|
@ -14,6 +14,8 @@ pub use error::*;
|
|||
pub use format::*;
|
||||
pub use kind::*;
|
||||
|
||||
use crate::instance::Instance;
|
||||
|
||||
pub type DocumentResult<T> = Result<T, DocumentError>;
|
||||
|
||||
/**
|
||||
|
@ -189,9 +191,86 @@ impl Document {
|
|||
}
|
||||
|
||||
/**
|
||||
Gets the underlying weak dom for this document.
|
||||
Creates a DataModel instance out of this document.
|
||||
|
||||
Will error if the document is not a place.
|
||||
*/
|
||||
pub fn dom(&self) -> Arc<RwLock<WeakDom>> {
|
||||
Arc::clone(&self.dom)
|
||||
pub fn into_data_model_instance(self) -> DocumentResult<Instance> {
|
||||
if self.kind != DocumentKind::Place {
|
||||
return Err(DocumentError::IntoDataModelInvalidArgs);
|
||||
}
|
||||
|
||||
// NOTE: We create a new scope here to avoid deadlocking,
|
||||
// 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))
|
||||
}
|
||||
|
||||
/**
|
||||
Creates an array of instances out of this document.
|
||||
|
||||
Will error if the document is not a model.
|
||||
*/
|
||||
pub fn into_instance_array(self) -> DocumentResult<Vec<Instance>> {
|
||||
if self.kind != DocumentKind::Model {
|
||||
return Err(DocumentError::IntoInstanceArrayInvalidArgs);
|
||||
}
|
||||
|
||||
// NOTE: We create a new scope here to avoid deadlocking,
|
||||
// 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)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a Document out of a DataModel instance.
|
||||
|
||||
Will error if the instance is not a DataModel.
|
||||
*/
|
||||
pub fn from_data_model_instance(instance: Instance) -> DocumentResult<Self> {
|
||||
if instance.class_name != "DataModel" {
|
||||
return Err(DocumentError::FromDataModelInvalidArgs);
|
||||
}
|
||||
|
||||
todo!()
|
||||
}
|
||||
|
||||
/**
|
||||
Creates an array of instances out of this document.
|
||||
|
||||
Will error if the document is not a model.
|
||||
*/
|
||||
pub fn from_instance_array(instances: Vec<Instance>) -> DocumentResult<Self> {
|
||||
for instance in &instances {
|
||||
if instance.class_name == "DataModel" {
|
||||
return Err(DocumentError::FromInstanceArrayInvalidArgs);
|
||||
}
|
||||
}
|
||||
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,13 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use blocking::unblock;
|
||||
use mlua::prelude::*;
|
||||
use tokio::fs;
|
||||
|
||||
use lune_roblox::{
|
||||
document::{Document, DocumentError, DocumentFormat, DocumentKind},
|
||||
instance::Instance,
|
||||
};
|
||||
|
||||
use crate::lua::table::TableBuilder;
|
||||
|
||||
|
@ -8,8 +17,79 @@ pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
|
|||
for pair in roblox_module.pairs::<LuaValue, LuaValue>() {
|
||||
roblox_constants.push(pair?);
|
||||
}
|
||||
// TODO: Add async functions for reading & writing documents, creating instances
|
||||
TableBuilder::new(lua)?
|
||||
.with_values(roblox_constants)?
|
||||
.with_async_function("readPlaceFile", read_place_file)?
|
||||
.with_async_function("readModelFile", read_model_file)?
|
||||
.with_async_function("writePlaceFile", write_place_file)?
|
||||
.with_async_function("writeModelFile", write_model_file)?
|
||||
.build_readonly()
|
||||
}
|
||||
|
||||
fn parse_file_path(path: String) -> LuaResult<(PathBuf, DocumentFormat)> {
|
||||
let file_path = PathBuf::from(path);
|
||||
let file_ext = file_path
|
||||
.extension()
|
||||
.ok_or_else(|| {
|
||||
LuaError::RuntimeError(format!(
|
||||
"Missing file extension for file path: '{}'",
|
||||
file_path.display()
|
||||
))
|
||||
})?
|
||||
.to_string_lossy();
|
||||
let doc_format = DocumentFormat::from_extension(&file_ext).ok_or_else(|| {
|
||||
LuaError::RuntimeError(format!(
|
||||
"Invalid file extension for writing place file: '{}'",
|
||||
file_ext
|
||||
))
|
||||
})?;
|
||||
Ok((file_path, doc_format))
|
||||
}
|
||||
|
||||
async fn read_place_file(lua: &Lua, path: String) -> LuaResult<LuaValue> {
|
||||
let bytes = fs::read(path).await.map_err(LuaError::external)?;
|
||||
let fut = unblock(move || {
|
||||
let doc = Document::from_bytes(bytes, DocumentKind::Place)?;
|
||||
let data_model = doc.into_data_model_instance()?;
|
||||
Ok::<_, DocumentError>(data_model)
|
||||
});
|
||||
fut.await?.to_lua(lua)
|
||||
}
|
||||
|
||||
async fn read_model_file(lua: &Lua, path: String) -> LuaResult<LuaValue> {
|
||||
let bytes = fs::read(path).await.map_err(LuaError::external)?;
|
||||
let fut = unblock(move || {
|
||||
let doc = Document::from_bytes(bytes, DocumentKind::Model)?;
|
||||
let instance_array = doc.into_instance_array()?;
|
||||
Ok::<_, DocumentError>(instance_array)
|
||||
});
|
||||
fut.await?.to_lua(lua)
|
||||
}
|
||||
|
||||
async fn write_place_file(_: &Lua, (path, data_model): (String, Instance)) -> LuaResult<()> {
|
||||
let (file_path, doc_format) = parse_file_path(path)?;
|
||||
let fut = unblock(move || {
|
||||
let doc = Document::from_data_model_instance(data_model)?;
|
||||
let bytes = doc.to_bytes_with_format(doc_format)?;
|
||||
Ok::<_, DocumentError>(bytes)
|
||||
});
|
||||
let bytes = fut.await?;
|
||||
fs::write(file_path, bytes)
|
||||
.await
|
||||
.map_err(LuaError::external)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write_model_file(_: &Lua, (path, instances): (String, Vec<Instance>)) -> LuaResult<()> {
|
||||
let (file_path, doc_format) = parse_file_path(path)?;
|
||||
let fut = unblock(move || {
|
||||
let doc = Document::from_instance_array(instances)?;
|
||||
let bytes = doc.to_bytes_with_format(doc_format)?;
|
||||
Ok::<_, DocumentError>(bytes)
|
||||
});
|
||||
let bytes = fut.await?;
|
||||
fs::write(file_path, bytes)
|
||||
.await
|
||||
.map_err(LuaError::external)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue