12 Commits
nil ... mlua10

8 changed files with 96 additions and 274 deletions

2
Cargo.lock generated
View File

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

View File

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

View File

@@ -46,21 +46,11 @@ impl Context{
self.superclass_iter("LuaSourceContainer").map(crate::runner::instance::Instance::new).collect()
}
fn get_service(&self,service:&str)->Option<Ref>{
self.dom.root().children().iter().find(|&&r|
self.dom.get_by_ref(r).is_some_and(|instance|instance.class==service)
).copied()
}
fn get_or_create_service(&mut self,service:&str,mut create:impl FnMut(&mut Context)->Ref)->Ref{
match self.get_service(service){
Some(referent)=>referent,
None=>create(self),
}
}
pub fn find_services(&self)->Option<Services>{
Some(Services{
workspace:self.get_service("Workspace")?,
nil:self.get_service("Nil")?,
workspace:*self.dom.root().children().iter().find(|&&r|
self.dom.get_by_ref(r).is_some_and(|instance|instance.class=="Workspace")
)?,
game:self.dom.root_ref(),
})
}
@@ -70,33 +60,11 @@ impl Context{
//insert services
let game=self.dom.root_ref();
let workspace=self.get_or_create_service("Workspace",|context|{
let terrain_bldr=InstanceBuilder::new("Terrain");
context.dom.insert(game,
InstanceBuilder::new("Workspace")
//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"));
// Special nonexistent class that holds instances parented to nil,
// which can still be referenced by scripts.
// Using a sentinel value as a ref because global variables are hard.
let nil=self.get_or_create_service("Nil",|context|
context.dom.insert(
game,InstanceBuilder::new("Nil")
.with_referent(
<Ref as std::str::FromStr>::from_str("ffffffffffffffffffffffffffffffff").unwrap()
)
)
let workspace=self.dom.insert(game,
InstanceBuilder::new("Workspace")
.with_child(InstanceBuilder::new("Terrain"))
);
self.dom.insert(game,InstanceBuilder::new("Lighting"));
//transfer original root instances into workspace
for instance in children{
@@ -106,7 +74,6 @@ impl Context{
Services{
game,
workspace,
nil,
}
}
}
@@ -114,5 +81,4 @@ impl Context{
pub struct Services{
pub game:Ref,
pub workspace:Ref,
pub nil:Ref,
}

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 mlua::{FromLua,FromLuaMulti,IntoLua,IntoLuaMulti};
use mlua::{FromLuaMulti,IntoLua,IntoLuaMulti};
use rbx_types::Ref;
use rbx_dom_weak::{InstanceBuilder,WeakDom};
@@ -10,14 +10,17 @@ pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
//class functions store
lua.set_app_data(ClassFunctions::default());
lua.register_userdata_type(<Instance as mlua::UserData>::register)?;
let instance_table=lua.create_table()?;
//Instance.new
instance_table.raw_set("new",
lua.create_function(|lua,(class_name,parent):(mlua::String,Option<Instance>)|{
let class_name_str=&*class_name.to_str()?;
let parent=parent.unwrap_or(Instance::nil());
let parent=parent.ok_or(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))))
})
})?
@@ -90,12 +93,6 @@ impl Instance{
pub const fn new(referent:Ref)->Self{
Self{referent}
}
// Using a sentinel value as a ref for nil instances because global variables are hard.
pub fn nil()->Self{
Self{
referent:<Ref as std::str::FromStr>::from_str("ffffffffffffffffffffffffffffffff").unwrap()
}
}
pub 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"))
}
@@ -129,12 +126,11 @@ impl mlua::UserData for Instance{
fields.add_field_method_get("Parent",|lua,this|{
dom_mut(lua,|dom|{
let instance=this.get(dom)?;
//TODO: return nil when parent is Nil instances
Ok(Instance::new(instance.parent()))
})
});
fields.add_field_method_set("Parent",|lua,this,val:Option<Instance>|{
let parent=val.unwrap_or(Instance::nil());
let parent=val.ok_or(mlua::Error::runtime("Nil Parent not yet supported"))?;
dom_mut(lua,|dom|{
dom.transfer_within(this.referent,parent.referent);
Ok(())
@@ -224,7 +220,7 @@ impl mlua::UserData for Instance{
);
methods.add_method("Destroy",|lua,this,()|
dom_mut(lua,|dom|{
dom.transfer_within(this.referent,Instance::nil().referent);
dom.destroy(this.referent);
Ok(())
})
);
@@ -243,29 +239,16 @@ impl mlua::UserData for Instance{
let class=db.classes.get(instance.class.as_str()).ok_or(mlua::Error::runtime("Class missing"))?;
//Find existing property
match instance.properties.get(index_str)
.cloned()
//Find default value
.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)
)
})
.or_else(||db.find_default_property(class,index_str))
{
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::CFrame(cf))=>return Into::<super::cframe::CFrame>::into(cf).into_lua(lua),
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))),
Some(&rbx_types::Variant::Int32(val))=>return Ok(val.into_lua(lua)),
Some(&rbx_types::Variant::Int64(val))=>return Ok(val.into_lua(lua)),
Some(&rbx_types::Variant::Float32(val))=>return Ok(val.into_lua(lua)),
Some(&rbx_types::Variant::Float64(val))=>return Ok(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::Vector3(v))=>return Ok(Into::<super::vector3::Vector3>::into(v).into_lua(lua)),
other=>println!("instance.properties.get(i)={other:?}"),
}
//find a function with a matching name
if let Some(function)=class_functions_mut(lua,|cf|{
@@ -273,18 +256,24 @@ impl mlua::UserData for Instance{
database:db,
descriptor:Some(class),
};
iter.find_map(|class|{
let mut class_methods=cf.get_or_create_class_methods(&class.name)?;
class_methods.get_or_create_function(lua,index_str)
.transpose()
}).transpose()
Ok(loop{
match iter.next(){
Some(class)=>match cf.get_or_create_class_function(lua,&class.name,index_str)?{
Some(function)=>break Some(function),
None=>(),
},
None=>break None,
}
})
})?{
return function.into_lua(lua);
return Ok(function.into_lua(lua));
}
//find a child with a matching name
find_first_child(dom,instance,index_str)
.map(|instance|Instance::new(instance.referent()))
.into_lua(lua)
Ok(
find_first_child(dom,instance,index_str)
.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)|{
@@ -301,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)))?;
match &property.data_type{
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()));
},
rbx_reflection::DataType::Value(rbx_types::VariantType::Float32)=>{
@@ -317,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"))?))
},
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())
},
_=>Err(mlua::Error::runtime("Expected Enum")),
@@ -325,7 +314,7 @@ impl mlua::UserData for Instance{
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Enum(typed_value));
},
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()));
},
rbx_reflection::DataType::Value(rbx_types::VariantType::Bool)=>{
@@ -336,14 +325,6 @@ impl mlua::UserData for Instance{
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()));
},
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:?}"))),
}
Ok(())
@@ -352,46 +333,29 @@ impl mlua::UserData for Instance{
}
}
/// A class function definition shorthand.
macro_rules! cf{
($f:expr)=>{
|lua,mut args|{
let this=Instance::from_lua(args.pop_front().unwrap_or(mlua::Value::Nil),lua)?;
$f(lua,this,FromLuaMulti::from_lua_multi(args,lua)?)?.into_lua_multi(lua)
fn get_service(lua:&mlua::Lua,tuple:mlua::MultiValue)->mlua::Result<mlua::MultiValue>{
let (_game,service):(Instance,mlua::String)=FromLuaMulti::from_lua_multi(tuple,lua)?;
dom_mut(lua,|dom|{
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"))
);
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>;
// TODO: use macros to define these with better organization
type FPointer=fn(&mlua::Lua,mlua::MultiValue)->mlua::Result<mlua::MultiValue>;
/// A double hash map of function pointers.
/// The class tree is walked by the Instance.__index metamethod to find available class methods.
type CFD=phf::Map<&'static str,// Class name
phf::Map<&'static str,// Method name
ClassFunctionPointer
>
>;
static CLASS_FUNCTION_DATABASE:CFD=phf::phf_map!{
static CLASS_FUNCTION_DATABASE:phf::Map<&str,phf::Map<&str,FPointer>>=phf::phf_map!{
"DataModel"=>phf::phf_map!{
"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(()))
},
"GetService"=>get_service as FPointer,
}
};
/// A store of created functions for each Roblox class.
@@ -399,91 +363,49 @@ static CLASS_FUNCTION_DATABASE:CFD=phf::phf_map!{
#[derive(Default)]
struct ClassFunctions{
classes:HashMap<&'static str,//ClassName
HashMap<&'static str,//Method name
HashMap<&'static str,//Function name
mlua::Function
>
>
}
impl ClassFunctions{
/// return self.classes[class] or create the ClassMethods and then return it
fn get_or_create_class_methods(&mut self,class:&str)->Option<ClassMethods>{
// Use get_entry to get the &'static str keys of the database
/// Someone please rewrite this, all it's supposed to do is
/// return self.classes[class][index] or create the function in the hashmap and then return it
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
CLASS_FUNCTION_DATABASE.get_entry(class)
.map(|(&static_class_str,method_pointers)|
ClassMethods{
method_pointers,
methods:self.classes.entry(static_class_str)
.or_insert_with(||HashMap::new()),
let f=match CLASS_FUNCTION_DATABASE.get_entry(class){
Some((&static_class_str,class_functions))=>{
match self.classes.entry(static_class_str){
Entry::Occupied(mut occupied_entry)=>{
match class_functions.get_entry(index){
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,
})
};
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 vector3;
pub mod instance;
mod number_sequence;
mod color_sequence;
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::cframe::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(())
}