1use crate::error::IsleError;
4use crate::hook::CancelToken;
5use crate::task::Task;
6use crate::thread;
7use crate::Request;
8use std::sync::mpsc;
9use std::sync::Mutex;
10use std::thread::JoinHandle;
11
12#[must_use = "use .shutdown() for clean thread join; dropping without shutdown leaks the thread"]
28pub struct Isle {
29 tx: mpsc::Sender<Request>,
30 join: Mutex<Option<JoinHandle<()>>>,
31}
32
33impl Isle {
34 pub fn spawn<F>(init: F) -> Result<Self, IsleError>
44 where
45 F: FnOnce(&mlua::Lua) -> Result<(), mlua::Error> + Send + 'static,
46 {
47 let (tx, rx) = mpsc::channel::<Request>();
48 let (init_tx, init_rx) = mpsc::channel::<Result<(), IsleError>>();
49
50 let join = std::thread::Builder::new()
51 .name("mlua-isle".into())
52 .spawn(move || {
53 let lua = mlua::Lua::new();
54 match init(&lua) {
55 Ok(()) => {
56 let _ = init_tx.send(Ok(()));
57 thread::run_loop(lua, rx);
58 }
59 Err(e) => {
60 let _ = init_tx.send(Err(IsleError::Init(e.to_string())));
61 }
62 }
63 })
64 .map_err(|e| IsleError::Init(format!("thread spawn failed: {e}")))?;
65
66 init_rx
68 .recv()
69 .map_err(|e| IsleError::Init(format!("init channel closed: {e}")))??;
70
71 Ok(Self {
72 tx,
73 join: Mutex::new(Some(join)),
74 })
75 }
76
77 pub fn eval(&self, code: &str) -> Result<String, IsleError> {
82 self.spawn_eval(code).wait()
83 }
84
85 pub fn spawn_eval(&self, code: &str) -> Task {
87 let cancel = CancelToken::new();
88 let (resp_tx, resp_rx) = mpsc::channel();
89
90 let req = Request::Eval {
91 code: code.to_string(),
92 cancel: cancel.clone(),
93 tx: resp_tx,
94 };
95
96 if self.tx.send(req).is_err() {
97 let (err_tx, err_rx) = mpsc::channel();
99 let _ = err_tx.send(Err(IsleError::Shutdown));
100 return Task::new(err_rx, cancel);
101 }
102
103 Task::new(resp_rx, cancel)
104 }
105
106 pub fn call(&self, func: &str, args: &[&str]) -> Result<String, IsleError> {
108 self.spawn_call(func, args).wait()
109 }
110
111 pub fn spawn_call(&self, func: &str, args: &[&str]) -> Task {
113 let cancel = CancelToken::new();
114 let (resp_tx, resp_rx) = mpsc::channel();
115
116 let req = Request::Call {
117 func: func.to_string(),
118 args: args.iter().map(|s| s.to_string()).collect(),
119 cancel: cancel.clone(),
120 tx: resp_tx,
121 };
122
123 if self.tx.send(req).is_err() {
124 let (err_tx, err_rx) = mpsc::channel();
125 let _ = err_tx.send(Err(IsleError::Shutdown));
126 return Task::new(err_rx, cancel);
127 }
128
129 Task::new(resp_rx, cancel)
130 }
131
132 pub fn exec<F>(&self, f: F) -> Result<String, IsleError>
143 where
144 F: FnOnce(&mlua::Lua) -> Result<String, IsleError> + Send + 'static,
145 {
146 self.spawn_exec(f).wait()
147 }
148
149 pub fn spawn_exec<F>(&self, f: F) -> Task
151 where
152 F: FnOnce(&mlua::Lua) -> Result<String, IsleError> + Send + 'static,
153 {
154 let cancel = CancelToken::new();
155 let (resp_tx, resp_rx) = mpsc::channel();
156
157 let req = Request::Exec {
158 f: Box::new(f),
159 cancel: cancel.clone(),
160 tx: resp_tx,
161 };
162
163 if self.tx.send(req).is_err() {
164 let (err_tx, err_rx) = mpsc::channel();
165 let _ = err_tx.send(Err(IsleError::Shutdown));
166 return Task::new(err_rx, cancel);
167 }
168
169 Task::new(resp_rx, cancel)
170 }
171
172 pub fn shutdown(self) -> Result<(), IsleError> {
177 let _ = self.tx.send(Request::Shutdown);
178 let handle = self.join.lock().map_err(|_| IsleError::ThreadPanic)?.take();
179 if let Some(join) = handle {
180 join.join().map_err(|_| IsleError::ThreadPanic)?;
181 }
182 Ok(())
183 }
184
185 pub fn is_alive(&self) -> bool {
187 self.join
188 .lock()
189 .ok()
190 .and_then(|guard| guard.as_ref().map(|j| !j.is_finished()))
191 .unwrap_or(false)
192 }
193}
194
195impl Drop for Isle {
196 fn drop(&mut self) {
197 let _ = self.tx.send(Request::Shutdown);
199 }
202}