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