mirror of
https://github.com/lune-org/lune.git
synced 2024-12-12 13:00:37 +00:00
Initial implementation of instances for roblox lib
This commit is contained in:
parent
560dc4acbe
commit
68515dc40a
12 changed files with 683 additions and 196 deletions
|
@ -1,11 +1,10 @@
|
|||
pub(crate) use rbx_dom_weak::types::{Variant as DomValue, VariantType as DomType};
|
||||
|
||||
mod conversion;
|
||||
mod extension;
|
||||
mod result;
|
||||
mod shared;
|
||||
|
||||
pub mod conversion;
|
||||
pub mod extension;
|
||||
pub mod result;
|
||||
pub mod types;
|
||||
|
||||
use result::*;
|
||||
use shared::*;
|
||||
|
||||
pub use crate::shared::userdata::*;
|
||||
|
|
|
@ -45,6 +45,19 @@ impl fmt::Display for DomConversionError {
|
|||
|
||||
impl Error for DomConversionError {}
|
||||
|
||||
impl From<DomConversionError> for LuaError {
|
||||
fn from(value: DomConversionError) -> Self {
|
||||
use DomConversionError as E;
|
||||
match value {
|
||||
E::LuaError(e) => e,
|
||||
E::External { message } => LuaError::external(message),
|
||||
E::FromDomValue { .. } | E::ToDomValue { .. } => {
|
||||
LuaError::RuntimeError(value.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LuaError> for DomConversionError {
|
||||
fn from(value: LuaError) -> Self {
|
||||
Self::LuaError(value)
|
||||
|
|
|
@ -12,12 +12,6 @@ use super::{super::*, Enum};
|
|||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Enums;
|
||||
|
||||
impl Enums {
|
||||
pub(crate) fn make_singleton(lua: &Lua) -> LuaResult<LuaAnyUserData> {
|
||||
lua.create_userdata(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaUserData for Enums {
|
||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
// Methods
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::path::Path;
|
|||
|
||||
use rbx_dom_weak::WeakDom;
|
||||
|
||||
use crate::instance::util::instance_is_a_service;
|
||||
use crate::shared::instance::class_is_a_service;
|
||||
|
||||
/**
|
||||
A document kind specifier.
|
||||
|
@ -65,7 +65,7 @@ impl DocumentKind {
|
|||
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) {
|
||||
if class_is_a_service(&child_inst.class).unwrap_or(false) {
|
||||
has_top_level_service = true;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use rbx_dom_weak::{types::Ref, WeakDom};
|
||||
use rbx_dom_weak::WeakDom;
|
||||
use rbx_xml::{
|
||||
DecodeOptions as XmlDecodeOptions, DecodePropertyBehavior as XmlDecodePropertyBehavior,
|
||||
EncodeOptions as XmlEncodeOptions, EncodePropertyBehavior as XmlEncodePropertyBehavior,
|
||||
|
@ -189,43 +189,9 @@ impl Document {
|
|||
}
|
||||
|
||||
/**
|
||||
Retrieves the root referent of the underlying weak dom.
|
||||
Gets the underlying weak dom for this document.
|
||||
*/
|
||||
pub fn get_root_ref(&self) -> Ref {
|
||||
let dom = self.dom.try_read().expect("Failed to lock dom");
|
||||
dom.root_ref()
|
||||
}
|
||||
|
||||
/**
|
||||
Retrieves all root child referents of the underlying weak dom.
|
||||
*/
|
||||
pub fn get_root_child_refs(&self) -> Vec<Ref> {
|
||||
let dom = self.dom.try_read().expect("Failed to lock dom");
|
||||
dom.root().children().to_vec()
|
||||
}
|
||||
|
||||
/**
|
||||
Retrieves a reference to the underlying weak dom.
|
||||
*/
|
||||
pub fn get_dom(&self) -> RwLockReadGuard<WeakDom> {
|
||||
self.dom.try_read().expect("Failed to lock dom")
|
||||
}
|
||||
|
||||
/**
|
||||
Retrieves a mutable reference to the underlying weak dom.
|
||||
*/
|
||||
pub fn get_dom_mut(&mut self) -> RwLockWriteGuard<WeakDom> {
|
||||
self.dom.try_write().expect("Failed to lock dom")
|
||||
}
|
||||
|
||||
/**
|
||||
Consumes the document, returning the underlying weak dom.
|
||||
|
||||
This may panic if the document has been cloned
|
||||
and still has another owner in memory.
|
||||
*/
|
||||
pub fn into_dom(self) -> WeakDom {
|
||||
let lock = Arc::try_unwrap(self.dom).expect("Document has multiple owners in memory");
|
||||
lock.into_inner().expect("Failed to lock dom")
|
||||
pub fn dom(&self) -> Arc<RwLock<WeakDom>> {
|
||||
Arc::clone(&self.dom)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1,435 @@
|
|||
pub mod util;
|
||||
use std::{
|
||||
fmt,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::{
|
||||
types::{Ref as DomRef, Variant as DomValue},
|
||||
Instance as DomInstance, InstanceBuilder as DomInstanceBuilder, WeakDom,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
datatypes::{
|
||||
conversion::{DomValueToLua, LuaToDomValue},
|
||||
types::EnumItem,
|
||||
userdata_impl_to_string,
|
||||
},
|
||||
shared::instance::{
|
||||
class_exists, class_is_a, find_property_enum, find_property_type, property_is_enum,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Instance {
|
||||
dom: Arc<RwLock<WeakDom>>,
|
||||
dom_ref: DomRef,
|
||||
class_name: String,
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
/**
|
||||
Creates a new `Instance` from a document and dom object ref.
|
||||
*/
|
||||
pub fn new(dom: &Arc<RwLock<WeakDom>>, dom_ref: DomRef) -> Self {
|
||||
let class_name = dom
|
||||
.read()
|
||||
.expect("Failed to get read access to document")
|
||||
.get_by_ref(dom_ref)
|
||||
.expect("Failed to find instance in document")
|
||||
.class
|
||||
.clone();
|
||||
Self {
|
||||
dom: Arc::clone(dom),
|
||||
dom_ref,
|
||||
class_name,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
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.
|
||||
*/
|
||||
pub fn new_orphaned(lua: &Lua, class_name: impl AsRef<str>) -> Self {
|
||||
let dom_lua = lua
|
||||
.app_data_mut::<Arc<RwLock<WeakDom>>>()
|
||||
.expect("Failed to find internal lua weak dom");
|
||||
let mut dom = dom_lua
|
||||
.write()
|
||||
.expect("Failed to get write access to document");
|
||||
|
||||
let class_name = class_name.as_ref();
|
||||
let dom_root = dom.root_ref();
|
||||
let dom_ref = dom.insert(dom_root, DomInstanceBuilder::new(class_name.to_string()));
|
||||
|
||||
Self {
|
||||
dom: Arc::clone(&dom_lua),
|
||||
dom_ref,
|
||||
class_name: class_name.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if the instance matches or inherits a given class name.
|
||||
*/
|
||||
pub fn is_a(&self, class_name: impl AsRef<str>) -> bool {
|
||||
class_is_a(&self.class_name, class_name).unwrap_or(false)
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if the instance has been destroyed.
|
||||
*/
|
||||
pub fn is_destroyed(&self) -> bool {
|
||||
self.dom
|
||||
.read()
|
||||
.expect("Failed to get read access to document")
|
||||
.get_by_ref(self.dom_ref)
|
||||
.is_none()
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if the instance is the root instance.
|
||||
*/
|
||||
pub fn is_root(&self) -> bool {
|
||||
self.dom
|
||||
.read()
|
||||
.expect("Failed to get read access to document")
|
||||
.root_ref()
|
||||
== self.dom_ref
|
||||
}
|
||||
|
||||
/**
|
||||
Gets the name of the instance, if it exists.
|
||||
*/
|
||||
pub fn get_name(&self) -> String {
|
||||
let dom = self
|
||||
.dom
|
||||
.read()
|
||||
.expect("Failed to get read access to document");
|
||||
dom.get_by_ref(self.dom_ref)
|
||||
.expect("Failed to find instance in document")
|
||||
.name
|
||||
.clone()
|
||||
}
|
||||
|
||||
/**
|
||||
Sets the name of the instance, if it exists.
|
||||
*/
|
||||
pub fn set_name(&self, name: impl Into<String>) {
|
||||
let mut dom = self
|
||||
.dom
|
||||
.write()
|
||||
.expect("Failed to get write access to document");
|
||||
dom.get_by_ref_mut(self.dom_ref)
|
||||
.expect("Failed to find instance in document")
|
||||
.name = name.into()
|
||||
}
|
||||
|
||||
/**
|
||||
Gets the parent of the instance, if it exists.
|
||||
*/
|
||||
pub fn get_parent(&self) -> Option<Instance> {
|
||||
let dom = self
|
||||
.dom
|
||||
.read()
|
||||
.expect("Failed to get read access to document");
|
||||
let parent_ref = dom
|
||||
.get_by_ref(self.dom_ref)
|
||||
.expect("Failed to find instance in document")
|
||||
.parent();
|
||||
if parent_ref == dom.root_ref() {
|
||||
None
|
||||
} else {
|
||||
Some(Self::new(&self.dom, parent_ref))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Sets the parent of the instance, if it exists.
|
||||
|
||||
Note that this can transfer between different weak doms,
|
||||
and assumes that separate doms always have unique root referents.
|
||||
|
||||
If doms do not have unique root referents then this operation may panic.
|
||||
*/
|
||||
pub fn set_parent(&self, parent: Instance) {
|
||||
let mut dom_source = self
|
||||
.dom
|
||||
.write()
|
||||
.expect("Failed to get read access to source document");
|
||||
let dom_target = parent
|
||||
.dom
|
||||
.read()
|
||||
.expect("Failed to get read access to target document");
|
||||
let target_ref = dom_target
|
||||
.get_by_ref(parent.dom_ref)
|
||||
.expect("Failed to find instance in target document")
|
||||
.parent();
|
||||
if dom_source.root_ref() == dom_target.root_ref() {
|
||||
dom_source.transfer_within(self.dom_ref, target_ref);
|
||||
} else {
|
||||
// NOTE: We must drop the previous dom_target read handle here first so
|
||||
// that we can get exclusive write access for transferring across doms
|
||||
drop(dom_target);
|
||||
let mut dom_target = parent
|
||||
.dom
|
||||
.try_write()
|
||||
.expect("Failed to get write access to target document");
|
||||
dom_source.transfer(self.dom_ref, &mut dom_target, target_ref)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Sets the parent of the instance, if it exists, to nil, making it orphaned.
|
||||
|
||||
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.
|
||||
*/
|
||||
pub fn set_parent_to_nil(&self, lua: &Lua) {
|
||||
let mut dom_source = self
|
||||
.dom
|
||||
.write()
|
||||
.expect("Failed to get read access to source document");
|
||||
let dom_lua = lua
|
||||
.app_data_mut::<Arc<RwLock<WeakDom>>>()
|
||||
.expect("Failed to find internal lua weak dom");
|
||||
let mut dom_target = dom_lua
|
||||
.write()
|
||||
.expect("Failed to get write access to target document");
|
||||
let target_ref = dom_target.root_ref();
|
||||
dom_source.transfer(self.dom_ref, &mut dom_target, target_ref)
|
||||
}
|
||||
|
||||
/**
|
||||
Gets a property for the instance, if it exists.
|
||||
*/
|
||||
pub fn get_property(&self, name: impl AsRef<str>) -> Option<DomValue> {
|
||||
self.dom
|
||||
.read()
|
||||
.expect("Failed to get read access to document")
|
||||
.get_by_ref(self.dom_ref)
|
||||
.expect("Failed to find instance in document")
|
||||
.properties
|
||||
.get(name.as_ref())
|
||||
.cloned()
|
||||
}
|
||||
|
||||
/**
|
||||
Sets a property for the instance.
|
||||
|
||||
Note that setting a property here will not fail even if the
|
||||
property does not actually exist for the instance class.
|
||||
*/
|
||||
pub fn set_property(&self, name: impl AsRef<str>, value: DomValue) {
|
||||
self.dom
|
||||
.write()
|
||||
.expect("Failed to get read access to document")
|
||||
.get_by_ref_mut(self.dom_ref)
|
||||
.expect("Failed to find instance in document")
|
||||
.properties
|
||||
.insert(name.as_ref().to_string(), value);
|
||||
}
|
||||
|
||||
/**
|
||||
Finds a child of the instance using the given predicate callback.
|
||||
*/
|
||||
pub fn find_child<F>(&self, predicate: F) -> Option<Instance>
|
||||
where
|
||||
F: Fn(&DomInstance) -> bool,
|
||||
{
|
||||
let dom = self
|
||||
.dom
|
||||
.read()
|
||||
.expect("Failed to get read access to document");
|
||||
let children = dom
|
||||
.get_by_ref(self.dom_ref)
|
||||
.expect("Failed to find instance in document")
|
||||
.children();
|
||||
children.iter().find_map(|child_ref| {
|
||||
if let Some(child_inst) = dom.get_by_ref(*child_ref) {
|
||||
if predicate(child_inst) {
|
||||
Some(Self::new(&self.dom, *child_ref))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
|
||||
datatype_table.set(
|
||||
"new",
|
||||
lua.create_function(|lua, class_name: String| {
|
||||
if class_exists(&class_name) {
|
||||
Instance::new_orphaned(lua, class_name).to_lua(lua)
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"{} is not a valid class name",
|
||||
class_name
|
||||
)))
|
||||
}
|
||||
})?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaUserData for Instance {
|
||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||
/*
|
||||
Getting a value does the following:
|
||||
|
||||
1. Check if it is a special property like "ClassName", "Name" or "Parent"
|
||||
2. Try to get a known instance property
|
||||
3. Try to get a current child of the instance
|
||||
4. No valid property or instance found, throw error
|
||||
*/
|
||||
methods.add_meta_method(LuaMetaMethod::Index, |lua, this, prop_name: String| {
|
||||
match prop_name.as_str() {
|
||||
"ClassName" => return this.class_name.clone().to_lua(lua),
|
||||
"Name" => {
|
||||
return this.get_name().to_lua(lua);
|
||||
}
|
||||
"Parent" => {
|
||||
return this.get_parent().to_lua(lua);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Some(prop) = this.get_property(&prop_name) {
|
||||
match LuaValue::dom_value_to_lua(lua, &prop) {
|
||||
Ok(value) => Ok(value),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
} else if let Some(inst) = this.find_child(|inst| inst.name == prop_name) {
|
||||
Ok(LuaValue::UserData(lua.create_userdata(inst)?))
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"{} is not a valid member of {}",
|
||||
prop_name, this
|
||||
)))
|
||||
}
|
||||
});
|
||||
/*
|
||||
Setting a value does the following:
|
||||
|
||||
1. Check if it is a special property like "ClassName", "Name" or "Parent"
|
||||
2. Check if a property exists for the wanted name
|
||||
3a. Set a strict enum from a given EnumItem OR
|
||||
3b. Set a normal property from a given value
|
||||
*/
|
||||
methods.add_meta_method_mut(
|
||||
LuaMetaMethod::NewIndex,
|
||||
|lua, this, (prop_name, prop_value): (String, LuaValue)| {
|
||||
match prop_name.as_str() {
|
||||
"ClassName" => {
|
||||
return Err(LuaError::RuntimeError(
|
||||
"ClassName can not be written to".to_string(),
|
||||
))
|
||||
}
|
||||
"Name" => {
|
||||
let name = String::from_lua(prop_value, lua)?;
|
||||
this.set_name(name);
|
||||
return Ok(());
|
||||
}
|
||||
"Parent" => {
|
||||
type Parent = Option<Instance>;
|
||||
match Parent::from_lua(prop_value, lua)? {
|
||||
Some(parent) => this.set_parent(parent),
|
||||
None => this.set_parent_to_nil(lua),
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let is_enum = match property_is_enum(&this.class_name, &prop_name) {
|
||||
Some(b) => b,
|
||||
None => {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"{} is not a valid member of {}",
|
||||
prop_name, this
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
if is_enum {
|
||||
let enum_name = find_property_enum(&this.class_name, &prop_name).unwrap();
|
||||
match EnumItem::from_lua(prop_value, lua) {
|
||||
Ok(given_enum) if given_enum.name == enum_name => {
|
||||
this.set_property(prop_name, DomValue::Enum(given_enum.into()));
|
||||
Ok(())
|
||||
}
|
||||
Ok(given_enum) => Err(LuaError::RuntimeError(format!(
|
||||
"Expected Enum.{}, got Enum.{}",
|
||||
enum_name, given_enum.name
|
||||
))),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
} else {
|
||||
let dom_type = find_property_type(&this.class_name, &prop_name).unwrap();
|
||||
match prop_value.lua_to_dom_value(lua, dom_type) {
|
||||
Ok(dom_value) => {
|
||||
this.set_property(prop_name, dom_value);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
/*
|
||||
Implementations of base methods on the Instance class
|
||||
|
||||
Currently implemented:
|
||||
|
||||
* FindFirstChild
|
||||
* FindFirstChildOfClass
|
||||
* FindFirstChildWhichIsA
|
||||
|
||||
Not yet implemented, but planned:
|
||||
|
||||
* Clone
|
||||
* Destroy
|
||||
* FindFirstDescendant
|
||||
* FindFirstAncestor
|
||||
* FindFirstAncestorOfClass
|
||||
* FindFirstAncestorWhichIsA
|
||||
* IsAncestorOf
|
||||
* IsDescendantOf
|
||||
* GetChildren
|
||||
* GetDescendants
|
||||
* GetFullName
|
||||
* GetAttribute
|
||||
* GetAttributes
|
||||
* SetAttribute
|
||||
*/
|
||||
methods.add_method("FindFirstChild", |lua, this, name: String| {
|
||||
this.find_child(|child| child.name == name).to_lua(lua)
|
||||
});
|
||||
methods.add_method("FindFirstChildOfClass", |lua, this, class_name: String| {
|
||||
this.find_child(|child| child.class == class_name)
|
||||
.to_lua(lua)
|
||||
});
|
||||
methods.add_method("FindFirstChildWhichIsA", |lua, this, class_name: String| {
|
||||
this.find_child(|child| class_is_a(&child.class, &class_name).unwrap_or(false))
|
||||
.to_lua(lua)
|
||||
});
|
||||
// FUTURE: We could pass the "methods" struct to some other functions
|
||||
// here to add inheritance-like behavior and class-specific methods
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Instance {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.get_name())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,107 +0,0 @@
|
|||
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: impl AsRef<str>, class_name: impl AsRef<str>) -> Option<bool> {
|
||||
let mut instance_class = instance_class.as_ref();
|
||||
let class_name = class_name.as_ref();
|
||||
|
||||
if class_name == "Instance" || instance_class == class_name {
|
||||
Some(true)
|
||||
} else {
|
||||
let db = rbx_reflection_database::get();
|
||||
|
||||
while instance_class != class_name {
|
||||
let class_descriptor = db.classes.get(instance_class)?;
|
||||
if let Some(sup) = &class_descriptor.superclass {
|
||||
instance_class = 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(instance_class: impl AsRef<str>) -> Option<bool> {
|
||||
let mut instance_class = instance_class.as_ref();
|
||||
|
||||
let db = rbx_reflection_database::get();
|
||||
|
||||
loop {
|
||||
let class_descriptor = db.classes.get(instance_class)?;
|
||||
if class_descriptor.tags.contains(&ClassTag::Service) {
|
||||
return Some(true);
|
||||
} else if let Some(sup) = &class_descriptor.superclass {
|
||||
instance_class = sup.borrow();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Some(false)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn is_a_class_valid() {
|
||||
assert_eq!(instance_is_a("Part", "Part"), Some(true));
|
||||
assert_eq!(instance_is_a("Part", "BasePart"), Some(true));
|
||||
assert_eq!(instance_is_a("Part", "PVInstance"), Some(true));
|
||||
assert_eq!(instance_is_a("Part", "Instance"), Some(true));
|
||||
|
||||
assert_eq!(instance_is_a("Workspace", "Workspace"), Some(true));
|
||||
assert_eq!(instance_is_a("Workspace", "Model"), Some(true));
|
||||
assert_eq!(instance_is_a("Workspace", "Instance"), Some(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_a_class_invalid() {
|
||||
assert_eq!(instance_is_a("Part", "part"), Some(false));
|
||||
assert_eq!(instance_is_a("Part", "Base-Part"), Some(false));
|
||||
assert_eq!(instance_is_a("Part", "Model"), Some(false));
|
||||
assert_eq!(instance_is_a("Part", "Paart"), Some(false));
|
||||
|
||||
assert_eq!(instance_is_a("Workspace", "Service"), Some(false));
|
||||
assert_eq!(instance_is_a("Workspace", "."), Some(false));
|
||||
assert_eq!(instance_is_a("Workspace", ""), Some(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_a_service_valid() {
|
||||
assert_eq!(instance_is_a_service("Workspace"), Some(true));
|
||||
assert_eq!(instance_is_a_service("PhysicsService"), Some(true));
|
||||
assert_eq!(instance_is_a_service("ReplicatedFirst"), Some(true));
|
||||
assert_eq!(instance_is_a_service("CSGDictionaryService"), Some(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_a_service_invalid() {
|
||||
assert_eq!(instance_is_a_service("Camera"), Some(false));
|
||||
assert_eq!(instance_is_a_service("Terrain"), Some(false));
|
||||
assert_eq!(instance_is_a_service("Work-space"), None);
|
||||
assert_eq!(instance_is_a_service("CSG Dictionary Service"), None);
|
||||
}
|
||||
}
|
|
@ -1,10 +1,17 @@
|
|||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::{InstanceBuilder as DomInstanceBuilder, WeakDom};
|
||||
|
||||
use crate::instance::Instance;
|
||||
|
||||
pub mod datatypes;
|
||||
pub mod document;
|
||||
pub mod instance;
|
||||
|
||||
fn make_dt<F>(lua: &Lua, f: F) -> LuaResult<LuaValue>
|
||||
pub(crate) mod shared;
|
||||
|
||||
fn make<F>(lua: &Lua, f: F) -> LuaResult<LuaValue>
|
||||
where
|
||||
F: Fn(&Lua, &LuaTable) -> LuaResult<()>,
|
||||
{
|
||||
|
@ -18,35 +25,43 @@ where
|
|||
fn make_all_datatypes(lua: &Lua) -> LuaResult<Vec<(&'static str, LuaValue)>> {
|
||||
use datatypes::types::*;
|
||||
Ok(vec![
|
||||
// Datatypes
|
||||
("Axes", make(lua, Axes::make_table)?),
|
||||
("BrickColor", make(lua, BrickColor::make_table)?),
|
||||
("CFrame", make(lua, CFrame::make_table)?),
|
||||
("Color3", make(lua, Color3::make_table)?),
|
||||
("ColorSequence", make(lua, ColorSequence::make_table)?),
|
||||
("ColorSequenceKeypoint", make(lua, ColorSequenceKeypoint::make_table)?),
|
||||
("Faces", make(lua, Faces::make_table)?),
|
||||
("Font", make(lua, Font::make_table)?),
|
||||
("NumberRange", make(lua, NumberRange::make_table)?),
|
||||
("NumberSequence", make(lua, NumberSequence::make_table)?),
|
||||
("NumberSequenceKeypoint", make(lua, NumberSequenceKeypoint::make_table)?),
|
||||
("PhysicalProperties", make(lua, PhysicalProperties::make_table)?),
|
||||
("Ray", make(lua, Ray::make_table)?),
|
||||
("Rect", make(lua, Rect::make_table)?),
|
||||
("UDim", make(lua, UDim::make_table)?),
|
||||
("UDim2", make(lua, UDim2::make_table)?),
|
||||
("Region3", make(lua, Region3::make_table)?),
|
||||
("Region3int16", make(lua, Region3int16::make_table)?),
|
||||
("Vector2", make(lua, Vector2::make_table)?),
|
||||
("Vector2int16", make(lua, Vector2int16::make_table)?),
|
||||
("Vector3", make(lua, Vector3::make_table)?),
|
||||
("Vector3int16", make(lua, Vector3int16::make_table)?),
|
||||
// Classes
|
||||
("Axes", make_dt(lua, Axes::make_table)?),
|
||||
("BrickColor", make_dt(lua, BrickColor::make_table)?),
|
||||
("CFrame", make_dt(lua, CFrame::make_table)?),
|
||||
("Color3", make_dt(lua, Color3::make_table)?),
|
||||
("ColorSequence", make_dt(lua, ColorSequence::make_table)?),
|
||||
("ColorSequenceKeypoint", make_dt(lua, ColorSequenceKeypoint::make_table)?),
|
||||
("Faces", make_dt(lua, Faces::make_table)?),
|
||||
("Font", make_dt(lua, Font::make_table)?),
|
||||
("NumberRange", make_dt(lua, NumberRange::make_table)?),
|
||||
("NumberSequence", make_dt(lua, NumberSequence::make_table)?),
|
||||
("NumberSequenceKeypoint", make_dt(lua, NumberSequenceKeypoint::make_table)?),
|
||||
("PhysicalProperties", make_dt(lua, PhysicalProperties::make_table)?),
|
||||
("Ray", make_dt(lua, Ray::make_table)?),
|
||||
("Rect", make_dt(lua, Rect::make_table)?),
|
||||
("UDim", make_dt(lua, UDim::make_table)?),
|
||||
("UDim2", make_dt(lua, UDim2::make_table)?),
|
||||
("Region3", make_dt(lua, Region3::make_table)?),
|
||||
("Region3int16", make_dt(lua, Region3int16::make_table)?),
|
||||
("Vector2", make_dt(lua, Vector2::make_table)?),
|
||||
("Vector2int16", make_dt(lua, Vector2int16::make_table)?),
|
||||
("Vector3", make_dt(lua, Vector3::make_table)?),
|
||||
("Vector3int16", make_dt(lua, Vector3int16::make_table)?),
|
||||
("Instance", make(lua, Instance::make_table)?),
|
||||
// Singletons
|
||||
("Enum", LuaValue::UserData(Enums::make_singleton(lua)?)),
|
||||
("Enum", Enums.to_lua(lua)?),
|
||||
])
|
||||
}
|
||||
|
||||
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)?;
|
||||
|
|
171
packages/lib-roblox/src/shared/instance.rs
Normal file
171
packages/lib-roblox/src/shared/instance.rs
Normal file
|
@ -0,0 +1,171 @@
|
|||
use std::borrow::{Borrow, Cow};
|
||||
|
||||
use rbx_dom_weak::types::VariantType as DomType;
|
||||
use rbx_reflection::{ClassTag, DataType};
|
||||
|
||||
/**
|
||||
Checks if the given property is an enum.
|
||||
|
||||
Returns `None` if the class or property does not exist.
|
||||
*/
|
||||
pub fn property_is_enum(
|
||||
instance_class: impl AsRef<str>,
|
||||
property_name: impl AsRef<str>,
|
||||
) -> Option<bool> {
|
||||
let db = rbx_reflection_database::get();
|
||||
let class = db.classes.get(instance_class.as_ref())?;
|
||||
let prop = class.properties.get(property_name.as_ref())?;
|
||||
|
||||
Some(matches!(prop.data_type, DataType::Enum(_)))
|
||||
}
|
||||
|
||||
/**
|
||||
Finds the type of a property of the given class.
|
||||
|
||||
Returns `None` if the class or property does not exist or if the property is an enum.
|
||||
*/
|
||||
pub fn find_property_type(
|
||||
instance_class: impl AsRef<str>,
|
||||
property_name: impl AsRef<str>,
|
||||
) -> Option<DomType> {
|
||||
let db = rbx_reflection_database::get();
|
||||
let class = db.classes.get(instance_class.as_ref())?;
|
||||
let prop = class.properties.get(property_name.as_ref())?;
|
||||
|
||||
if let DataType::Value(typ) = prop.data_type {
|
||||
Some(typ)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Finds the enum name of a property of the given class.
|
||||
|
||||
Returns `None` if the class or property does not exist or if the property is *not* an enum.
|
||||
*/
|
||||
pub fn find_property_enum(
|
||||
instance_class: impl AsRef<str>,
|
||||
property_name: impl AsRef<str>,
|
||||
) -> Option<Cow<'static, str>> {
|
||||
let db = rbx_reflection_database::get();
|
||||
let class = db.classes.get(instance_class.as_ref())?;
|
||||
let prop = class.properties.get(property_name.as_ref())?;
|
||||
|
||||
if let DataType::Enum(name) = &prop.data_type {
|
||||
Some(Cow::Borrowed(name))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if an instance class exists in the reflection database.
|
||||
*/
|
||||
pub fn class_exists(class_name: impl AsRef<str>) -> bool {
|
||||
let db = rbx_reflection_database::get();
|
||||
db.classes.contains_key(class_name.as_ref())
|
||||
}
|
||||
|
||||
/**
|
||||
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.
|
||||
*/
|
||||
pub fn class_is_a(instance_class: impl AsRef<str>, class_name: impl AsRef<str>) -> Option<bool> {
|
||||
let mut instance_class = instance_class.as_ref();
|
||||
let class_name = class_name.as_ref();
|
||||
|
||||
if class_name == "Instance" || instance_class == class_name {
|
||||
Some(true)
|
||||
} else {
|
||||
let db = rbx_reflection_database::get();
|
||||
|
||||
while instance_class != class_name {
|
||||
let class_descriptor = db.classes.get(instance_class)?;
|
||||
if let Some(sup) = &class_descriptor.superclass {
|
||||
instance_class = sup.borrow();
|
||||
} else {
|
||||
return Some(false);
|
||||
}
|
||||
}
|
||||
|
||||
Some(true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if an instance class is a service.
|
||||
|
||||
This is separate from [`class_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 class_is_a_service(instance_class: impl AsRef<str>) -> Option<bool> {
|
||||
let mut instance_class = instance_class.as_ref();
|
||||
|
||||
let db = rbx_reflection_database::get();
|
||||
|
||||
loop {
|
||||
let class_descriptor = db.classes.get(instance_class)?;
|
||||
if class_descriptor.tags.contains(&ClassTag::Service) {
|
||||
return Some(true);
|
||||
} else if let Some(sup) = &class_descriptor.superclass {
|
||||
instance_class = sup.borrow();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Some(false)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn is_a_class_valid() {
|
||||
assert_eq!(class_is_a("Part", "Part"), Some(true));
|
||||
assert_eq!(class_is_a("Part", "BasePart"), Some(true));
|
||||
assert_eq!(class_is_a("Part", "PVInstance"), Some(true));
|
||||
assert_eq!(class_is_a("Part", "Instance"), Some(true));
|
||||
|
||||
assert_eq!(class_is_a("Workspace", "Workspace"), Some(true));
|
||||
assert_eq!(class_is_a("Workspace", "Model"), Some(true));
|
||||
assert_eq!(class_is_a("Workspace", "Instance"), Some(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_a_class_invalid() {
|
||||
assert_eq!(class_is_a("Part", "part"), Some(false));
|
||||
assert_eq!(class_is_a("Part", "Base-Part"), Some(false));
|
||||
assert_eq!(class_is_a("Part", "Model"), Some(false));
|
||||
assert_eq!(class_is_a("Part", "Paart"), Some(false));
|
||||
|
||||
assert_eq!(class_is_a("Workspace", "Service"), Some(false));
|
||||
assert_eq!(class_is_a("Workspace", "."), Some(false));
|
||||
assert_eq!(class_is_a("Workspace", ""), Some(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_a_service_valid() {
|
||||
assert_eq!(class_is_a_service("Workspace"), Some(true));
|
||||
assert_eq!(class_is_a_service("PhysicsService"), Some(true));
|
||||
assert_eq!(class_is_a_service("ReplicatedFirst"), Some(true));
|
||||
assert_eq!(class_is_a_service("CSGDictionaryService"), Some(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_a_service_invalid() {
|
||||
assert_eq!(class_is_a_service("Camera"), Some(false));
|
||||
assert_eq!(class_is_a_service("Terrain"), Some(false));
|
||||
assert_eq!(class_is_a_service("Work-space"), None);
|
||||
assert_eq!(class_is_a_service("CSG Dictionary Service"), None);
|
||||
}
|
||||
}
|
2
packages/lib-roblox/src/shared/mod.rs
Normal file
2
packages/lib-roblox/src/shared/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub(crate) mod instance;
|
||||
pub(crate) mod userdata;
|
|
@ -5,7 +5,7 @@ use mlua::prelude::*;
|
|||
// Utility functions
|
||||
|
||||
type ListWriter = dyn Fn(&mut fmt::Formatter<'_>, bool, &str) -> fmt::Result;
|
||||
pub(super) fn make_list_writer() -> Box<ListWriter> {
|
||||
pub fn make_list_writer() -> Box<ListWriter> {
|
||||
let first = RefCell::new(true);
|
||||
Box::new(move |f, flag, literal| {
|
||||
if flag {
|
||||
|
@ -21,14 +21,14 @@ pub(super) fn make_list_writer() -> Box<ListWriter> {
|
|||
|
||||
// Userdata metamethod implementations
|
||||
|
||||
pub(super) fn userdata_impl_to_string<D>(_: &Lua, datatype: &D, _: ()) -> LuaResult<String>
|
||||
pub fn userdata_impl_to_string<D>(_: &Lua, datatype: &D, _: ()) -> LuaResult<String>
|
||||
where
|
||||
D: LuaUserData + ToString + 'static,
|
||||
{
|
||||
Ok(datatype.to_string())
|
||||
}
|
||||
|
||||
pub(super) fn userdata_impl_eq<D>(_: &Lua, datatype: &D, value: LuaValue) -> LuaResult<bool>
|
||||
pub fn userdata_impl_eq<D>(_: &Lua, datatype: &D, value: LuaValue) -> LuaResult<bool>
|
||||
where
|
||||
D: LuaUserData + PartialEq + 'static,
|
||||
{
|
||||
|
@ -43,28 +43,28 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn userdata_impl_unm<D>(_: &Lua, datatype: &D, _: ()) -> LuaResult<D>
|
||||
pub fn userdata_impl_unm<D>(_: &Lua, datatype: &D, _: ()) -> LuaResult<D>
|
||||
where
|
||||
D: LuaUserData + ops::Neg<Output = D> + Copy,
|
||||
{
|
||||
Ok(-*datatype)
|
||||
}
|
||||
|
||||
pub(super) fn userdata_impl_add<D>(_: &Lua, datatype: &D, value: D) -> LuaResult<D>
|
||||
pub fn userdata_impl_add<D>(_: &Lua, datatype: &D, value: D) -> LuaResult<D>
|
||||
where
|
||||
D: LuaUserData + ops::Add<Output = D> + Copy,
|
||||
{
|
||||
Ok(*datatype + value)
|
||||
}
|
||||
|
||||
pub(super) fn userdata_impl_sub<D>(_: &Lua, datatype: &D, value: D) -> LuaResult<D>
|
||||
pub fn userdata_impl_sub<D>(_: &Lua, datatype: &D, value: D) -> LuaResult<D>
|
||||
where
|
||||
D: LuaUserData + ops::Sub<Output = D> + Copy,
|
||||
{
|
||||
Ok(*datatype - value)
|
||||
}
|
||||
|
||||
pub(super) fn userdata_impl_mul_f32<D>(_: &Lua, datatype: &D, rhs: LuaValue) -> LuaResult<D>
|
||||
pub fn userdata_impl_mul_f32<D>(_: &Lua, datatype: &D, rhs: LuaValue) -> LuaResult<D>
|
||||
where
|
||||
D: LuaUserData + ops::Mul<D, Output = D> + ops::Mul<f32, Output = D> + Copy + 'static,
|
||||
{
|
||||
|
@ -89,7 +89,7 @@ where
|
|||
})
|
||||
}
|
||||
|
||||
pub(super) fn userdata_impl_mul_i32<D>(_: &Lua, datatype: &D, rhs: LuaValue) -> LuaResult<D>
|
||||
pub fn userdata_impl_mul_i32<D>(_: &Lua, datatype: &D, rhs: LuaValue) -> LuaResult<D>
|
||||
where
|
||||
D: LuaUserData + ops::Mul<D, Output = D> + ops::Mul<i32, Output = D> + Copy + 'static,
|
||||
{
|
||||
|
@ -114,7 +114,7 @@ where
|
|||
})
|
||||
}
|
||||
|
||||
pub(super) fn userdata_impl_div_f32<D>(_: &Lua, datatype: &D, rhs: LuaValue) -> LuaResult<D>
|
||||
pub fn userdata_impl_div_f32<D>(_: &Lua, datatype: &D, rhs: LuaValue) -> LuaResult<D>
|
||||
where
|
||||
D: LuaUserData + ops::Div<D, Output = D> + ops::Div<f32, Output = D> + Copy + 'static,
|
||||
{
|
||||
|
@ -139,7 +139,7 @@ where
|
|||
})
|
||||
}
|
||||
|
||||
pub(super) fn userdata_impl_div_i32<D>(_: &Lua, datatype: &D, rhs: LuaValue) -> LuaResult<D>
|
||||
pub fn userdata_impl_div_i32<D>(_: &Lua, datatype: &D, rhs: LuaValue) -> LuaResult<D>
|
||||
where
|
||||
D: LuaUserData + ops::Div<D, Output = D> + ops::Div<i32, Output = D> + Copy + 'static,
|
||||
{
|
|
@ -36,13 +36,13 @@ async-trait = "0.1"
|
|||
blocking = "1.3"
|
||||
dialoguer = "0.10"
|
||||
directories = "4.0"
|
||||
dunce = "1.0"
|
||||
pin-project = "1.0"
|
||||
os_str_bytes = "6.4"
|
||||
|
||||
hyper = { version = "0.14", features = ["full"] }
|
||||
hyper-tungstenite = { version = "0.9" }
|
||||
tokio-tungstenite = { version = "0.18" }
|
||||
dunce = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0"
|
||||
|
|
Loading…
Reference in a new issue