#![allow(unused_imports)] #![allow(clippy::missing_errors_doc)] use std::{ cell::Cell, future::Future, process::ExitCode, rc::Weak as WeakRc, sync::Weak as WeakArc, }; use async_executor::{Executor, Task}; use mlua::prelude::*; use tracing::trace; use crate::{ exit::Exit, queue::{DeferredThreadQueue, FuturesQueue, SpawnedThreadQueue}, result_map::ThreadResultMap, scheduler::Scheduler, thread_id::ThreadId, }; /** Trait for any struct that can be turned into an [`LuaThread`] and passed to the scheduler, implemented for the following types: - Lua threads ([`LuaThread`]) - Lua functions ([`LuaFunction`]) - Lua chunks ([`LuaChunk`]) */ pub trait IntoLuaThread<'lua> { /** Converts the value into a Lua thread. # Errors Errors when out of memory. */ fn into_lua_thread(self, lua: &'lua Lua) -> LuaResult>; } impl<'lua> IntoLuaThread<'lua> for LuaThread<'lua> { fn into_lua_thread(self, _: &'lua Lua) -> LuaResult> { Ok(self) } } impl<'lua> IntoLuaThread<'lua> for LuaFunction<'lua> { fn into_lua_thread(self, lua: &'lua Lua) -> LuaResult> { lua.create_thread(self) } } impl<'lua> IntoLuaThread<'lua> for LuaChunk<'lua, '_> { fn into_lua_thread(self, lua: &'lua Lua) -> LuaResult> { lua.create_thread(self.into_function()?) } } impl<'lua, T> IntoLuaThread<'lua> for &T where T: IntoLuaThread<'lua> + Clone, { fn into_lua_thread(self, lua: &'lua Lua) -> LuaResult> { self.clone().into_lua_thread(lua) } } /** Trait for interacting with the current [`Scheduler`]. Provides extra methods on the [`Lua`] struct for: - Setting the exit code and forcibly stopping the scheduler - Pushing (spawning) and deferring (pushing to the back) lua threads - Tracking and getting the result of lua threads */ pub trait LuaSchedulerExt<'lua> { /** Sets the exit code of the current scheduler. See [`Scheduler::set_exit_code`] for more information. # Panics Panics if called outside of a running [`Scheduler`]. */ fn set_exit_code(&self, code: ExitCode); /** Pushes (spawns) a lua thread to the **front** of the current scheduler. See [`Scheduler::push_thread_front`] for more information. # Panics Panics if called outside of a running [`Scheduler`]. */ fn push_thread_front( &'lua self, thread: impl IntoLuaThread<'lua>, args: impl IntoLuaMulti<'lua>, ) -> LuaResult; /** Pushes (defers) a lua thread to the **back** of the current scheduler. See [`Scheduler::push_thread_back`] for more information. # Panics Panics if called outside of a running [`Scheduler`]. */ fn push_thread_back( &'lua self, thread: impl IntoLuaThread<'lua>, args: impl IntoLuaMulti<'lua>, ) -> LuaResult; /** Registers the given thread to be tracked within the current scheduler. Must be called before waiting for a thread to complete or getting its result. */ fn track_thread(&'lua self, id: ThreadId); /** Gets the result of the given thread. See [`Scheduler::get_thread_result`] for more information. # Panics Panics if called outside of a running [`Scheduler`]. */ fn get_thread_result(&'lua self, id: ThreadId) -> Option>>; /** Waits for the given thread to complete. See [`Scheduler::wait_for_thread`] for more information. # Panics Panics if called outside of a running [`Scheduler`]. */ fn wait_for_thread(&'lua self, id: ThreadId) -> impl Future; } /** Trait for interacting with the [`Executor`] for the current [`Scheduler`]. Provides extra methods on the [`Lua`] struct for: - Spawning thread-local (`!Send`) futures on the current executor - Spawning background (`Send`) futures on the current executor - Spawning blocking tasks on a separate thread pool */ pub trait LuaSpawnExt<'lua> { /** Spawns the given future on the current executor and returns its [`Task`]. # Panics Panics if called outside of a running [`Scheduler`]. # Example usage ```rust use async_io::block_on; use mlua::prelude::*; use mlua_luau_scheduler::*; fn main() -> LuaResult<()> { let lua = Lua::new(); lua.globals().set( "spawnBackgroundTask", lua.create_async_function(|lua, ()| async move { lua.spawn(async move { println!("Hello from background task!"); }).await; Ok(()) })? )?; let sched = Scheduler::new(&lua); sched.push_thread_front(lua.load("spawnBackgroundTask()"), ()); block_on(sched.run()); Ok(()) } ``` */ fn spawn(&self, fut: F) -> Task where F: Future + Send + 'static, T: Send + 'static; /** Spawns the given thread-local future on the current executor. Note that this future will run detached and always to completion, preventing the [`Scheduler`] was spawned on from completing until done. # Panics Panics if called outside of a running [`Scheduler`]. # Example usage ```rust use async_io::block_on; use mlua::prelude::*; use mlua_luau_scheduler::*; fn main() -> LuaResult<()> { let lua = Lua::new(); lua.globals().set( "spawnLocalTask", lua.create_async_function(|lua, ()| async move { lua.spawn_local(async move { println!("Hello from local task!"); }); Ok(()) })? )?; let sched = Scheduler::new(&lua); sched.push_thread_front(lua.load("spawnLocalTask()"), ()); block_on(sched.run()); Ok(()) } ``` */ fn spawn_local(&self, fut: F) where F: Future + 'static; /** Spawns the given blocking function and returns its [`Task`]. This function will run on a separate thread pool and not block the current executor. # Panics Panics if called outside of a running [`Scheduler`]. # Example usage ```rust use async_io::block_on; use mlua::prelude::*; use mlua_luau_scheduler::*; fn main() -> LuaResult<()> { let lua = Lua::new(); lua.globals().set( "spawnBlockingTask", lua.create_async_function(|lua, ()| async move { lua.spawn_blocking(|| { println!("Hello from blocking task!"); }).await; Ok(()) })? )?; let sched = Scheduler::new(&lua); sched.push_thread_front(lua.load("spawnBlockingTask()"), ()); block_on(sched.run()); Ok(()) } ``` */ fn spawn_blocking(&self, f: F) -> Task where F: FnOnce() -> T + Send + 'static, T: Send + 'static; } impl<'lua> LuaSchedulerExt<'lua> for Lua { fn set_exit_code(&self, code: ExitCode) { let exit = self .app_data_ref::() .expect("exit code can only be set from within an active scheduler"); exit.set(code); } fn push_thread_front( &'lua self, thread: impl IntoLuaThread<'lua>, args: impl IntoLuaMulti<'lua>, ) -> LuaResult { let queue = self .app_data_ref::() .expect("lua threads can only be pushed from within an active scheduler"); queue.push_item(self, thread, args) } fn push_thread_back( &'lua self, thread: impl IntoLuaThread<'lua>, args: impl IntoLuaMulti<'lua>, ) -> LuaResult { let queue = self .app_data_ref::() .expect("lua threads can only be pushed from within an active scheduler"); queue.push_item(self, thread, args) } fn track_thread(&'lua self, id: ThreadId) { let map = self .app_data_ref::() .expect("lua threads can only be tracked from within an active scheduler"); map.track(id); } fn get_thread_result(&'lua self, id: ThreadId) -> Option>> { let map = self .app_data_ref::() .expect("lua threads results can only be retrieved from within an active scheduler"); map.remove(id).map(|r| r.value(self)) } fn wait_for_thread(&'lua self, id: ThreadId) -> impl Future { let map = self .app_data_ref::() .expect("lua threads results can only be retrieved from within an active scheduler"); async move { map.listen(id).await } } } impl<'lua> LuaSpawnExt<'lua> for Lua { fn spawn(&self, fut: F) -> Task where F: Future + Send + 'static, T: Send + 'static, { let exec = self .app_data_ref::>() .expect("tasks can only be spawned within an active scheduler") .upgrade() .expect("executor was dropped"); trace!("spawning future on executor"); exec.spawn(fut) } fn spawn_local(&self, fut: F) where F: Future + 'static, { let queue = self .app_data_ref::>() .expect("tasks can only be spawned within an active scheduler") .upgrade() .expect("executor was dropped"); trace!("spawning local task on executor"); queue.push_item(fut); } fn spawn_blocking(&self, f: F) -> Task where F: FnOnce() -> T + Send + 'static, T: Send + 'static, { let exec = self .app_data_ref::>() .expect("tasks can only be spawned within an active scheduler") .upgrade() .expect("executor was dropped"); trace!("spawning blocking task on executor"); exec.spawn(blocking::unblock(f)) } }