Compare commits

...

4 Commits

Author SHA1 Message Date
73dca41859 wip: associated values store 2024-10-17 15:09:06 -07:00
55b8e56f20 rename ClassFunctions -> ClassMethodsStore 2024-10-17 14:14:42 -07:00
420907a14a RBXScriptSignal 2024-10-17 13:16:00 -07:00
43ab764b18 wip runservice 2024-10-17 11:33:20 -07:00
3 changed files with 139 additions and 10 deletions

View File

@ -8,7 +8,7 @@ use super::vector3::Vector3;
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{ 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(ClassMethodsStore::default());
let instance_table=lua.create_table()?; let instance_table=lua.create_table()?;
@ -34,8 +34,8 @@ fn dom_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut WeakDom)->mlua::Result<T>)->m
let mut dom=lua.app_data_mut::<&'static mut WeakDom>().ok_or(mlua::Error::runtime("DataModel missing"))?; let mut dom=lua.app_data_mut::<&'static mut WeakDom>().ok_or(mlua::Error::runtime("DataModel missing"))?;
f(&mut *dom) f(&mut *dom)
} }
fn class_functions_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut ClassFunctions)->mlua::Result<T>)->mlua::Result<T>{ fn class_functions_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut ClassMethodsStore)->mlua::Result<T>)->mlua::Result<T>{
let mut cf=lua.app_data_mut::<ClassFunctions>().ok_or(mlua::Error::runtime("ClassFunctions missing"))?; let mut cf=lua.app_data_mut::<ClassMethodsStore>().ok_or(mlua::Error::runtime("ClassFunctions missing"))?;
f(&mut *cf) f(&mut *cf)
} }
@ -369,12 +369,13 @@ static CLASS_FUNCTION_DATABASE:CFD=phf::phf_map!{
"GetService"=>cf!(|lua,_this,service:mlua::String|{ "GetService"=>cf!(|lua,_this,service:mlua::String|{
dom_mut(lua,|dom|{ dom_mut(lua,|dom|{
//dom.root_ref()==this.referent ? //dom.root_ref()==this.referent ?
match &*service.to_str()?{ let service=&*service.to_str()?;
"Lighting"=>{ match service{
let referent=find_first_child_of_class(dom,dom.root(),"Lighting") "Lighting"|"RunService"=>{
let referent=find_first_child_of_class(dom,dom.root(),service)
.map(|instance|instance.referent()) .map(|instance|instance.referent())
.unwrap_or_else(|| .unwrap_or_else(||
dom.insert(dom.root_ref(),InstanceBuilder::new("Lighting")) dom.insert(dom.root_ref(),InstanceBuilder::new(service))
); );
Ok(Instance::new(referent)) Ok(Instance::new(referent))
}, },
@ -391,14 +392,14 @@ static CLASS_FUNCTION_DATABASE:CFD=phf::phf_map!{
/// A store of created functions for each Roblox class. /// A store of created functions for each Roblox class.
/// Functions are created the first time they are accessed and stored in this data structure. /// Functions are created the first time they are accessed and stored in this data structure.
#[derive(Default)] #[derive(Default)]
struct ClassFunctions{ struct ClassMethodsStore{
classes:HashMap<&'static str,//ClassName classes:HashMap<&'static str,//ClassName
HashMap<&'static str,//Method name HashMap<&'static str,//Method name
mlua::Function mlua::Function
> >
> >
} }
impl ClassFunctions{ impl ClassMethodsStore{
/// return self.classes[class] or create the ClassMethods and then return it /// return self.classes[class] or create the ClassMethods and then return it
fn get_or_create_class_methods(&mut self,class:&str)->Option<ClassMethods>{ fn get_or_create_class_methods(&mut self,class:&str)->Option<ClassMethods>{
// Use get_entry to get the &'static str keys of the database // Use get_entry to get the &'static str keys of the database
@ -481,3 +482,58 @@ fn find_virtual_property(
//Transform Source property with provided function //Transform Source property with provided function
(virtual_property.pointer)(variant) (virtual_property.pointer)(variant)
} }
// 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
CreateUserData
>
>;
static LAZY_USER_DATA:LUD=phf::phf_map!{
"RunService"=>phf::phf_map!{
"RenderStepped"=>|lua|{
lua.create_any_userdata(super::script_signal::ScriptSignal::new())
},
},
};
struct InstanceValueStore{
values:HashMap<Ref,
HashMap<&'static str,
mlua::AnyUserData
>
>,
}
struct InstanceValues<'a>{
named_values:&'static phf::Map<&'static str,CreateUserData>,
values:&'a mut HashMap<&'static str,mlua::AnyUserData>,
}
impl InstanceValueStore{
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()),
}
)
}
}
impl InstanceValues<'_>{
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,
})
}
}

View File

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

View File

@ -0,0 +1,72 @@
use std::{cell::RefCell,rc::Rc};
#[derive(Clone)]
pub struct ScriptSignal{
// Emulate the garbage roblox api.
// ScriptConnection should not exist.
// :Disconnect should be a method on ScriptSignal, and this would be avoided entirely.
callbacks:Rc<RefCell<Vec<mlua::Function>>>,
}
pub struct ScriptConnection{
signal:ScriptSignal,
function:mlua::Function,
}
impl ScriptSignal{
pub fn new()->Self{
Self{
callbacks:Rc::new(RefCell::new(Vec::new())),
}
}
// This eats the Lua error
pub fn fire(&self,args:mlua::MultiValue){
// Make a copy of the list in case Lua attempts to modify it during the loop
let functions=self.callbacks.borrow().clone();
for function in functions{
//wee let's allocate for our function calls
if let Err(e)=function.call::<mlua::MultiValue>(args.clone()){
println!("Script Signal Error: {e}");
}
}
}
pub fn connect(&self,function:mlua::Function)->ScriptConnection{
self.callbacks.borrow_mut().push(function.clone());
ScriptConnection{
signal:self.clone(),
function,
}
}
}
impl ScriptConnection{
pub fn position(&self)->Option<usize>{
self.signal.callbacks.borrow().iter().position(|function|function==&self.function)
}
}
impl mlua::UserData for ScriptSignal{
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
methods.add_method("Connect",|_lua,this,f:mlua::Function|
Ok(this.connect(f))
);
// Fire is not allowed to be called from Lua
// methods.add_method("Fire",|_lua,this,args:mlua::MultiValue|
// Ok(this.fire(args))
// );
}
}
//type_from_lua_userdata!(ScriptSignal);
impl mlua::UserData for ScriptConnection{
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
fields.add_field_method_get("Connected",|_,this|{
Ok(this.position().is_some())
});
}
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
methods.add_method("Disconnect",|_,this,_:()|{
if let Some(index)=this.position(){
this.signal.callbacks.borrow_mut().remove(index);
}
Ok(())
});
}
}