roblox_emulator: use extended instances

This commit is contained in:
Quaternions 2025-04-17 15:42:44 -07:00
parent bef16f65ff
commit 0a3825ca2d
Signed by: Quaternions
GPG Key ID: D0DF5964F79AC131
7 changed files with 140 additions and 146 deletions

@ -1,4 +1,5 @@
use rbx_dom_weak::{types::Ref,ustr,InstanceBuilder,WeakDom};
use crate::types::WeakDom;
use rbx_dom_weak::{types::Ref,ustr,InstanceBuilder};
pub fn class_is_a(class:&str,superclass:&str)->bool{
class==superclass
@ -19,7 +20,7 @@ impl Context{
pub const fn new(dom:WeakDom)->Self{
Self{dom}
}
pub fn script_singleton(source:String)->(Context,crate::runner::instance::Instance,Services){
pub fn script_singleton(source:String)->(Context,crate::runner::instance::InstanceRef,Services){
let script=InstanceBuilder::new("Script")
.with_property("Source",rbx_types::Variant::String(source));
let script_ref=script.referent();
@ -28,7 +29,7 @@ impl Context{
.with_child(script)
));
let services=context.convert_into_place();
(context,crate::runner::instance::Instance::new(script_ref),services)
(context,crate::runner::instance::InstanceRef::new(script_ref),services)
}
pub fn from_ref(dom:&WeakDom)->&Context{
unsafe{&*(dom as *const WeakDom as *const Context)}
@ -39,24 +40,24 @@ impl Context{
/// Creates an iterator over all items of a particular class.
pub fn superclass_iter<'a>(&'a self,superclass:&'a str)->impl Iterator<Item=Ref>+'a{
self.dom.descendants().filter(|&instance|
class_is_a(instance.class.as_ref(),superclass)
).map(|instance|instance.referent())
class_is_a(instance.as_ref().class.as_ref(),superclass)
).map(|instance|instance.as_ref().referent())
}
pub fn scripts(&self)->Vec<crate::runner::instance::Instance>{
self.superclass_iter("LuaSourceContainer").map(crate::runner::instance::Instance::new).collect()
pub fn scripts(&self)->Vec<crate::runner::instance::InstanceRef>{
self.superclass_iter("LuaSourceContainer").map(crate::runner::instance::InstanceRef::new).collect()
}
pub fn find_services(&self)->Option<Services>{
Some(Services{
workspace:*self.dom.root().children().iter().find(|&&r|
self.dom.get_by_ref(r).is_some_and(|instance|instance.class=="Workspace")
workspace:*self.dom.root().as_ref().children().iter().find(|&&r|
self.dom.get_by_ref(r).is_some_and(|instance|instance.as_ref().class=="Workspace")
)?,
game:self.dom.root_ref(),
})
}
pub fn convert_into_place(&mut self)->Services{
//snapshot root instances
let children=self.dom.root().children().to_owned();
let children=self.dom.root().as_ref().children().to_owned();
//insert services
let game=self.dom.root_ref();
@ -69,7 +70,7 @@ impl Context{
);
{
//Lowercase and upper case workspace property!
let game=self.dom.root_mut();
let game=self.dom.root_mut().as_mut();
game.properties.insert(ustr("workspace"),rbx_types::Variant::Ref(workspace));
game.properties.insert(ustr("Workspace"),rbx_types::Variant::Ref(workspace));
}

@ -1,3 +1,4 @@
pub mod types;
pub mod runner;
pub mod context;
#[cfg(feature="run-service")]

@ -2,25 +2,25 @@ use std::collections::{hash_map::Entry,HashMap};
use mlua::{FromLua,FromLuaMulti,IntoLua,IntoLuaMulti};
use rbx_types::Ref;
use rbx_dom_weak::{ustr,Ustr,InstanceBuilder,WeakDom};
use rbx_dom_weak::{ustr,Ustr,InstanceBuilder};
use crate::runner::vector3::Vector3;
use crate::types::{WeakDom,Instance};
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
//class functions store
lua.set_app_data(ClassMethodsStore::default());
lua.set_app_data(InstanceValueStore::default());
let instance_table=lua.create_table()?;
//Instance.new
instance_table.raw_set("new",
lua.create_function(|lua,(class_name,parent):(mlua::String,Option<Instance>)|{
lua.create_function(|lua,(class_name,parent):(mlua::String,Option<InstanceRef>)|{
let class_name_str=&*class_name.to_str()?;
let parent=parent.ok_or_else(||mlua::Error::runtime("Nil Parent not yet supported"))?;
dom_mut(lua,|dom|{
//TODO: Nil instances
Ok(Instance::new(dom.insert(parent.referent,InstanceBuilder::new(class_name_str))))
Ok(InstanceRef::new(dom.insert(parent.referent,InstanceBuilder::new(class_name_str))))
})
})?
)?;
@ -44,10 +44,12 @@ fn coerce_float32(value:&mlua::Value)->Option<f32>{
}
}
fn get_full_name(dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance)->String{
fn get_full_name(dom:&WeakDom,instance:&Instance)->String{
let instance=instance.as_ref();
let mut full_name=instance.name.clone();
let mut pref=instance.parent();
while let Some(parent)=dom.get_by_ref(pref){
let parent=parent.as_ref();
full_name.insert(0,'.');
full_name.insert_str(0,parent.name.as_str());
pref=parent.parent();
@ -55,10 +57,10 @@ fn get_full_name(dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance)->S
full_name
}
//helper function for script
pub fn get_name_source(lua:&mlua::Lua,script:Instance)->Result<(String,String),mlua::Error>{
pub fn get_name_source(lua:&mlua::Lua,script:InstanceRef)->Result<(String,String),mlua::Error>{
dom_mut(lua,|dom|{
let instance=script.get(dom)?;
let source=match instance.properties.get(&ustr("Source")){
let source=match instance.as_ref().properties.get(&ustr("Source")){
Some(rbx_dom_weak::types::Variant::String(s))=>s.clone(),
_=>Err(mlua::Error::external("Missing script.Source"))?,
};
@ -66,46 +68,46 @@ pub fn get_name_source(lua:&mlua::Lua,script:Instance)->Result<(String,String),m
})
}
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_child<'a>(dom:&'a WeakDom,instance:&Instance,name:&str)->Option<&'a Instance>{
instance.as_ref().children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|inst.as_ref().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_descendant<'a>(dom:&'a WeakDom,instance:&Instance,name:&str)->Option<&'a Instance>{
dom.descendants_of(instance.as_ref().referent()).find(|&inst|inst.as_ref().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_child_of_class<'a>(dom:&'a WeakDom,instance:&Instance,class:&str)->Option<&'a Instance>{
instance.as_ref().children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|inst.as_ref().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_descendant_of_class<'a>(dom:&'a WeakDom,instance:&Instance,class:&str)->Option<&'a Instance>{
dom.descendants_of(instance.as_ref().referent()).find(|&inst|inst.as_ref().class==class)
}
#[derive(Clone,Copy)]
pub struct Instance{
pub struct InstanceRef{
referent:Ref,
}
impl Instance{
impl InstanceRef{
pub const fn new(referent:Ref)->Self{
Self{referent}
}
pub fn get<'a>(&self,dom:&'a WeakDom)->mlua::Result<&'a rbx_dom_weak::Instance>{
pub fn get<'a>(&self,dom:&'a WeakDom)->mlua::Result<&'a 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>{
pub fn get_mut<'a>(&self,dom:&'a mut WeakDom)->mlua::Result<&'a mut Instance>{
dom.get_by_ref_mut(self.referent).ok_or_else(||mlua::Error::runtime("Instance missing"))
}
}
type_from_lua_userdata!(Instance);
type_from_lua_userdata!(InstanceRef);
impl mlua::UserData for Instance{
impl mlua::UserData for InstanceRef{
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
fields.add_field_method_get("Parent",|lua,this|{
dom_mut(lua,|dom|{
let instance=this.get(dom)?;
Ok(Instance::new(instance.parent()))
let instance=this.get(dom)?.as_ref();
Ok(InstanceRef::new(instance.parent()))
})
});
fields.add_field_method_set("Parent",|lua,this,val:Option<Instance>|{
fields.add_field_method_set("Parent",|lua,this,val:Option<InstanceRef>|{
let parent=val.ok_or_else(||mlua::Error::runtime("Nil Parent not yet supported"))?;
dom_mut(lua,|dom|{
dom.transfer_within(this.referent,parent.referent);
@ -114,13 +116,13 @@ impl mlua::UserData for Instance{
});
fields.add_field_method_get("Name",|lua,this|{
dom_mut(lua,|dom|{
let instance=this.get(dom)?;
let instance=this.get(dom)?.as_ref();
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)?;
let instance=this.get_mut(dom)?.as_mut();
//Why does this need to be cloned?
instance.name=val.to_str()?.to_owned();
Ok(())
@ -128,7 +130,7 @@ impl mlua::UserData for Instance{
});
fields.add_field_method_get("ClassName",|lua,this|{
dom_mut(lua,|dom|{
let instance=this.get(dom)?;
let instance=this.get(dom)?.as_ref();
Ok(instance.class.to_owned())
})
});
@ -136,17 +138,17 @@ impl mlua::UserData for Instance{
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
methods.add_method("GetChildren",|lua,this,_:()|
dom_mut(lua,|dom|{
let instance=this.get(dom)?;
let instance=this.get(dom)?.as_ref();
let children:Vec<_>=instance
.children()
.iter()
.copied()
.map(Instance::new)
.map(InstanceRef::new)
.collect();
Ok(children)
})
);
fn ffc(lua:&mlua::Lua,this:&Instance,(name,search_descendants):(mlua::String,Option<bool>))->mlua::Result<Option<Instance>>{
fn ffc(lua:&mlua::Lua,this:&InstanceRef,(name,search_descendants):(mlua::String,Option<bool>))->mlua::Result<Option<InstanceRef>>{
let name_str=&*name.to_str()?;
dom_mut(lua,|dom|{
let instance=this.get(dom)?;
@ -156,7 +158,7 @@ impl mlua::UserData for Instance{
false=>find_first_child(dom,instance,name_str),
}
.map(|instance|
Instance::new(instance.referent())
InstanceRef::new(instance.as_ref().referent())
)
)
})
@ -172,7 +174,7 @@ impl mlua::UserData for Instance{
false=>find_first_child_of_class(dom,this.get(dom)?,class_str),
}
.map(|instance|
Instance::new(instance.referent())
InstanceRef::new(instance.as_ref().referent())
)
)
})
@ -182,7 +184,7 @@ impl mlua::UserData for Instance{
let children:Vec<_>=dom
.descendants_of(this.referent)
.map(|instance|
Instance::new(instance.referent())
InstanceRef::new(instance.as_ref().referent())
)
.collect();
Ok(children)
@ -190,7 +192,7 @@ impl mlua::UserData for Instance{
);
methods.add_method("IsA",|lua,this,classname:mlua::String|
dom_mut(lua,|dom|{
let instance=this.get(dom)?;
let instance=this.get(dom)?.as_ref();
Ok(crate::context::class_is_a(instance.class.as_str(),&*classname.to_str()?))
})
);
@ -200,39 +202,40 @@ impl mlua::UserData for Instance{
Ok(())
})
);
methods.add_meta_function(mlua::MetaMethod::ToString,|lua,this:Instance|{
methods.add_meta_function(mlua::MetaMethod::ToString,|lua,this:InstanceRef|{
dom_mut(lua,|dom|{
let instance=this.get(dom)?;
let instance=this.get(dom)?.as_ref();
Ok(instance.name.clone())
})
});
methods.add_meta_function(mlua::MetaMethod::Index,|lua,(this,index):(Instance,mlua::String)|{
methods.add_meta_function(mlua::MetaMethod::Index,|lua,(this,index):(InstanceRef,mlua::String)|{
let index_str=&*index.to_str()?;
let index_ustr=ustr(index_str);
dom_mut(lua,|dom|{
let instance=this.get(dom)?;
let instance_ref=instance.as_ref();
//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"))?;
let class=db.classes.get(instance_ref.class.as_str()).ok_or_else(||mlua::Error::runtime("Class missing"))?;
//Find existing property
match instance.properties.get(&index_ustr)
match instance_ref.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_ustr)
find_virtual_property(&instance_ref.properties,class,&index_ustr)
))
{
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::Ref(val))=>return Instance::new(val).into_lua(lua),
Some(rbx_types::Variant::Ref(val))=>return InstanceRef::new(val).into_lua(lua),
Some(rbx_types::Variant::CFrame(cf))=>return Into::<crate::runner::cframe::CFrame>::into(cf).into_lua(lua),
Some(rbx_types::Variant::Vector3(v))=>return Into::<crate::runner::vector3::Vector3>::into(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))),
other=>return Err(mlua::Error::runtime(format!("Instance.__index Unsupported property type instance={} index={index_str} value={other:?}",instance_ref.name))),
}
//find a function with a matching name
if let Some(function)=class_methods_store_mut(lua,|cf|{
@ -246,24 +249,21 @@ impl mlua::UserData for Instance{
}
//find or create an associated userdata object
if let Some(value)=instance_value_store_mut(lua,|ivs|{
//TODO: walk class tree somehow
match ivs.get_or_create_instance_values(&instance){
Some(mut instance_values)=>instance_values.get_or_create_value(lua,index_str),
None=>Ok(None)
}
})?{
let instance=this.get_mut(dom)?;
if let Some(value)=instance.get_or_create_userdata(lua,index_ustr)?{
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(instance.referent()))
.map(|instance|InstanceRef::new(instance.as_ref().referent()))
.into_lua(lua)
})
});
methods.add_meta_function(mlua::MetaMethod::NewIndex,|lua,(this,index,value):(Instance,mlua::String,mlua::Value)|{
methods.add_meta_function(mlua::MetaMethod::NewIndex,|lua,(this,index,value):(InstanceRef,mlua::String,mlua::Value)|{
dom_mut(lua,|dom|{
let instance=this.get_mut(dom)?;
let instance=this.get_mut(dom)?.as_mut();
//println!("__newindex t={} i={index:?} v={value:?}",instance.name);
let index_str=&*index.to_str()?;
let index_ustr=ustr(index_str);
@ -331,7 +331,7 @@ impl mlua::UserData for Instance{
macro_rules! cf{
($f:expr)=>{
|lua,mut args|{
let this=Instance::from_lua(args.pop_front().unwrap_or(mlua::Value::Nil),lua)?;
let this=InstanceRef::from_lua(args.pop_front().unwrap_or(mlua::Value::Nil),lua)?;
$f(lua,this,FromLuaMulti::from_lua_multi(args,lua)?)?.into_lua_multi(lua)
}
};
@ -354,13 +354,13 @@ static CLASS_FUNCTION_DATABASE:CFD=phf::phf_map!{
match service{
"Lighting"|"RunService"=>{
let referent=find_first_child_of_class(dom,dom.root(),service)
.map(|instance|instance.referent())
.map(|instance|instance.as_ref().referent())
.unwrap_or_else(||
dom.insert(dom.root_ref(),InstanceBuilder::new(service))
);
Ok(Instance::new(referent))
Ok(InstanceRef::new(referent))
},
other=>Err::<Instance,_>(mlua::Error::runtime(format!("Service '{other}' not supported"))),
other=>Err::<InstanceRef,_>(mlua::Error::runtime(format!("Service '{other}' not supported"))),
}
})
}),
@ -467,64 +467,3 @@ fn find_virtual_property(
//Transform Source property with provided function
(virtual_property.pointer)(variant)
}
// lazy-loaded per-instance userdata values
// This whole thing is a bad idea and a garbage collection nightmare.
// TODO: recreate rbx_dom_weak with my own instance type that owns this data.
type CreateUserData=fn(&mlua::Lua)->mlua::Result<mlua::AnyUserData>;
type LUD=phf::Map<&'static str,// Class name
phf::Map<&'static str,// Value name
CreateUserData
>
>;
static LAZY_USER_DATA:LUD=phf::phf_map!{
"RunService"=>phf::phf_map!{
"RenderStepped"=>|lua|{
lua.create_any_userdata(crate::runner::script_signal::ScriptSignal::new())
},
},
};
#[derive(Default)]
pub struct InstanceValueStore{
values:HashMap<Ref,
HashMap<&'static str,
mlua::AnyUserData
>
>,
}
pub struct InstanceValues<'a>{
named_values:&'static phf::Map<&'static str,CreateUserData>,
values:&'a mut HashMap<&'static str,mlua::AnyUserData>,
}
impl InstanceValueStore{
pub fn get_or_create_instance_values(&mut self,instance:&rbx_dom_weak::Instance)->Option<InstanceValues>{
LAZY_USER_DATA.get(instance.class.as_str())
.map(|named_values|
InstanceValues{
named_values,
values:self.values.entry(instance.referent())
.or_insert_with(||HashMap::new()),
}
)
}
}
impl InstanceValues<'_>{
pub fn get_or_create_value(&mut self,lua:&mlua::Lua,index:&str)->mlua::Result<Option<mlua::AnyUserData>>{
Ok(match self.named_values.get_entry(index){
Some((&static_index_str,&function_pointer))=>Some(
match self.values.entry(static_index_str){
Entry::Occupied(entry)=>entry.get().clone(),
Entry::Vacant(entry)=>entry.insert(
function_pointer(lua)?
).clone(),
}
),
None=>None,
})
}
}
pub fn instance_value_store_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut InstanceValueStore)->mlua::Result<T>)->mlua::Result<T>{
let mut cf=lua.app_data_mut::<InstanceValueStore>().ok_or_else(||mlua::Error::runtime("InstanceValueStore missing"))?;
f(&mut *cf)
}

@ -1,2 +1,2 @@
pub mod instance;
pub use instance::Instance;
pub use instance::InstanceRef;

@ -7,7 +7,7 @@ mod color3;
mod cframe;
mod vector3;
pub mod instance;
mod script_signal;
pub mod script_signal;
mod color_sequence;
mod number_sequence;

@ -1,6 +1,7 @@
use crate::context::Context;
#[cfg(feature="run-service")]
use crate::scheduler::scheduler_mut;
use crate::types::WeakDom;
pub struct Runner{
lua:mlua::Lua,
@ -60,11 +61,11 @@ impl Runner{
pub fn runnable_context_with_services<'a>(self,context:&'a mut Context,services:&crate::context::Services)->Result<Runnable<'a>,Error>{
{
let globals=self.lua.globals();
globals.set("game",super::instance::Instance::new(services.game)).map_err(Error::RustLua)?;
globals.set("workspace",super::instance::Instance::new(services.workspace)).map_err(Error::RustLua)?;
globals.set("game",super::instance::InstanceRef::new(services.game)).map_err(Error::RustLua)?;
globals.set("workspace",super::instance::InstanceRef::new(services.workspace)).map_err(Error::RustLua)?;
}
//this makes set_app_data shut up about the lifetime
self.lua.set_app_data::<&'static mut rbx_dom_weak::WeakDom>(unsafe{core::mem::transmute(&mut context.dom)});
self.lua.set_app_data::<&'static mut WeakDom>(unsafe{core::mem::transmute(&mut context.dom)});
#[cfg(feature="run-service")]
self.lua.set_app_data::<crate::scheduler::Scheduler>(crate::scheduler::Scheduler::default());
Ok(Runnable{
@ -81,14 +82,14 @@ pub struct Runnable<'a>{
}
impl Runnable<'_>{
pub fn drop_context(self)->Runner{
self.lua.remove_app_data::<&'static mut rbx_dom_weak::WeakDom>();
self.lua.remove_app_data::<&'static mut WeakDom>();
#[cfg(feature="run-service")]
self.lua.remove_app_data::<crate::scheduler::Scheduler>();
Runner{
lua:self.lua,
}
}
pub fn run_script(&self,script:super::instance::Instance)->Result<(),Error>{
pub fn run_script(&self,script:super::instance::InstanceRef)->Result<(),Error>{
let (name,source)=super::instance::instance::get_name_source(&self.lua,script).map_err(Error::RustLua)?;
self.lua.globals().raw_set("script",script).map_err(Error::RustLua)?;
let f=self.lua.load(source.as_str())
@ -123,20 +124,15 @@ impl Runnable<'_>{
}
#[cfg(feature="run-service")]
pub fn run_service_step(&self)->Result<(),mlua::Error>{
let render_stepped=super::instance::instance::dom_mut(&self.lua,|dom|{
let render_stepped_signal=super::instance::instance::dom_mut(&self.lua,|dom|{
let run_service=super::instance::instance::find_first_child_of_class(dom,dom.root(),"RunService").ok_or_else(||mlua::Error::runtime("RunService missing"))?;
super::instance::instance::instance_value_store_mut(&self.lua,|instance_value_store|{
//unwrap because I trust my find_first_child_of_class function to
let mut instance_values=instance_value_store.get_or_create_instance_values(run_service).ok_or_else(||mlua::Error::runtime("RunService InstanceValues missing"))?;
let render_stepped=instance_values.get_or_create_value(&self.lua,"RenderStepped")?;
//let stepped=instance_values.get_or_create_value(&self.lua,"Stepped")?;
//let heartbeat=instance_values.get_or_create_value(&self.lua,"Heartbeat")?;
Ok(render_stepped)
Ok(match run_service.userdata.get(&rbx_dom_weak::ustr("RenderStepped")){
Some(render_stepped)=>Some(render_stepped.borrow::<super::script_signal::ScriptSignal>()?.clone()),
None=>None
})
})?;
if let Some(render_stepped)=render_stepped{
let signal:&super::script_signal::ScriptSignal=&*render_stepped.borrow()?;
signal.fire(&mlua::MultiValue::new());
if let Some(render_stepped_signal)=render_stepped_signal{
render_stepped_signal.fire(&mlua::MultiValue::new());
}
Ok(())
}

@ -0,0 +1,57 @@
pub struct Instance{
pub(crate) instance:rbx_dom_weak::Instance,
pub(crate) userdata:rbx_dom_weak::UstrMap<mlua::AnyUserData>,
}
impl AsRef<rbx_dom_weak::Instance> for Instance{
fn as_ref(&self)->&rbx_dom_weak::Instance{
&self.instance
}
}
impl AsMut<rbx_dom_weak::Instance> for Instance{
fn as_mut(&mut self)->&mut rbx_dom_weak::Instance{
&mut self.instance
}
}
impl From<Instance> for rbx_dom_weak::Instance{
fn from(instance:Instance)->Self{
instance.instance
}
}
impl From<rbx_dom_weak::Instance> for Instance{
fn from(instance:rbx_dom_weak::Instance)->Self{
Self{
instance,
userdata:Default::default(),
}
}
}
pub type WeakDom=rbx_dom_weak::GenericWeakDom<Instance>;
// 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
>
>;
static LAZY_USER_DATA:LUD=phf::phf_map!{
"RunService"=>phf::phf_map!{
"RenderStepped"=>|lua|lua.create_any_userdata(crate::runner::script_signal::ScriptSignal::new()),
},
};
impl Instance{
pub fn get_or_create_userdata(&mut self,lua:&mlua::Lua,index:rbx_dom_weak::Ustr)->mlua::Result<Option<mlua::AnyUserData>>{
use std::collections::hash_map::Entry;
Ok(match LAZY_USER_DATA.get(self.instance.class.as_str()){
Some(userdata_map)=>match self.userdata.entry(index){
Entry::Occupied(entry)=>Some(entry.get().clone()),
Entry::Vacant(entry)=>match userdata_map.get(index.as_str()){
Some(create_userdata)=>Some(entry.insert(create_userdata(lua)?).clone()),
None=>None,
},
},
None=>None,
})
}
}