From bb8e131464d85ffe798af41aafa79a36fc006680 Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Thu, 17 Apr 2025 18:05:33 -0700
Subject: [PATCH] roblox_emulator: use extended instances

---
 .../src/runner/instance/instance.rs           | 73 ++++++-------------
 lib/roblox_emulator/src/runner/runner.rs      | 17 ++---
 2 files changed, 27 insertions(+), 63 deletions(-)

diff --git a/lib/roblox_emulator/src/runner/instance/instance.rs b/lib/roblox_emulator/src/runner/instance/instance.rs
index 5e4d836..2bb131c 100644
--- a/lib/roblox_emulator/src/runner/instance/instance.rs
+++ b/lib/roblox_emulator/src/runner/instance/instance.rs
@@ -15,7 +15,6 @@ fn static_ustr(s:&'static str)->rbx_dom_weak::Ustr{
 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 table=lua.create_table()?;
 
@@ -329,16 +328,13 @@ 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)=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)
@@ -585,8 +581,6 @@ fn find_virtual_property(
 }
 
 // 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
@@ -616,47 +610,22 @@ static LAZY_USER_DATA:LUD=phf::phf_map!{
 		"DescendantRemoved"=>create_script_signal,
 	},
 };
-#[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()),
-				}
-			)
+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(),
+		}));
 	}
-}
-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)
+	Ok(None)
 }
diff --git a/lib/roblox_emulator/src/runner/runner.rs b/lib/roblox_emulator/src/runner/runner.rs
index 817bc6d..8220fe6 100644
--- a/lib/roblox_emulator/src/runner/runner.rs
+++ b/lib/roblox_emulator/src/runner/runner.rs
@@ -123,20 +123,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(())
 	}