#![doc(hidden)] use derive_more::{Deref, DerefMut}; use luaffi::{Module, Registry}; use luajit::{Chunk, State}; use std::rc::Rc; use tokio::{ task::{JoinHandle, LocalSet, futures::TaskLocalFuture, spawn_local}, task_local, }; pub type ErrorFn = dyn Fn(&luajit::Error); pub struct Builder { registry: Registry, report_err: Rc, } impl Builder { pub fn new() -> Self { Self { registry: Registry::new(), report_err: Rc::new(|err| match err.trace() { Some(trace) => eprintln!("unhandled lua error: {err}\n{trace}"), None => eprintln!("unhandled lua error: {err}"), }), } } pub fn registry(&self) -> &Registry { &self.registry } pub fn unhandled_error(&mut self, handler: impl Fn(&luajit::Error) + 'static) -> &mut Self { self.report_err = Rc::new(handler); self } pub fn module(&mut self) -> &mut Self { self.registry.preload::(); self } pub fn build(&self) -> luajit::Result { Ok(Runtime { cx: Context { state: { let mut s = State::new()?; s.eval(Chunk::new(self.registry.build()).path("[luby]"), 0, 0)?; s }, report_err: self.report_err.clone(), }, tasks: LocalSet::new(), }) } } #[derive(Deref, DerefMut)] pub struct Runtime { #[deref] #[deref_mut] cx: Context, tasks: LocalSet, } impl Runtime { pub fn spawn( &self, f: impl AsyncFnOnce(&mut Context) -> T + 'static, ) -> JoinHandle { self.tasks .spawn_local(async move { f(&mut CURRENT.with(|s| s.new_thread())).await }) } } impl IntoFuture for Runtime { type Output = (); type IntoFuture = TaskLocalFuture; fn into_future(self) -> Self::IntoFuture { CURRENT.scope(self.cx, self.tasks) } } task_local! { static CURRENT: Context; } #[derive(Deref, DerefMut)] pub struct Context { #[deref] #[deref_mut] state: State, report_err: Rc, } impl Context { pub fn new_thread(&self) -> Self { Self { state: self.state.new_thread(), report_err: self.report_err.clone(), } } pub fn report_error(&self, err: &luajit::Error) { (self.report_err)(&err); } } pub fn spawn(f: impl AsyncFnOnce(&mut Context) -> T + 'static) -> JoinHandle { // SAFETY: `new_thread` must be called inside `spawn_local` because this free-standing spawn // function may be called via ffi from lua, and it is not safe to access the lua state within // ffi calls. spawn_local(async move { f(&mut CURRENT.with(|s| s.new_thread())).await }) }