From fd2aa199498f5c4b7a8d851c380e4668cabddcf6 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 18 Oct 2024 11:09:00 -0700 Subject: [PATCH] rewrite ScriptSignal with more features --- src/runner/runner.rs | 3 +- src/runner/script_signal.rs | 128 +++++++++++++++++++++++++++++++----- 2 files changed, 112 insertions(+), 19 deletions(-) diff --git a/src/runner/runner.rs b/src/runner/runner.rs index 7137229..4b25bd6 100644 --- a/src/runner/runner.rs +++ b/src/runner/runner.rs @@ -33,6 +33,7 @@ fn init(lua:&mlua::Lua)->mlua::Result<()>{ #[cfg(feature="run-service")] crate::scheduler::set_globals(lua,&globals)?; + super::script_signal::set_globals(lua,&globals)?; super::r#enum::set_globals(lua,&globals)?; super::color3::set_globals(lua,&globals)?; super::vector3::set_globals(lua,&globals)?; @@ -135,7 +136,7 @@ impl Runnable<'_>{ })?; if let Some(render_stepped)=render_stepped{ let signal:&super::script_signal::ScriptSignal=&*render_stepped.borrow()?; - signal.fire(mlua::MultiValue::new()); + signal.fire(&mlua::MultiValue::new()); } Ok(()) } diff --git a/src/runner/script_signal.rs b/src/runner/script_signal.rs index 992ab95..45eab8e 100644 --- a/src/runner/script_signal.rs +++ b/src/runner/script_signal.rs @@ -1,44 +1,98 @@ use std::{cell::RefCell,rc::Rc}; +use mlua::UserDataFields; + #[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>>, +struct FunctionList{ + functions:Vec, } -pub struct ScriptConnection{ - signal:ScriptSignal, - function:mlua::Function, -} -impl ScriptSignal{ +impl FunctionList{ pub fn new()->Self{ Self{ - callbacks:Rc::new(RefCell::new(Vec::new())), + functions:Vec::new(), } } // This eats the Lua error - pub fn fire(&self,args:mlua::MultiValue){ + 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{ + for function in self.functions{ //wee let's allocate for our function calls if let Err(e)=function.call::(args.clone()){ println!("Script Signal Error: {e}"); } } } +} +#[derive(Clone)] +struct RcFunctionList{ + functions:Rc>, +} +impl RcFunctionList{ + pub fn new()->Self{ + Self{ + functions:Rc::new(RefCell::new(FunctionList::new())), + } + } + pub fn fire(&self,args:&mlua::MultiValue){ + // Make a copy of the list in case Lua attempts to modify it during the loop + self.functions.borrow().clone().fire(args) + } +} +#[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. + connections:RcFunctionList, + once:RcFunctionList, + wait:Rc>>, +} +pub struct ScriptConnection{ + connection:RcFunctionList, + function:mlua::Function, +} +impl ScriptSignal{ + pub fn new()->Self{ + Self{ + connections:RcFunctionList::new(), + once:RcFunctionList::new(), + wait:Rc::new(RefCell::new(Vec::new())), + } + } + pub fn fire(&self,args:&mlua::MultiValue){ + self.connections.fire(args); + //Replace the FunctionList with an empty one and drop the borrow + let once=std::mem::replace(&mut *self.once.functions.borrow_mut(),FunctionList::new()); + once.fire(args); + //resume threads waiting for this signal + let threads=std::mem::replace(&mut *self.wait.borrow_mut(),Vec::new()); + for thread in threads{ + if let Err(e)=thread.resume::(args.clone()){ + println!("Script Signal thread resume Error: {e}"); + } + } + } pub fn connect(&self,function:mlua::Function)->ScriptConnection{ - self.callbacks.borrow_mut().push(function.clone()); + self.connections.functions.borrow_mut().functions.push(function.clone()); ScriptConnection{ - signal:self.clone(), + connection:self.connections.clone(), function, } } + pub fn once(&self,function:mlua::Function)->ScriptConnection{ + self.once.functions.borrow_mut().functions.push(function.clone()); + ScriptConnection{ + connection:self.once.clone(), + function, + } + } + pub fn wait(&self,thread:mlua::Thread){ + self.wait.borrow_mut().push(thread); + } } impl ScriptConnection{ pub fn position(&self)->Option{ - self.signal.callbacks.borrow().iter().position(|function|function==&self.function) + self.connection.functions.borrow().functions.iter().position(|function|function==&self.function) } } @@ -47,6 +101,9 @@ impl mlua::UserData for ScriptSignal{ methods.add_method("Connect",|_lua,this,f:mlua::Function| Ok(this.connect(f)) ); + methods.add_method("Once",|_lua,this,f:mlua::Function| + Ok(this.once(f)) + ); // Fire is not allowed to be called from Lua // methods.add_method("Fire",|_lua,this,args:mlua::MultiValue| // Ok(this.fire(args)) @@ -71,9 +128,44 @@ impl mlua::UserData for ScriptConnection{ fn add_methods>(methods:&mut M){ methods.add_method("Disconnect",|_,this,_:()|{ if let Some(index)=this.position(){ - this.signal.callbacks.borrow_mut().remove(index); + this.connection.functions.borrow_mut().functions.remove(index); } Ok(()) }); } } + +fn wait_thread(lua:&mlua::Lua,this:ScriptSignal)->Result<(),mlua::Error>{ + Ok(this.wait(lua.current_thread())) +} + +// This is used to avoid calling coroutine.yield from the rust side. +const LUA_WAIT:&str= +"local coroutine_yield=coroutine.yield +local wait_thread=wait_thread +return function(signal) + wait_thread(signal) + return coroutine_yield() +end"; + +pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{ + let coroutine_table=globals.get::("coroutine")?; + let wait_thread=lua.create_function(wait_thread)?; + + //create wait function environment + let wait_env=lua.create_table()?; + wait_env.raw_set("coroutine",coroutine_table)?; + wait_env.raw_set("wait_thread",wait_thread)?; + + //construct wait function from Lua code + let wait=lua.load(LUA_WAIT) + .set_name("wait") + .set_environment(wait_env) + .call::(())?; + + lua.register_userdata_type::(|reg|{ + reg.add_field("Wait",wait); + })?; + + Ok(()) +}