mirror of
https://github.com/lune-org/lune.git
synced 2024-12-13 13:30:38 +00:00
Separate userdata impl for Instance out into new module for maintainability
This commit is contained in:
parent
eafb566e91
commit
31e625aa71
2 changed files with 369 additions and 350 deletions
356
src/roblox/instance/base.rs
Normal file
356
src/roblox/instance/base.rs
Normal file
|
@ -0,0 +1,356 @@
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
use rbx_dom_weak::{
|
||||||
|
types::{Variant as DomValue, VariantType as DomType},
|
||||||
|
Instance as DomInstance,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::roblox::{
|
||||||
|
datatypes::{
|
||||||
|
attributes::{ensure_valid_attribute_name, ensure_valid_attribute_value},
|
||||||
|
conversion::{DomValueToLua, LuaToDomValue},
|
||||||
|
types::EnumItem,
|
||||||
|
userdata_impl_eq, userdata_impl_to_string,
|
||||||
|
},
|
||||||
|
shared::instance::{class_is_a, find_property_info},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{data_model, Instance};
|
||||||
|
|
||||||
|
pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
|
||||||
|
m.add_meta_method(LuaMetaMethod::ToString, |lua, this, ()| {
|
||||||
|
ensure_not_destroyed(this)?;
|
||||||
|
userdata_impl_to_string(lua, this, ())
|
||||||
|
});
|
||||||
|
m.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||||
|
m.add_meta_method(LuaMetaMethod::Index, instance_property_get);
|
||||||
|
m.add_meta_method_mut(LuaMetaMethod::NewIndex, instance_property_set);
|
||||||
|
m.add_method("Clone", |lua, this, ()| {
|
||||||
|
ensure_not_destroyed(this)?;
|
||||||
|
this.clone_instance().into_lua(lua)
|
||||||
|
});
|
||||||
|
m.add_method_mut("Destroy", |_, this, ()| {
|
||||||
|
this.destroy();
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
m.add_method_mut("ClearAllChildren", |_, this, ()| {
|
||||||
|
this.clear_all_children();
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
m.add_method("GetChildren", |lua, this, ()| {
|
||||||
|
ensure_not_destroyed(this)?;
|
||||||
|
this.get_children().into_lua(lua)
|
||||||
|
});
|
||||||
|
m.add_method("GetDescendants", |lua, this, ()| {
|
||||||
|
ensure_not_destroyed(this)?;
|
||||||
|
this.get_descendants().into_lua(lua)
|
||||||
|
});
|
||||||
|
m.add_method("GetFullName", |lua, this, ()| {
|
||||||
|
ensure_not_destroyed(this)?;
|
||||||
|
this.get_full_name().into_lua(lua)
|
||||||
|
});
|
||||||
|
m.add_method("FindFirstAncestor", |lua, this, name: String| {
|
||||||
|
ensure_not_destroyed(this)?;
|
||||||
|
this.find_ancestor(|child| child.name == name).into_lua(lua)
|
||||||
|
});
|
||||||
|
m.add_method(
|
||||||
|
"FindFirstAncestorOfClass",
|
||||||
|
|lua, this, class_name: String| {
|
||||||
|
ensure_not_destroyed(this)?;
|
||||||
|
this.find_ancestor(|child| child.class == class_name)
|
||||||
|
.into_lua(lua)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
m.add_method(
|
||||||
|
"FindFirstAncestorWhichIsA",
|
||||||
|
|lua, this, class_name: String| {
|
||||||
|
ensure_not_destroyed(this)?;
|
||||||
|
this.find_ancestor(|child| class_is_a(&child.class, &class_name).unwrap_or(false))
|
||||||
|
.into_lua(lua)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
m.add_method(
|
||||||
|
"FindFirstChild",
|
||||||
|
|lua, this, (name, recursive): (String, Option<bool>)| {
|
||||||
|
ensure_not_destroyed(this)?;
|
||||||
|
let predicate = |child: &DomInstance| child.name == name;
|
||||||
|
if matches!(recursive, Some(true)) {
|
||||||
|
this.find_descendant(predicate).into_lua(lua)
|
||||||
|
} else {
|
||||||
|
this.find_child(predicate).into_lua(lua)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
m.add_method(
|
||||||
|
"FindFirstChildOfClass",
|
||||||
|
|lua, this, (class_name, recursive): (String, Option<bool>)| {
|
||||||
|
ensure_not_destroyed(this)?;
|
||||||
|
let predicate = |child: &DomInstance| child.class == class_name;
|
||||||
|
if matches!(recursive, Some(true)) {
|
||||||
|
this.find_descendant(predicate).into_lua(lua)
|
||||||
|
} else {
|
||||||
|
this.find_child(predicate).into_lua(lua)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
m.add_method(
|
||||||
|
"FindFirstChildWhichIsA",
|
||||||
|
|lua, this, (class_name, recursive): (String, Option<bool>)| {
|
||||||
|
ensure_not_destroyed(this)?;
|
||||||
|
let predicate =
|
||||||
|
|child: &DomInstance| class_is_a(&child.class, &class_name).unwrap_or(false);
|
||||||
|
if matches!(recursive, Some(true)) {
|
||||||
|
this.find_descendant(predicate).into_lua(lua)
|
||||||
|
} else {
|
||||||
|
this.find_child(predicate).into_lua(lua)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
m.add_method("IsA", |_, this, class_name: String| {
|
||||||
|
ensure_not_destroyed(this)?;
|
||||||
|
Ok(class_is_a(&this.class_name, class_name).unwrap_or(false))
|
||||||
|
});
|
||||||
|
m.add_method(
|
||||||
|
"IsAncestorOf",
|
||||||
|
|_, this, instance: LuaUserDataRef<Instance>| {
|
||||||
|
ensure_not_destroyed(this)?;
|
||||||
|
Ok(instance
|
||||||
|
.find_ancestor(|ancestor| ancestor.referent() == this.dom_ref)
|
||||||
|
.is_some())
|
||||||
|
},
|
||||||
|
);
|
||||||
|
m.add_method(
|
||||||
|
"IsDescendantOf",
|
||||||
|
|_, this, instance: LuaUserDataRef<Instance>| {
|
||||||
|
ensure_not_destroyed(this)?;
|
||||||
|
Ok(this
|
||||||
|
.find_ancestor(|ancestor| ancestor.referent() == instance.dom_ref)
|
||||||
|
.is_some())
|
||||||
|
},
|
||||||
|
);
|
||||||
|
m.add_method("GetAttribute", |lua, this, name: String| {
|
||||||
|
ensure_not_destroyed(this)?;
|
||||||
|
match this.get_attribute(name) {
|
||||||
|
Some(attribute) => Ok(LuaValue::dom_value_to_lua(lua, &attribute)?),
|
||||||
|
None => Ok(LuaValue::Nil),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
m.add_method("GetAttributes", |lua, this, ()| {
|
||||||
|
ensure_not_destroyed(this)?;
|
||||||
|
let attributes = this.get_attributes();
|
||||||
|
let tab = lua.create_table_with_capacity(0, attributes.len())?;
|
||||||
|
for (key, value) in attributes.into_iter() {
|
||||||
|
tab.set(key, LuaValue::dom_value_to_lua(lua, &value)?)?;
|
||||||
|
}
|
||||||
|
Ok(tab)
|
||||||
|
});
|
||||||
|
m.add_method(
|
||||||
|
"SetAttribute",
|
||||||
|
|lua, this, (attribute_name, lua_value): (String, LuaValue)| {
|
||||||
|
ensure_not_destroyed(this)?;
|
||||||
|
ensure_valid_attribute_name(&attribute_name)?;
|
||||||
|
match lua_value.lua_to_dom_value(lua, None) {
|
||||||
|
Ok(dom_value) => {
|
||||||
|
ensure_valid_attribute_value(&dom_value)?;
|
||||||
|
this.set_attribute(attribute_name, dom_value);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(e) => Err(e.into()),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
m.add_method("GetTags", |_, this, ()| {
|
||||||
|
ensure_not_destroyed(this)?;
|
||||||
|
Ok(this.get_tags())
|
||||||
|
});
|
||||||
|
m.add_method("HasTag", |_, this, tag: String| {
|
||||||
|
ensure_not_destroyed(this)?;
|
||||||
|
Ok(this.has_tag(tag))
|
||||||
|
});
|
||||||
|
m.add_method("AddTag", |_, this, tag: String| {
|
||||||
|
ensure_not_destroyed(this)?;
|
||||||
|
this.add_tag(tag);
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
m.add_method("RemoveTag", |_, this, tag: String| {
|
||||||
|
ensure_not_destroyed(this)?;
|
||||||
|
this.remove_tag(tag);
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_not_destroyed(inst: &Instance) -> LuaResult<()> {
|
||||||
|
if inst.is_destroyed() {
|
||||||
|
Err(LuaError::RuntimeError(
|
||||||
|
"Instance has been destroyed".to_string(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Gets a property value for an instance.
|
||||||
|
|
||||||
|
Getting 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
|
||||||
|
2a. Get an existing instance property OR
|
||||||
|
2b. Get a property from a known default value
|
||||||
|
3. Get a current child of the instance
|
||||||
|
4. No valid property or instance found, throw error
|
||||||
|
*/
|
||||||
|
fn instance_property_get<'lua>(
|
||||||
|
lua: &'lua Lua,
|
||||||
|
this: &Instance,
|
||||||
|
prop_name: String,
|
||||||
|
) -> LuaResult<LuaValue<'lua>> {
|
||||||
|
ensure_not_destroyed(this)?;
|
||||||
|
|
||||||
|
match prop_name.as_str() {
|
||||||
|
"ClassName" => return this.get_class_name().into_lua(lua),
|
||||||
|
"Name" => {
|
||||||
|
return this.get_name().into_lua(lua);
|
||||||
|
}
|
||||||
|
"Parent" => {
|
||||||
|
return this.get_parent().into_lua(lua);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(info) = find_property_info(&this.class_name, &prop_name) {
|
||||||
|
if let Some(prop) = this.get_property(&prop_name) {
|
||||||
|
if let DomValue::Enum(enum_value) = prop {
|
||||||
|
let enum_name = info.enum_name.ok_or_else(|| {
|
||||||
|
LuaError::RuntimeError(format!(
|
||||||
|
"Failed to get property '{}' - encountered unknown enum",
|
||||||
|
prop_name
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
EnumItem::from_enum_name_and_value(&enum_name, enum_value.to_u32())
|
||||||
|
.ok_or_else(|| {
|
||||||
|
LuaError::RuntimeError(format!(
|
||||||
|
"Failed to get property '{}' - Enum.{} does not contain numeric value {}",
|
||||||
|
prop_name, enum_name, enum_value.to_u32()
|
||||||
|
))
|
||||||
|
})?
|
||||||
|
.into_lua(lua)
|
||||||
|
} else {
|
||||||
|
Ok(LuaValue::dom_value_to_lua(lua, &prop)?)
|
||||||
|
}
|
||||||
|
} else if let (Some(enum_name), Some(enum_value)) = (info.enum_name, info.enum_default) {
|
||||||
|
EnumItem::from_enum_name_and_value(&enum_name, enum_value)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
LuaError::RuntimeError(format!(
|
||||||
|
"Failed to get property '{}' - Enum.{} does not contain numeric value {}",
|
||||||
|
prop_name, enum_name, enum_value
|
||||||
|
))
|
||||||
|
})?
|
||||||
|
.into_lua(lua)
|
||||||
|
} else if let Some(prop_default) = info.value_default {
|
||||||
|
Ok(LuaValue::dom_value_to_lua(lua, prop_default)?)
|
||||||
|
} else if info.value_type.is_some() {
|
||||||
|
if info.value_type == Some(DomType::Ref) {
|
||||||
|
Ok(LuaValue::Nil)
|
||||||
|
} else {
|
||||||
|
Err(LuaError::RuntimeError(format!(
|
||||||
|
"Failed to get property '{}' - missing default value",
|
||||||
|
prop_name
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(LuaError::RuntimeError(format!(
|
||||||
|
"Failed to get property '{}' - malformed property info",
|
||||||
|
prop_name
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
} 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
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Sets a property value for an instance.
|
||||||
|
|
||||||
|
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
|
||||||
|
2a. Set a strict enum from a given EnumItem OR
|
||||||
|
2b. Set a normal property from a given value
|
||||||
|
*/
|
||||||
|
fn instance_property_set<'lua>(
|
||||||
|
lua: &'lua Lua,
|
||||||
|
this: &mut Instance,
|
||||||
|
(prop_name, prop_value): (String, LuaValue<'lua>),
|
||||||
|
) -> LuaResult<()> {
|
||||||
|
ensure_not_destroyed(this)?;
|
||||||
|
|
||||||
|
match prop_name.as_str() {
|
||||||
|
"ClassName" => {
|
||||||
|
return Err(LuaError::RuntimeError(
|
||||||
|
"Failed to set ClassName - property is read-only".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
"Name" => {
|
||||||
|
let name = String::from_lua(prop_value, lua)?;
|
||||||
|
this.set_name(name);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
"Parent" => {
|
||||||
|
if this.get_class_name() == data_model::CLASS_NAME {
|
||||||
|
return Err(LuaError::RuntimeError(
|
||||||
|
"Failed to set Parent - DataModel can not be reparented".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
type Parent<'lua> = Option<LuaUserDataRef<'lua, Instance>>;
|
||||||
|
let parent = Parent::from_lua(prop_value, lua)?;
|
||||||
|
this.set_parent(parent.map(|p| p.clone()));
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let info = match find_property_info(&this.class_name, &prop_name) {
|
||||||
|
Some(b) => b,
|
||||||
|
None => {
|
||||||
|
return Err(LuaError::RuntimeError(format!(
|
||||||
|
"{} is not a valid member of {}",
|
||||||
|
prop_name, this
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(enum_name) = info.enum_name {
|
||||||
|
match LuaUserDataRef::<EnumItem>::from_lua(prop_value, lua) {
|
||||||
|
Ok(given_enum) if given_enum.parent.desc.name == enum_name => {
|
||||||
|
this.set_property(prop_name, DomValue::Enum((*given_enum).clone().into()));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Ok(given_enum) => Err(LuaError::RuntimeError(format!(
|
||||||
|
"Failed to set property '{}' - expected Enum.{}, got Enum.{}",
|
||||||
|
prop_name, enum_name, given_enum.parent.desc.name
|
||||||
|
))),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
} else if let Some(dom_type) = info.value_type {
|
||||||
|
match prop_value.lua_to_dom_value(lua, Some(dom_type)) {
|
||||||
|
Ok(dom_value) => {
|
||||||
|
this.set_property(prop_name, dom_value);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(e) => Err(e.into()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(LuaError::RuntimeError(format!(
|
||||||
|
"Failed to set property '{}' - malformed property info",
|
||||||
|
prop_name
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,22 +8,13 @@ use std::{
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use rbx_dom_weak::{
|
use rbx_dom_weak::{
|
||||||
types::{
|
types::{Attributes as DomAttributes, Ref as DomRef, Variant as DomValue},
|
||||||
Attributes as DomAttributes, Ref as DomRef, Variant as DomValue, VariantType as DomType,
|
|
||||||
},
|
|
||||||
Instance as DomInstance, InstanceBuilder as DomInstanceBuilder, WeakDom,
|
Instance as DomInstance, InstanceBuilder as DomInstanceBuilder, WeakDom,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::roblox::{
|
use crate::roblox::shared::instance::{class_exists, class_is_a};
|
||||||
datatypes::{
|
|
||||||
attributes::{ensure_valid_attribute_name, ensure_valid_attribute_value},
|
|
||||||
conversion::{DomValueToLua, LuaToDomValue},
|
|
||||||
types::EnumItem,
|
|
||||||
userdata_impl_eq, userdata_impl_to_string,
|
|
||||||
},
|
|
||||||
shared::instance::{class_exists, class_is_a, find_property_info},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
pub(crate) mod base;
|
||||||
pub(crate) mod data_model;
|
pub(crate) mod data_model;
|
||||||
pub(crate) mod workspace;
|
pub(crate) mod workspace;
|
||||||
|
|
||||||
|
@ -202,16 +193,6 @@ impl Instance {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ensure_not_destroyed(&self) -> LuaResult<()> {
|
|
||||||
if self.is_destroyed() {
|
|
||||||
Err(LuaError::RuntimeError(
|
|
||||||
"Instance has been destroyed".to_string(),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_destroyed(&self) -> bool {
|
fn is_destroyed(&self) -> bool {
|
||||||
// NOTE: This property can not be cached since instance references
|
// NOTE: This property can not be cached since instance references
|
||||||
// other than this one may have destroyed this one, and we don't
|
// other than this one may have destroyed this one, and we don't
|
||||||
|
@ -766,341 +747,23 @@ impl Instance {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Here we add inheritance-like behavior for instances by creating
|
||||||
|
fields that are restricted to specific classnames / base classes
|
||||||
|
|
||||||
|
Note that we should try to be conservative with how many classes
|
||||||
|
and methods we support here - we should only implement methods that
|
||||||
|
are necessary for modifying the dom and / or having ergonomic access
|
||||||
|
to the dom, not try to replicate Roblox engine behavior of instances
|
||||||
|
*/
|
||||||
impl LuaUserData for Instance {
|
impl LuaUserData for Instance {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
||||||
// Here we add inheritance-like behavior for instances by creating
|
|
||||||
// fields that are restricted to specific classnames / base classes
|
|
||||||
data_model::add_fields(fields);
|
data_model::add_fields(fields);
|
||||||
workspace::add_fields(fields);
|
workspace::add_fields(fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, |lua, this, ()| {
|
base::add_methods(methods);
|
||||||
this.ensure_not_destroyed()?;
|
|
||||||
userdata_impl_to_string(lua, this, ())
|
|
||||||
});
|
|
||||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
|
||||||
/*
|
|
||||||
Getting 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
|
|
||||||
2a. Get an existing instance property OR
|
|
||||||
2b. Get a property from a known default value
|
|
||||||
3. 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| {
|
|
||||||
this.ensure_not_destroyed()?;
|
|
||||||
|
|
||||||
match prop_name.as_str() {
|
|
||||||
"ClassName" => return this.get_class_name().into_lua(lua),
|
|
||||||
"Name" => {
|
|
||||||
return this.get_name().into_lua(lua);
|
|
||||||
}
|
|
||||||
"Parent" => {
|
|
||||||
return this.get_parent().into_lua(lua);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(info) = find_property_info(&this.class_name, &prop_name) {
|
|
||||||
if let Some(prop) = this.get_property(&prop_name) {
|
|
||||||
if let DomValue::Enum(enum_value) = prop {
|
|
||||||
let enum_name = info.enum_name.ok_or_else(|| {
|
|
||||||
LuaError::RuntimeError(format!(
|
|
||||||
"Failed to get property '{}' - encountered unknown enum",
|
|
||||||
prop_name
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
EnumItem::from_enum_name_and_value(&enum_name, enum_value.to_u32())
|
|
||||||
.ok_or_else(|| {
|
|
||||||
LuaError::RuntimeError(format!(
|
|
||||||
"Failed to get property '{}' - Enum.{} does not contain numeric value {}",
|
|
||||||
prop_name, enum_name, enum_value.to_u32()
|
|
||||||
))
|
|
||||||
})?
|
|
||||||
.into_lua(lua)
|
|
||||||
} else {
|
|
||||||
Ok(LuaValue::dom_value_to_lua(lua, &prop)?)
|
|
||||||
}
|
|
||||||
} else if let (Some(enum_name), Some(enum_value)) = (info.enum_name, info.enum_default) {
|
|
||||||
EnumItem::from_enum_name_and_value(&enum_name, enum_value)
|
|
||||||
.ok_or_else(|| {
|
|
||||||
LuaError::RuntimeError(format!(
|
|
||||||
"Failed to get property '{}' - Enum.{} does not contain numeric value {}",
|
|
||||||
prop_name, enum_name, enum_value
|
|
||||||
))
|
|
||||||
})?
|
|
||||||
.into_lua(lua)
|
|
||||||
} else if let Some(prop_default) = info.value_default {
|
|
||||||
Ok(LuaValue::dom_value_to_lua(lua, prop_default)?)
|
|
||||||
} else if info.value_type.is_some() {
|
|
||||||
if info.value_type == Some(DomType::Ref) {
|
|
||||||
Ok(LuaValue::Nil)
|
|
||||||
} else {
|
|
||||||
Err(LuaError::RuntimeError(format!(
|
|
||||||
"Failed to get property '{}' - missing default value",
|
|
||||||
prop_name
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(LuaError::RuntimeError(format!(
|
|
||||||
"Failed to get property '{}' - malformed property info",
|
|
||||||
prop_name
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
} 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
|
|
||||||
2a. Set a strict enum from a given EnumItem OR
|
|
||||||
2b. Set a normal property from a given value
|
|
||||||
*/
|
|
||||||
methods.add_meta_method_mut(
|
|
||||||
LuaMetaMethod::NewIndex,
|
|
||||||
|lua, this, (prop_name, prop_value): (String, LuaValue)| {
|
|
||||||
this.ensure_not_destroyed()?;
|
|
||||||
|
|
||||||
match prop_name.as_str() {
|
|
||||||
"ClassName" => {
|
|
||||||
return Err(LuaError::RuntimeError(format!(
|
|
||||||
"Failed to set property '{}' - property is read-only",
|
|
||||||
prop_name
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
"Name" => {
|
|
||||||
let name = String::from_lua(prop_value, lua)?;
|
|
||||||
this.set_name(name);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
"Parent" => {
|
|
||||||
if this.get_class_name() == data_model::CLASS_NAME {
|
|
||||||
return Err(LuaError::RuntimeError(format!(
|
|
||||||
"Failed to set property '{}' - DataModel can not be reparented",
|
|
||||||
prop_name
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
type Parent<'lua> = Option<LuaUserDataRef<'lua, Instance>>;
|
|
||||||
let parent = Parent::from_lua(prop_value, lua)?;
|
|
||||||
this.set_parent(parent.map(|p| p.clone()));
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let info = match find_property_info(&this.class_name, &prop_name) {
|
|
||||||
Some(b) => b,
|
|
||||||
None => {
|
|
||||||
return Err(LuaError::RuntimeError(format!(
|
|
||||||
"{} is not a valid member of {}",
|
|
||||||
prop_name, this
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(enum_name) = info.enum_name {
|
|
||||||
match LuaUserDataRef::<EnumItem>::from_lua(prop_value, lua) {
|
|
||||||
Ok(given_enum) if given_enum.parent.desc.name == enum_name => {
|
|
||||||
this.set_property(
|
|
||||||
prop_name,
|
|
||||||
DomValue::Enum((*given_enum).clone().into()),
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Ok(given_enum) => Err(LuaError::RuntimeError(format!(
|
|
||||||
"Failed to set property '{}' - expected Enum.{}, got Enum.{}",
|
|
||||||
prop_name, enum_name, given_enum.parent.desc.name
|
|
||||||
))),
|
|
||||||
Err(e) => Err(e),
|
|
||||||
}
|
|
||||||
} else if let Some(dom_type) = info.value_type {
|
|
||||||
match prop_value.lua_to_dom_value(lua, Some(dom_type)) {
|
|
||||||
Ok(dom_value) => {
|
|
||||||
this.set_property(prop_name, dom_value);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Err(e) => Err(e.into()),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(LuaError::RuntimeError(format!(
|
|
||||||
"Failed to set property '{}' - malformed property info",
|
|
||||||
prop_name
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
/*
|
|
||||||
Implementations of base methods on the Instance class
|
|
||||||
|
|
||||||
It should be noted that any methods that deal with events
|
|
||||||
and/or have functionality that affects instances other
|
|
||||||
than this instance itself are intentionally left out.
|
|
||||||
*/
|
|
||||||
methods.add_method("Clone", |lua, this, ()| {
|
|
||||||
this.ensure_not_destroyed()?;
|
|
||||||
this.clone_instance().into_lua(lua)
|
|
||||||
});
|
|
||||||
methods.add_method_mut("Destroy", |_, this, ()| {
|
|
||||||
this.destroy();
|
|
||||||
Ok(())
|
|
||||||
});
|
|
||||||
methods.add_method_mut("ClearAllChildren", |_, this, ()| {
|
|
||||||
this.clear_all_children();
|
|
||||||
Ok(())
|
|
||||||
});
|
|
||||||
methods.add_method("GetChildren", |lua, this, ()| {
|
|
||||||
this.ensure_not_destroyed()?;
|
|
||||||
this.get_children().into_lua(lua)
|
|
||||||
});
|
|
||||||
methods.add_method("GetDescendants", |lua, this, ()| {
|
|
||||||
this.ensure_not_destroyed()?;
|
|
||||||
this.get_descendants().into_lua(lua)
|
|
||||||
});
|
|
||||||
methods.add_method("GetFullName", |lua, this, ()| {
|
|
||||||
this.ensure_not_destroyed()?;
|
|
||||||
this.get_full_name().into_lua(lua)
|
|
||||||
});
|
|
||||||
methods.add_method("FindFirstAncestor", |lua, this, name: String| {
|
|
||||||
this.ensure_not_destroyed()?;
|
|
||||||
this.find_ancestor(|child| child.name == name).into_lua(lua)
|
|
||||||
});
|
|
||||||
methods.add_method(
|
|
||||||
"FindFirstAncestorOfClass",
|
|
||||||
|lua, this, class_name: String| {
|
|
||||||
this.ensure_not_destroyed()?;
|
|
||||||
this.find_ancestor(|child| child.class == class_name)
|
|
||||||
.into_lua(lua)
|
|
||||||
},
|
|
||||||
);
|
|
||||||
methods.add_method(
|
|
||||||
"FindFirstAncestorWhichIsA",
|
|
||||||
|lua, this, class_name: String| {
|
|
||||||
this.ensure_not_destroyed()?;
|
|
||||||
this.find_ancestor(|child| class_is_a(&child.class, &class_name).unwrap_or(false))
|
|
||||||
.into_lua(lua)
|
|
||||||
},
|
|
||||||
);
|
|
||||||
methods.add_method(
|
|
||||||
"FindFirstChild",
|
|
||||||
|lua, this, (name, recursive): (String, Option<bool>)| {
|
|
||||||
this.ensure_not_destroyed()?;
|
|
||||||
let predicate = |child: &DomInstance| child.name == name;
|
|
||||||
if matches!(recursive, Some(true)) {
|
|
||||||
this.find_descendant(predicate).into_lua(lua)
|
|
||||||
} else {
|
|
||||||
this.find_child(predicate).into_lua(lua)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
methods.add_method(
|
|
||||||
"FindFirstChildOfClass",
|
|
||||||
|lua, this, (class_name, recursive): (String, Option<bool>)| {
|
|
||||||
this.ensure_not_destroyed()?;
|
|
||||||
let predicate = |child: &DomInstance| child.class == class_name;
|
|
||||||
if matches!(recursive, Some(true)) {
|
|
||||||
this.find_descendant(predicate).into_lua(lua)
|
|
||||||
} else {
|
|
||||||
this.find_child(predicate).into_lua(lua)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
methods.add_method(
|
|
||||||
"FindFirstChildWhichIsA",
|
|
||||||
|lua, this, (class_name, recursive): (String, Option<bool>)| {
|
|
||||||
this.ensure_not_destroyed()?;
|
|
||||||
let predicate =
|
|
||||||
|child: &DomInstance| class_is_a(&child.class, &class_name).unwrap_or(false);
|
|
||||||
if matches!(recursive, Some(true)) {
|
|
||||||
this.find_descendant(predicate).into_lua(lua)
|
|
||||||
} else {
|
|
||||||
this.find_child(predicate).into_lua(lua)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
methods.add_method("IsA", |_, this, class_name: String| {
|
|
||||||
this.ensure_not_destroyed()?;
|
|
||||||
Ok(class_is_a(&this.class_name, class_name).unwrap_or(false))
|
|
||||||
});
|
|
||||||
methods.add_method(
|
|
||||||
"IsAncestorOf",
|
|
||||||
|_, this, instance: LuaUserDataRef<Instance>| {
|
|
||||||
this.ensure_not_destroyed()?;
|
|
||||||
Ok(instance
|
|
||||||
.find_ancestor(|ancestor| ancestor.referent() == this.dom_ref)
|
|
||||||
.is_some())
|
|
||||||
},
|
|
||||||
);
|
|
||||||
methods.add_method(
|
|
||||||
"IsDescendantOf",
|
|
||||||
|_, this, instance: LuaUserDataRef<Instance>| {
|
|
||||||
this.ensure_not_destroyed()?;
|
|
||||||
Ok(this
|
|
||||||
.find_ancestor(|ancestor| ancestor.referent() == instance.dom_ref)
|
|
||||||
.is_some())
|
|
||||||
},
|
|
||||||
);
|
|
||||||
methods.add_method("GetAttribute", |lua, this, name: String| {
|
|
||||||
this.ensure_not_destroyed()?;
|
|
||||||
match this.get_attribute(name) {
|
|
||||||
Some(attribute) => Ok(LuaValue::dom_value_to_lua(lua, &attribute)?),
|
|
||||||
None => Ok(LuaValue::Nil),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
methods.add_method("GetAttributes", |lua, this, ()| {
|
|
||||||
this.ensure_not_destroyed()?;
|
|
||||||
let attributes = this.get_attributes();
|
|
||||||
let tab = lua.create_table_with_capacity(0, attributes.len())?;
|
|
||||||
for (key, value) in attributes.into_iter() {
|
|
||||||
tab.set(key, LuaValue::dom_value_to_lua(lua, &value)?)?;
|
|
||||||
}
|
|
||||||
Ok(tab)
|
|
||||||
});
|
|
||||||
methods.add_method(
|
|
||||||
"SetAttribute",
|
|
||||||
|lua, this, (attribute_name, lua_value): (String, LuaValue)| {
|
|
||||||
this.ensure_not_destroyed()?;
|
|
||||||
ensure_valid_attribute_name(&attribute_name)?;
|
|
||||||
match lua_value.lua_to_dom_value(lua, None) {
|
|
||||||
Ok(dom_value) => {
|
|
||||||
ensure_valid_attribute_value(&dom_value)?;
|
|
||||||
this.set_attribute(attribute_name, dom_value);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Err(e) => Err(e.into()),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
methods.add_method("GetTags", |_, this, ()| {
|
|
||||||
this.ensure_not_destroyed()?;
|
|
||||||
Ok(this.get_tags())
|
|
||||||
});
|
|
||||||
methods.add_method("HasTag", |_, this, tag: String| {
|
|
||||||
this.ensure_not_destroyed()?;
|
|
||||||
Ok(this.has_tag(tag))
|
|
||||||
});
|
|
||||||
methods.add_method("AddTag", |_, this, tag: String| {
|
|
||||||
this.ensure_not_destroyed()?;
|
|
||||||
this.add_tag(tag);
|
|
||||||
Ok(())
|
|
||||||
});
|
|
||||||
methods.add_method("RemoveTag", |_, this, tag: String| {
|
|
||||||
this.ensure_not_destroyed()?;
|
|
||||||
this.remove_tag(tag);
|
|
||||||
Ok(())
|
|
||||||
});
|
|
||||||
// Here we add inheritance-like behavior for instances by creating
|
|
||||||
// methods that are restricted to specific classnames / base classes
|
|
||||||
data_model::add_methods(methods);
|
data_model::add_methods(methods);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue