12 Commits

8 changed files with 86 additions and 233 deletions

2
Cargo.lock generated
View File

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

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "roblox_emulator" name = "roblox_emulator"
version = "0.4.4" version = "0.4.1"
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,19 +60,10 @@ 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")
//Set Workspace.Terrain property equal to Terrain .with_child(InstanceBuilder::new("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

@@ -1,31 +0,0 @@
#[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::{FromLua,FromLuaMulti,IntoLua,IntoLuaMulti}; use mlua::{FromLuaMulti,IntoLua,IntoLuaMulti};
use rbx_types::Ref; use rbx_types::Ref;
use rbx_dom_weak::{InstanceBuilder,WeakDom}; use rbx_dom_weak::{InstanceBuilder,WeakDom};
@@ -10,6 +10,8 @@ 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
@@ -237,29 +239,16 @@ 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).cloned()) .or_else(||db.find_default_property(class,index_str))
//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 val.into_lua(lua), Some(&rbx_types::Variant::Int32(val))=>return Ok(val.into_lua(lua)),
Some(rbx_types::Variant::Int64(val))=>return val.into_lua(lua), Some(&rbx_types::Variant::Int64(val))=>return Ok(val.into_lua(lua)),
Some(rbx_types::Variant::Float32(val))=>return val.into_lua(lua), Some(&rbx_types::Variant::Float32(val))=>return Ok(val.into_lua(lua)),
Some(rbx_types::Variant::Float64(val))=>return val.into_lua(lua), Some(&rbx_types::Variant::Float64(val))=>return Ok(val.into_lua(lua)),
Some(rbx_types::Variant::Ref(val))=>return Instance::new(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::CFrame(cf))=>return Into::<super::cframe::CFrame>::into(cf).into_lua(lua), Some(&rbx_types::Variant::Vector3(v))=>return Ok(Into::<super::vector3::Vector3>::into(v).into_lua(lua)),
Some(rbx_types::Variant::Vector3(v))=>return Into::<super::vector3::Vector3>::into(v).into_lua(lua), other=>println!("instance.properties.get(i)={other:?}"),
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|{
@@ -267,18 +256,24 @@ impl mlua::UserData for Instance{
database:db, database:db,
descriptor:Some(class), descriptor:Some(class),
}; };
iter.find_map(|class|{ Ok(loop{
let mut class_methods=cf.get_or_create_class_methods(&class.name)?; match iter.next(){
class_methods.get_or_create_function(lua,index_str) Some(class)=>match cf.get_or_create_class_function(lua,&class.name,index_str)?{
.transpose() Some(function)=>break Some(function),
}).transpose() None=>(),
},
None=>break None,
}
})
})?{ })?{
return function.into_lua(lua); return Ok(function.into_lua(lua));
} }
//find a child with a matching name //find a child with a matching name
find_first_child(dom,instance,index_str) Ok(
.map(|instance|Instance::new(instance.referent())) find_first_child(dom,instance,index_str)
.into_lua(lua) .map(|instance|Instance::new(instance.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):(Instance,mlua::String,mlua::Value)|{
@@ -295,7 +290,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"))?.borrow()?; 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())); 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)=>{
@@ -311,7 +306,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.borrow()?; let e:super::r#enum::Enum=any_user_data.take()?;
Ok(e.into()) Ok(e.into())
}, },
_=>Err(mlua::Error::runtime("Expected Enum")), _=>Err(mlua::Error::runtime("Expected Enum")),
@@ -319,7 +314,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"))?.borrow()?; let typed_value:super::color3::Color3=value.as_userdata().ok_or(mlua::Error::runtime("Expected Color3"))?.take()?;
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)=>{
@@ -330,14 +325,6 @@ 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(())
@@ -346,46 +333,29 @@ impl mlua::UserData for Instance{
} }
} }
/// A class function definition shorthand. fn get_service(lua:&mlua::Lua,tuple:mlua::MultiValue)->mlua::Result<mlua::MultiValue>{
macro_rules! cf{ let (_game,service):(Instance,mlua::String)=FromLuaMulti::from_lua_multi(tuple,lua)?;
($f:expr)=>{ dom_mut(lua,|dom|{
|lua,mut args|{ match &*service.to_str()?{
let this=Instance::from_lua(args.pop_front().unwrap_or(mlua::Value::Nil),lua)?; "Lighting"=>{
$f(lua,this,FromLuaMulti::from_lua_multi(args,lua)?)?.into_lua_multi(lua) 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"))
);
Instance::new(referent).into_lua_multi(lua)
},
other=>Err::<mlua::MultiValue,_>(mlua::Error::runtime(format!("Service '{other}' not supported"))),
} }
}; })
} }
type ClassFunctionPointer=fn(&mlua::Lua,mlua::MultiValue)->mlua::Result<mlua::MultiValue>; type FPointer=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.
type CFD=phf::Map<&'static str,// Class name static CLASS_FUNCTION_DATABASE:phf::Map<&str,phf::Map<&str,FPointer>>=phf::phf_map!{
phf::Map<&'static str,// Method name
ClassFunctionPointer
>
>;
static CLASS_FUNCTION_DATABASE:CFD=phf::phf_map!{
"DataModel"=>phf::phf_map!{ "DataModel"=>phf::phf_map!{
"GetService"=>cf!(|lua,_this,service:mlua::String|{ "GetService"=>get_service as FPointer,
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.
@@ -393,91 +363,49 @@ static CLASS_FUNCTION_DATABASE:CFD=phf::phf_map!{
#[derive(Default)] #[derive(Default)]
struct ClassFunctions{ struct ClassFunctions{
classes:HashMap<&'static str,//ClassName classes:HashMap<&'static str,//ClassName
HashMap<&'static str,//Method name HashMap<&'static str,//Function name
mlua::Function mlua::Function
> >
> >
} }
impl ClassFunctions{ impl ClassFunctions{
/// return self.classes[class] or create the ClassMethods and then return it /// Someone please rewrite this, all it's supposed to do is
fn get_or_create_class_methods(&mut self,class:&str)->Option<ClassMethods>{ /// return self.classes[class][index] or create the function in the hashmap and then return it
// Use get_entry to get the &'static str keys of the database 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 key of the database
// and use it as a key for the classes hashmap // and use it as a key for the classes hashmap
CLASS_FUNCTION_DATABASE.get_entry(class) let f=match CLASS_FUNCTION_DATABASE.get_entry(class){
.map(|(&static_class_str,method_pointers)| Some((&static_class_str,class_functions))=>{
ClassMethods{ match self.classes.entry(static_class_str){
method_pointers, Entry::Occupied(mut occupied_entry)=>{
methods:self.classes.entry(static_class_str) match class_functions.get_entry(index){
.or_insert_with(||HashMap::new()), Some((&static_index_str,function_pointer))=>{
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,
}
},
} }
) },
}
}
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, None=>None,
}) };
Ok(f)
} }
} }
/// 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,7 +7,5 @@ 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

@@ -1,31 +0,0 @@
#[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,8 +34,6 @@ 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(())
} }