24 Commits

Author SHA1 Message Date
f259022666 non-functional ColorSequence skeleton 2024-10-06 18:57:55 -07:00
52b4b12b43 non-functional NumberSequence skeleton 2024-10-06 18:57:55 -07:00
25577211b2 cursed userdata take 2024-10-06 18:57:42 -07:00
fe91de5668 remove and_then 2024-10-06 18:57:42 -07:00
6dfa46bc67 VIRTUAL_PROPERTY_DATABASE (another goddamn superclass walk) 2024-10-06 17:55:38 -07:00
3ad9d45452 don't create and drop a new function every call 2024-10-06 16:00:14 -07:00
9a03ac199a deref function pointer, not sure if this does anything 2024-10-06 13:53:41 -07:00
aa1db9551e match has better line width balance 2024-10-06 13:45:44 -07:00
692697f82b split get_or_create_class_function into two parts in case you want to get multiple functions from the same class 2024-10-06 11:42:31 -07:00
5e45e952d9 v0.4.4 less terrible all around 2024-10-06 11:26:18 -07:00
09dd442948 class tree walk can be idiomized with find_map and transpose 2024-10-06 10:49:41 -07:00
726ffeca21 ClassFunction exceptional simplification 2024-10-06 10:43:00 -07:00
082de122b1 fix double convert 2024-10-06 10:26:58 -07:00
bf0c4f74c3 error instead of print, expanded message 2024-10-05 22:36:46 -07:00
97e2010826 split type onto multiple lines 2024-10-05 22:36:46 -07:00
de363bc271 no print when none 2024-10-05 22:17:56 -07:00
849b0813b9 change cf macro to include Instance as the first argument 2024-10-05 22:15:10 -07:00
fac1f318d7 v0.4.3 rewrite Instances to be a single UserData type using a class function database 2024-10-05 21:54:36 -07:00
a23e8fc36f return Instance from Instance.__Index 2024-10-05 21:53:06 -07:00
58edaf3291 Instance.__index whole thing was wrong 2024-10-05 21:53:06 -07:00
d8aacb9ed2 class function database can only return functions, not values 2024-10-05 21:49:16 -07:00
35d60cde16 todo: use macros for class function database 2024-10-05 21:38:34 -07:00
69d43d8bea class function database + update mlua to beta version to avoid unsafe 2024-10-05 21:38:34 -07:00
b2beef4726 v0.4.2 additional implementations 2024-10-05 16:36:01 -07:00
8 changed files with 234 additions and 87 deletions

2
Cargo.lock generated
View File

