Initial implementation of instances for roblox lib

This commit is contained in:
Filip Tibell 2023-03-19 13:23:25 +01:00
parent 560dc4acbe
commit 68515dc40a
No known key found for this signature in database
12 changed files with 683 additions and 196 deletions

View file

@ -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::*;

View file

@ -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)

View file

@ -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

View file

@ -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;
}

View file

@ -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)
}
}

View file

@ -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())
}
}

View file

@ -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);
}
}

View file

@ -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)?;

View 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);
}
}

View file

@ -0,0 +1,2 @@
pub(crate) mod instance;
pub(crate) mod userdata;

View file

@ -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,
{

View file

@ -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"