use mlua::IntoLua; use rbx_types::Ref; use rbx_dom_weak::{InstanceBuilder,WeakDom}; use super::vector3::Vector3; // LMAO look at this function! fn dom(lua:&mlua::Lua,mut f:impl FnMut(&mut WeakDom)->mlua::Result)->mlua::Result{ let mut dom=lua.app_data_mut::<&'static mut WeakDom>().ok_or(mlua::Error::runtime("DataModel missing"))?; f(&mut *dom) } fn coerce_float32(value:&mlua::Value)->Option{ match value{ &mlua::Value::Integer(i)=>Some(i as f32), &mlua::Value::Number(f)=>Some(f as f32), _=>None, } } 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 } //workaround until I have an enum of classes struct Dereferent(Ref); impl mlua::UserData for Dereferent{} type_from_lua_userdata!(Dereferent); trait Referent{ fn referent(&self)->Ref; fn get<'a>(&self,dom:&'a WeakDom)->mlua::Result<&'a rbx_dom_weak::Instance>{ dom.get_by_ref(self.referent()).ok_or(mlua::Error::runtime("Instance missing")) } 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(mlua::Error::runtime("Instance missing")) } } macro_rules! class{ ($class:ident)=>{ pub struct $class{ referent:Ref, } impl $class{ pub const fn new(referent:Ref)->Self{ Self{referent} } } impl Referent for $class{ fn referent(&self)->Ref{ self.referent } } type_from_lua_userdata!($class); }; } macro_rules! class_composition{ ($class:ident,($($superclass:ident),*))=>{ impl mlua::UserData for $class{ fn add_fields<'lua,F:mlua::UserDataFields<'lua,Self>>(fields:&mut F){ fields.add_field_method_get("Referent",|_,this|{ Ok(Dereferent(this.referent())) }); $( $superclass::composition_add_fields(fields); )* } fn add_methods<'lua,M:mlua::UserDataMethods<'lua,Self>>(methods:&mut M){ $( $superclass::composition_add_methods(methods); )* } } }; } class!(Instance); class_composition!(Instance,(Instance)); impl Instance{ fn composition_add_fields<'lua,T:Referent,F:mlua::UserDataFields<'lua,T>>(fields:&mut F){ fields.add_field_method_get("Parent",|lua,this|{ dom(lua,|dom|{ let instance=this.get(dom)?; Ok(Instance::new(instance.parent())) }) }); fields.add_field_method_set("Parent",|lua,this,val:mlua::AnyUserData|{ let Dereferent(referent)=mlua::AnyUserDataExt::get(&val,"Referent")?; dom(lua,|dom|{ dom.transfer_within(this.referent(),referent); Ok(()) }) }); fields.add_field_method_get("Name",|lua,this|{ dom(lua,|dom|{ let instance=this.get(dom)?; Ok(instance.name.clone()) }) }); fields.add_field_method_set("Name",|lua,this,val:mlua::String|{ dom(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(lua,|dom|{ let instance=this.get(dom)?; Ok(instance.class.clone()) }) }); } fn composition_add_methods<'lua,T:Referent,M:mlua::UserDataMethods<'lua,T>>(methods:&mut M){ methods.add_method("GetChildren",|lua,this,_:()| dom(lua,|dom|{ let instance=this.get(dom)?; let children:Vec<_>=instance .children() .iter() .copied() .map(Instance::new) .collect(); Ok(children) }) ); methods.add_method("GetDescendants",|lua,this,_:()| dom(lua,|dom|{ let children:Vec<_>=dom .descendants_of(this.referent()) .map(|instance| Instance::new(instance.referent()) ) .collect(); Ok(children) }) ); methods.add_method("IsA",|lua,this,classname:mlua::String| dom(lua,|dom|{ let instance=this.get(dom)?; Ok(crate::context::class_is_a(instance.class.as_str(),classname.to_str()?)) }) ); methods.add_meta_function(mlua::MetaMethod::Index,|lua,(this,index):(Self,mlua::String)|{ let index_str=index.to_str()?; dom(lua,|dom|{ let instance=this.get(dom)?; //find a child with a matching name let maybe_child=instance.children() .iter() .find(|&&r| dom.get_by_ref(r) .is_some_and(|instance|instance.name==index_str) ); match maybe_child{ Some(&referent)=>Instance::new(referent).into_lua(lua), None=>mlua::Value::Nil.into_lua(lua), } }) }); methods.add_meta_function(mlua::MetaMethod::NewIndex,|lua,(this,index,value):(Self,mlua::String,mlua::Value)| dom(lua,|dom|{ //println!("__newindex t={this:?} i={index:?} v={value:?}"); let instance=this.get_mut(dom)?; let index_str=index.to_str()?; let db=rbx_reflection_database::get(); let class=db.classes.get(instance.class.as_str()).ok_or(mlua::Error::runtime("Class missing"))?; let property=db.find_default_property(class,index_str).ok_or(mlua::Error::runtime(format!("Property '{index_str}' missing on class '{}'",class.name)))?; match property{ rbx_types::Variant::Vector3(_)=>{ let typed_value:Vector3=value.as_userdata().ok_or(mlua::Error::runtime("Expected Userdata"))?.take()?; instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Vector3(typed_value.into())); }, rbx_types::Variant::Float32(_)=>{ let typed_value:f32=coerce_float32(&value).ok_or(mlua::Error::runtime("Expected f32"))?; instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Float32(typed_value)); }, rbx_types::Variant::Enum(_)=>{ let typed_value:super::r#enum::Enum=value.as_userdata().ok_or(mlua::Error::runtime("Expected Userdata"))?.take()?; instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Enum(typed_value.into())); }, rbx_types::Variant::Color3(_)=>{ let typed_value:super::color3::Color3=value.as_userdata().ok_or(mlua::Error::runtime("Expected Userdata"))?.take()?; instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Color3(typed_value.into())); }, other=>return Err(mlua::Error::runtime(format!("Unimplemented property type: {other:?}"))), } Ok(()) }) ); } } class!(DataModel); class_composition!(DataModel,(Instance,DataModel)); impl DataModel{ fn composition_add_fields<'lua,T:Referent,F:mlua::UserDataFields<'lua,T>>(fields:&mut F){ fields.add_field_method_get("PlaceId",|lua,this|{ Ok(mlua::Value::Integer(0)) }); } fn composition_add_methods<'lua,T,M:mlua::UserDataMethods<'lua,T>>(methods:&mut M){ methods.add_method("GetService",|lua,this,service:String| dom(lua,|dom|{ match service.as_str(){ "Lighting"=>{ let referent=dom.root() .children() .iter() .find(|&&c| dom.get_by_ref(c).is_some_and(|c|c.class=="Lighting") ).map(|r|*r) .unwrap_or_else(|| dom.insert(dom.root_ref(),InstanceBuilder::new("Lighting")) ); Lighting::new(referent).into_lua(lua) }, other=>Err::(mlua::Error::runtime(format!("Service '{other}' not supported"))), } }) ); } } class!(Workspace); class_composition!(Workspace,(Instance)); class!(Lighting); class_composition!(Lighting,(Instance)); #[derive(Debug)] pub enum GetScriptError{ NoScript, NoSource, } class!(Script); class_composition!(Script,(Instance)); impl Script{ pub fn get_name_source(&self,context:&crate::context::Context)->Result<(String,String),GetScriptError>{ let instance=context.dom.get_by_ref(self.referent).ok_or(GetScriptError::NoScript)?; let source=match instance.properties.get("Source").ok_or(GetScriptError::NoSource)?{ rbx_dom_weak::types::Variant::String(s)=>s.clone(), _=>Err(GetScriptError::NoSource)?, }; Ok((get_full_name(&context.dom,instance),source)) } } class!(Terrain); class_composition!(Terrain,(Instance,Terrain)); impl Terrain{ fn composition_add_fields<'lua,T:Referent,F:mlua::UserDataFields<'lua,T>>(fields:&mut F){ } fn composition_add_methods<'lua,T,M:mlua::UserDataMethods<'lua,T>>(methods:&mut M){ methods.add_method("FillBlock",|lua,this,_:(super::cframe::CFrame,Vector3,super::r#enum::Enum)| Ok(())//Ok(mlua::Value::Nil) ) } }