@@ -402,7 +402,7 @@ dependencies = [
[[package]] [[package]]
name = "roblox_emulator" name = "roblox_emulator"
version = "0.4.1" version = "0.4.4"
dependencies = [ dependencies = [
"glam", "glam",
"mlua", "mlua",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "roblox_emulator" name = "roblox_emulator"
version = "0.4.1" version = "0.4.4"
edition = "2021" edition = "2021"
repository = "https://git.itzana.me/StrafesNET/roblox_emulator" repository = "https://git.itzana.me/StrafesNET/roblox_emulator"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"

View File

@@ -60,10 +60,19 @@ impl Context{
//insert services //insert services
let game=self.dom.root_ref(); let game=self.dom.root_ref();
let terrain_bldr=InstanceBuilder::new("Terrain");
let workspace=self.dom.insert(game, let workspace=self.dom.insert(game,
InstanceBuilder::new("Workspace") InstanceBuilder::new("Workspace")
.with_child(InstanceBuilder::new("Terrain")) //Set Workspace.Terrain property equal to Terrain
.with_property("Terrain",terrain_bldr.referent())
.with_child(terrain_bldr)
); );
{
//Lowercase and upper case workspace property!
let game=self.dom.root_mut();
game.properties.insert("workspace".to_owned(),rbx_types::Variant::Ref(workspace));
game.properties.insert("Workspace".to_owned(),rbx_types::Variant::Ref(workspace));
}
self.dom.insert(game,InstanceBuilder::new("Lighting")); self.dom.insert(game,InstanceBuilder::new("Lighting"));
//transfer original root instances into workspace //transfer original root instances into workspace

View File

@@ -0,0 +1,31 @@
#[derive(Clone,Copy)]
pub struct ColorSequence{}
impl ColorSequence{
pub const fn new()->Self{
Self{}
}
}
impl Into<rbx_types::ColorSequence> for ColorSequence{
fn into(self)->rbx_types::ColorSequence{
rbx_types::ColorSequence{
keypoints:Vec::new()
}
}
}
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let number_sequence_table=lua.create_table()?;
number_sequence_table.raw_set("new",
lua.create_function(|_,_:mlua::MultiValue|
Ok(ColorSequence::new())
)?
)?;
globals.set("ColorSequence",number_sequence_table)?;
Ok(())
}
impl mlua::UserData for ColorSequence{}
type_from_lua_userdata!(ColorSequence);

View File

@@ -1,6 +1,6 @@
use std::collections::{hash_map::Entry,HashMap}; use std::collections::{hash_map::Entry,HashMap};
use mlua::{FromLuaMulti,IntoLua,IntoLuaMulti}; use mlua::{FromLua,FromLuaMulti,IntoLua,IntoLuaMulti};
use rbx_types::Ref; use rbx_types::Ref;
use rbx_dom_weak::{InstanceBuilder,WeakDom}; use rbx_dom_weak::{InstanceBuilder,WeakDom};
@@ -10,8 +10,6 @@ pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
//class functions store //class functions store
lua.set_app_data(ClassFunctions::default()); lua.set_app_data(ClassFunctions::default());
lua.register_userdata_type(<Instance as mlua::UserData>::register)?;
let instance_table=lua.create_table()?; let instance_table=lua.create_table()?;
//Instance.new //Instance.new
@@ -239,16 +237,29 @@ impl mlua::UserData for Instance{
let class=db.classes.get(instance.class.as_str()).ok_or(mlua::Error::runtime("Class missing"))?; let class=db.classes.get(instance.class.as_str()).ok_or(mlua::Error::runtime("Class missing"))?;
//Find existing property //Find existing property
match instance.properties.get(index_str) match instance.properties.get(index_str)
.cloned()
//Find default value //Find default value
.or_else(||db.find_default_property(class,index_str)) .or_else(||db.find_default_property(class,index_str).cloned())
//Find virtual property
.or_else(||{
SuperClassIter{
database:db,
descriptor:Some(class),
}
.find_map(|class|
find_virtual_property(&instance.properties,class,index_str)
)
})
{ {
Some(&rbx_types::Variant::Int32(val))=>return Ok(val.into_lua(lua)), Some(rbx_types::Variant::Int32(val))=>return val.into_lua(lua),
Some(&rbx_types::Variant::Int64(val))=>return Ok(val.into_lua(lua)), Some(rbx_types::Variant::Int64(val))=>return val.into_lua(lua),
Some(&rbx_types::Variant::Float32(val))=>return Ok(val.into_lua(lua)), Some(rbx_types::Variant::Float32(val))=>return val.into_lua(lua),
Some(&rbx_types::Variant::Float64(val))=>return Ok(val.into_lua(lua)), Some(rbx_types::Variant::Float64(val))=>return val.into_lua(lua),
Some(&rbx_types::Variant::CFrame(cf))=>return Ok(Into::<super::cframe::CFrame>::into(cf).into_lua(lua)), Some(rbx_types::Variant::Ref(val))=>return Instance::new(val).into_lua(lua),
Some(&rbx_types::Variant::Vector3(v))=>return Ok(Into::<super::vector3::Vector3>::into(v).into_lua(lua)), Some(rbx_types::Variant::CFrame(cf))=>return Into::<super::cframe::CFrame>::into(cf).into_lua(lua),
other=>println!("instance.properties.get(i)={other:?}"), Some(rbx_types::Variant::Vector3(v))=>return Into::<super::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))),
} }
//find a function with a matching name //find a function with a matching name
if let Some(function)=class_functions_mut(lua,|cf|{ if let Some(function)=class_functions_mut(lua,|cf|{
@@ -256,24 +267,18 @@ impl mlua::UserData for Instance{
database:db, database:db,
descriptor:Some(class), descriptor:Some(class),
}; };
Ok(loop{ iter.find_map(|class|{
match iter.next(){ let mut class_methods=cf.get_or_create_class_methods(&class.name)?;
Some(class)=>match cf.get_or_create_class_function(lua,&class.name,index_str)?{ class_methods.get_or_create_function(lua,index_str)
Some(function)=>break Some(function), .transpose()
None=>(), }).transpose()
},
None=>break None,
}
})
})?{ })?{
return Ok(function.into_lua(lua)); return function.into_lua(lua);
} }
//find a child with a matching name //find a child with a matching name
Ok( find_first_child(dom,instance,index_str)
find_first_child(dom,instance,index_str) .map(|instance|Instance::new(instance.referent()))
.map(|instance|Instance::new(instance.referent())) .into_lua(lua)
.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):(Instance,mlua::String,mlua::Value)|{
@@ -290,7 +295,7 @@ impl mlua::UserData for Instance{
let property=iter.find_map(|cls|cls.properties.get(index_str)).ok_or(mlua::Error::runtime(format!("Property '{index_str}' missing on class '{}'",class.name)))?; let property=iter.find_map(|cls|cls.properties.get(index_str)).ok_or(mlua::Error::runtime(format!("Property '{index_str}' missing on class '{}'",class.name)))?;
match &property.data_type{ match &property.data_type{
rbx_reflection::DataType::Value(rbx_types::VariantType::Vector3)=>{ rbx_reflection::DataType::Value(rbx_types::VariantType::Vector3)=>{
let typed_value:Vector3=value.as_userdata().ok_or(mlua::Error::runtime("Expected Userdata"))?.take()?; let typed_value:Vector3=*value.as_userdata().ok_or(mlua::Error::runtime("Expected Userdata"))?.borrow()?;
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Vector3(typed_value.into())); instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Vector3(typed_value.into()));
}, },
rbx_reflection::DataType::Value(rbx_types::VariantType::Float32)=>{ rbx_reflection::DataType::Value(rbx_types::VariantType::Float32)=>{
@@ -306,7 +311,7 @@ impl mlua::UserData for Instance{
Ok(rbx_types::Enum::from_u32(*e.items.get(&*s.to_str()?).ok_or(mlua::Error::runtime("Invalid enum item"))?)) Ok(rbx_types::Enum::from_u32(*e.items.get(&*s.to_str()?).ok_or(mlua::Error::runtime("Invalid enum item"))?))
}, },
mlua::Value::UserData(any_user_data)=>{ mlua::Value::UserData(any_user_data)=>{
let e:super::r#enum::Enum=any_user_data.take()?; let e:super::r#enum::Enum=*any_user_data.borrow()?;
Ok(e.into()) Ok(e.into())
}, },
_=>Err(mlua::Error::runtime("Expected Enum")), _=>Err(mlua::Error::runtime("Expected Enum")),
@@ -314,7 +319,7 @@ impl mlua::UserData for Instance{
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Enum(typed_value)); instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Enum(typed_value));
}, },
rbx_reflection::DataType::Value(rbx_types::VariantType::Color3)=>{ rbx_reflection::DataType::Value(rbx_types::VariantType::Color3)=>{
let typed_value:super::color3::Color3=value.as_userdata().ok_or(mlua::Error::runtime("Expected Color3"))?.take()?; let typed_value:super::color3::Color3=*value.as_userdata().ok_or(mlua::Error::runtime("Expected Color3"))?.borrow()?;
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Color3(typed_value.into())); instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Color3(typed_value.into()));
}, },
rbx_reflection::DataType::Value(rbx_types::VariantType::Bool)=>{ rbx_reflection::DataType::Value(rbx_types::VariantType::Bool)=>{
@@ -325,6 +330,14 @@ impl mlua::UserData for Instance{
let typed_value=value.as_str().ok_or(mlua::Error::runtime("Expected boolean"))?; let typed_value=value.as_str().ok_or(mlua::Error::runtime("Expected boolean"))?;
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::String(typed_value.to_owned())); instance.properties.insert(index_str.to_owned(),rbx_types::Variant::String(typed_value.to_owned()));
}, },
rbx_reflection::DataType::Value(rbx_types::VariantType::NumberSequence)=>{
let typed_value:super::number_sequence::NumberSequence=*value.as_userdata().ok_or(mlua::Error::runtime("Expected NumberSequence"))?.borrow()?;
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::NumberSequence(typed_value.into()));
},
rbx_reflection::DataType::Value(rbx_types::VariantType::ColorSequence)=>{
let typed_value:super::color_sequence::ColorSequence=*value.as_userdata().ok_or(mlua::Error::runtime("Expected ColorSequence"))?.borrow()?;
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::ColorSequence(typed_value.into()));
},
other=>return Err(mlua::Error::runtime(format!("Unimplemented property type: {other:?}"))), other=>return Err(mlua::Error::runtime(format!("Unimplemented property type: {other:?}"))),
} }
Ok(()) Ok(())
@@ -333,29 +346,46 @@ impl mlua::UserData for Instance{
} }
} }
fn get_service(lua:&mlua::Lua,tuple:mlua::MultiValue)->mlua::Result<mlua::MultiValue>{ /// A class function definition shorthand.
let (_game,service):(Instance,mlua::String)=FromLuaMulti::from_lua_multi(tuple,lua)?; macro_rules! cf{
dom_mut(lua,|dom|{ ($f:expr)=>{
match &*service.to_str()?{ |lua,mut args|{
"Lighting"=>{ let this=Instance::from_lua(args.pop_front().unwrap_or(mlua::Value::Nil),lua)?;
let referent=find_first_child_of_class(dom,dom.root(),"Lighting") $f(lua,this,FromLuaMulti::from_lua_multi(args,lua)?)?.into_lua_multi(lua)
.map(|instance|instance.referent())
.unwrap_or_else(||
dom.insert(dom.root_ref(),InstanceBuilder::new("Lighting"))
);
Instance::new(referent).into_lua_multi(lua)
},
other=>Err::<mlua::MultiValue,_>(mlua::Error::runtime(format!("Service '{other}' not supported"))),
} }
}) };
} }
type FPointer=fn(&mlua::Lua,mlua::MultiValue)->mlua::Result<mlua::MultiValue>; 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. /// A double hash map of function pointers.
/// The class tree is walked by the Instance.__index metamethod to find available class methods. /// The class tree is walked by the Instance.__index metamethod to find available class methods.
static CLASS_FUNCTION_DATABASE:phf::Map<&str,phf::Map<&str,FPointer>>=phf::phf_map!{ type CFD=phf::Map<&'static str,// Class name
phf::Map<&'static str,// Method name
ClassFunctionPointer
>
>;
static CLASS_FUNCTION_DATABASE:CFD=phf::phf_map!{
"DataModel"=>phf::phf_map!{ "DataModel"=>phf::phf_map!{
"GetService"=>get_service as FPointer, "GetService"=>cf!(|lua,_this,service:mlua::String|{
} dom_mut(lua,|dom|{
//dom.root_ref()==this.referent ?
match &*service.to_str()?{
"Lighting"=>{
let referent=find_first_child_of_class(dom,dom.root(),"Lighting")
.map(|instance|instance.referent())
.unwrap_or_else(||
dom.insert(dom.root_ref(),InstanceBuilder::new("Lighting"))
);
Ok(Instance::new(referent))
},
other=>Err::<Instance,_>(mlua::Error::runtime(format!("Service '{other}' not supported"))),
}
})
}),
},
"Terrain"=>phf::phf_map!{
"FillBlock"=>cf!(|_lua,_,_:(super::cframe::CFrame,Vector3,super::r#enum::Enum)|mlua::Result::Ok(()))
},
}; };
/// A store of created functions for each Roblox class. /// A store of created functions for each Roblox class.
@@ -363,49 +393,91 @@ static CLASS_FUNCTION_DATABASE:phf::Map<&str,phf::Map<&str,FPointer>>=phf::phf_m
#[derive(Default)] #[derive(Default)]
struct ClassFunctions{ struct ClassFunctions{
classes:HashMap<&'static str,//ClassName classes:HashMap<&'static str,//ClassName
HashMap<&'static str,//Function name HashMap<&'static str,//Method name
mlua::Function mlua::Function
> >
> >
} }
impl ClassFunctions{ impl ClassFunctions{
/// Someone please rewrite this, all it's supposed to do is /// return self.classes[class] or create the ClassMethods and then return it
/// return self.classes[class][index] or create the function in the hashmap and then return it fn get_or_create_class_methods(&mut self,class:&str)->Option<ClassMethods>{
fn get_or_create_class_function(&mut self,lua:&mlua::Lua,class:&str,index:&str)->mlua::Result<Option<mlua::Function>>{ // Use get_entry to get the &'static str keys of the database
// Use get_entry to get the &'static str key of the database
// and use it as a key for the classes hashmap // and use it as a key for the classes hashmap
let f=match CLASS_FUNCTION_DATABASE.get_entry(class){ CLASS_FUNCTION_DATABASE.get_entry(class)
Some((&static_class_str,class_functions))=>{ .map(|(&static_class_str,method_pointers)|
match self.classes.entry(static_class_str){ ClassMethods{
Entry::Occupied(mut occupied_entry)=>{ method_pointers,
match class_functions.get_entry(index){ methods:self.classes.entry(static_class_str)
Some((&static_index_str,function_pointer))=>{ .or_insert_with(||HashMap::new()),
match occupied_entry.get_mut().entry(static_index_str){
Entry::Occupied(occupied_entry)=>{
Some(occupied_entry.get().clone())
},
Entry::Vacant(vacant_entry)=>{
Some(vacant_entry.insert(lua.create_function(function_pointer)?).clone())
},
}
},
None=>None,
}
},
Entry::Vacant(vacant_entry)=>{
match class_functions.get_entry(index){
Some((&static_index_str,function_pointer))=>{
let mut h=HashMap::new();
h.entry(static_index_str).or_insert(lua.create_function(function_pointer)?);
vacant_entry.insert(h).get(static_index_str).map(|f|f.clone())
},
None=>None,
}
},
} }
}, )
None=>None,
};
Ok(f)
} }
} }
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,
})
}
}
/// 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:&HashMap<String,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(virtual_property.property)?;
//Transform Source property with provided function
(virtual_property.pointer)(variant)
}

