mlua_luau_scheduler/traits.rs
1#![allow(unused_imports)]
2#![allow(clippy::missing_errors_doc)]
3
4use std::{
5 cell::Cell, future::Future, process::ExitCode, rc::Weak as WeakRc, sync::Weak as WeakArc,
6};
7
8use async_executor::{Executor, Task};
9use mlua::prelude::*;
10use tracing::trace;
11
12use crate::{
13 exit::Exit,
14 queue::{DeferredThreadQueue, FuturesQueue, SpawnedThreadQueue},
15 scheduler::Scheduler,
16 threads::{ThreadId, ThreadMap},
17};
18
19/**
20 Trait for any struct that can be turned into an [`LuaThread`]
21 and passed to the scheduler, implemented for the following types:
22
23 - Lua threads ([`LuaThread`])
24 - Lua functions ([`LuaFunction`])
25 - Lua chunks ([`LuaChunk`])
26*/
27pub trait IntoLuaThread {
28 /**
29 Converts the value into a Lua thread.
30
31 # Errors
32
33 Errors when out of memory.
34 */
35 fn into_lua_thread(self, lua: &Lua) -> LuaResult<LuaThread>;
36}
37
38impl IntoLuaThread for LuaThread {
39 fn into_lua_thread(self, _: &Lua) -> LuaResult<LuaThread> {
40 Ok(self)
41 }
42}
43
44impl IntoLuaThread for LuaFunction {
45 fn into_lua_thread(self, lua: &Lua) -> LuaResult<LuaThread> {
46 lua.create_thread(self)
47 }
48}
49
50impl IntoLuaThread for LuaChunk<'_> {
51 fn into_lua_thread(self, lua: &Lua) -> LuaResult<LuaThread> {
52 lua.create_thread(self.into_function()?)
53 }
54}
55
56impl<T> IntoLuaThread for &T
57where
58 T: IntoLuaThread + Clone,
59{
60 fn into_lua_thread(self, lua: &Lua) -> LuaResult<LuaThread> {
61 self.clone().into_lua_thread(lua)
62 }
63}
64
65/**
66 Trait for interacting with the current [`Scheduler`].
67
68 Provides extra methods on the [`Lua`] struct for:
69
70 - Setting the exit code and forcibly stopping the scheduler
71 - Pushing (spawning) and deferring (pushing to the back) lua threads
72 - Tracking and getting the result of lua threads
73*/
74pub trait LuaSchedulerExt {
75 /**
76 Sets the exit code of the current scheduler.
77
78 See [`Scheduler::set_exit_code`] for more information.
79
80 # Panics
81
82 Panics if called outside of a running [`Scheduler`].
83 */
84 fn set_exit_code(&self, code: u8);
85
86 /**
87 Pushes (spawns) a lua thread to the **front** of the current scheduler.
88
89 See [`Scheduler::push_thread_front`] for more information.
90
91 # Panics
92
93 Panics if called outside of a running [`Scheduler`].
94 */
95 fn push_thread_front(
96 &self,
97 thread: impl IntoLuaThread,
98 args: impl IntoLuaMulti,
99 ) -> LuaResult<ThreadId>;
100
101 /**
102 Pushes (defers) a lua thread to the **back** of the current scheduler.
103
104 See [`Scheduler::push_thread_back`] for more information.
105
106 # Panics
107
108 Panics if called outside of a running [`Scheduler`].
109 */
110 fn push_thread_back(
111 &self,
112 thread: impl IntoLuaThread,
113 args: impl IntoLuaMulti,
114 ) -> LuaResult<ThreadId>;
115
116 /**
117 Registers the given thread to be tracked within the current scheduler.
118
119 Must be called before waiting for a thread to complete or getting its result.
120 */
121 fn track_thread(&self, id: ThreadId);
122
123 /**
124 Gets the result of the given thread.
125
126 See [`Scheduler::get_thread_result`] for more information.
127
128 # Panics
129
130 Panics if called outside of a running [`Scheduler`].
131 */
132 fn get_thread_result(&self, id: ThreadId) -> Option<LuaResult<LuaMultiValue>>;
133
134 /**
135 Waits for the given thread to complete.
136
137 See [`Scheduler::wait_for_thread`] for more information.
138
139 # Panics
140
141 Panics if called outside of a running [`Scheduler`].
142 */
143 fn wait_for_thread(&self, id: ThreadId) -> impl Future<Output = ()>;
144}
145
146/**
147 Trait for interacting with the [`Executor`] for the current [`Scheduler`].
148
149 Provides extra methods on the [`Lua`] struct for:
150
151 - Spawning thread-local (`!Send`) futures on the current executor
152 - Spawning background (`Send`) futures on the current executor
153 - Spawning blocking tasks on a separate thread pool
154*/
155pub trait LuaSpawnExt {
156 /**
157 Spawns the given future on the current executor and returns its [`Task`].
158
159 # Panics
160
161 Panics if called outside of a running [`Scheduler`].
162
163 # Example usage
164
165 ```rust
166 use async_io::block_on;
167
168 use mlua::prelude::*;
169 use mlua_luau_scheduler::*;
170
171 fn main() -> LuaResult<()> {
172 let lua = Lua::new();
173
174 lua.globals().set(
175 "spawnBackgroundTask",
176 lua.create_async_function(|lua, ()| async move {
177 lua.spawn(async move {
178 println!("Hello from background task!");
179 }).await;
180 Ok(())
181 })?
182 )?;
183
184 let sched = Scheduler::new(lua.clone());
185 sched.push_thread_front(lua.load("spawnBackgroundTask()"), ());
186 block_on(sched.run());
187
188 Ok(())
189 }
190 ```
191 */
192 fn spawn<F, T>(&self, fut: F) -> Task<T>
193 where
194 F: Future<Output = T> + Send + 'static,
195 T: Send + 'static;
196
197 /**
198 Spawns the given thread-local future on the current executor.
199
200 Note that this future will run detached and always to completion,
201 preventing the [`Scheduler`] was spawned on from completing until done.
202
203 # Panics
204
205 Panics if called outside of a running [`Scheduler`].
206
207 # Example usage
208
209 ```rust
210 use async_io::block_on;
211
212 use mlua::prelude::*;
213 use mlua_luau_scheduler::*;
214
215 fn main() -> LuaResult<()> {
216 let lua = Lua::new();
217
218 lua.globals().set(
219 "spawnLocalTask",
220 lua.create_async_function(|lua, ()| async move {
221 lua.spawn_local(async move {
222 println!("Hello from local task!");
223 });
224 Ok(())
225 })?
226 )?;
227
228 let sched = Scheduler::new(lua.clone());
229 sched.push_thread_front(lua.load("spawnLocalTask()"), ());
230 block_on(sched.run());
231
232 Ok(())
233 }
234 ```
235 */
236 fn spawn_local<F>(&self, fut: F)
237 where
238 F: Future<Output = ()> + 'static;
239
240 /**
241 Spawns the given blocking function and returns its [`Task`].
242
243 This function will run on a separate thread pool and not block the current executor.
244
245 # Panics
246
247 Panics if called outside of a running [`Scheduler`].
248
249 # Example usage
250
251 ```rust
252 use async_io::block_on;
253
254 use mlua::prelude::*;
255 use mlua_luau_scheduler::*;
256
257 fn main() -> LuaResult<()> {
258 let lua = Lua::new();
259
260 lua.globals().set(
261 "spawnBlockingTask",
262 lua.create_async_function(|lua, ()| async move {
263 lua.spawn_blocking(|| {
264 println!("Hello from blocking task!");
265 }).await;
266 Ok(())
267 })?
268 )?;
269
270 let sched = Scheduler::new(lua.clone());
271 sched.push_thread_front(lua.load("spawnBlockingTask()"), ());
272 block_on(sched.run());
273
274 Ok(())
275 }
276 ```
277 */
278 fn spawn_blocking<F, T>(&self, f: F) -> Task<T>
279 where
280 F: FnOnce() -> T + Send + 'static,
281 T: Send + 'static;
282}
283
284impl LuaSchedulerExt for Lua {
285 fn set_exit_code(&self, code: u8) {
286 let exit = self
287 .app_data_ref::<Exit>()
288 .expect("exit code can only be set from within an active scheduler");
289 exit.set(code);
290 }
291
292 fn push_thread_front(
293 &self,
294 thread: impl IntoLuaThread,
295 args: impl IntoLuaMulti,
296 ) -> LuaResult<ThreadId> {
297 let queue = self
298 .app_data_ref::<SpawnedThreadQueue>()
299 .expect("lua threads can only be pushed from within an active scheduler");
300 queue.push_item(self, thread, args)
301 }
302
303 fn push_thread_back(
304 &self,
305 thread: impl IntoLuaThread,
306 args: impl IntoLuaMulti,
307 ) -> LuaResult<ThreadId> {
308 let queue = self
309 .app_data_ref::<DeferredThreadQueue>()
310 .expect("lua threads can only be pushed from within an active scheduler");
311 queue.push_item(self, thread, args)
312 }
313
314 fn track_thread(&self, id: ThreadId) {
315 let map = self
316 .app_data_ref::<ThreadMap>()
317 .expect("lua threads can only be tracked from within an active scheduler");
318 map.track(id);
319 }
320
321 fn get_thread_result(&self, id: ThreadId) -> Option<LuaResult<LuaMultiValue>> {
322 let map = self
323 .app_data_ref::<ThreadMap>()
324 .expect("lua threads results can only be retrieved from within an active scheduler");
325 map.remove(id)
326 }
327
328 fn wait_for_thread(&self, id: ThreadId) -> impl Future<Output = ()> {
329 let map = self
330 .app_data_ref::<ThreadMap>()
331 .expect("lua threads results can only be retrieved from within an active scheduler");
332 map.listen(id)
333 }
334}
335
336impl LuaSpawnExt for Lua {
337 fn spawn<F, T>(&self, fut: F) -> Task<T>
338 where
339 F: Future<Output = T> + Send + 'static,
340 T: Send + 'static,
341 {
342 let exec = self
343 .app_data_ref::<WeakArc<Executor>>()
344 .expect("tasks can only be spawned within an active scheduler")
345 .upgrade()
346 .expect("executor was dropped");
347 trace!("spawning future on executor");
348 exec.spawn(fut)
349 }
350
351 fn spawn_local<F>(&self, fut: F)
352 where
353 F: Future<Output = ()> + 'static,
354 {
355 let queue = self
356 .app_data_ref::<FuturesQueue>()
357 .expect("tasks can only be spawned within an active scheduler");
358 trace!("spawning local task on executor");
359 queue.push_item(fut);
360 }
361
362 fn spawn_blocking<F, T>(&self, f: F) -> Task<T>
363 where
364 F: FnOnce() -> T + Send + 'static,
365 T: Send + 'static,
366 {
367 let exec = self
368 .app_data_ref::<WeakArc<Executor>>()
369 .expect("tasks can only be spawned within an active scheduler")
370 .upgrade()
371 .expect("executor was dropped");
372 trace!("spawning blocking task on executor");
373 exec.spawn(blocking::unblock(f))
374 }
375}