use crate::error::IsleError;
use crate::hook::CancelToken;
use crate::task::Task;
use crate::thread;
use crate::Request;
use std::sync::mpsc;
use std::sync::Mutex;
use std::thread::JoinHandle;
#[must_use = "use .shutdown() for clean thread join; dropping without shutdown leaks the thread"]
pub struct Isle {
tx: mpsc::Sender<Request>,
join: Mutex<Option<JoinHandle<()>>>,
}
impl Isle {
pub fn spawn<F>(init: F) -> Result<Self, IsleError>
where
F: FnOnce(&mlua::Lua) -> Result<(), mlua::Error> + Send + 'static,
{
let (tx, rx) = mpsc::channel::<Request>();
let (init_tx, init_rx) = mpsc::channel::<Result<(), IsleError>>();
let join = std::thread::Builder::new()
.name("mlua-isle".into())
.spawn(move || {
let lua = mlua::Lua::new();
match init(&lua) {
Ok(()) => {
let _ = init_tx.send(Ok(()));
thread::run_loop(lua, rx);
}
Err(e) => {
let _ = init_tx.send(Err(IsleError::Init(e.to_string())));
}
}
})
.map_err(|e| IsleError::Init(format!("thread spawn failed: {e}")))?;
init_rx
.recv()
.map_err(|e| IsleError::Init(format!("init channel closed: {e}")))??;
Ok(Self {
tx,
join: Mutex::new(Some(join)),
})
}
pub fn eval(&self, code: &str) -> Result<String, IsleError> {
self.spawn_eval(code).wait()
}
pub fn spawn_eval(&self, code: &str) -> Task {
let cancel = CancelToken::new();
let (resp_tx, resp_rx) = mpsc::channel();
let req = Request::Eval {
code: code.to_string(),
cancel: cancel.clone(),
tx: resp_tx,
};
if self.tx.send(req).is_err() {
let (err_tx, err_rx) = mpsc::channel();
let _ = err_tx.send(Err(IsleError::Shutdown));
return Task::new(err_rx, cancel);
}
Task::new(resp_rx, cancel)
}
pub fn call(&self, func: &str, args: &[&str]) -> Result<String, IsleError> {
self.spawn_call(func, args).wait()
}
pub fn spawn_call(&self, func: &str, args: &[&str]) -> Task {
let cancel = CancelToken::new();
let (resp_tx, resp_rx) = mpsc::channel();
let req = Request::Call {
func: func.to_string(),
args: args.iter().map(|s| s.to_string()).collect(),
cancel: cancel.clone(),
tx: resp_tx,
};
if self.tx.send(req).is_err() {
let (err_tx, err_rx) = mpsc::channel();
let _ = err_tx.send(Err(IsleError::Shutdown));
return Task::new(err_rx, cancel);
}
Task::new(resp_rx, cancel)
}
pub fn exec<F>(&self, f: F) -> Result<String, IsleError>
where
F: FnOnce(&mlua::Lua) -> Result<String, IsleError> + Send + 'static,
{
self.spawn_exec(f).wait()
}
pub fn spawn_exec<F>(&self, f: F) -> Task
where
F: FnOnce(&mlua::Lua) -> Result<String, IsleError> + Send + 'static,
{
let cancel = CancelToken::new();
let (resp_tx, resp_rx) = mpsc::channel();
let req = Request::Exec {
f: Box::new(f),
cancel: cancel.clone(),
tx: resp_tx,
};
if self.tx.send(req).is_err() {
let (err_tx, err_rx) = mpsc::channel();
let _ = err_tx.send(Err(IsleError::Shutdown));
return Task::new(err_rx, cancel);
}
Task::new(resp_rx, cancel)
}
pub fn shutdown(self) -> Result<(), IsleError> {
let _ = self.tx.send(Request::Shutdown);
let handle = self.join.lock().map_err(|_| IsleError::ThreadPanic)?.take();
if let Some(join) = handle {
join.join().map_err(|_| IsleError::ThreadPanic)?;
}
Ok(())
}
pub fn is_alive(&self) -> bool {
self.join
.lock()
.ok()
.and_then(|guard| guard.as_ref().map(|j| !j.is_finished()))
.unwrap_or(false)
}
}
impl Drop for Isle {
fn drop(&mut self) {
let _ = self.tx.send(Request::Shutdown);
}
}