mirror of
https://github.com/CompeyDev/lune-packaging.git
synced 2025-01-09 20:29:10 +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;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Error)]
|
#[derive(Debug, Clone, Error)]
|
||||||
|
@ -12,4 +13,18 @@ pub enum DocumentError {
|
||||||
ReadError(String),
|
ReadError(String),
|
||||||
#[error("Failed to write document to buffer")]
|
#[error("Failed to write document to buffer")]
|
||||||
WriteError(String),
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
use rbx_dom_weak::WeakDom;
|
use rbx_dom_weak::{InstanceBuilder as DomInstanceBuilder, 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,
|
||||||
|
@ -14,6 +14,8 @@ pub use error::*;
|
||||||
pub use format::*;
|
pub use format::*;
|
||||||
pub use kind::*;
|
pub use kind::*;
|
||||||
|
|
||||||
|
use crate::instance::Instance;
|
||||||
|
|
||||||
pub type DocumentResult<T> = Result<T, DocumentError>;
|
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>> {
|
pub fn into_data_model_instance(self) -> DocumentResult<Instance> {
|
||||||
Arc::clone(&self.dom)
|
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 mlua::prelude::*;
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
|
use lune_roblox::{
|
||||||
|
document::{Document, DocumentError, DocumentFormat, DocumentKind},
|
||||||
|
instance::Instance,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::lua::table::TableBuilder;
|
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>() {
|
for pair in roblox_module.pairs::<LuaValue, LuaValue>() {
|
||||||
roblox_constants.push(pair?);
|
roblox_constants.push(pair?);
|
||||||
}
|
}
|
||||||
// TODO: Add async functions for reading & writing documents, creating instances
|
|
||||||
TableBuilder::new(lua)?
|
TableBuilder::new(lua)?
|
||||||
.with_values(roblox_constants)?
|
.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()
|
.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