Compare commits

...

5 Commits

Author SHA1 Message Date
03e9d989f8 make scheduler into a crate feature 2024-10-16 20:55:42 -07:00
5ddb3dacfd run script as a thread 2024-10-16 20:55:42 -07:00
8950bcbf02 game ticks api 2024-10-16 20:55:42 -07:00
9c1807ec76 wait 2024-10-16 20:21:53 -07:00
752ad6bd95 scheduler 2024-10-16 19:53:58 -07:00
4 changed files with 143 additions and 4 deletions

View File

@ -7,6 +7,10 @@ license = "MIT OR Apache-2.0"
description = "Run embedded Luau scripts which manipulate the DOM." description = "Run embedded Luau scripts which manipulate the DOM."
authors = ["Rhys Lloyd <krakow20@gmail.com>"] authors = ["Rhys Lloyd <krakow20@gmail.com>"]
[features]
default=["run-service"]
run-service=[]
[dependencies] [dependencies]
glam = "0.29.0" glam = "0.29.0"
mlua = { version = "0.10.0-beta", features = ["luau"] } mlua = { version = "0.10.0-beta", features = ["luau"] }

View File

@ -1,5 +1,7 @@
pub mod runner; pub mod runner;
pub mod context; pub mod context;
#[cfg(feature="run-service")]
pub(crate) mod scheduler;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;

View File

@ -1,4 +1,6 @@
use crate::context::Context; use crate::context::Context;
#[cfg(feature="run-service")]
use crate::scheduler::scheduler_mut;
pub struct Runner{ pub struct Runner{
lua:mlua::Lua, lua:mlua::Lua,
@ -29,6 +31,8 @@ fn init(lua:&mlua::Lua)->mlua::Result<()>{
//global environment //global environment
let globals=lua.globals(); let globals=lua.globals();
#[cfg(feature="run-service")]
crate::scheduler::set_globals(lua,&globals)?;
super::r#enum::set_globals(lua,&globals)?; super::r#enum::set_globals(lua,&globals)?;
super::color3::set_globals(lua,&globals)?; super::color3::set_globals(lua,&globals)?;
super::vector3::set_globals(lua,&globals)?; super::vector3::set_globals(lua,&globals)?;
@ -60,6 +64,8 @@ impl Runner{
} }
//this makes set_app_data shut up about the lifetime //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)}); 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>(crate::scheduler::Scheduler::default());
Ok(Runnable{ Ok(Runnable{
lua:self.lua, lua:self.lua,
_lifetime:&std::marker::PhantomData _lifetime:&std::marker::PhantomData
@ -75,15 +81,43 @@ pub struct Runnable<'a>{
impl Runnable<'_>{ impl Runnable<'_>{
pub fn drop_context(self)->Runner{ pub fn drop_context(self)->Runner{
self.lua.remove_app_data::<&'static mut rbx_dom_weak::WeakDom>(); self.lua.remove_app_data::<&'static mut rbx_dom_weak::WeakDom>();
#[cfg(feature="run-service")]
self.lua.remove_app_data::<crate::scheduler::Scheduler>();
Runner{ Runner{
lua:self.lua, lua:self.lua,
} }
} }
pub fn run_script(&self,script:super::instance::Instance)->Result<(),Error>{ 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)?; let (name,source)=super::instance::get_name_source(&self.lua,script).map_err(Error::RustLua)?;
self.lua.globals().set("script",script).map_err(Error::RustLua)?; self.lua.globals().raw_set("script",script).map_err(Error::RustLua)?;
self.lua.load(source.as_str()) let f=self.lua.load(source.as_str())
.set_name(name) .set_name(name).into_function().map_err(Error::RustLua)?;
.exec().map_err(|error|Error::Lua{source,error}) // TODO: set_environment without losing the ability to print from Lua
let thread=self.lua.create_thread(f).map_err(Error::RustLua)?;
thread.resume::<mlua::MultiValue>(()).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<bool,mlua::Error>{
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::<mlua::MultiValue>((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(())
} }
} }

99
src/scheduler.rs Normal file
View File

@ -0,0 +1,99 @@
pub use tick::Tick;
mod tick{
#[derive(Clone,Copy,Default,Hash,PartialEq,Eq,PartialOrd,Ord)]
pub struct Tick(u64);
impl Tick{
pub const fn new(value:u64)->Self{
Self(value)
}
pub const fn get(&self)->u64{
self.0
}
}
impl std::ops::Add<u64> for Tick{
type Output=Self;
fn add(self,rhs:u64)->Self::Output{
Self(self.0+rhs)
}
}
impl std::ops::Sub<u64> for Tick{
type Output=Self;
fn sub(self,rhs:u64)->Self::Output{
Self(self.0-rhs)
}
}
impl std::ops::AddAssign<u64> for Tick{
fn add_assign(&mut self,rhs:u64){
self.0+=rhs;
}
}
impl std::ops::SubAssign<u64> for Tick{
fn sub_assign(&mut self,rhs:u64){
self.0-=rhs;
}
}
}
#[derive(Default)]
pub struct Scheduler{
tick:Tick,
schedule:std::collections::HashMap<Tick,Vec<mlua::Thread>>,
}
impl Scheduler{
pub const fn tick(&self)->Tick{
self.tick
}
pub fn has_scheduled_threads(&self)->bool{
!self.schedule.is_empty()
}
pub fn schedule_thread(&mut self,delay:u64,thread:mlua::Thread){
self.schedule.entry(self.tick+delay.max(1))
.or_insert(Vec::new())
.push(thread);
}
pub fn tick_threads(&mut self)->Option<Vec<mlua::Thread>>{
self.tick+=1;
self.schedule.remove(&self.tick)
}
}
fn coerce_float64(value:&mlua::Value)->Option<f64>{
match value{
&mlua::Value::Integer(i)=>Some(i as f64),
&mlua::Value::Number(f)=>Some(f),
_=>None,
}
}
pub fn scheduler_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut crate::scheduler::Scheduler)->mlua::Result<T>)->mlua::Result<T>{
let mut scheduler=lua.app_data_mut::<crate::scheduler::Scheduler>().ok_or(mlua::Error::runtime("Scheduler missing"))?;
f(&mut *scheduler)
}
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let schedule_thread=lua.create_function(move|lua,dt:mlua::Value|{
let delay=coerce_float64(&dt).ok_or(mlua::Error::runtime("Expected float"))?.max(0.0)*60.0;
if delay<u64::MAX as f64{
scheduler_mut(lua,|scheduler|{
scheduler.schedule_thread((delay as u64).max(2),lua.current_thread());
Ok(())
}).unwrap();
}
Ok(())
})?;
let wait_env=lua.create_table()?;
wait_env.raw_set("coroutine",globals.get::<mlua::Table>("coroutine")?)?;
wait_env.raw_set("schedule_thread",schedule_thread)?;
let wait=lua.load("
local coroutine_yield=coroutine.yield
local schedule_thread=schedule_thread
return function(dt)
schedule_thread(dt)
return coroutine_yield()
end
")
.set_name("wait")
.set_environment(wait_env)
.call::<mlua::Function>(())?;
globals.raw_set("wait",wait)?;
Ok(())
}