use crate::context::Context; #[cfg(feature="run-service")] use crate::scheduler::scheduler_mut; pub struct Runner{ lua:mlua::Lua, } #[derive(Debug)] pub enum Error{ Lua{ source:String, error:mlua::Error }, RustLua(mlua::Error), NoServices, } impl std::fmt::Display for Error{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ match self{ Self::Lua{source,error}=>write!(f,"lua error: source:\n{source}\n{error}"), Self::RustLua(error)=>write!(f,"rust-side lua error: {error}"), other=>write!(f,"{other:?}"), } } } impl std::error::Error for Error{} fn init(lua:&mlua::Lua)->mlua::Result<()>{ lua.sandbox(true)?; //global environment let globals=lua.globals(); #[cfg(feature="run-service")] crate::scheduler::set_globals(lua,&globals)?; super::r#enum::set_globals(lua,&globals)?; super::color3::set_globals(lua,&globals)?; 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(()) } impl Runner{ pub fn new()->Result{ let runner=Self{ lua:mlua::Lua::new(), }; init(&runner.lua).map_err(Error::RustLua)?; Ok(runner) } pub fn runnable_context<'a>(self,context:&'a mut Context)->Result,Error>{ let services=context.find_services().ok_or(Error::NoServices)?; self.runnable_context_with_services(context,&services) } pub fn runnable_context_with_services<'a>(self,context:&'a mut Context,services:&crate::context::Services)->Result,Error>{ { let globals=self.lua.globals(); globals.set("game",super::instance::Instance::new(services.game)).map_err(Error::RustLua)?; globals.set("workspace",super::instance::Instance::new(services.workspace)).map_err(Error::RustLua)?; } //this makes set_app_data shut up about the lifetime self.lua.set_app_data::<&'static mut rbx_dom_weak::WeakDom>(unsafe{core::mem::transmute(&mut context.dom)}); #[cfg(feature="run-service")] self.lua.set_app_data::(crate::scheduler::Scheduler::default()); Ok(Runnable{ lua:self.lua, _lifetime:&std::marker::PhantomData }) } } //Runnable is the same thing but has context set, which it holds the lifetime for. pub struct Runnable<'a>{ lua:mlua::Lua, _lifetime:&'a std::marker::PhantomData<()> } impl Runnable<'_>{ pub fn drop_context(self)->Runner{ self.lua.remove_app_data::<&'static mut rbx_dom_weak::WeakDom>(); #[cfg(feature="run-service")] self.lua.remove_app_data::(); Runner{ lua:self.lua, } } pub fn run_script(&self,script:super::instance::Instance)->Result<(),Error>{ let (name,source)=super::instance::get_name_source(&self.lua,script).map_err(Error::RustLua)?; self.lua.globals().raw_set("script",script).map_err(Error::RustLua)?; let f=self.lua.load(source.as_str()) .set_name(name).into_function().map_err(Error::RustLua)?; // TODO: set_environment without losing the ability to print from Lua let thread=self.lua.create_thread(f).map_err(Error::RustLua)?; thread.resume::(()).map_err(|error|Error::Lua{source,error})?; // wait() is called from inside Lua and goes to a rust function that schedules the thread and then yields // No need to schedule the thread here Ok(()) } #[cfg(feature="run-service")] pub fn has_scheduled_threads(&self)->Result{ scheduler_mut(&self.lua,|scheduler| Ok(scheduler.has_scheduled_threads()) ) } #[cfg(feature="run-service")] pub fn game_tick(&self)->Result<(),mlua::Error>{ if let Some(threads)=scheduler_mut(&self.lua,|scheduler|Ok(scheduler.tick_threads()))?{ for thread in threads{ //TODO: return dt and total run time let result=thread.resume::((1.0/30.0,0.0)) .map_err(|error|Error::Lua{source:"source unavailable".to_owned(),error}); match result{ Ok(_)=>(), Err(e)=>println!("game_tick Error: {e}"), } } } Ok(()) } }