mirror of
https://github.com/lune-org/lune.git
synced 2024-12-12 13:00:37 +00:00
Implement auto detection of document kind for roblox lib
This commit is contained in:
parent
5bdc968ffe
commit
0f4c36219f
6 changed files with 168 additions and 14 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -858,6 +858,8 @@ dependencies = [
|
|||
"mlua",
|
||||
"rbx_binary",
|
||||
"rbx_dom_weak",
|
||||
"rbx_reflection",
|
||||
"rbx_reflection_database",
|
||||
"rbx_xml",
|
||||
"thiserror",
|
||||
]
|
||||
|
|
|
@ -21,6 +21,8 @@ thiserror = "1.0"
|
|||
|
||||
rbx_binary = { git = "https://github.com/rojo-rbx/rbx-dom", rev = "ce4c5bf7b18c813417ad14cc37e5abe281dfb51a" }
|
||||
rbx_dom_weak = { git = "https://github.com/rojo-rbx/rbx-dom", rev = "ce4c5bf7b18c813417ad14cc37e5abe281dfb51a" }
|
||||
rbx_reflection = { git = "https://github.com/rojo-rbx/rbx-dom", rev = "ce4c5bf7b18c813417ad14cc37e5abe281dfb51a" }
|
||||
rbx_reflection_database = { git = "https://github.com/rojo-rbx/rbx-dom", rev = "ce4c5bf7b18c813417ad14cc37e5abe281dfb51a" }
|
||||
rbx_xml = { git = "https://github.com/rojo-rbx/rbx-dom", rev = "ce4c5bf7b18c813417ad14cc37e5abe281dfb51a" }
|
||||
|
||||
# TODO: Split lune lib out into something like lune-core so
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
use std::path::Path;
|
||||
|
||||
use rbx_dom_weak::WeakDom;
|
||||
|
||||
use crate::instance::instance_is_a_service;
|
||||
|
||||
/**
|
||||
A document kind specifier.
|
||||
|
||||
|
@ -51,13 +55,29 @@ impl DocumentKind {
|
|||
}
|
||||
|
||||
/**
|
||||
Try to detect a document kind specifier from file contents.
|
||||
Try to detect a document kind specifier from a weak dom.
|
||||
|
||||
Returns `None` if the file contents do not seem to be from a valid roblox file.
|
||||
Returns `None` if the given dom is empty and as such can not have its kind inferred.
|
||||
*/
|
||||
pub fn from_bytes(_bytes: impl AsRef<[u8]>) -> Option<Self> {
|
||||
// TODO: Implement this, read comment below
|
||||
todo!("Investigate if it is possible to detect document kind from contents")
|
||||
pub fn from_weak_dom(dom: &WeakDom) -> Option<Self> {
|
||||
let mut has_top_level_child = false;
|
||||
let mut has_top_level_service = false;
|
||||
for child_ref in dom.root().children() {
|
||||
if let Some(child_inst) = dom.get_by_ref(*child_ref) {
|
||||
has_top_level_child = true;
|
||||
if instance_is_a_service(&child_inst.class).unwrap_or(false) {
|
||||
has_top_level_service = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if has_top_level_service {
|
||||
Some(Self::Place)
|
||||
} else if has_top_level_child {
|
||||
Some(Self::Model)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,6 +85,8 @@ impl DocumentKind {
|
|||
mod tests {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use rbx_dom_weak::InstanceBuilder;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
@ -149,5 +171,39 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
// TODO: Add tests here for the from_bytes implementation
|
||||
#[test]
|
||||
fn from_weak_dom() {
|
||||
let empty = WeakDom::new(InstanceBuilder::new("Instance"));
|
||||
assert_eq!(DocumentKind::from_weak_dom(&empty), None);
|
||||
|
||||
let with_services = WeakDom::new(
|
||||
InstanceBuilder::new("Instance")
|
||||
.with_child(InstanceBuilder::new("Workspace"))
|
||||
.with_child(InstanceBuilder::new("ReplicatedStorage")),
|
||||
);
|
||||
assert_eq!(
|
||||
DocumentKind::from_weak_dom(&with_services),
|
||||
Some(DocumentKind::Place)
|
||||
);
|
||||
|
||||
let with_children = WeakDom::new(
|
||||
InstanceBuilder::new("Instance")
|
||||
.with_child(InstanceBuilder::new("Model"))
|
||||
.with_child(InstanceBuilder::new("Part")),
|
||||
);
|
||||
assert_eq!(
|
||||
DocumentKind::from_weak_dom(&with_children),
|
||||
Some(DocumentKind::Model)
|
||||
);
|
||||
|
||||
let with_mixed = WeakDom::new(
|
||||
InstanceBuilder::new("Instance")
|
||||
.with_child(InstanceBuilder::new("Workspace"))
|
||||
.with_child(InstanceBuilder::new("Part")),
|
||||
);
|
||||
assert_eq!(
|
||||
DocumentKind::from_weak_dom(&with_mixed),
|
||||
Some(DocumentKind::Place)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,15 +51,10 @@ impl Document {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Decodes and creates a new document from a byte buffer.
|
||||
|
||||
This will automatically handle and detect if the document should be decoded
|
||||
using a roblox binary or roblox xml format, and if it is a model or place file.
|
||||
*/
|
||||
pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, DocumentError> {
|
||||
fn from_bytes_inner(
|
||||
bytes: impl AsRef<[u8]>,
|
||||
) -> Result<(DocumentFormat, WeakDom), DocumentError> {
|
||||
let bytes = bytes.as_ref();
|
||||
let kind = DocumentKind::from_bytes(bytes).ok_or(DocumentError::UnknownKind)?;
|
||||
let format = DocumentFormat::from_bytes(bytes).ok_or(DocumentError::UnknownFormat)?;
|
||||
let dom = match format {
|
||||
DocumentFormat::InternalRoot => Err(DocumentError::InternalRootReadWrite),
|
||||
|
@ -72,6 +67,37 @@ impl Document {
|
|||
.map_err(|err| DocumentError::ReadError(err.to_string()))
|
||||
}
|
||||
}?;
|
||||
Ok((format, dom))
|
||||
}
|
||||
|
||||
/**
|
||||
Decodes and creates a new document from a byte buffer.
|
||||
|
||||
This will automatically handle and detect if the document should be decoded
|
||||
using a roblox binary or roblox xml format, and if it is a model or place file.
|
||||
|
||||
Note that detection of model vs place file is heavily dependent on the structure
|
||||
of the file, and a model file with services in it will detect as a place file, so
|
||||
if possible using [`Document::from_bytes`] with an explicit kind should be preferred.
|
||||
*/
|
||||
pub fn from_bytes_auto(bytes: impl AsRef<[u8]>) -> Result<Self, DocumentError> {
|
||||
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)),
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
Decodes and creates a new document from a byte buffer.
|
||||
|
||||
This will automatically handle and detect if the document
|
||||
should be decoded using a roblox binary or roblox xml format.
|
||||
*/
|
||||
pub fn from_bytes(bytes: impl AsRef<[u8]>, kind: DocumentKind) -> Result<Self, DocumentError> {
|
||||
let (format, dom) = Self::from_bytes_inner(bytes)?;
|
||||
Ok(Self {
|
||||
kind,
|
||||
format,
|
||||
|
|
66
packages/lib-roblox/src/instance/mod.rs
Normal file
66
packages/lib-roblox/src/instance/mod.rs
Normal file
|
@ -0,0 +1,66 @@
|
|||
use std::borrow::Borrow;
|
||||
|
||||
use rbx_reflection::ClassTag;
|
||||
|
||||
/**
|
||||
Checks if an instance class matches a given class or superclass, similar to
|
||||
[Instance::IsA](https://create.roblox.com/docs/reference/engine/classes/Instance#IsA)
|
||||
from the Roblox standard library.
|
||||
|
||||
Note that this function may return `None` if it encounters a class or superclass
|
||||
that does not exist in the currently known class reflection database.
|
||||
*/
|
||||
#[allow(dead_code)]
|
||||
pub fn instance_is_a(
|
||||
instance_class_name: impl AsRef<str>,
|
||||
class_name: impl AsRef<str>,
|
||||
) -> Option<bool> {
|
||||
let instance_class_name = instance_class_name.as_ref();
|
||||
let class_name = class_name.as_ref();
|
||||
|
||||
if class_name == "Instance" || instance_class_name == class_name {
|
||||
Some(true)
|
||||
} else {
|
||||
let db = rbx_reflection_database::get();
|
||||
|
||||
let mut super_class_name = instance_class_name;
|
||||
while super_class_name != class_name {
|
||||
let class_descriptor = db.classes.get(super_class_name)?;
|
||||
if let Some(sup) = &class_descriptor.superclass {
|
||||
super_class_name = sup.borrow();
|
||||
} else {
|
||||
return Some(false);
|
||||
}
|
||||
}
|
||||
|
||||
Some(true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if an instance class is a service.
|
||||
|
||||
This is separate from [`instance_is_a`] since services do not share a
|
||||
common base class, and are instead determined through reflection tags.
|
||||
|
||||
Note that this function may return `None` if it encounters a class or superclass
|
||||
that does not exist in the currently known class reflection database.
|
||||
*/
|
||||
pub fn instance_is_a_service(class_name: impl AsRef<str>) -> Option<bool> {
|
||||
let mut class_name = class_name.as_ref();
|
||||
|
||||
let db = rbx_reflection_database::get();
|
||||
|
||||
loop {
|
||||
let class_descriptor = db.classes.get(class_name)?;
|
||||
if class_descriptor.tags.contains(&ClassTag::Service) {
|
||||
return Some(true);
|
||||
} else if let Some(sup) = &class_descriptor.superclass {
|
||||
class_name = sup.borrow();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Some(false)
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
use mlua::prelude::*;
|
||||
|
||||
mod instance;
|
||||
|
||||
pub mod document;
|
||||
|
||||
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
|
|
Loading…
Reference in a new issue