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}