Skip to content

Commit

Permalink
Start work on custom instance properties and methods in roblox builtin
Browse files Browse the repository at this point in the history
  • Loading branch information
filiptibell committed Oct 6, 2023
1 parent 02d812f commit 9fe3b02
Show file tree
Hide file tree
Showing 6 changed files with 311 additions and 36 deletions.
44 changes: 43 additions & 1 deletion src/lune/builtins/roblox/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
roblox::{
self,
document::{Document, DocumentError, DocumentFormat, DocumentKind},
instance::Instance,
instance::{registry::InstanceRegistry, Instance},
reflection::Database as ReflectionDatabase,
},
};
Expand All @@ -31,6 +31,8 @@ pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
.with_async_function("serializeModel", serialize_model)?
.with_function("getAuthCookie", get_auth_cookie)?
.with_function("getReflectionDatabase", get_reflection_database)?
.with_function("implementProperty", implement_property)?
.with_function("implementMethod", implement_method)?
.build_readonly()
}

Expand Down Expand Up @@ -105,3 +107,43 @@ fn get_auth_cookie(_: &Lua, raw: Option<bool>) -> LuaResult<Option<String>> {
fn get_reflection_database(_: &Lua, _: ()) -> LuaResult<ReflectionDatabase> {
Ok(*REFLECTION_DATABASE.get_or_init(ReflectionDatabase::new))
}

fn implement_property(
lua: &Lua,
(class_name, property_name, property_getter, property_setter): (
String,
String,
LuaFunction,
Option<LuaFunction>,
),
) -> LuaResult<()> {
let property_setter = match property_setter {
Some(setter) => setter,
None => {
let property_name = property_name.clone();
lua.create_function(move |_, _: LuaMultiValue| {
Err::<(), _>(LuaError::runtime(format!(
"Property '{property_name}' is read-only"
)))
})?
}
};
// TODO: Wrap getter and setter functions in async compat layers,
// the roblox library does not know about the Lune runtime or the
// scheduler and users may want to call async functions, some of
// which are not obvious that they are async such as print, warn, ...
InstanceRegistry::insert_property_getter(lua, &class_name, &property_name, property_getter)
.into_lua_err()?;
InstanceRegistry::insert_property_setter(lua, &class_name, &property_name, property_setter)
.into_lua_err()?;
Ok(())
}

fn implement_method(
lua: &Lua,
(class_name, method_name, method): (String, String, LuaFunction),
) -> LuaResult<()> {
// TODO: Same as above, wrap the provided method in an async compat layer
InstanceRegistry::insert_method(lua, &class_name, &method_name, method).into_lua_err()?;
Ok(())
}
63 changes: 33 additions & 30 deletions src/roblox/instance/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::roblox::{
shared::instance::{class_is_a, find_property_info},
};

use super::{data_model, Instance};
use super::{data_model, registry::InstanceRegistry, Instance};

pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
m.add_meta_method(LuaMetaMethod::ToString, |lua, this, ()| {
Expand Down Expand Up @@ -267,6 +267,10 @@ fn instance_property_get<'lua>(
}
} else if let Some(inst) = this.find_child(|inst| inst.name == prop_name) {
Ok(LuaValue::UserData(lua.create_userdata(inst)?))
} else if let Some(getter) = InstanceRegistry::find_property_getter(lua, this, &prop_name) {
getter.call(this.clone())
} else if let Some(method) = InstanceRegistry::find_method(lua, this, &prop_name) {
Ok(LuaValue::Function(method))
} else {
Err(LuaError::RuntimeError(format!(
"{} is not a valid member of {}",
Expand Down Expand Up @@ -317,40 +321,39 @@ fn instance_property_set<'lua>(
_ => {}
}

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(())
if let Some(info) = find_property_info(&this.class_name, &prop_name) {
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),
}
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(())
} 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()),
}
Err(e) => Err(e.into()),
} else {
Err(LuaError::RuntimeError(format!(
"Failed to set property '{}' - malformed property info",
prop_name
)))
}
} else if let Some(setter) = InstanceRegistry::find_property_setter(lua, this, &prop_name) {
setter.call((this.clone(), prop_value))
} else {
Err(LuaError::RuntimeError(format!(
"Failed to set property '{}' - malformed property info",
prop_name
"{} is not a valid member of {}",
prop_name, this
)))
}
}
4 changes: 2 additions & 2 deletions src/roblox/instance/data_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use super::Instance;

pub const CLASS_NAME: &str = "DataModel";

pub fn add_fields<'lua, M: LuaUserDataFields<'lua, Instance>>(m: &mut M) {
add_class_restricted_getter(m, CLASS_NAME, "Workspace", data_model_get_workspace);
pub fn add_fields<'lua, F: LuaUserDataFields<'lua, Instance>>(f: &mut F) {
add_class_restricted_getter(f, CLASS_NAME, "Workspace", data_model_get_workspace);
}

pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
Expand Down
5 changes: 5 additions & 0 deletions src/roblox/instance/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ pub(crate) mod data_model;
pub(crate) mod terrain;
pub(crate) mod workspace;

pub mod registry;

const PROPERTY_NAME_ATTRIBUTES: &str = "Attributes";
const PROPERTY_NAME_TAGS: &str = "Tags";

Expand Down Expand Up @@ -735,6 +737,9 @@ impl LuaExportsTable<'_> for Instance {
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
If a user wants to replicate Roblox engine behavior, they can use the
instance registry, and register properties + methods from the lua side
*/
impl LuaUserData for Instance {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
Expand Down
Loading

0 comments on commit 9fe3b02

Please sign in to comment.