View File

@@ -7,5 +7,7 @@ mod color3;
mod cframe; mod cframe;
mod vector3; mod vector3;
pub mod instance; pub mod instance;
mod number_sequence;
mod color_sequence;
pub use runner::{Runner,Error}; pub use runner::{Runner,Error};

View File

@@ -0,0 +1,31 @@
#[derive(Clone,Copy)]
pub struct NumberSequence{}
impl NumberSequence{
pub const fn new()->Self{
Self{}
}
}
impl Into<rbx_types::NumberSequence> for NumberSequence{
fn into(self)->rbx_types::NumberSequence{
rbx_types::NumberSequence{
keypoints:Vec::new()
}
}
}
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let number_sequence_table=lua.create_table()?;
number_sequence_table.raw_set("new",
lua.create_function(|_,_:mlua::MultiValue|
Ok(NumberSequence::new())
)?
)?;
globals.set("NumberSequence",number_sequence_table)?;
Ok(())
}
impl mlua::UserData for NumberSequence{}
type_from_lua_userdata!(NumberSequence);

View File

@@ -34,6 +34,8 @@ fn init(lua:&mlua::Lua)->mlua::Result<()>{
super::vector3::set_globals(lua,&globals)?; super::vector3::set_globals(lua,&globals)?;
super::cframe::set_globals(lua,&globals)?; super::cframe::set_globals(lua,&globals)?;
super::instance::set_globals(lua,&globals)?; super::instance::set_globals(lua,&globals)?;
super::number_sequence::set_globals(lua,&globals)?;
super::color_sequence::set_globals(lua,&globals)?;
Ok(()) Ok(())
} }