Skip to main content

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}