forked from StrafesNET/strafe-project
640 lines
24 KiB
Rust
640 lines
24 KiB
Rust
use std::collections::{hash_map::Entry,HashMap};
|
|
|
|
use mlua::{FromLua,FromLuaMulti,IntoLua,IntoLuaMulti};
|
|
use rbx_types::Ref;
|
|
use rbx_dom_weak::{Ustr,InstanceBuilder,WeakDom};
|
|
|
|
use crate::util::static_ustr;
|
|
use crate::runner::vector3::Vector3;
|
|
use crate::runner::number::Number;
|
|
|
|
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
|
//class functions store
|
|
lua.set_app_data(ClassMethodsStore::default());
|
|
|
|
let table=lua.create_table()?;
|
|
|
|
//Instance.new
|
|
table.raw_set("new",
|
|
lua.create_function(|lua,(class_name,parent):(mlua::String,Option<Instance>)|{
|
|
let class_name_str=&*class_name.to_str()?;
|
|
let parent_ref=parent.map_or(Ref::none(),|instance|instance.referent);
|
|
dom_mut(lua,|dom|{
|
|
Ok(Instance::new_unchecked(dom.insert(parent_ref,InstanceBuilder::new(class_name_str))))
|
|
})
|
|
})?
|
|
)?;
|
|
|
|
globals.set("Instance",table)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// LMAO look at this function!
|
|
pub fn dom_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut WeakDom)->mlua::Result<T>)->mlua::Result<T>{
|
|
let mut dom=lua.app_data_mut::<crate::context::LuaAppData>().ok_or_else(||mlua::Error::runtime("DataModel missing"))?;
|
|
f(*dom)
|
|
}
|
|
|
|
pub fn class_is_a(class:&str,superclass:&str)->bool{
|
|
let db=rbx_reflection_database::get();
|
|
let (Some(class),Some(superclass))=(db.classes.get(class),db.classes.get(superclass))else{
|
|
return false;
|
|
};
|
|
db.has_superclass(class,superclass)
|
|
}
|
|
fn get_full_name(dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance)->String{
|
|
let mut full_name=instance.name.clone();
|
|
let mut pref=instance.parent();
|
|
while let Some(parent)=dom.get_by_ref(pref){
|
|
full_name.insert(0,'.');
|
|
full_name.insert_str(0,parent.name.as_str());
|
|
pref=parent.parent();
|
|
}
|
|
full_name
|
|
}
|
|
//helper function for script
|
|
pub fn get_name_source(lua:&mlua::Lua,script:Instance)->Result<(String,String),mlua::Error>{
|
|
dom_mut(lua,|dom|{
|
|
let instance=script.get(dom)?;
|
|
let source=match instance.properties.get(&static_ustr("Source")){
|
|
Some(rbx_dom_weak::types::Variant::String(s))=>s.clone(),
|
|
_=>Err(mlua::Error::external("Missing script.Source"))?,
|
|
};
|
|
Ok((get_full_name(dom,instance),source))
|
|
})
|
|
}
|
|
|
|
pub fn find_first_child<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,name:&str)->Option<&'a rbx_dom_weak::Instance>{
|
|
instance.children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|inst.name==name)
|
|
}
|
|
pub fn find_first_descendant<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,name:&str)->Option<&'a rbx_dom_weak::Instance>{
|
|
dom.descendants_of(instance.referent()).find(|&inst|inst.name==name)
|
|
}
|
|
|
|
pub fn find_first_child_of_class<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,class:&str)->Option<&'a rbx_dom_weak::Instance>{
|
|
instance.children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|inst.class==class)
|
|
}
|
|
pub fn find_first_descendant_of_class<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,class:&str)->Option<&'a rbx_dom_weak::Instance>{
|
|
dom.descendants_of(instance.referent()).find(|&inst|inst.class==class)
|
|
}
|
|
|
|
pub fn find_first_child_which_is_a<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,superclass:&str)->Option<&'a rbx_dom_weak::Instance>{
|
|
let db=rbx_reflection_database::get();
|
|
let superclass_descriptor=db.classes.get(superclass)?;
|
|
instance.children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|{
|
|
db.classes.get(inst.class.as_str()).is_some_and(|descriptor|db.has_superclass(descriptor,superclass_descriptor))
|
|
})
|
|
}
|
|
pub fn find_first_descendant_which_is_a<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,superclass:&str)->Option<&'a rbx_dom_weak::Instance>{
|
|
let db=rbx_reflection_database::get();
|
|
let superclass_descriptor=db.classes.get(superclass)?;
|
|
dom.descendants_of(instance.referent()).find(|inst|{
|
|
db.classes.get(inst.class.as_str()).is_some_and(|descriptor|db.has_superclass(descriptor,superclass_descriptor))
|
|
})
|
|
}
|
|
|
|
#[derive(Clone,Copy)]
|
|
pub struct Instance{
|
|
referent:Ref,
|
|
}
|
|
impl Instance{
|
|
pub const fn new_unchecked(referent:Ref)->Self{
|
|
Self{referent}
|
|
}
|
|
pub fn new(referent:Ref)->Option<Self>{
|
|
referent.is_some().then_some(Self{referent})
|
|
}
|
|
pub fn get<'a>(&self,dom:&'a WeakDom)->mlua::Result<&'a rbx_dom_weak::Instance>{
|
|
dom.get_by_ref(self.referent).ok_or_else(||mlua::Error::runtime("Instance missing"))
|
|
}
|
|
pub fn get_mut<'a>(&self,dom:&'a mut WeakDom)->mlua::Result<&'a mut rbx_dom_weak::Instance>{
|
|
dom.get_by_ref_mut(self.referent).ok_or_else(||mlua::Error::runtime("Instance missing"))
|
|
}
|
|
}
|
|
type_from_lua_userdata!(Instance);
|
|
|
|
impl mlua::UserData for Instance{
|
|
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
|
|
fn get_parent(lua:&mlua::Lua,this:&Instance)->mlua::Result<Option<Instance>>{
|
|
dom_mut(lua,|dom|{
|
|
let instance=this.get(dom)?;
|
|
Ok(Instance::new(instance.parent()))
|
|
})
|
|
}
|
|
fields.add_field_method_get("parent",get_parent);
|
|
fields.add_field_method_get("Parent",get_parent);
|
|
fn set_parent(lua:&mlua::Lua,this:&mut Instance,new_parent:Option<Instance>)->mlua::Result<()>{
|
|
let parent_ref=new_parent.map_or(Ref::none(),|instance|instance.referent);
|
|
dom_mut(lua,|dom|{
|
|
dom.transfer_within(this.referent,parent_ref);
|
|
Ok(())
|
|
})
|
|
}
|
|
fields.add_field_method_set("parent",set_parent);
|
|
fields.add_field_method_set("Parent",set_parent);
|
|
fields.add_field_method_get("Name",|lua,this|{
|
|
dom_mut(lua,|dom|{
|
|
let instance=this.get(dom)?;
|
|
Ok(instance.name.clone())
|
|
})
|
|
});
|
|
fields.add_field_method_set("Name",|lua,this,val:mlua::String|{
|
|
dom_mut(lua,|dom|{
|
|
let instance=this.get_mut(dom)?;
|
|
//Why does this need to be cloned?
|
|
instance.name=val.to_str()?.to_owned();
|
|
Ok(())
|
|
})
|
|
});
|
|
fields.add_field_method_get("ClassName",|lua,this|{
|
|
dom_mut(lua,|dom|{
|
|
let instance=this.get(dom)?;
|
|
Ok(instance.class.to_owned())
|
|
})
|
|
});
|
|
}
|
|
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
|
|
fn clone(lua:&mlua::Lua,this:&Instance,_:())->mlua::Result<Instance>{
|
|
dom_mut(lua,|dom|{
|
|
let instance_ref=dom.clone_within(this.referent);
|
|
Ok(Instance::new_unchecked(instance_ref))
|
|
})
|
|
}
|
|
methods.add_method("clone",clone);
|
|
methods.add_method("Clone",clone);
|
|
fn get_children(lua:&mlua::Lua,this:&Instance,_:())->mlua::Result<Vec<Instance>>{
|
|
dom_mut(lua,|dom|{
|
|
let instance=this.get(dom)?;
|
|
let children:Vec<_>=instance
|
|
.children()
|
|
.iter()
|
|
.copied()
|
|
.map(Instance::new_unchecked)
|
|
.collect();
|
|
Ok(children)
|
|
})
|
|
}
|
|
methods.add_method("children",get_children);
|
|
methods.add_method("GetChildren",get_children);
|
|
fn ffc(lua:&mlua::Lua,this:&Instance,(name,search_descendants):(mlua::String,Option<bool>))->mlua::Result<Option<Instance>>{
|
|
let name_str=&*name.to_str()?;
|
|
dom_mut(lua,|dom|{
|
|
let instance=this.get(dom)?;
|
|
Ok(
|
|
match search_descendants.unwrap_or(false){
|
|
true=>find_first_descendant(dom,instance,name_str),
|
|
false=>find_first_child(dom,instance,name_str),
|
|
}
|
|
.map(|instance|
|
|
Instance::new_unchecked(instance.referent())
|
|
)
|
|
)
|
|
})
|
|
}
|
|
methods.add_method("FindFirstChild",ffc);
|
|
methods.add_method("WaitForChild",ffc);
|
|
methods.add_method("FindFirstChildOfClass",|lua,this,(class,search_descendants):(mlua::String,Option<bool>)|{
|
|
let class_str=&*class.to_str()?;
|
|
dom_mut(lua,|dom|{
|
|
let inst=this.get(dom)?;
|
|
Ok(
|
|
match search_descendants.unwrap_or(false){
|
|
true=>find_first_descendant_of_class(dom,inst,class_str),
|
|
false=>find_first_child_of_class(dom,inst,class_str),
|
|
}
|
|
.map(|instance|
|
|
Instance::new_unchecked(instance.referent())
|
|
)
|
|
)
|
|
})
|
|
});
|
|
methods.add_method("FindFirstChildWhichIsA",|lua,this,(class,search_descendants):(mlua::String,Option<bool>)|{
|
|
let class_str=&*class.to_str()?;
|
|
dom_mut(lua,|dom|{
|
|
let inst=this.get(dom)?;
|
|
Ok(
|
|
match search_descendants.unwrap_or(false){
|
|
true=>find_first_descendant_which_is_a(dom,inst,class_str),
|
|
false=>find_first_child_which_is_a(dom,inst,class_str),
|
|
}
|
|
.map(|instance|
|
|
Instance::new_unchecked(instance.referent())
|
|
)
|
|
)
|
|
})
|
|
});
|
|
methods.add_method("GetDescendants",|lua,this,_:()|
|
|
dom_mut(lua,|dom|{
|
|
let children:Vec<_>=dom
|
|
.descendants_of(this.referent)
|
|
.map(|instance|
|
|
Instance::new_unchecked(instance.referent())
|
|
)
|
|
.collect();
|
|
Ok(children)
|
|
})
|
|
);
|
|
methods.add_method("IsAncestorOf",|lua,this,descendant:Instance|
|
|
dom_mut(lua,|dom|{
|
|
let instance=descendant.get(dom)?;
|
|
Ok(std::iter::successors(Some(instance),|inst|dom.get_by_ref(inst.parent())).any(|inst|inst.referent()==this.referent))
|
|
})
|
|
);
|
|
methods.add_method("IsDescendantOf",|lua,this,ancestor:Instance|
|
|
dom_mut(lua,|dom|{
|
|
let instance=this.get(dom)?;
|
|
Ok(std::iter::successors(Some(instance),|inst|dom.get_by_ref(inst.parent())).any(|inst|inst.referent()==ancestor.referent))
|
|
})
|
|
);
|
|
fn is_a(lua:&mlua::Lua,this:&Instance,classname:mlua::String)->mlua::Result<bool>{
|
|
dom_mut(lua,|dom|{
|
|
let instance=this.get(dom)?;
|
|
Ok(class_is_a(instance.class.as_str(),&*classname.to_str()?))
|
|
})
|
|
}
|
|
methods.add_method("isA",is_a);
|
|
methods.add_method("IsA",is_a);
|
|
methods.add_method("Destroy",|lua,this,()|
|
|
dom_mut(lua,|dom|{
|
|
dom.transfer_within(this.referent,Ref::none());
|
|
Ok(())
|
|
})
|
|
);
|
|
methods.add_meta_function(mlua::MetaMethod::ToString,|lua,this:Instance|{
|
|
dom_mut(lua,|dom|{
|
|
let instance=this.get(dom)?;
|
|
Ok(instance.name.clone())
|
|
})
|
|
});
|
|
methods.add_meta_function(mlua::MetaMethod::Index,|lua,(this,index):(Instance,mlua::String)|{
|
|
let index_str=&*index.to_str()?;
|
|
dom_mut(lua,|dom|{
|
|
let instance=this.get(dom)?;
|
|
//println!("__index t={} i={index:?}",instance.name);
|
|
let db=rbx_reflection_database::get();
|
|
let class=db.classes.get(instance.class.as_str()).ok_or_else(||mlua::Error::runtime("Class missing"))?;
|
|
// Find existing property
|
|
// Interestingly, ustr can know ahead of time if
|
|
// a property does not exist in any runtime instance
|
|
match Ustr::from_existing(index_str)
|
|
.and_then(|index_ustr|
|
|
instance.properties.get(&index_ustr).cloned()
|
|
)
|
|
//Find default value
|
|
.or_else(||db.find_default_property(class,index_str).cloned())
|
|
//Find virtual property
|
|
.or_else(||db.superclasses_iter(class).find_map(|class|
|
|
find_virtual_property(&instance.properties,class,index_str)
|
|
))
|
|
{
|
|
Some(rbx_types::Variant::Bool(val))=>return val.into_lua(lua),
|
|
Some(rbx_types::Variant::Int32(val))=>return val.into_lua(lua),
|
|
Some(rbx_types::Variant::Int64(val))=>return val.into_lua(lua),
|
|
Some(rbx_types::Variant::Float32(val))=>return val.into_lua(lua),
|
|
Some(rbx_types::Variant::Float64(val))=>return val.into_lua(lua),
|
|
Some(rbx_types::Variant::String(val))=>return val.into_lua(lua),
|
|
Some(rbx_types::Variant::Ref(val))=>return Instance::new_unchecked(val).into_lua(lua),
|
|
Some(rbx_types::Variant::Enum(e))=>return crate::runner::r#enum::EnumItem::from(e).into_lua(lua),
|
|
Some(rbx_types::Variant::Color3(c))=>return crate::runner::color3::Color3::from(c).into_lua(lua),
|
|
Some(rbx_types::Variant::CFrame(cf))=>return crate::runner::cframe::CFrame::from(cf).into_lua(lua),
|
|
Some(rbx_types::Variant::Vector2(v))=>return crate::runner::vector2::Vector2::from(v).into_lua(lua),
|
|
Some(rbx_types::Variant::Vector3(v))=>return crate::runner::vector3::Vector3::from(v).into_lua(lua),
|
|
None=>(),
|
|
other=>return Err(mlua::Error::runtime(format!("Instance.__index Unsupported property type instance={} index={index_str} value={other:?}",instance.name))),
|
|
}
|
|
//find a function with a matching name
|
|
if let Some(function)=class_methods_store_mut(lua,|cf|{
|
|
db.superclasses_iter(class).find_map(|class|{
|
|
let mut class_methods=cf.get_or_create_class_methods(&class.name)?;
|
|
class_methods.get_or_create_function(lua,index_str)
|
|
.transpose()
|
|
}).transpose()
|
|
})?{
|
|
return function.into_lua(lua);
|
|
}
|
|
|
|
//find or create an associated userdata object
|
|
let instance=this.get_mut(dom)?;
|
|
if let Some(value)=get_or_create_userdata(instance,lua,index_str)?{
|
|
return value.into_lua(lua);
|
|
}
|
|
// drop mutable borrow
|
|
//find a child with a matching name
|
|
let instance=this.get(dom)?;
|
|
find_first_child(dom,instance,index_str)
|
|
.map(|instance|Instance::new_unchecked(instance.referent()))
|
|
.into_lua(lua)
|
|
})
|
|
});
|
|
methods.add_meta_function(mlua::MetaMethod::NewIndex,|lua,(this,index,value):(Instance,mlua::String,mlua::Value)|{
|
|
let index_str=&*index.to_str()?;
|
|
dom_mut(lua,|dom|{
|
|
let instance=this.get_mut(dom)?;
|
|
let db=rbx_reflection_database::get();
|
|
let class=db.classes.get(instance.class.as_str()).ok_or_else(||mlua::Error::runtime("Class missing"))?;
|
|
let property=db.superclasses_iter(class).find_map(|cls|
|
|
cls.properties.get(index_str)
|
|
).ok_or_else(||
|
|
mlua::Error::runtime(format!("Property '{index_str}' missing on class '{}'",class.name))
|
|
)?;
|
|
let value=match &property.data_type{
|
|
rbx_reflection::DataType::Value(rbx_types::VariantType::Float32)=>{
|
|
let typed_value=Number::from_lua(value.clone(),lua)?.to_f32();
|
|
rbx_types::Variant::Float32(typed_value)
|
|
},
|
|
rbx_reflection::DataType::Enum(enum_name)=>{
|
|
let typed_value=match &value{
|
|
&mlua::Value::Integer(int)=>Ok(rbx_types::Enum::from_u32(int as u32)),
|
|
&mlua::Value::Number(num)=>Ok(rbx_types::Enum::from_u32(num as u32)),
|
|
mlua::Value::String(s)=>{
|
|
let e=db.enums.get(enum_name).ok_or_else(||mlua::Error::runtime("Database DataType Enum name does not exist"))?;
|
|
Ok(rbx_types::Enum::from_u32(*e.items.get(&*s.to_str()?).ok_or_else(||mlua::Error::runtime("Invalid enum item"))?))
|
|
},
|
|
mlua::Value::UserData(any_user_data)=>{
|
|
let e:crate::runner::r#enum::EnumItem=*any_user_data.borrow()?;
|
|
Ok(e.into())
|
|
},
|
|
_=>Err(mlua::Error::runtime("Expected Enum")),
|
|
}?;
|
|
rbx_types::Variant::Enum(typed_value)
|
|
},
|
|
rbx_reflection::DataType::Value(rbx_types::VariantType::Color3)=>{
|
|
let typed_value:crate::runner::color3::Color3=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected Color3"))?.borrow()?;
|
|
rbx_types::Variant::Color3(typed_value.into())
|
|
},
|
|
rbx_reflection::DataType::Value(rbx_types::VariantType::Bool)=>{
|
|
let typed_value=value.as_boolean().ok_or_else(||mlua::Error::runtime("Expected boolean"))?;
|
|
rbx_types::Variant::Bool(typed_value)
|
|
},
|
|
rbx_reflection::DataType::Value(rbx_types::VariantType::Int32)=>{
|
|
let typed_value=value.as_i32().ok_or_else(||mlua::Error::runtime("Expected Int32"))?;
|
|
rbx_types::Variant::Int32(typed_value)
|
|
},
|
|
rbx_reflection::DataType::Value(rbx_types::VariantType::String)=>{
|
|
let typed_value=value.as_str().ok_or_else(||mlua::Error::runtime("Expected string"))?;
|
|
rbx_types::Variant::String(typed_value.to_owned())
|
|
},
|
|
rbx_reflection::DataType::Value(rbx_types::VariantType::UDim2)=>{
|
|
let typed_value:&crate::runner::udim2::UDim2=&*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected UDim2"))?.borrow()?;
|
|
rbx_types::Variant::UDim2(typed_value.clone().into())
|
|
},
|
|
rbx_reflection::DataType::Value(rbx_types::VariantType::NumberRange)=>{
|
|
let typed_value:&crate::runner::number_range::NumberRange=&*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected NumberRange"))?.borrow()?;
|
|
rbx_types::Variant::NumberRange(typed_value.clone().into())
|
|
},
|
|
rbx_reflection::DataType::Value(rbx_types::VariantType::NumberSequence)=>{
|
|
let typed_value:&crate::runner::number_sequence::NumberSequence=&*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected NumberSequence"))?.borrow()?;
|
|
rbx_types::Variant::NumberSequence(typed_value.clone().into())
|
|
},
|
|
rbx_reflection::DataType::Value(rbx_types::VariantType::ColorSequence)=>{
|
|
let typed_value:&crate::runner::color_sequence::ColorSequence=&*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected ColorSequence"))?.borrow()?;
|
|
rbx_types::Variant::ColorSequence(typed_value.clone().into())
|
|
},
|
|
rbx_reflection::DataType::Value(rbx_types::VariantType::Vector2)=>{
|
|
let typed_value:crate::runner::vector2::Vector2=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected Vector2"))?.borrow()?;
|
|
rbx_types::Variant::Vector2(typed_value.clone().into())
|
|
},
|
|
rbx_reflection::DataType::Value(rbx_types::VariantType::Vector3)=>{
|
|
let typed_value:crate::runner::vector3::Vector3=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected Vector3"))?.borrow()?;
|
|
rbx_types::Variant::Vector3(typed_value.clone().into())
|
|
},
|
|
rbx_reflection::DataType::Value(rbx_types::VariantType::CFrame)=>{
|
|
let typed_value:crate::runner::cframe::CFrame=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected CFrame"))?.borrow()?;
|
|
rbx_types::Variant::CFrame(typed_value.clone().into())
|
|
},
|
|
rbx_reflection::DataType::Value(rbx_types::VariantType::ContentId)=>{
|
|
let typed_value=value.as_str().ok_or_else(||mlua::Error::runtime("Expected string"))?.to_owned();
|
|
rbx_types::Variant::ContentId(typed_value.into())
|
|
},
|
|
rbx_reflection::DataType::Value(rbx_types::VariantType::Ref)=>{
|
|
// why clone?
|
|
let typed_value=Option::<Instance>::from_lua(value.clone(),lua)?;
|
|
rbx_types::Variant::Ref(typed_value.map_or(Ref::none(),|instance|instance.referent))
|
|
},
|
|
other=>return Err(mlua::Error::runtime(format!("Unimplemented property type: {other:?}"))),
|
|
};
|
|
// the index is known to be a real property at this point
|
|
// allow creating a permanent ustr (memory leak)
|
|
let index_ustr=rbx_dom_weak::ustr(index_str);
|
|
instance.properties.insert(index_ustr,value);
|
|
Ok(())
|
|
})
|
|
});
|
|
}
|
|
}
|
|
|
|
/// A class function definition shorthand.
|
|
macro_rules! cf{
|
|
($f:expr)=>{
|
|
|lua,mut args|{
|
|
let this=Instance::from_lua(args.pop_front().unwrap_or(mlua::Value::Nil),lua)?;
|
|
$f(lua,this,FromLuaMulti::from_lua_multi(args,lua)?)?.into_lua_multi(lua)
|
|
}
|
|
};
|
|
}
|
|
type ClassFunctionPointer=fn(&mlua::Lua,mlua::MultiValue)->mlua::Result<mlua::MultiValue>;
|
|
// TODO: use macros to define these with better organization
|
|
/// A double hash map of function pointers.
|
|
/// The class tree is walked by the Instance.__index metamethod to find available class methods.
|
|
type CFD=phf::Map<&'static str,// Class name
|
|
phf::Map<&'static str,// Method name
|
|
ClassFunctionPointer
|
|
>
|
|
>;
|
|
const GET_SERVICE:ClassFunctionPointer=cf!(|lua,_this,service:mlua::String|{
|
|
dom_mut(lua,|dom|{
|
|
//dom.root_ref()==this.referent ?
|
|
let service=&*service.to_str()?;
|
|
match service{
|
|
"Lighting"|"RunService"|"Players"|"Workspace"|"MaterialService"|"TweenService"=>{
|
|
let referent=find_first_child_of_class(dom,dom.root(),service)
|
|
.map(|instance|instance.referent())
|
|
.unwrap_or_else(||
|
|
dom.insert(dom.root_ref(),InstanceBuilder::new(service))
|
|
);
|
|
Ok(Instance::new_unchecked(referent))
|
|
},
|
|
other=>Err(mlua::Error::runtime(format!("Service '{other}' not supported"))),
|
|
}
|
|
})
|
|
});
|
|
const GET_PLAYERS:ClassFunctionPointer=cf!(|_lua,_this,()|->mlua::Result<_>{
|
|
Ok(Vec::<Instance>::new())
|
|
});
|
|
const NO_OP:ClassFunctionPointer=cf!(|_lua,_this,_:mlua::MultiValue|->mlua::Result<_>{Ok(())});
|
|
static CLASS_FUNCTION_DATABASE:CFD=phf::phf_map!{
|
|
"DataModel"=>phf::phf_map!{
|
|
"service"=>GET_SERVICE,
|
|
"GetService"=>GET_SERVICE,
|
|
},
|
|
"Terrain"=>phf::phf_map!{
|
|
"FillBlock"=>cf!(|_lua,_,_:(crate::runner::cframe::CFrame,Vector3,crate::runner::r#enum::EnumItem)|mlua::Result::Ok(())),
|
|
"SetMaterialColor"=>cf!(|_lua,_,_:(crate::runner::r#enum::EnumItem,crate::runner::color3::Color3)|mlua::Result::Ok(())),
|
|
},
|
|
"Players"=>phf::phf_map!{
|
|
"players"=>GET_PLAYERS,
|
|
"GetPlayers"=>GET_PLAYERS,
|
|
},
|
|
"Sound"=>phf::phf_map!{
|
|
"Play"=>NO_OP,
|
|
},
|
|
"TweenService"=>phf::phf_map!{
|
|
"Create"=>cf!(|_lua,_,(instance,tween_info,goal):(Instance,crate::runner::tween_info::TweenInfo,mlua::Table)|->mlua::Result<_>{
|
|
Ok(crate::runner::tween::Tween::create(
|
|
instance,
|
|
tween_info,
|
|
goal,
|
|
))
|
|
}),
|
|
},
|
|
};
|
|
|
|
/// A store of created functions for each Roblox class.
|
|
/// Functions are created the first time they are accessed and stored in this data structure.
|
|
#[derive(Default)]
|
|
struct ClassMethodsStore{
|
|
classes:HashMap<&'static str,//ClassName
|
|
HashMap<&'static str,//Method name
|
|
mlua::Function
|
|
>
|
|
>
|
|
}
|
|
impl ClassMethodsStore{
|
|
/// return self.classes[class] or create the ClassMethods and then return it
|
|
fn get_or_create_class_methods(&mut self,class:&str)->Option<ClassMethods>{
|
|
// Use get_entry to get the &'static str keys of the database
|
|
// and use it as a key for the classes hashmap
|
|
CLASS_FUNCTION_DATABASE.get_entry(class)
|
|
.map(|(&static_class_str,method_pointers)|
|
|
ClassMethods{
|
|
method_pointers,
|
|
methods:self.classes.entry(static_class_str)
|
|
.or_insert_with(||HashMap::new()),
|
|
}
|
|
)
|
|
}
|
|
}
|
|
struct ClassMethods<'a>{
|
|
method_pointers:&'static phf::Map<&'static str,ClassFunctionPointer>,
|
|
methods:&'a mut HashMap<&'static str,mlua::Function>,
|
|
}
|
|
impl ClassMethods<'_>{
|
|
/// return self.methods[index] or create the function in the hashmap and then return it
|
|
fn get_or_create_function(&mut self,lua:&mlua::Lua,index:&str)->mlua::Result<Option<mlua::Function>>{
|
|
Ok(match self.method_pointers.get_entry(index){
|
|
Some((&static_index_str,&function_pointer))=>Some(
|
|
match self.methods.entry(static_index_str){
|
|
Entry::Occupied(entry)=>entry.get().clone(),
|
|
Entry::Vacant(entry)=>entry.insert(
|
|
lua.create_function(function_pointer)?
|
|
).clone(),
|
|
}
|
|
),
|
|
None=>None,
|
|
})
|
|
}
|
|
}
|
|
fn class_methods_store_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut ClassMethodsStore)->mlua::Result<T>)->mlua::Result<T>{
|
|
let mut cf=lua.app_data_mut::<ClassMethodsStore>().ok_or_else(||mlua::Error::runtime("ClassMethodsStore missing"))?;
|
|
f(&mut *cf)
|
|
}
|
|
|
|
/// A virtual property pointer definition shorthand.
|
|
type VirtualPropertyFunctionPointer=fn(&rbx_types::Variant)->Option<rbx_types::Variant>;
|
|
const fn vpp(
|
|
property:&'static str,
|
|
pointer:VirtualPropertyFunctionPointer,
|
|
)->VirtualProperty{
|
|
VirtualProperty{
|
|
property,
|
|
pointer,
|
|
}
|
|
}
|
|
struct VirtualProperty{
|
|
property:&'static str,// Source property name
|
|
pointer:VirtualPropertyFunctionPointer,
|
|
}
|
|
type VPD=phf::Map<&'static str,// Class name
|
|
phf::Map<&'static str,// Virtual property name
|
|
VirtualProperty
|
|
>
|
|
>;
|
|
static VIRTUAL_PROPERTY_DATABASE:VPD=phf::phf_map!{
|
|
"BasePart"=>phf::phf_map!{
|
|
"Position"=>vpp("CFrame",|c:&rbx_types::Variant|{
|
|
let c=match c{
|
|
rbx_types::Variant::CFrame(c)=>c,
|
|
_=>return None,//fail silently and ungracefully
|
|
};
|
|
Some(rbx_types::Variant::Vector3(c.position))
|
|
}),
|
|
},
|
|
};
|
|
|
|
fn find_virtual_property(
|
|
properties:&rbx_dom_weak::UstrMap<rbx_types::Variant>,
|
|
class:&rbx_reflection::ClassDescriptor,
|
|
index:&str,
|
|
)->Option<rbx_types::Variant>{
|
|
//Find virtual property
|
|
let class_virtual_properties=VIRTUAL_PROPERTY_DATABASE.get(&class.name)?;
|
|
let virtual_property=class_virtual_properties.get(index)?;
|
|
|
|
//Get source property
|
|
let variant=properties.get(&static_ustr(virtual_property.property))?;
|
|
|
|
//Transform Source property with provided function
|
|
(virtual_property.pointer)(variant)
|
|
}
|
|
|
|
// lazy-loaded per-instance userdata values
|
|
type CreateUserData=fn(&mlua::Lua)->mlua::Result<mlua::AnyUserData>;
|
|
type LUD=phf::Map<&'static str,// Class name
|
|
phf::Map<&'static str,// Value name
|
|
CreateUserData
|
|
>
|
|
>;
|
|
fn create_script_signal(lua:&mlua::Lua)->mlua::Result<mlua::AnyUserData>{
|
|
lua.create_any_userdata(crate::runner::script_signal::ScriptSignal::new())
|
|
}
|
|
static LAZY_USER_DATA:LUD=phf::phf_map!{
|
|
"RunService"=>phf::phf_map!{
|
|
"Stepped"=>create_script_signal,
|
|
"Heartbeat"=>create_script_signal,
|
|
"RenderStepped"=>create_script_signal,
|
|
},
|
|
"Players"=>phf::phf_map!{
|
|
"PlayerAdded"=>create_script_signal,
|
|
},
|
|
"BasePart"=>phf::phf_map!{
|
|
"Touched"=>create_script_signal,
|
|
"TouchEnded"=>create_script_signal,
|
|
},
|
|
"Instance"=>phf::phf_map!{
|
|
"ChildAdded"=>create_script_signal,
|
|
"ChildRemoved"=>create_script_signal,
|
|
"DescendantAdded"=>create_script_signal,
|
|
"DescendantRemoved"=>create_script_signal,
|
|
},
|
|
};
|
|
fn get_or_create_userdata(instance:&mut rbx_dom_weak::Instance,lua:&mlua::Lua,index:&str)->mlua::Result<Option<mlua::AnyUserData>>{
|
|
use std::collections::hash_map::Entry;
|
|
let db=rbx_reflection_database::get();
|
|
let Some(class)=db.classes.get(instance.class.as_str())else{
|
|
return Ok(None)
|
|
};
|
|
if let Some((&static_str,create_userdata))=db.superclasses_iter(class).find_map(|superclass|
|
|
// find pair (class,index)
|
|
LAZY_USER_DATA.get(&superclass.name)
|
|
.and_then(|map|map.get_entry(index))
|
|
){
|
|
let index_ustr=static_ustr(static_str);
|
|
return Ok(Some(match instance.userdata.entry(index_ustr){
|
|
Entry::Occupied(entry)=>entry.get().clone(),
|
|
Entry::Vacant(entry)=>entry.insert(create_userdata(lua)?).clone(),
|
|
}));
|
|
}
|
|
Ok(None)
|
|
}
|