From c80e50428384106370485bdd7cf7840909f581d9 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Wed, 16 Aug 2023 15:30:46 -0500 Subject: [PATCH 001/103] Clean slate --- src/lune/error.rs | 8 +- src/lune/mod.rs | 64 +---------- src/{lune => old}/builtins/fs.rs | 2 +- src/{lune => old}/builtins/luau.rs | 2 +- src/{lune => old}/builtins/mod.rs | 0 src/{lune => old}/builtins/net.rs | 2 +- src/{lune => old}/builtins/process.rs | 2 +- src/{lune => old}/builtins/roblox.rs | 2 +- src/{lune => old}/builtins/serde.rs | 2 +- src/{lune => old}/builtins/stdio.rs | 2 +- src/{lune => old}/builtins/task.rs | 2 +- src/{lune => old}/builtins/top_level.rs | 2 +- src/old/error.rs | 83 ++++++++++++++ src/{lune => old}/importer/mod.rs | 2 +- src/{lune => old}/importer/require.rs | 2 +- src/{lune => old}/importer/require_waker.rs | 0 src/{lune => old}/lua/async_ext.rs | 2 +- src/{lune => old}/lua/create.rs | 0 src/{lune => old}/lua/fs/copy.rs | 0 src/{lune => old}/lua/fs/metadata.rs | 0 src/{lune => old}/lua/fs/mod.rs | 0 src/{lune => old}/lua/fs/options.rs | 0 src/{lune => old}/lua/luau/mod.rs | 0 src/{lune => old}/lua/luau/options.rs | 0 src/{lune => old}/lua/mod.rs | 0 src/{lune => old}/lua/net/client.rs | 0 src/{lune => old}/lua/net/config.rs | 0 src/{lune => old}/lua/net/mod.rs | 0 src/{lune => old}/lua/net/response.rs | 0 src/{lune => old}/lua/net/server.rs | 2 +- src/{lune => old}/lua/net/websocket.rs | 2 +- src/{lune => old}/lua/process/mod.rs | 0 src/{lune => old}/lua/process/tee_writer.rs | 0 .../lua/serde/compress_decompress.rs | 0 src/{lune => old}/lua/serde/encode_decode.rs | 0 src/{lune => old}/lua/serde/mod.rs | 0 src/{lune => old}/lua/stdio/formatting.rs | 2 +- src/{lune => old}/lua/stdio/mod.rs | 0 src/{lune => old}/lua/stdio/prompt.rs | 0 src/{lune => old}/lua/table/builder.rs | 2 +- src/{lune => old}/lua/table/mod.rs | 0 src/{lune => old}/lua/task/ext/async_ext.rs | 2 +- src/{lune => old}/lua/task/ext/mod.rs | 0 src/{lune => old}/lua/task/ext/resume_ext.rs | 0 .../lua/task/ext/schedule_ext.rs | 0 src/{lune => old}/lua/task/mod.rs | 0 src/{lune => old}/lua/task/proxy.rs | 0 src/{lune => old}/lua/task/scheduler.rs | 0 .../lua/task/scheduler_handle.rs | 0 .../lua/task/scheduler_message.rs | 0 src/{lune => old}/lua/task/scheduler_state.rs | 0 src/{lune => old}/lua/task/task_kind.rs | 0 src/{lune => old}/lua/task/task_reference.rs | 0 src/{lune => old}/lua/task/task_waiter.rs | 0 src/old/mod.rs | 107 ++++++++++++++++++ 55 files changed, 213 insertions(+), 83 deletions(-) rename src/{lune => old}/builtins/fs.rs (99%) rename src/{lune => old}/builtins/luau.rs (97%) rename src/{lune => old}/builtins/mod.rs (100%) rename src/{lune => old}/builtins/net.rs (99%) rename src/{lune => old}/builtins/process.rs (99%) rename src/{lune => old}/builtins/roblox.rs (98%) rename src/{lune => old}/builtins/serde.rs (98%) rename src/{lune => old}/builtins/stdio.rs (99%) rename src/{lune => old}/builtins/task.rs (99%) rename src/{lune => old}/builtins/top_level.rs (98%) create mode 100644 src/old/error.rs rename src/{lune => old}/importer/mod.rs (96%) rename src/{lune => old}/importer/require.rs (99%) rename src/{lune => old}/importer/require_waker.rs (100%) rename src/{lune => old}/lua/async_ext.rs (97%) rename src/{lune => old}/lua/create.rs (100%) rename src/{lune => old}/lua/fs/copy.rs (100%) rename src/{lune => old}/lua/fs/metadata.rs (100%) rename src/{lune => old}/lua/fs/mod.rs (100%) rename src/{lune => old}/lua/fs/options.rs (100%) rename src/{lune => old}/lua/luau/mod.rs (100%) rename src/{lune => old}/lua/luau/options.rs (100%) rename src/{lune => old}/lua/mod.rs (100%) rename src/{lune => old}/lua/net/client.rs (100%) rename src/{lune => old}/lua/net/config.rs (100%) rename src/{lune => old}/lua/net/mod.rs (100%) rename src/{lune => old}/lua/net/response.rs (100%) rename src/{lune => old}/lua/net/server.rs (99%) rename src/{lune => old}/lua/net/websocket.rs (99%) rename src/{lune => old}/lua/process/mod.rs (100%) rename src/{lune => old}/lua/process/tee_writer.rs (100%) rename src/{lune => old}/lua/serde/compress_decompress.rs (100%) rename src/{lune => old}/lua/serde/encode_decode.rs (100%) rename src/{lune => old}/lua/serde/mod.rs (100%) rename src/{lune => old}/lua/stdio/formatting.rs (99%) rename src/{lune => old}/lua/stdio/mod.rs (100%) rename src/{lune => old}/lua/stdio/prompt.rs (100%) rename src/{lune => old}/lua/table/builder.rs (97%) rename src/{lune => old}/lua/table/mod.rs (100%) rename src/{lune => old}/lua/task/ext/async_ext.rs (99%) rename src/{lune => old}/lua/task/ext/mod.rs (100%) rename src/{lune => old}/lua/task/ext/resume_ext.rs (100%) rename src/{lune => old}/lua/task/ext/schedule_ext.rs (100%) rename src/{lune => old}/lua/task/mod.rs (100%) rename src/{lune => old}/lua/task/proxy.rs (100%) rename src/{lune => old}/lua/task/scheduler.rs (100%) rename src/{lune => old}/lua/task/scheduler_handle.rs (100%) rename src/{lune => old}/lua/task/scheduler_message.rs (100%) rename src/{lune => old}/lua/task/scheduler_state.rs (100%) rename src/{lune => old}/lua/task/task_kind.rs (100%) rename src/{lune => old}/lua/task/task_reference.rs (100%) rename src/{lune => old}/lua/task/task_waiter.rs (100%) create mode 100644 src/old/mod.rs diff --git a/src/lune/error.rs b/src/lune/error.rs index c4d9f8c..dcb9222 100644 --- a/src/lune/error.rs +++ b/src/lune/error.rs @@ -5,8 +5,6 @@ use std::{ use mlua::prelude::*; -use crate::lune::lua::stdio::formatting::pretty_format_luau_error; - /** An opaque error type for formatted lua errors. */ @@ -66,11 +64,7 @@ impl From for LuneError { impl Display for LuneError { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - write!( - f, - "{}", - pretty_format_luau_error(&self.error, !self.disable_colors) - ) + write!(f, "{}", self.error) // TODO: Pretty formatting } } diff --git a/src/lune/mod.rs b/src/lune/mod.rs index a5050b7..6e5c286 100644 --- a/src/lune/mod.rs +++ b/src/lune/mod.rs @@ -1,12 +1,6 @@ use std::process::ExitCode; -use lua::task::{TaskScheduler, TaskSchedulerResumeExt, TaskSchedulerScheduleExt}; use mlua::prelude::*; -use tokio::task::LocalSet; - -pub mod builtins; -pub mod importer; -pub mod lua; mod error; @@ -37,17 +31,7 @@ impl Lune { } /** - Runs a Lune script. - - This will create a new sandboxed Luau environment with the configured - globals and arguments, running inside of a [`tokio::task::LocalSet`]. - - Some Lune globals may spawn separate tokio tasks on other threads, but the Luau - environment itself is guaranteed to run on a single thread in the local set. - - Note that this will create a static Lua instance and task scheduler that will - both live for the remainer of the program, and that this leaks memory using - [`Box::leak`] that will then get deallocated when the program exits. + Runs a Lune script inside of a new Luau VM. */ pub async fn run( &self, @@ -61,47 +45,9 @@ impl Lune { async fn run_inner( &self, - script_name: impl AsRef, - script_contents: impl AsRef<[u8]>, - ) -> Result { - // Create our special lune-flavored Lua object with extra registry values - let lua = lua::create_lune_lua()?.into_static(); - // Create our task scheduler and all globals - // NOTE: Some globals require the task scheduler to exist on startup - let sched = TaskScheduler::new(lua)?.into_static(); - lua.set_app_data(sched); - importer::create(lua, self.args.clone())?; - // Create the main thread and schedule it - let main_chunk = lua - .load(script_contents.as_ref()) - .set_name(script_name.as_ref()) - .into_function()?; - let main_thread = lua.create_thread(main_chunk)?; - let main_thread_args = LuaValue::Nil.into_lua_multi(lua)?; - sched.schedule_blocking(main_thread, main_thread_args)?; - // Keep running the scheduler until there are either no tasks - // left to run, or until a task requests to exit the process - let exit_code = LocalSet::new() - .run_until(async move { - let mut got_error = false; - loop { - let result = sched.resume_queue().await; - if let Some(err) = result.get_lua_error() { - eprintln!("{}", LuneError::from(err)); - got_error = true; - } - if result.is_done() { - if let Some(exit_code) = result.get_exit_code() { - break exit_code; - } else if got_error { - break ExitCode::FAILURE; - } else { - break ExitCode::SUCCESS; - } - } - } - }) - .await; - Ok(exit_code) + _script_name: impl AsRef, + _script_contents: impl AsRef<[u8]>, + ) -> LuaResult { + Ok(ExitCode::SUCCESS) } } diff --git a/src/lune/builtins/fs.rs b/src/old/builtins/fs.rs similarity index 99% rename from src/lune/builtins/fs.rs rename to src/old/builtins/fs.rs index fd2db01..d2b9bba 100644 --- a/src/lune/builtins/fs.rs +++ b/src/old/builtins/fs.rs @@ -4,7 +4,7 @@ use std::path::{PathBuf, MAIN_SEPARATOR}; use mlua::prelude::*; use tokio::fs; -use crate::lune::lua::{ +use crate::lune_temp::lua::{ fs::{copy, FsMetadata, FsWriteOptions}, table::TableBuilder, }; diff --git a/src/lune/builtins/luau.rs b/src/old/builtins/luau.rs similarity index 97% rename from src/lune/builtins/luau.rs rename to src/old/builtins/luau.rs index 6d0c7e4..54aeb6a 100644 --- a/src/lune/builtins/luau.rs +++ b/src/old/builtins/luau.rs @@ -1,6 +1,6 @@ use mlua::prelude::*; -use crate::lune::lua::{ +use crate::lune_temp::lua::{ luau::{LuauCompileOptions, LuauLoadOptions}, table::TableBuilder, }; diff --git a/src/lune/builtins/mod.rs b/src/old/builtins/mod.rs similarity index 100% rename from src/lune/builtins/mod.rs rename to src/old/builtins/mod.rs diff --git a/src/lune/builtins/net.rs b/src/old/builtins/net.rs similarity index 99% rename from src/lune/builtins/net.rs rename to src/old/builtins/net.rs index 0be55b5..e313928 100644 --- a/src/lune/builtins/net.rs +++ b/src/old/builtins/net.rs @@ -9,7 +9,7 @@ use hyper::{ }; use tokio::{sync::mpsc, task}; -use crate::lune::lua::{ +use crate::lune_temp::lua::{ net::{ NetClient, NetClientBuilder, NetLocalExec, NetService, NetWebSocket, RequestConfig, ServeConfig, diff --git a/src/lune/builtins/process.rs b/src/old/builtins/process.rs similarity index 99% rename from src/lune/builtins/process.rs rename to src/old/builtins/process.rs index 51e7c6f..6ef0fa8 100644 --- a/src/lune/builtins/process.rs +++ b/src/old/builtins/process.rs @@ -11,7 +11,7 @@ use mlua::prelude::*; use os_str_bytes::RawOsString; use tokio::process::Command; -use crate::lune::lua::{ +use crate::lune_temp::lua::{ process::pipe_and_inherit_child_process_stdio, table::TableBuilder, task::TaskScheduler, }; diff --git a/src/lune/builtins/roblox.rs b/src/old/builtins/roblox.rs similarity index 98% rename from src/lune/builtins/roblox.rs rename to src/old/builtins/roblox.rs index 2141d5c..2664f03 100644 --- a/src/lune/builtins/roblox.rs +++ b/src/old/builtins/roblox.rs @@ -10,7 +10,7 @@ use crate::roblox::{ use tokio::task; -use crate::lune::lua::table::TableBuilder; +use crate::lune_temp::lua::table::TableBuilder; static REFLECTION_DATABASE: OnceCell = OnceCell::new(); diff --git a/src/lune/builtins/serde.rs b/src/old/builtins/serde.rs similarity index 98% rename from src/lune/builtins/serde.rs rename to src/old/builtins/serde.rs index 5069337..09a4abb 100644 --- a/src/lune/builtins/serde.rs +++ b/src/old/builtins/serde.rs @@ -1,6 +1,6 @@ use mlua::prelude::*; -use crate::lune::lua::{ +use crate::lune_temp::lua::{ serde::{ compress, decompress, CompressDecompressFormat, EncodeDecodeConfig, EncodeDecodeFormat, }, diff --git a/src/lune/builtins/stdio.rs b/src/old/builtins/stdio.rs similarity index 99% rename from src/lune/builtins/stdio.rs rename to src/old/builtins/stdio.rs index acd3727..98a768d 100644 --- a/src/lune/builtins/stdio.rs +++ b/src/old/builtins/stdio.rs @@ -5,7 +5,7 @@ use tokio::{ task, }; -use crate::lune::lua::{ +use crate::lune_temp::lua::{ stdio::{ formatting::{ format_style, pretty_format_multi_value, style_from_color_str, style_from_style_str, diff --git a/src/lune/builtins/task.rs b/src/old/builtins/task.rs similarity index 99% rename from src/lune/builtins/task.rs rename to src/old/builtins/task.rs index 98a0383..3db015a 100644 --- a/src/lune/builtins/task.rs +++ b/src/old/builtins/task.rs @@ -1,6 +1,6 @@ use mlua::prelude::*; -use crate::lune::lua::{ +use crate::lune_temp::lua::{ async_ext::LuaAsyncExt, table::TableBuilder, task::{ diff --git a/src/lune/builtins/top_level.rs b/src/old/builtins/top_level.rs similarity index 98% rename from src/lune/builtins/top_level.rs rename to src/old/builtins/top_level.rs index a101fb0..0261a21 100644 --- a/src/lune/builtins/top_level.rs +++ b/src/old/builtins/top_level.rs @@ -4,7 +4,7 @@ use std::io::{self, Write as _}; #[cfg(feature = "roblox")] use crate::roblox::datatypes::extension::RobloxUserdataTypenameExt; -use crate::lune::lua::{ +use crate::lune_temp::lua::{ stdio::formatting::{format_label, pretty_format_multi_value}, task::TaskReference, }; diff --git a/src/old/error.rs b/src/old/error.rs new file mode 100644 index 0000000..e184e2e --- /dev/null +++ b/src/old/error.rs @@ -0,0 +1,83 @@ +use std::{ + error::Error, + fmt::{Debug, Display, Formatter, Result as FmtResult}, +}; + +use mlua::prelude::*; + +use crate::lune_temp::lua::stdio::formatting::pretty_format_luau_error; + +/** + An opaque error type for formatted lua errors. +*/ +#[derive(Debug, Clone)] +pub struct LuneError { + error: LuaError, + disable_colors: bool, +} + +impl LuneError { + /** + Enables colorization of the error message when formatted using the [`Display`] trait. + + Colorization is enabled by default. + */ + #[doc(hidden)] + pub fn enable_colors(mut self) -> Self { + self.disable_colors = false; + self + } + + /** + Disables colorization of the error message when formatted using the [`Display`] trait. + + Colorization is enabled by default. + */ + #[doc(hidden)] + pub fn disable_colors(mut self) -> Self { + self.disable_colors = true; + self + } + + /** + Returns `true` if the error can likely be fixed by appending more input to the source code. + + See [`mlua::Error::SyntaxError`] for more information. + */ + pub fn is_incomplete_input(&self) -> bool { + matches!( + self.error, + LuaError::SyntaxError { + incomplete_input: true, + .. + } + ) + } +} + +impl From for LuneError { + fn from(value: LuaError) -> Self { + Self { + error: value, + disable_colors: false, + } + } +} + +impl Display for LuneError { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + write!( + f, + "{}", + pretty_format_luau_error(&self.error, !self.disable_colors) + ) + } +} + +impl Error for LuneError { + // TODO: Comment this out when we are ready to also re-export + // `mlua` as part of our public library interface in Lune + // fn cause(&self) -> Option<&dyn Error> { + // Some(&self.error) + // } +} diff --git a/src/lune/importer/mod.rs b/src/old/importer/mod.rs similarity index 96% rename from src/lune/importer/mod.rs rename to src/old/importer/mod.rs index de3cdf6..86165cc 100644 --- a/src/lune/importer/mod.rs +++ b/src/old/importer/mod.rs @@ -3,7 +3,7 @@ use mlua::prelude::*; mod require; mod require_waker; -use crate::lune::builtins::{self, top_level}; +use crate::lune_temp::builtins::{self, top_level}; pub fn create(lua: &'static Lua, args: Vec) -> LuaResult<()> { // Create all builtins diff --git a/src/lune/importer/require.rs b/src/old/importer/require.rs similarity index 99% rename from src/lune/importer/require.rs rename to src/old/importer/require.rs index 113c593..0f1d7f4 100644 --- a/src/lune/importer/require.rs +++ b/src/old/importer/require.rs @@ -10,7 +10,7 @@ use mlua::prelude::*; use tokio::fs; use tokio::sync::Mutex as AsyncMutex; -use crate::lune::lua::{ +use crate::lune_temp::lua::{ table::TableBuilder, task::{TaskScheduler, TaskSchedulerScheduleExt}, }; diff --git a/src/lune/importer/require_waker.rs b/src/old/importer/require_waker.rs similarity index 100% rename from src/lune/importer/require_waker.rs rename to src/old/importer/require_waker.rs diff --git a/src/lune/lua/async_ext.rs b/src/old/lua/async_ext.rs similarity index 97% rename from src/lune/lua/async_ext.rs rename to src/old/lua/async_ext.rs index afdd29b..7e6fef9 100644 --- a/src/lune/lua/async_ext.rs +++ b/src/old/lua/async_ext.rs @@ -2,7 +2,7 @@ use async_trait::async_trait; use futures_util::Future; use mlua::prelude::*; -use crate::lune::{lua::table::TableBuilder, lua::task::TaskScheduler}; +use crate::lune_temp::{lua::table::TableBuilder, lua::task::TaskScheduler}; use super::task::TaskSchedulerAsyncExt; diff --git a/src/lune/lua/create.rs b/src/old/lua/create.rs similarity index 100% rename from src/lune/lua/create.rs rename to src/old/lua/create.rs diff --git a/src/lune/lua/fs/copy.rs b/src/old/lua/fs/copy.rs similarity index 100% rename from src/lune/lua/fs/copy.rs rename to src/old/lua/fs/copy.rs diff --git a/src/lune/lua/fs/metadata.rs b/src/old/lua/fs/metadata.rs similarity index 100% rename from src/lune/lua/fs/metadata.rs rename to src/old/lua/fs/metadata.rs diff --git a/src/lune/lua/fs/mod.rs b/src/old/lua/fs/mod.rs similarity index 100% rename from src/lune/lua/fs/mod.rs rename to src/old/lua/fs/mod.rs diff --git a/src/lune/lua/fs/options.rs b/src/old/lua/fs/options.rs similarity index 100% rename from src/lune/lua/fs/options.rs rename to src/old/lua/fs/options.rs diff --git a/src/lune/lua/luau/mod.rs b/src/old/lua/luau/mod.rs similarity index 100% rename from src/lune/lua/luau/mod.rs rename to src/old/lua/luau/mod.rs diff --git a/src/lune/lua/luau/options.rs b/src/old/lua/luau/options.rs similarity index 100% rename from src/lune/lua/luau/options.rs rename to src/old/lua/luau/options.rs diff --git a/src/lune/lua/mod.rs b/src/old/lua/mod.rs similarity index 100% rename from src/lune/lua/mod.rs rename to src/old/lua/mod.rs diff --git a/src/lune/lua/net/client.rs b/src/old/lua/net/client.rs similarity index 100% rename from src/lune/lua/net/client.rs rename to src/old/lua/net/client.rs diff --git a/src/lune/lua/net/config.rs b/src/old/lua/net/config.rs similarity index 100% rename from src/lune/lua/net/config.rs rename to src/old/lua/net/config.rs diff --git a/src/lune/lua/net/mod.rs b/src/old/lua/net/mod.rs similarity index 100% rename from src/lune/lua/net/mod.rs rename to src/old/lua/net/mod.rs diff --git a/src/lune/lua/net/response.rs b/src/old/lua/net/response.rs similarity index 100% rename from src/lune/lua/net/response.rs rename to src/old/lua/net/response.rs diff --git a/src/lune/lua/net/server.rs b/src/old/lua/net/server.rs similarity index 99% rename from src/lune/lua/net/server.rs rename to src/old/lua/net/server.rs index d3428df..807a0f2 100644 --- a/src/lune/lua/net/server.rs +++ b/src/old/lua/net/server.rs @@ -12,7 +12,7 @@ use hyper::{Body, Request, Response}; use hyper_tungstenite::{is_upgrade_request as is_ws_upgrade_request, upgrade as ws_upgrade}; use tokio::task; -use crate::lune::{ +use crate::lune_temp::{ lua::table::TableBuilder, lua::task::{TaskScheduler, TaskSchedulerAsyncExt, TaskSchedulerScheduleExt}, }; diff --git a/src/lune/lua/net/websocket.rs b/src/old/lua/net/websocket.rs similarity index 99% rename from src/lune/lua/net/websocket.rs rename to src/old/lua/net/websocket.rs index 1057b1b..ece0ce9 100644 --- a/src/lune/lua/net/websocket.rs +++ b/src/old/lua/net/websocket.rs @@ -22,7 +22,7 @@ use hyper_tungstenite::{ }; use tokio_tungstenite::MaybeTlsStream; -use crate::lune::lua::table::TableBuilder; +use crate::lune_temp::lua::table::TableBuilder; const WEB_SOCKET_IMPL_LUA: &str = r#" return freeze(setmetatable({ diff --git a/src/lune/lua/process/mod.rs b/src/old/lua/process/mod.rs similarity index 100% rename from src/lune/lua/process/mod.rs rename to src/old/lua/process/mod.rs diff --git a/src/lune/lua/process/tee_writer.rs b/src/old/lua/process/tee_writer.rs similarity index 100% rename from src/lune/lua/process/tee_writer.rs rename to src/old/lua/process/tee_writer.rs diff --git a/src/lune/lua/serde/compress_decompress.rs b/src/old/lua/serde/compress_decompress.rs similarity index 100% rename from src/lune/lua/serde/compress_decompress.rs rename to src/old/lua/serde/compress_decompress.rs diff --git a/src/lune/lua/serde/encode_decode.rs b/src/old/lua/serde/encode_decode.rs similarity index 100% rename from src/lune/lua/serde/encode_decode.rs rename to src/old/lua/serde/encode_decode.rs diff --git a/src/lune/lua/serde/mod.rs b/src/old/lua/serde/mod.rs similarity index 100% rename from src/lune/lua/serde/mod.rs rename to src/old/lua/serde/mod.rs diff --git a/src/lune/lua/stdio/formatting.rs b/src/old/lua/stdio/formatting.rs similarity index 99% rename from src/lune/lua/stdio/formatting.rs rename to src/old/lua/stdio/formatting.rs index 8437299..02dd286 100644 --- a/src/lune/lua/stdio/formatting.rs +++ b/src/old/lua/stdio/formatting.rs @@ -4,7 +4,7 @@ use console::{colors_enabled, set_colors_enabled, style, Style}; use mlua::prelude::*; use once_cell::sync::Lazy; -use crate::lune::lua::task::TaskReference; +use crate::lune_temp::lua::task::TaskReference; const MAX_FORMAT_DEPTH: usize = 4; diff --git a/src/lune/lua/stdio/mod.rs b/src/old/lua/stdio/mod.rs similarity index 100% rename from src/lune/lua/stdio/mod.rs rename to src/old/lua/stdio/mod.rs diff --git a/src/lune/lua/stdio/prompt.rs b/src/old/lua/stdio/prompt.rs similarity index 100% rename from src/lune/lua/stdio/prompt.rs rename to src/old/lua/stdio/prompt.rs diff --git a/src/lune/lua/table/builder.rs b/src/old/lua/table/builder.rs similarity index 97% rename from src/lune/lua/table/builder.rs rename to src/old/lua/table/builder.rs index b320707..836313b 100644 --- a/src/lune/lua/table/builder.rs +++ b/src/old/lua/table/builder.rs @@ -2,7 +2,7 @@ use std::future::Future; use mlua::prelude::*; -use crate::lune::lua::async_ext::LuaAsyncExt; +use crate::lune_temp::lua::async_ext::LuaAsyncExt; pub struct TableBuilder { lua: &'static Lua, diff --git a/src/lune/lua/table/mod.rs b/src/old/lua/table/mod.rs similarity index 100% rename from src/lune/lua/table/mod.rs rename to src/old/lua/table/mod.rs diff --git a/src/lune/lua/task/ext/async_ext.rs b/src/old/lua/task/ext/async_ext.rs similarity index 99% rename from src/lune/lua/task/ext/async_ext.rs rename to src/old/lua/task/ext/async_ext.rs index 681c01a..26d1da8 100644 --- a/src/lune/lua/task/ext/async_ext.rs +++ b/src/old/lua/task/ext/async_ext.rs @@ -6,7 +6,7 @@ use futures_util::Future; use mlua::prelude::*; use tokio::time::{sleep, Instant}; -use crate::lune::lua::task::TaskKind; +use crate::lune_temp::lua::task::TaskKind; use super::super::{ scheduler::TaskReference, scheduler::TaskScheduler, scheduler_handle::TaskSchedulerAsyncHandle, diff --git a/src/lune/lua/task/ext/mod.rs b/src/old/lua/task/ext/mod.rs similarity index 100% rename from src/lune/lua/task/ext/mod.rs rename to src/old/lua/task/ext/mod.rs diff --git a/src/lune/lua/task/ext/resume_ext.rs b/src/old/lua/task/ext/resume_ext.rs similarity index 100% rename from src/lune/lua/task/ext/resume_ext.rs rename to src/old/lua/task/ext/resume_ext.rs diff --git a/src/lune/lua/task/ext/schedule_ext.rs b/src/old/lua/task/ext/schedule_ext.rs similarity index 100% rename from src/lune/lua/task/ext/schedule_ext.rs rename to src/old/lua/task/ext/schedule_ext.rs diff --git a/src/lune/lua/task/mod.rs b/src/old/lua/task/mod.rs similarity index 100% rename from src/lune/lua/task/mod.rs rename to src/old/lua/task/mod.rs diff --git a/src/lune/lua/task/proxy.rs b/src/old/lua/task/proxy.rs similarity index 100% rename from src/lune/lua/task/proxy.rs rename to src/old/lua/task/proxy.rs diff --git a/src/lune/lua/task/scheduler.rs b/src/old/lua/task/scheduler.rs similarity index 100% rename from src/lune/lua/task/scheduler.rs rename to src/old/lua/task/scheduler.rs diff --git a/src/lune/lua/task/scheduler_handle.rs b/src/old/lua/task/scheduler_handle.rs similarity index 100% rename from src/lune/lua/task/scheduler_handle.rs rename to src/old/lua/task/scheduler_handle.rs diff --git a/src/lune/lua/task/scheduler_message.rs b/src/old/lua/task/scheduler_message.rs similarity index 100% rename from src/lune/lua/task/scheduler_message.rs rename to src/old/lua/task/scheduler_message.rs diff --git a/src/lune/lua/task/scheduler_state.rs b/src/old/lua/task/scheduler_state.rs similarity index 100% rename from src/lune/lua/task/scheduler_state.rs rename to src/old/lua/task/scheduler_state.rs diff --git a/src/lune/lua/task/task_kind.rs b/src/old/lua/task/task_kind.rs similarity index 100% rename from src/lune/lua/task/task_kind.rs rename to src/old/lua/task/task_kind.rs diff --git a/src/lune/lua/task/task_reference.rs b/src/old/lua/task/task_reference.rs similarity index 100% rename from src/lune/lua/task/task_reference.rs rename to src/old/lua/task/task_reference.rs diff --git a/src/lune/lua/task/task_waiter.rs b/src/old/lua/task/task_waiter.rs similarity index 100% rename from src/lune/lua/task/task_waiter.rs rename to src/old/lua/task/task_waiter.rs diff --git a/src/old/mod.rs b/src/old/mod.rs new file mode 100644 index 0000000..a5050b7 --- /dev/null +++ b/src/old/mod.rs @@ -0,0 +1,107 @@ +use std::process::ExitCode; + +use lua::task::{TaskScheduler, TaskSchedulerResumeExt, TaskSchedulerScheduleExt}; +use mlua::prelude::*; +use tokio::task::LocalSet; + +pub mod builtins; +pub mod importer; +pub mod lua; + +mod error; + +pub use error::LuneError; + +#[derive(Clone, Debug, Default)] +pub struct Lune { + args: Vec, +} + +impl Lune { + /** + Creates a new Lune script runner. + */ + pub fn new() -> Self { + Self::default() + } + + /** + Arguments to give in `process.args` for a Lune script. + */ + pub fn with_args(mut self, args: V) -> Self + where + V: Into>, + { + self.args = args.into(); + self + } + + /** + Runs a Lune script. + + This will create a new sandboxed Luau environment with the configured + globals and arguments, running inside of a [`tokio::task::LocalSet`]. + + Some Lune globals may spawn separate tokio tasks on other threads, but the Luau + environment itself is guaranteed to run on a single thread in the local set. + + Note that this will create a static Lua instance and task scheduler that will + both live for the remainer of the program, and that this leaks memory using + [`Box::leak`] that will then get deallocated when the program exits. + */ + pub async fn run( + &self, + script_name: impl AsRef, + script_contents: impl AsRef<[u8]>, + ) -> Result { + self.run_inner(script_name, script_contents) + .await + .map_err(LuneError::from) + } + + async fn run_inner( + &self, + script_name: impl AsRef, + script_contents: impl AsRef<[u8]>, + ) -> Result { + // Create our special lune-flavored Lua object with extra registry values + let lua = lua::create_lune_lua()?.into_static(); + // Create our task scheduler and all globals + // NOTE: Some globals require the task scheduler to exist on startup + let sched = TaskScheduler::new(lua)?.into_static(); + lua.set_app_data(sched); + importer::create(lua, self.args.clone())?; + // Create the main thread and schedule it + let main_chunk = lua + .load(script_contents.as_ref()) + .set_name(script_name.as_ref()) + .into_function()?; + let main_thread = lua.create_thread(main_chunk)?; + let main_thread_args = LuaValue::Nil.into_lua_multi(lua)?; + sched.schedule_blocking(main_thread, main_thread_args)?; + // Keep running the scheduler until there are either no tasks + // left to run, or until a task requests to exit the process + let exit_code = LocalSet::new() + .run_until(async move { + let mut got_error = false; + loop { + let result = sched.resume_queue().await; + if let Some(err) = result.get_lua_error() { + eprintln!("{}", LuneError::from(err)); + got_error = true; + } + if result.is_done() { + if let Some(exit_code) = result.get_exit_code() { + break exit_code; + } else if got_error { + break ExitCode::FAILURE; + } else { + break ExitCode::SUCCESS; + } + } + } + }) + .await; + Ok(exit_code) + } +} From 6757e1a1a87b3119a43d954381fe4a14945baa78 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Wed, 16 Aug 2023 20:38:05 -0500 Subject: [PATCH 002/103] Initial barebones scheduler implementation and interface --- src/lune/mod.rs | 23 +++++----- src/lune/scheduler/impl_runner.rs | 71 ++++++++++++++++++++++++++++++ src/lune/scheduler/impl_threads.rs | 70 +++++++++++++++++++++++++++++ src/lune/scheduler/mod.rs | 70 +++++++++++++++++++++++++++++ src/lune/scheduler/state.rs | 38 ++++++++++++++++ src/lune/scheduler/thread.rs | 58 ++++++++++++++++++++++++ src/lune/scheduler/traits.rs | 59 +++++++++++++++++++++++++ tests/globals/coroutine.luau | 6 +-- 8 files changed, 381 insertions(+), 14 deletions(-) create mode 100644 src/lune/scheduler/impl_runner.rs create mode 100644 src/lune/scheduler/impl_threads.rs create mode 100644 src/lune/scheduler/mod.rs create mode 100644 src/lune/scheduler/state.rs create mode 100644 src/lune/scheduler/thread.rs create mode 100644 src/lune/scheduler/traits.rs diff --git a/src/lune/mod.rs b/src/lune/mod.rs index 6e5c286..2477893 100644 --- a/src/lune/mod.rs +++ b/src/lune/mod.rs @@ -1,8 +1,11 @@ -use std::process::ExitCode; +use std::{process::ExitCode, sync::Arc}; use mlua::prelude::*; mod error; +mod scheduler; + +use self::scheduler::Scheduler; pub use error::LuneError; @@ -38,16 +41,14 @@ impl Lune { script_name: impl AsRef, script_contents: impl AsRef<[u8]>, ) -> Result { - self.run_inner(script_name, script_contents) - .await - .map_err(LuneError::from) - } + let lua = Arc::new(Lua::new()); + let sched = Scheduler::new(Arc::clone(&lua)); - async fn run_inner( - &self, - _script_name: impl AsRef, - _script_contents: impl AsRef<[u8]>, - ) -> LuaResult { - Ok(ExitCode::SUCCESS) + let main = lua + .load(script_contents.as_ref()) + .set_name(script_name.as_ref()); + sched.push_front(main, ())?; + + Ok(sched.run_to_completion().await) } } diff --git a/src/lune/scheduler/impl_runner.rs b/src/lune/scheduler/impl_runner.rs new file mode 100644 index 0000000..3abdee0 --- /dev/null +++ b/src/lune/scheduler/impl_runner.rs @@ -0,0 +1,71 @@ +use std::process::ExitCode; + +use mlua::prelude::*; + +use super::SchedulerImpl; + +impl SchedulerImpl { + /** + Runs all lua threads to completion, gathering any results they produce. + */ + fn run_threads(&self) -> Vec> { + let mut results = Vec::new(); + + while let Some((thread, args)) = self + .pop_thread() + .expect("Failed to pop thread from scheduler") + { + let res = thread.resume(args); + self.state.add_resumption(); + + if let Err(e) = &res { + self.state.add_error(); + eprintln!("{e}"); // TODO: Pretty print the lua error here + } + + results.push(res); + + if self.state.has_exit_code() { + break; + } + } + + results + } + + /** + Runs the scheduler to completion, both normal lua threads and futures. + + This will emit lua output and errors to stdout and stderr. + */ + pub async fn run_to_completion(&self) -> ExitCode { + loop { + // 1. Run lua threads until exit or there are none left + let results = self.run_threads(); + + // 2. If we got a manual exit code from lua we should not continue + if self.state.has_exit_code() { + break; + } + + // 3. Wait for the next future to complete, this may + // add more lua threads to run in the next iteration + + // TODO: Implement this + + // 4. If did not resume any lua threads, and we have no futures + // queued either, we have run the scheduler until completion + if results.is_empty() { + break; + } + } + + if let Some(code) = self.state.exit_code() { + ExitCode::from(code) + } else if self.state.has_errored() { + ExitCode::FAILURE + } else { + ExitCode::SUCCESS + } + } +} diff --git a/src/lune/scheduler/impl_threads.rs b/src/lune/scheduler/impl_threads.rs new file mode 100644 index 0000000..d17873b --- /dev/null +++ b/src/lune/scheduler/impl_threads.rs @@ -0,0 +1,70 @@ +use mlua::prelude::*; + +use super::{thread::SchedulerThread, traits::IntoLuaThread, SchedulerImpl}; + +impl<'lua> SchedulerImpl { + /** + Pops the next thread to run, from the front of the scheduler. + + Returns `None` if there are no threads left to run. + */ + pub(super) fn pop_thread( + &'lua self, + ) -> LuaResult, LuaMultiValue<'lua>)>> { + match self + .threads + .try_borrow_mut() + .into_lua_err() + .context("Failed to borrow threads vec")? + .pop_front() + { + Some(thread) => { + let (thread, args) = thread.into_inner(&self.lua); + Ok(Some((thread, args))) + } + None => Ok(None), + } + } + + /** + Schedules the `thread` to be resumed with the given `args` + right away, before any other currently scheduled threads. + */ + pub fn push_front( + &'lua self, + thread: impl IntoLuaThread<'lua>, + args: impl IntoLuaMulti<'lua>, + ) -> LuaResult<()> { + let thread = thread.into_lua_thread(&self.lua)?; + let args = args.into_lua_multi(&self.lua)?; + + self.threads + .try_borrow_mut() + .into_lua_err() + .context("Failed to borrow threads vec")? + .push_front(SchedulerThread::new(&self.lua, thread, args)?); + + Ok(()) + } + + /** + Schedules the `thread` to be resumed with the given `args` + after all other current threads have been resumed. + */ + pub fn push_back( + &'lua self, + thread: impl IntoLuaThread<'lua>, + args: impl IntoLuaMulti<'lua>, + ) -> LuaResult<()> { + let thread = thread.into_lua_thread(&self.lua)?; + let args = args.into_lua_multi(&self.lua)?; + + self.threads + .try_borrow_mut() + .into_lua_err() + .context("Failed to borrow threads vec")? + .push_back(SchedulerThread::new(&self.lua, thread, args)?); + + Ok(()) + } +} diff --git a/src/lune/scheduler/mod.rs b/src/lune/scheduler/mod.rs new file mode 100644 index 0000000..acc814c --- /dev/null +++ b/src/lune/scheduler/mod.rs @@ -0,0 +1,70 @@ +use std::{cell::RefCell, collections::VecDeque, ops::Deref, sync::Arc}; + +use mlua::prelude::*; + +mod state; +mod thread; +mod traits; + +mod impl_runner; +mod impl_threads; + +use self::{state::SchedulerState, thread::SchedulerThread}; + +/** + Scheduler for Lua threads. + + Can be cheaply cloned, and any clone will refer + to the same underlying scheduler and Lua struct. +*/ +#[derive(Debug, Clone)] +pub struct Scheduler(Arc); + +impl Scheduler { + /** + Creates a new scheduler for the given [`Lua`] struct. + */ + pub fn new(lua: Arc) -> Self { + assert!( + lua.app_data_ref::().is_none() && lua.app_data_ref::<&Self>().is_none(), + "Only one scheduler may be created per Lua struct" + ); + + let inner = SchedulerImpl::new(Arc::clone(&lua)); + let sched = Self(Arc::new(inner)); + + lua.set_app_data(sched.clone()); + lua.set_interrupt(move |_| Ok(LuaVmState::Continue)); + + sched + } +} + +impl Deref for Scheduler { + type Target = SchedulerImpl; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/** + Implementation of scheduler for Lua threads. + + Not meant to be used directly, use [`Scheduler`] instead. +*/ +#[derive(Debug)] +pub struct SchedulerImpl { + lua: Arc, + state: SchedulerState, + threads: RefCell>, +} + +impl SchedulerImpl { + fn new(lua: Arc) -> Self { + Self { + lua, + state: SchedulerState::new(), + threads: RefCell::new(VecDeque::new()), + } + } +} diff --git a/src/lune/scheduler/state.rs b/src/lune/scheduler/state.rs new file mode 100644 index 0000000..2590211 --- /dev/null +++ b/src/lune/scheduler/state.rs @@ -0,0 +1,38 @@ +use std::cell::RefCell; + +#[derive(Debug, Default)] +pub struct SchedulerState { + exit_code: RefCell>, + num_resumptions: RefCell, + num_errors: RefCell, +} + +impl SchedulerState { + pub fn new() -> Self { + Self::default() + } + + pub fn add_resumption(&self) { + *self.num_resumptions.borrow_mut() += 1; + } + + pub fn add_error(&self) { + *self.num_errors.borrow_mut() += 1; + } + + pub fn has_errored(&self) -> bool { + *self.num_errors.borrow() > 0 + } + + pub fn exit_code(&self) -> Option { + *self.exit_code.borrow() + } + + pub fn has_exit_code(&self) -> bool { + self.exit_code.borrow().is_some() + } + + pub fn set_exit_code(&self, code: impl Into) { + self.exit_code.replace(Some(code.into())); + } +} diff --git a/src/lune/scheduler/thread.rs b/src/lune/scheduler/thread.rs new file mode 100644 index 0000000..da43880 --- /dev/null +++ b/src/lune/scheduler/thread.rs @@ -0,0 +1,58 @@ +use mlua::prelude::*; + +/** + Container for registry keys that point to a thread and thread arguments. +*/ +#[derive(Debug)] +pub(super) struct SchedulerThread { + key_thread: LuaRegistryKey, + key_args: LuaRegistryKey, +} + +impl SchedulerThread { + /** + Creates a new scheduler thread container from the given thread and arguments. + + May fail if an allocation error occurs, is not fallible otherwise. + */ + pub(super) fn new<'lua>( + lua: &'lua Lua, + thread: LuaThread<'lua>, + args: LuaMultiValue<'lua>, + ) -> LuaResult { + let args_vec = args.into_vec(); + + let key_thread = lua + .create_registry_value(thread) + .context("Failed to store value in registry")?; + let key_args = lua + .create_registry_value(args_vec) + .context("Failed to store value in registry")?; + + Ok(Self { + key_thread, + key_args, + }) + } + + /** + Extracts the inner thread and args from the container. + */ + pub(super) fn into_inner(self, lua: &Lua) -> (LuaThread<'_>, LuaMultiValue<'_>) { + let thread = lua + .registry_value(&self.key_thread) + .expect("Failed to get thread from registry"); + let args_vec = lua + .registry_value(&self.key_args) + .expect("Failed to get thread args from registry"); + + let args = LuaMultiValue::from_vec(args_vec); + + lua.remove_registry_value(self.key_thread) + .expect("Failed to remove thread from registry"); + lua.remove_registry_value(self.key_args) + .expect("Failed to remove thread args from registry"); + + (thread, args) + } +} diff --git a/src/lune/scheduler/traits.rs b/src/lune/scheduler/traits.rs new file mode 100644 index 0000000..5a05d5d --- /dev/null +++ b/src/lune/scheduler/traits.rs @@ -0,0 +1,59 @@ +use mlua::prelude::*; + +use super::Scheduler; + +/** + Trait for extensions to the [`Lua`] struct, allowing + for access to the scheduler without having to import + it or handle registry / app data references manually. +*/ +pub trait LuaSchedulerExt { + /** + Get a strong reference to the scheduler for the [`Lua`] struct. + + Note that if this reference is not dropped, `Lua` can + not be dropped either because of the strong reference. + */ + fn scheduler(&self) -> Scheduler; +} + +impl LuaSchedulerExt for Lua { + fn scheduler(&self) -> Scheduler { + self.app_data_ref::() + .expect("Lua struct is missing scheduler") + .clone() + } +} + +/** + Trait for any struct that can be turned into an [`LuaThread`] + and given 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. + */ + 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, 'a> IntoLuaThread<'lua> for LuaChunk<'lua, 'a> { + fn into_lua_thread(self, lua: &'lua Lua) -> LuaResult> { + lua.create_thread(self.into_function()?) + } +} diff --git a/tests/globals/coroutine.luau b/tests/globals/coroutine.luau index 8db0c42..7239bb7 100644 --- a/tests/globals/coroutine.luau +++ b/tests/globals/coroutine.luau @@ -1,5 +1,3 @@ -local task = require("@lune/task") - -- Coroutines should return true, ret values OR false, error local function pass() @@ -55,7 +53,9 @@ local success2 = coroutine.resume(thread2) assert(success1 == false, "Coroutine resume on dead coroutines should return false") assert(success2 == false, "Coroutine resume on dead coroutines should return false") --- Wait should work inside native lua coroutines +-- Task library wait should work inside native lua coroutines + +local task = require("@lune/task") local flag: boolean = false coroutine.resume(coroutine.create(function() From 4fa76aa27f9fd5ef0300c571c857f1726d75fc64 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Wed, 16 Aug 2023 22:00:15 -0500 Subject: [PATCH 003/103] Implement thread return value broadcasting in scheduler --- src/lune/scheduler/impl_runner.rs | 86 +++++++++++++++++++----------- src/lune/scheduler/impl_threads.rs | 66 +++++++++++++++++++---- src/lune/scheduler/mod.rs | 14 ++++- src/lune/scheduler/thread.rs | 36 +++++++++++++ 4 files changed, 161 insertions(+), 41 deletions(-) diff --git a/src/lune/scheduler/impl_runner.rs b/src/lune/scheduler/impl_runner.rs index 3abdee0..f822657 100644 --- a/src/lune/scheduler/impl_runner.rs +++ b/src/lune/scheduler/impl_runner.rs @@ -1,64 +1,90 @@ -use std::process::ExitCode; +use std::{process::ExitCode, sync::Arc}; use mlua::prelude::*; +use tokio::task::LocalSet; use super::SchedulerImpl; impl SchedulerImpl { /** - Runs all lua threads to completion, gathering any results they produce. - */ - fn run_threads(&self) -> Vec> { - let mut results = Vec::new(); + Runs all lua threads to completion. - while let Some((thread, args)) = self + Returns `true` if any thread was resumed, `false` otherwise. + */ + fn run_lua_threads(&self) -> bool { + if self.state.has_exit_code() { + return false; + } + + let mut resumed_any = false; + + while let Some((thread, args, sender)) = self .pop_thread() .expect("Failed to pop thread from scheduler") { - let res = thread.resume(args); + let res = thread.resume::<_, LuaMultiValue>(args); self.state.add_resumption(); + resumed_any = true; - if let Err(e) = &res { + if let Err(err) = &res { self.state.add_error(); - eprintln!("{e}"); // TODO: Pretty print the lua error here + eprint!("{err}"); // TODO: Pretty print the lua error here } - results.push(res); + if sender.receiver_count() > 0 { + sender + .send(res.map(|v| { + Arc::new( + self.lua + .create_registry_value(v.into_vec()) + .expect("Failed to store return values in registry"), + ) + })) + .expect("Failed to broadcast return values of thread"); + } if self.state.has_exit_code() { break; } } - results + resumed_any } /** - Runs the scheduler to completion, both normal lua threads and futures. + Runs the scheduler to completion in a [`LocalSet`], + both normal lua threads and futures, prioritizing + lua threads over completion of any pending futures. - This will emit lua output and errors to stdout and stderr. + Will emit lua output and errors to stdout and stderr. */ pub async fn run_to_completion(&self) -> ExitCode { - loop { - // 1. Run lua threads until exit or there are none left - let results = self.run_threads(); + let fut = async move { + loop { + // 1. Run lua threads until exit or there are none left, + // if any thread was resumed it may have spawned futures + let resumed_lua = self.run_lua_threads(); - // 2. If we got a manual exit code from lua we should not continue - if self.state.has_exit_code() { - break; + // 2. If we got a manual exit code from lua we should + // not try to wait for any pending futures to complete + if self.state.has_exit_code() { + break; + } + + // 3. Wait for the next future to complete, this may + // add more lua threads to run in the next iteration + + // TODO: Implement futures resumption + + // 4. If we did not resume any lua threads, and we have no futures + // queued either, we have now run the scheduler until completion + if !resumed_lua { + break; + } } + }; - // 3. Wait for the next future to complete, this may - // add more lua threads to run in the next iteration - - // TODO: Implement this - - // 4. If did not resume any lua threads, and we have no futures - // queued either, we have run the scheduler until completion - if results.is_empty() { - break; - } - } + LocalSet::new().run_until(fut).await; if let Some(code) = self.state.exit_code() { ExitCode::from(code) diff --git a/src/lune/scheduler/impl_threads.rs b/src/lune/scheduler/impl_threads.rs index d17873b..7343595 100644 --- a/src/lune/scheduler/impl_threads.rs +++ b/src/lune/scheduler/impl_threads.rs @@ -1,6 +1,10 @@ use mlua::prelude::*; -use super::{thread::SchedulerThread, traits::IntoLuaThread, SchedulerImpl}; +use super::{ + thread::{SchedulerThread, SchedulerThreadId, SchedulerThreadSender}, + traits::IntoLuaThread, + SchedulerImpl, +}; impl<'lua> SchedulerImpl { /** @@ -10,7 +14,7 @@ impl<'lua> SchedulerImpl { */ pub(super) fn pop_thread( &'lua self, - ) -> LuaResult, LuaMultiValue<'lua>)>> { + ) -> LuaResult, LuaMultiValue<'lua>, SchedulerThreadSender)>> { match self .threads .try_borrow_mut() @@ -19,8 +23,14 @@ impl<'lua> SchedulerImpl { .pop_front() { Some(thread) => { + let thread_id = &thread.id(); let (thread, args) = thread.into_inner(&self.lua); - Ok(Some((thread, args))) + let sender = self + .thread_senders + .borrow_mut() + .remove(thread_id) + .expect("Missing thread sender"); + Ok(Some((thread, args, sender))) } None => Ok(None), } @@ -34,17 +44,23 @@ impl<'lua> SchedulerImpl { &'lua self, thread: impl IntoLuaThread<'lua>, args: impl IntoLuaMulti<'lua>, - ) -> LuaResult<()> { + ) -> LuaResult { let thread = thread.into_lua_thread(&self.lua)?; let args = args.into_lua_multi(&self.lua)?; + let thread = SchedulerThread::new(&self.lua, thread, args)?; + let thread_id = thread.id(); + self.threads .try_borrow_mut() .into_lua_err() .context("Failed to borrow threads vec")? - .push_front(SchedulerThread::new(&self.lua, thread, args)?); + .push_front(thread); + self.thread_senders + .borrow_mut() + .insert(thread_id, SchedulerThreadSender::new(1)); - Ok(()) + Ok(thread_id) } /** @@ -55,16 +71,48 @@ impl<'lua> SchedulerImpl { &'lua self, thread: impl IntoLuaThread<'lua>, args: impl IntoLuaMulti<'lua>, - ) -> LuaResult<()> { + ) -> LuaResult { let thread = thread.into_lua_thread(&self.lua)?; let args = args.into_lua_multi(&self.lua)?; + let thread = SchedulerThread::new(&self.lua, thread, args)?; + let thread_id = thread.id(); + self.threads .try_borrow_mut() .into_lua_err() .context("Failed to borrow threads vec")? - .push_back(SchedulerThread::new(&self.lua, thread, args)?); + .push_back(thread); + self.thread_senders + .borrow_mut() + .insert(thread_id, SchedulerThreadSender::new(1)); - Ok(()) + Ok(thread_id) + } + + /** + Waits for the given thread to finish running, and returns its result. + */ + pub async fn wait_for_thread( + &'lua self, + thread_id: SchedulerThreadId, + ) -> LuaResult> { + let mut recv = { + let senders = self.thread_senders.borrow(); + let sender = senders + .get(&thread_id) + .expect("Tried to wait for thread that is not queued"); + sender.subscribe() + }; + match recv.recv().await.expect("Failed to receive thread result") { + Err(e) => Err(e), + Ok(k) => { + let vals = self + .lua + .registry_value::>(&k) + .expect("Received invalid registry key for thread"); + Ok(LuaMultiValue::from_vec(vals)) + } + } } } diff --git a/src/lune/scheduler/mod.rs b/src/lune/scheduler/mod.rs index acc814c..0ce3de7 100644 --- a/src/lune/scheduler/mod.rs +++ b/src/lune/scheduler/mod.rs @@ -1,4 +1,9 @@ -use std::{cell::RefCell, collections::VecDeque, ops::Deref, sync::Arc}; +use std::{ + cell::RefCell, + collections::{HashMap, VecDeque}, + ops::Deref, + sync::Arc, +}; use mlua::prelude::*; @@ -9,7 +14,10 @@ mod traits; mod impl_runner; mod impl_threads; -use self::{state::SchedulerState, thread::SchedulerThread}; +use self::{ + state::SchedulerState, + thread::{SchedulerThread, SchedulerThreadId, SchedulerThreadSender}, +}; /** Scheduler for Lua threads. @@ -57,6 +65,7 @@ pub struct SchedulerImpl { lua: Arc, state: SchedulerState, threads: RefCell>, + thread_senders: RefCell>, } impl SchedulerImpl { @@ -65,6 +74,7 @@ impl SchedulerImpl { lua, state: SchedulerState::new(), threads: RefCell::new(VecDeque::new()), + thread_senders: RefCell::new(HashMap::new()), } } } diff --git a/src/lune/scheduler/thread.rs b/src/lune/scheduler/thread.rs index da43880..9ddd5a7 100644 --- a/src/lune/scheduler/thread.rs +++ b/src/lune/scheduler/thread.rs @@ -1,10 +1,38 @@ +use std::sync::Arc; + use mlua::prelude::*; +use rand::Rng; +use tokio::sync::broadcast::Sender; + +/** + Type alias for a broadcast [`Sender`], which will + broadcast the result and return values of a lua thread. + + The return values are stored in the lua registry as a + `Vec>`, and the registry key pointing to + those values will be sent using the broadcast sender. +*/ +pub type SchedulerThreadSender = Sender>>; + +/** + Unique, randomly generated id for a scheduler thread. +*/ +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub struct SchedulerThreadId(u128); + +impl SchedulerThreadId { + fn gen() -> Self { + // FUTURE: Use a faster rng here? + Self(rand::thread_rng().gen()) + } +} /** Container for registry keys that point to a thread and thread arguments. */ #[derive(Debug)] pub(super) struct SchedulerThread { + scheduler_id: SchedulerThreadId, key_thread: LuaRegistryKey, key_args: LuaRegistryKey, } @@ -30,6 +58,7 @@ impl SchedulerThread { .context("Failed to store value in registry")?; Ok(Self { + scheduler_id: SchedulerThreadId::gen(), key_thread, key_args, }) @@ -55,4 +84,11 @@ impl SchedulerThread { (thread, args) } + + /** + Retrieves the unique, randomly generated id for this scheduler thread. + */ + pub(super) fn id(&self) -> SchedulerThreadId { + self.scheduler_id + } } From d1a2dc2fa662f48cdf98cfbf22ecb8e8bfc59e52 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Wed, 16 Aug 2023 22:04:42 -0500 Subject: [PATCH 004/103] Use atomics in scheduler state instead of refcell --- src/lune/scheduler/state.rs | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/lune/scheduler/state.rs b/src/lune/scheduler/state.rs index 2590211..25df33b 100644 --- a/src/lune/scheduler/state.rs +++ b/src/lune/scheduler/state.rs @@ -1,10 +1,11 @@ -use std::cell::RefCell; +use std::sync::atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering}; #[derive(Debug, Default)] pub struct SchedulerState { - exit_code: RefCell>, - num_resumptions: RefCell, - num_errors: RefCell, + exit_state: AtomicBool, + exit_code: AtomicU8, + num_resumptions: AtomicUsize, + num_errors: AtomicUsize, } impl SchedulerState { @@ -13,26 +14,31 @@ impl SchedulerState { } pub fn add_resumption(&self) { - *self.num_resumptions.borrow_mut() += 1; + self.num_resumptions.fetch_add(1, Ordering::Relaxed); } pub fn add_error(&self) { - *self.num_errors.borrow_mut() += 1; + self.num_errors.fetch_add(1, Ordering::Relaxed); } pub fn has_errored(&self) -> bool { - *self.num_errors.borrow() > 0 + self.num_errors.load(Ordering::SeqCst) > 0 } pub fn exit_code(&self) -> Option { - *self.exit_code.borrow() + if self.exit_state.load(Ordering::SeqCst) { + Some(self.exit_code.load(Ordering::SeqCst)) + } else { + None + } } pub fn has_exit_code(&self) -> bool { - self.exit_code.borrow().is_some() + self.exit_state.load(Ordering::SeqCst) } pub fn set_exit_code(&self, code: impl Into) { - self.exit_code.replace(Some(code.into())); + self.exit_state.store(true, Ordering::SeqCst); + self.exit_code.store(code.into(), Ordering::SeqCst); } } From eafc42531f1cce6374443759e4bad42908de6803 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Wed, 16 Aug 2023 22:40:59 -0500 Subject: [PATCH 005/103] Implement futures resumption --- src/lune/scheduler/impl_runner.rs | 34 ++++++++++++++++++++++++------ src/lune/scheduler/impl_threads.rs | 23 ++++++++++++++++++++ src/lune/scheduler/mod.rs | 5 +++++ 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/lune/scheduler/impl_runner.rs b/src/lune/scheduler/impl_runner.rs index f822657..80e21e0 100644 --- a/src/lune/scheduler/impl_runner.rs +++ b/src/lune/scheduler/impl_runner.rs @@ -1,5 +1,6 @@ use std::{process::ExitCode, sync::Arc}; +use futures_util::StreamExt; use mlua::prelude::*; use tokio::task::LocalSet; @@ -51,6 +52,28 @@ impl SchedulerImpl { resumed_any } + /** + Runs futures until none are left or a future spawned a new lua thread. + + Returns `true` if any future was resumed, `false` otherwise. + */ + async fn run_futures(&self) -> bool { + let mut resumed_any = false; + + let mut futs = self + .futures + .try_lock() + .expect("Failed to lock futures for resumption"); + while futs.next().await.is_some() { + resumed_any = true; + if self.has_thread() { + break; + } + } + + resumed_any + } + /** Runs the scheduler to completion in a [`LocalSet`], both normal lua threads and futures, prioritizing @@ -71,14 +94,13 @@ impl SchedulerImpl { break; } - // 3. Wait for the next future to complete, this may - // add more lua threads to run in the next iteration - - // TODO: Implement futures resumption + // 3. Keep resuming futures until we get a new lua thread to + // resume, or until we don't have any futures left to wait for + let resumed_fut = self.run_futures().await; // 4. If we did not resume any lua threads, and we have no futures - // queued either, we have now run the scheduler until completion - if !resumed_lua { + // remaining either, we have now run the scheduler until completion + if !resumed_lua && !resumed_fut { break; } } diff --git a/src/lune/scheduler/impl_threads.rs b/src/lune/scheduler/impl_threads.rs index 7343595..5377dcc 100644 --- a/src/lune/scheduler/impl_threads.rs +++ b/src/lune/scheduler/impl_threads.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use mlua::prelude::*; use super::{ @@ -7,6 +9,17 @@ use super::{ }; impl<'lua> SchedulerImpl { + /** + Checks if there are any lua threads to run. + */ + pub(super) fn has_thread(&self) -> bool { + !self + .threads + .try_borrow() + .expect("Failed to borrow threads vec") + .is_empty() + } + /** Pops the next thread to run, from the front of the scheduler. @@ -111,6 +124,16 @@ impl<'lua> SchedulerImpl { .lua .registry_value::>(&k) .expect("Received invalid registry key for thread"); + + // NOTE: This is not strictly necessary, mlua can clean + // up registry values on its own, but doing this will add + // some extra safety and clean up registry values faster + if let Some(key) = Arc::into_inner(k) { + self.lua + .remove_registry_value(key) + .expect("Failed to remove registry key for thread"); + } + Ok(LuaMultiValue::from_vec(vals)) } } diff --git a/src/lune/scheduler/mod.rs b/src/lune/scheduler/mod.rs index 0ce3de7..0ada0c2 100644 --- a/src/lune/scheduler/mod.rs +++ b/src/lune/scheduler/mod.rs @@ -2,10 +2,13 @@ use std::{ cell::RefCell, collections::{HashMap, VecDeque}, ops::Deref, + pin::Pin, sync::Arc, }; +use futures_util::{stream::FuturesUnordered, Future}; use mlua::prelude::*; +use tokio::sync::Mutex as AsyncMutex; mod state; mod thread; @@ -66,6 +69,7 @@ pub struct SchedulerImpl { state: SchedulerState, threads: RefCell>, thread_senders: RefCell>, + futures: AsyncMutex>>>>, } impl SchedulerImpl { @@ -75,6 +79,7 @@ impl SchedulerImpl { state: SchedulerState::new(), threads: RefCell::new(VecDeque::new()), thread_senders: RefCell::new(HashMap::new()), + futures: AsyncMutex::new(FuturesUnordered::new()), } } } From 0a5305b9470daeba8e216a18ac047a97dcee8cfb Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Wed, 16 Aug 2023 22:51:40 -0500 Subject: [PATCH 006/103] Add api for scheduling plain futures in scheduler --- src/lune/scheduler/impl_async.rs | 18 ++++++++++++++++++ src/lune/scheduler/impl_runner.rs | 2 +- src/lune/scheduler/mod.rs | 1 + 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/lune/scheduler/impl_async.rs diff --git a/src/lune/scheduler/impl_async.rs b/src/lune/scheduler/impl_async.rs new file mode 100644 index 0000000..4b2d279 --- /dev/null +++ b/src/lune/scheduler/impl_async.rs @@ -0,0 +1,18 @@ +use futures_util::Future; + +use super::SchedulerImpl; + +impl SchedulerImpl { + /** + Schedules a plain future to run whenever the scheduler is available. + */ + pub fn schedule_future(&self, fut: F) + where + F: Future + 'static, + { + self.futures + .try_lock() + .expect("Failed to lock futures queue") + .push(Box::pin(fut)) + } +} diff --git a/src/lune/scheduler/impl_runner.rs b/src/lune/scheduler/impl_runner.rs index 80e21e0..0299ad1 100644 --- a/src/lune/scheduler/impl_runner.rs +++ b/src/lune/scheduler/impl_runner.rs @@ -63,7 +63,7 @@ impl SchedulerImpl { let mut futs = self .futures .try_lock() - .expect("Failed to lock futures for resumption"); + .expect("Failed to lock futures queue"); while futs.next().await.is_some() { resumed_any = true; if self.has_thread() { diff --git a/src/lune/scheduler/mod.rs b/src/lune/scheduler/mod.rs index 0ada0c2..3df4a89 100644 --- a/src/lune/scheduler/mod.rs +++ b/src/lune/scheduler/mod.rs @@ -14,6 +14,7 @@ mod state; mod thread; mod traits; +mod impl_async; mod impl_runner; mod impl_threads; From 7c8af9730f9b036f636bdf730393a67b47afcde7 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Thu, 17 Aug 2023 00:23:20 -0500 Subject: [PATCH 007/103] Some work on async functions for new scheduler --- Cargo.lock | 40 ++++++++++++++++++++++++++++ Cargo.toml | 1 + src/lune/scheduler/impl_async.rs | 29 +++++++++++++++++--- src/lune/scheduler/traits.rs | 45 +++++++++++++++++++++++++++++++- 4 files changed, 111 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c7e4220..b26b0f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1140,6 +1140,7 @@ dependencies = [ "bstr", "erased-serde", "mlua-sys", + "mlua_derive", "num-traits", "once_cell", "rustc-hash", @@ -1159,6 +1160,21 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "mlua_derive" +version = "0.9.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b900bf5619cdf327722cb39424409856a46f3a516628117a896cdbeecc0a9b1" +dependencies = [ + "itertools", + "once_cell", + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn 2.0.28", +] + [[package]] name = "nibble_vec" version = "0.1.0" @@ -1331,6 +1347,30 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro-hack" version = "0.5.20+deprecated" diff --git a/Cargo.toml b/Cargo.toml index 0bc1d5c..deabb10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,6 +78,7 @@ urlencoding = "2.1" ### RUNTIME mlua = { version = "0.9.0-beta.3", features = [ + "macros", "luau", "luau-jit", "serialize", diff --git a/src/lune/scheduler/impl_async.rs b/src/lune/scheduler/impl_async.rs index 4b2d279..8ded33b 100644 --- a/src/lune/scheduler/impl_async.rs +++ b/src/lune/scheduler/impl_async.rs @@ -1,18 +1,41 @@ use futures_util::Future; +use mlua::prelude::*; -use super::SchedulerImpl; +use super::{traits::IntoLuaThread, SchedulerImpl}; -impl SchedulerImpl { +impl<'lua> SchedulerImpl { /** Schedules a plain future to run whenever the scheduler is available. */ pub fn schedule_future(&self, fut: F) where - F: Future + 'static, + F: 'static + Future, { self.futures .try_lock() .expect("Failed to lock futures queue") .push(Box::pin(fut)) } + /** + Schedules the given `thread` to run when the given `fut` completes. + */ + pub fn schedule_thread(&'lua self, thread: T, fut: F) -> LuaResult<()> + where + T: IntoLuaThread<'lua>, + R: IntoLuaMulti<'lua>, + F: 'static + Future>, + { + let thread = thread.into_lua_thread(&self.lua)?; + + let fut = async move { + let rets = fut.await.expect("Failed to receive result"); + self.push_back(thread, rets) + .expect("Failed to schedule future thread"); + }; + + // TODO: Lifetime issues + // self.schedule_future(fut); + + Ok(()) + } } diff --git a/src/lune/scheduler/traits.rs b/src/lune/scheduler/traits.rs index 5a05d5d..3901458 100644 --- a/src/lune/scheduler/traits.rs +++ b/src/lune/scheduler/traits.rs @@ -1,4 +1,5 @@ -use mlua::prelude::*; +use futures_util::Future; +use mlua::{chunk, prelude::*}; use super::Scheduler; @@ -15,6 +16,20 @@ pub trait LuaSchedulerExt { not be dropped either because of the strong reference. */ fn scheduler(&self) -> Scheduler; + + /** + Creates a function callable from Lua that runs an async + closure and returns the results of it to the call site. + */ + fn create_async_function<'lua, A, R, F, FR>( + &'lua self, + func: F, + ) -> LuaResult> + where + A: FromLuaMulti<'lua>, + R: IntoLuaMulti<'lua>, + F: 'static + Fn(&'lua Lua, A) -> FR, + FR: 'static + Future>; } impl LuaSchedulerExt for Lua { @@ -23,6 +38,34 @@ impl LuaSchedulerExt for Lua { .expect("Lua struct is missing scheduler") .clone() } + + fn create_async_function<'lua, A, R, F, FR>(&'lua self, func: F) -> LuaResult> + where + A: FromLuaMulti<'lua>, + R: IntoLuaMulti<'lua>, + F: 'static + Fn(&'lua Lua, A) -> FR, + FR: 'static + Future>, + { + let async_yield = self + .globals() + .get::<_, LuaTable>("coroutine")? + .get::<_, LuaFunction>("yield")?; + let async_schedule = self.create_function(move |lua: &Lua, args: A| { + let thread = lua.current_thread(); + let future = func(lua, args); + // TODO: Add to scheduler + Ok(()) + })?; + + let async_func = self + .load(chunk!({ + $async_schedule(...) + return $async_yield() + })) + .set_name("async") + .into_function()?; + Ok(async_func) + } } /** From e08908e22ea798ef427be21d936275c734b3c550 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Thu, 17 Aug 2023 18:54:05 -0500 Subject: [PATCH 008/103] Update mlua version --- Cargo.lock | 16 ++++++++-------- Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b26b0f2..fe200cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1019,9 +1019,9 @@ checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "luau0-src" -version = "0.6.0+luau588" +version = "0.7.0+luau590" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c628f5525cc62a89a2d478b2ee619c77b35da55c8e3231752f3b8fe528a6c49" +checksum = "13f07237e0d84bd4e3ed6b14335d53332acc4e8b1660e740bba2566ddb93cfb1" dependencies = [ "cc", ] @@ -1133,9 +1133,9 @@ dependencies = [ [[package]] name = "mlua" -version = "0.9.0-rc.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a6500a9fb74b519a85ac206cd57f9f91b270ce39d6cb12ab06a8ed29c3563d" +checksum = "5d6356d163eb2ebaa9caafc3a3ed6aedc686128e913557cd3a5591fd4cd726f5" dependencies = [ "bstr", "erased-serde", @@ -1150,9 +1150,9 @@ dependencies = [ [[package]] name = "mlua-sys" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa5b61f6c943d77dd6ab5f670865670f65b978400127c8bf31c2df7d6e76289a" +checksum = "3ec8b54eddb76093069cce9eeffb4c7b3a1a0fe66962d7bd44c4867928149ca3" dependencies = [ "cc", "cfg-if", @@ -1162,9 +1162,9 @@ dependencies = [ [[package]] name = "mlua_derive" -version = "0.9.0-rc.2" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b900bf5619cdf327722cb39424409856a46f3a516628117a896cdbeecc0a9b1" +checksum = "0f359220f24e6452dd82a3f50d7242d4aab822b5594798048e953d7a9e0314c6" dependencies = [ "itertools", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index deabb10..0874b3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,7 +77,7 @@ urlencoding = "2.1" ### RUNTIME -mlua = { version = "0.9.0-beta.3", features = [ +mlua = { version = "0.9.0", features = [ "macros", "luau", "luau-jit", From 6416ef5fb7abc9202a7f8600b65bbb7ab7fb4b4a Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Thu, 17 Aug 2023 20:00:01 -0500 Subject: [PATCH 009/103] More work on scheduler async stuff --- Cargo.toml | 3 +- src/lune/scheduler/impl_async.rs | 31 +++++++++-------- src/lune/scheduler/impl_runner.rs | 2 +- src/lune/scheduler/impl_threads.rs | 12 +++---- src/lune/scheduler/mod.rs | 21 +++++------- src/lune/scheduler/thread.rs | 28 ++++++---------- src/lune/scheduler/traits.rs | 53 ++++++++++++++++-------------- 7 files changed, 73 insertions(+), 77 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0874b3f..bc43256 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,10 +78,11 @@ urlencoding = "2.1" ### RUNTIME mlua = { version = "0.9.0", features = [ - "macros", "luau", "luau-jit", "serialize", + "macros", + "unstable", ] } tokio = { version = "1.24", features = ["full"] } diff --git a/src/lune/scheduler/impl_async.rs b/src/lune/scheduler/impl_async.rs index 8ded33b..b87c645 100644 --- a/src/lune/scheduler/impl_async.rs +++ b/src/lune/scheduler/impl_async.rs @@ -1,9 +1,12 @@ use futures_util::Future; use mlua::prelude::*; -use super::{traits::IntoLuaThread, SchedulerImpl}; +use super::{traits::IntoLuaOwnedThread, SchedulerImpl}; -impl<'lua> SchedulerImpl { +impl<'lua, 'fut> SchedulerImpl +where + 'lua: 'fut, +{ /** Schedules a plain future to run whenever the scheduler is available. */ @@ -11,30 +14,30 @@ impl<'lua> SchedulerImpl { where F: 'static + Future, { - self.futures + let futs = self + .futures .try_lock() - .expect("Failed to lock futures queue") - .push(Box::pin(fut)) + .expect("Failed to lock futures queue"); + futs.push(Box::pin(fut)) } + /** Schedules the given `thread` to run when the given `fut` completes. */ - pub fn schedule_thread(&'lua self, thread: T, fut: F) -> LuaResult<()> + pub fn schedule_future_thread(&'lua self, thread: T, fut: F) -> LuaResult<()> where - T: IntoLuaThread<'lua>, - R: IntoLuaMulti<'lua>, + T: 'static + IntoLuaOwnedThread, F: 'static + Future>, + R: IntoLuaMulti<'fut>, { - let thread = thread.into_lua_thread(&self.lua)?; + let thread = thread.into_owned_lua_thread(&self.lua)?; - let fut = async move { + // FIXME: We use self in the future below, so this doesn't compile... how to fix? + self.schedule_future(async move { let rets = fut.await.expect("Failed to receive result"); self.push_back(thread, rets) .expect("Failed to schedule future thread"); - }; - - // TODO: Lifetime issues - // self.schedule_future(fut); + }); Ok(()) } diff --git a/src/lune/scheduler/impl_runner.rs b/src/lune/scheduler/impl_runner.rs index 0299ad1..0ce52b8 100644 --- a/src/lune/scheduler/impl_runner.rs +++ b/src/lune/scheduler/impl_runner.rs @@ -6,7 +6,7 @@ use tokio::task::LocalSet; use super::SchedulerImpl; -impl SchedulerImpl { +impl<'lua> SchedulerImpl { /** Runs all lua threads to completion. diff --git a/src/lune/scheduler/impl_threads.rs b/src/lune/scheduler/impl_threads.rs index 5377dcc..ca3de93 100644 --- a/src/lune/scheduler/impl_threads.rs +++ b/src/lune/scheduler/impl_threads.rs @@ -4,7 +4,7 @@ use mlua::prelude::*; use super::{ thread::{SchedulerThread, SchedulerThreadId, SchedulerThreadSender}, - traits::IntoLuaThread, + traits::IntoLuaOwnedThread, SchedulerImpl, }; @@ -27,7 +27,7 @@ impl<'lua> SchedulerImpl { */ pub(super) fn pop_thread( &'lua self, - ) -> LuaResult, LuaMultiValue<'lua>, SchedulerThreadSender)>> { + ) -> LuaResult, SchedulerThreadSender)>> { match self .threads .try_borrow_mut() @@ -55,10 +55,10 @@ impl<'lua> SchedulerImpl { */ pub fn push_front( &'lua self, - thread: impl IntoLuaThread<'lua>, + thread: impl IntoLuaOwnedThread, args: impl IntoLuaMulti<'lua>, ) -> LuaResult { - let thread = thread.into_lua_thread(&self.lua)?; + let thread = thread.into_owned_lua_thread(&self.lua)?; let args = args.into_lua_multi(&self.lua)?; let thread = SchedulerThread::new(&self.lua, thread, args)?; @@ -82,10 +82,10 @@ impl<'lua> SchedulerImpl { */ pub fn push_back( &'lua self, - thread: impl IntoLuaThread<'lua>, + thread: impl IntoLuaOwnedThread, args: impl IntoLuaMulti<'lua>, ) -> LuaResult { - let thread = thread.into_lua_thread(&self.lua)?; + let thread = thread.into_owned_lua_thread(&self.lua)?; let args = args.into_lua_multi(&self.lua)?; let thread = SchedulerThread::new(&self.lua, thread, args)?; diff --git a/src/lune/scheduler/mod.rs b/src/lune/scheduler/mod.rs index 3df4a89..3a2a070 100644 --- a/src/lune/scheduler/mod.rs +++ b/src/lune/scheduler/mod.rs @@ -30,32 +30,29 @@ use self::{ to the same underlying scheduler and Lua struct. */ #[derive(Debug, Clone)] -pub struct Scheduler(Arc); +pub struct Scheduler { + lua: Arc, + inner: Arc, +} impl Scheduler { /** Creates a new scheduler for the given [`Lua`] struct. */ pub fn new(lua: Arc) -> Self { - assert!( - lua.app_data_ref::().is_none() && lua.app_data_ref::<&Self>().is_none(), - "Only one scheduler may be created per Lua struct" - ); + let sched_lua = Arc::clone(&lua); + let sched_impl = SchedulerImpl::new(sched_lua); - let inner = SchedulerImpl::new(Arc::clone(&lua)); - let sched = Self(Arc::new(inner)); + let inner = Arc::new(sched_impl); - lua.set_app_data(sched.clone()); - lua.set_interrupt(move |_| Ok(LuaVmState::Continue)); - - sched + Self { lua, inner } } } impl Deref for Scheduler { type Target = SchedulerImpl; fn deref(&self) -> &Self::Target { - &self.0 + &self.inner } } diff --git a/src/lune/scheduler/thread.rs b/src/lune/scheduler/thread.rs index 9ddd5a7..8362cc1 100644 --- a/src/lune/scheduler/thread.rs +++ b/src/lune/scheduler/thread.rs @@ -33,8 +33,8 @@ impl SchedulerThreadId { #[derive(Debug)] pub(super) struct SchedulerThread { scheduler_id: SchedulerThreadId, - key_thread: LuaRegistryKey, - key_args: LuaRegistryKey, + thread: LuaOwnedThread, + args: LuaRegistryKey, } impl SchedulerThread { @@ -45,44 +45,36 @@ impl SchedulerThread { */ pub(super) fn new<'lua>( lua: &'lua Lua, - thread: LuaThread<'lua>, + thread: LuaOwnedThread, args: LuaMultiValue<'lua>, ) -> LuaResult { let args_vec = args.into_vec(); - let key_thread = lua - .create_registry_value(thread) - .context("Failed to store value in registry")?; - let key_args = lua + let args = lua .create_registry_value(args_vec) .context("Failed to store value in registry")?; Ok(Self { scheduler_id: SchedulerThreadId::gen(), - key_thread, - key_args, + thread, + args, }) } /** Extracts the inner thread and args from the container. */ - pub(super) fn into_inner(self, lua: &Lua) -> (LuaThread<'_>, LuaMultiValue<'_>) { - let thread = lua - .registry_value(&self.key_thread) - .expect("Failed to get thread from registry"); + pub(super) fn into_inner(self, lua: &Lua) -> (LuaOwnedThread, LuaMultiValue<'_>) { let args_vec = lua - .registry_value(&self.key_args) + .registry_value(&self.args) .expect("Failed to get thread args from registry"); let args = LuaMultiValue::from_vec(args_vec); - lua.remove_registry_value(self.key_thread) - .expect("Failed to remove thread from registry"); - lua.remove_registry_value(self.key_args) + lua.remove_registry_value(self.args) .expect("Failed to remove thread args from registry"); - (thread, args) + (self.thread, args) } /** diff --git a/src/lune/scheduler/traits.rs b/src/lune/scheduler/traits.rs index 3901458..0b26b7c 100644 --- a/src/lune/scheduler/traits.rs +++ b/src/lune/scheduler/traits.rs @@ -10,12 +10,9 @@ use super::Scheduler; */ pub trait LuaSchedulerExt { /** - Get a strong reference to the scheduler for the [`Lua`] struct. - - Note that if this reference is not dropped, `Lua` can - not be dropped either because of the strong reference. + Get a reference to the scheduler for the [`Lua`] struct. */ - fn scheduler(&self) -> Scheduler; + fn scheduler(&self) -> &Scheduler; /** Creates a function callable from Lua that runs an async @@ -33,10 +30,10 @@ pub trait LuaSchedulerExt { } impl LuaSchedulerExt for Lua { - fn scheduler(&self) -> Scheduler { - self.app_data_ref::() + fn scheduler(&self) -> &Scheduler { + *self + .app_data_ref::<&Scheduler>() .expect("Lua struct is missing scheduler") - .clone() } fn create_async_function<'lua, A, R, F, FR>(&'lua self, func: F) -> LuaResult> @@ -46,21 +43,21 @@ impl LuaSchedulerExt for Lua { F: 'static + Fn(&'lua Lua, A) -> FR, FR: 'static + Future>, { - let async_yield = self + let coroutine_yield = self .globals() .get::<_, LuaTable>("coroutine")? .get::<_, LuaFunction>("yield")?; - let async_schedule = self.create_function(move |lua: &Lua, args: A| { - let thread = lua.current_thread(); + let schedule = LuaFunction::wrap(move |lua: &Lua, args: A| { + let thread = lua.current_thread().into_owned(); let future = func(lua, args); - // TODO: Add to scheduler + lua.scheduler().schedule_future_thread(thread, future); Ok(()) - })?; + }); let async_func = self .load(chunk!({ - $async_schedule(...) - return $async_yield() + $schedule(...) + return $coroutine_yield() })) .set_name("async") .into_function()?; @@ -76,27 +73,33 @@ impl LuaSchedulerExt for Lua { - Lua functions ([`LuaFunction`]) - Lua chunks ([`LuaChunk`]) */ -pub trait IntoLuaThread<'lua> { +pub trait IntoLuaOwnedThread { /** Converts the value into a lua thread. */ - fn into_lua_thread(self, lua: &'lua Lua) -> LuaResult>; + fn into_owned_lua_thread(self, lua: &Lua) -> LuaResult; } -impl<'lua> IntoLuaThread<'lua> for LuaThread<'lua> { - fn into_lua_thread(self, _: &'lua Lua) -> LuaResult> { +impl IntoLuaOwnedThread for LuaOwnedThread { + fn into_owned_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> IntoLuaOwnedThread for LuaThread<'lua> { + fn into_owned_lua_thread(self, _lua: &Lua) -> LuaResult { + Ok(self.into_owned()) } } -impl<'lua, 'a> IntoLuaThread<'lua> for LuaChunk<'lua, 'a> { - fn into_lua_thread(self, lua: &'lua Lua) -> LuaResult> { - lua.create_thread(self.into_function()?) +impl<'lua> IntoLuaOwnedThread for LuaFunction<'lua> { + fn into_owned_lua_thread(self, lua: &Lua) -> LuaResult { + Ok(lua.create_thread(self)?.into_owned()) + } +} + +impl<'lua, 'a> IntoLuaOwnedThread for LuaChunk<'lua, 'a> { + fn into_owned_lua_thread(self, lua: &Lua) -> LuaResult { + Ok(lua.create_thread(self.into_function()?)?.into_owned()) } } From ab386e000dfbb533e0920b12c941ea2ec7e06b48 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Thu, 17 Aug 2023 21:24:20 -0500 Subject: [PATCH 010/103] Add lifetimes because they are fun --- src/lune/scheduler/impl_async.rs | 10 ++-- src/lune/scheduler/impl_runner.rs | 31 ++++++++++-- src/lune/scheduler/impl_threads.rs | 5 +- src/lune/scheduler/mod.rs | 21 ++++---- src/lune/scheduler/traits.rs | 79 ++++++++++++++++++------------ 5 files changed, 95 insertions(+), 51 deletions(-) diff --git a/src/lune/scheduler/impl_async.rs b/src/lune/scheduler/impl_async.rs index b87c645..b43f0e7 100644 --- a/src/lune/scheduler/impl_async.rs +++ b/src/lune/scheduler/impl_async.rs @@ -3,7 +3,7 @@ use mlua::prelude::*; use super::{traits::IntoLuaOwnedThread, SchedulerImpl}; -impl<'lua, 'fut> SchedulerImpl +impl<'lua, 'fut> SchedulerImpl<'fut> where 'lua: 'fut, { @@ -12,7 +12,7 @@ where */ pub fn schedule_future(&self, fut: F) where - F: 'static + Future, + F: 'fut + Future, { let futs = self .futures @@ -24,11 +24,11 @@ where /** Schedules the given `thread` to run when the given `fut` completes. */ - pub fn schedule_future_thread(&'lua self, thread: T, fut: F) -> LuaResult<()> + pub fn schedule_future_thread(&'fut self, thread: T, fut: F) -> LuaResult<()> where - T: 'static + IntoLuaOwnedThread, - F: 'static + Future>, + T: IntoLuaOwnedThread, R: IntoLuaMulti<'fut>, + F: 'fut + Future>, { let thread = thread.into_owned_lua_thread(&self.lua)?; diff --git a/src/lune/scheduler/impl_runner.rs b/src/lune/scheduler/impl_runner.rs index 0ce52b8..ff37ca0 100644 --- a/src/lune/scheduler/impl_runner.rs +++ b/src/lune/scheduler/impl_runner.rs @@ -4,15 +4,18 @@ use futures_util::StreamExt; use mlua::prelude::*; use tokio::task::LocalSet; -use super::SchedulerImpl; +use super::{traits::IntoLuaOwnedThread, SchedulerImpl}; -impl<'lua> SchedulerImpl { +impl<'lua, 'fut> SchedulerImpl<'fut> +where + 'lua: 'fut, +{ /** Runs all lua threads to completion. Returns `true` if any thread was resumed, `false` otherwise. */ - fn run_lua_threads(&self) -> bool { + fn run_lua_threads(&'lua self) -> bool { if self.state.has_exit_code() { return false; } @@ -57,7 +60,7 @@ impl<'lua> SchedulerImpl { Returns `true` if any future was resumed, `false` otherwise. */ - async fn run_futures(&self) -> bool { + async fn run_futures(&'lua self) -> bool { let mut resumed_any = false; let mut futs = self @@ -81,7 +84,7 @@ impl<'lua> SchedulerImpl { Will emit lua output and errors to stdout and stderr. */ - pub async fn run_to_completion(&self) -> ExitCode { + pub async fn run_to_completion(&'lua self) -> ExitCode { let fut = async move { loop { // 1. Run lua threads until exit or there are none left, @@ -116,4 +119,22 @@ impl<'lua> SchedulerImpl { ExitCode::SUCCESS } } + + /** + Schedules a new main thread and runs the scheduler until completion. + + See [`Self::run_to_completion`] for more info. + */ + pub async fn run_main( + &'lua self, + main: impl IntoLuaOwnedThread, + args: impl IntoLuaMulti<'lua>, + ) -> ExitCode { + let thread = main + .into_owned_lua_thread(&self.lua) + .expect("Failed to create thread for main"); + self.push_back(thread, args) + .expect("Failed to queue thread for main"); + self.run_to_completion().await + } } diff --git a/src/lune/scheduler/impl_threads.rs b/src/lune/scheduler/impl_threads.rs index ca3de93..da043c0 100644 --- a/src/lune/scheduler/impl_threads.rs +++ b/src/lune/scheduler/impl_threads.rs @@ -8,7 +8,10 @@ use super::{ SchedulerImpl, }; -impl<'lua> SchedulerImpl { +impl<'lua, 'fut> SchedulerImpl<'fut> +where + 'lua: 'fut, +{ /** Checks if there are any lua threads to run. */ diff --git a/src/lune/scheduler/mod.rs b/src/lune/scheduler/mod.rs index 3a2a070..a6937c3 100644 --- a/src/lune/scheduler/mod.rs +++ b/src/lune/scheduler/mod.rs @@ -18,6 +18,8 @@ mod impl_async; mod impl_runner; mod impl_threads; +pub use self::traits::*; + use self::{ state::SchedulerState, thread::{SchedulerThread, SchedulerThreadId, SchedulerThreadSender}, @@ -30,12 +32,11 @@ use self::{ to the same underlying scheduler and Lua struct. */ #[derive(Debug, Clone)] -pub struct Scheduler { - lua: Arc, - inner: Arc, +pub(crate) struct Scheduler<'fut> { + inner: Arc>, } -impl Scheduler { +impl<'fut> Scheduler<'fut> { /** Creates a new scheduler for the given [`Lua`] struct. */ @@ -45,12 +46,12 @@ impl Scheduler { let inner = Arc::new(sched_impl); - Self { lua, inner } + Self { inner } } } -impl Deref for Scheduler { - type Target = SchedulerImpl; +impl<'fut> Deref for Scheduler<'fut> { + type Target = SchedulerImpl<'fut>; fn deref(&self) -> &Self::Target { &self.inner } @@ -62,15 +63,15 @@ impl Deref for Scheduler { Not meant to be used directly, use [`Scheduler`] instead. */ #[derive(Debug)] -pub struct SchedulerImpl { +pub(crate) struct SchedulerImpl<'fut> { lua: Arc, state: SchedulerState, threads: RefCell>, thread_senders: RefCell>, - futures: AsyncMutex>>>>, + futures: AsyncMutex + 'fut>>>>, } -impl SchedulerImpl { +impl<'fut> SchedulerImpl<'fut> { fn new(lua: Arc) -> Self { Self { lua, diff --git a/src/lune/scheduler/traits.rs b/src/lune/scheduler/traits.rs index 0b26b7c..bd24567 100644 --- a/src/lune/scheduler/traits.rs +++ b/src/lune/scheduler/traits.rs @@ -1,64 +1,83 @@ +use std::sync::Arc; + use futures_util::Future; -use mlua::{chunk, prelude::*}; +use mlua::prelude::*; use super::Scheduler; +const ASYNC_IMPL_LUA: &str = r#" +schedule(...) +return yield() +"#; + /** Trait for extensions to the [`Lua`] struct, allowing for access to the scheduler without having to import it or handle registry / app data references manually. */ -pub trait LuaSchedulerExt { +pub trait LuaSchedulerExt<'lua, 'fut> +where + 'lua: 'fut, +{ /** - Get a reference to the scheduler for the [`Lua`] struct. + Creates a new [`Lua`] struct with a [`Scheduler`]. */ - fn scheduler(&self) -> &Scheduler; + fn new_with_scheduler() -> Arc; /** Creates a function callable from Lua that runs an async closure and returns the results of it to the call site. */ - fn create_async_function<'lua, A, R, F, FR>( - &'lua self, - func: F, - ) -> LuaResult> + fn create_async_function(&'lua self, func: F) -> LuaResult> where A: FromLuaMulti<'lua>, R: IntoLuaMulti<'lua>, F: 'static + Fn(&'lua Lua, A) -> FR, - FR: 'static + Future>; + FR: 'fut + Future>; } -impl LuaSchedulerExt for Lua { - fn scheduler(&self) -> &Scheduler { - *self - .app_data_ref::<&Scheduler>() - .expect("Lua struct is missing scheduler") +impl<'lua, 'fut> LuaSchedulerExt<'lua, 'fut> for Lua +where + 'lua: 'fut, +{ + fn new_with_scheduler() -> Arc { + let lua = Arc::new(Lua::new()); + lua.set_app_data(Scheduler::new(Arc::clone(&lua))); + lua } - fn create_async_function<'lua, A, R, F, FR>(&'lua self, func: F) -> LuaResult> + fn create_async_function(&'lua self, func: F) -> LuaResult> where A: FromLuaMulti<'lua>, R: IntoLuaMulti<'lua>, F: 'static + Fn(&'lua Lua, A) -> FR, - FR: 'static + Future>, + FR: 'fut + Future>, { - let coroutine_yield = self - .globals() - .get::<_, LuaTable>("coroutine")? - .get::<_, LuaFunction>("yield")?; - let schedule = LuaFunction::wrap(move |lua: &Lua, args: A| { - let thread = lua.current_thread().into_owned(); - let future = func(lua, args); - lua.scheduler().schedule_future_thread(thread, future); - Ok(()) - }); + let async_env = self.create_table_with_capacity(0, 2)?; + + async_env.set( + "yield", + self.globals() + .get::<_, LuaTable>("coroutine")? + .get::<_, LuaFunction>("yield")?, + )?; + + async_env.set( + "schedule", + LuaFunction::wrap(move |lua: &Lua, args: A| { + let _thread = lua.current_thread().into_owned(); + let _future = func(lua, args); + let _sched = lua + .app_data_ref::<&Scheduler>() + .expect("Lua struct is missing scheduler"); + // FIXME: `self` escapes outside of method + // sched.schedule_future_thread(thread, future)?; + Ok(()) + }), + )?; let async_func = self - .load(chunk!({ - $schedule(...) - return $coroutine_yield() - })) + .load(ASYNC_IMPL_LUA) .set_name("async") .into_function()?; Ok(async_func) From 1b7287a74278fae05e464b97af6dc170965420e5 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Thu, 17 Aug 2023 21:37:09 -0500 Subject: [PATCH 011/103] Methods take owned threads --- src/lune/mod.rs | 10 +++++++--- src/lune/scheduler/impl_async.rs | 10 +++------- src/lune/scheduler/impl_runner.rs | 20 +------------------- src/lune/scheduler/impl_threads.rs | 7 ++----- 4 files changed, 13 insertions(+), 34 deletions(-) diff --git a/src/lune/mod.rs b/src/lune/mod.rs index 2477893..c625719 100644 --- a/src/lune/mod.rs +++ b/src/lune/mod.rs @@ -44,11 +44,15 @@ impl Lune { let lua = Arc::new(Lua::new()); let sched = Scheduler::new(Arc::clone(&lua)); - let main = lua + let main_fn = lua .load(script_contents.as_ref()) - .set_name(script_name.as_ref()); - sched.push_front(main, ())?; + .set_name(script_name.as_ref()) + .into_function()?; + let main_thread = lua.create_thread(main_fn)?.into_owned(); + sched + .push_back(main_thread, ()) + .expect("Failed to enqueue thread for main"); Ok(sched.run_to_completion().await) } } diff --git a/src/lune/scheduler/impl_async.rs b/src/lune/scheduler/impl_async.rs index b43f0e7..7e1ca67 100644 --- a/src/lune/scheduler/impl_async.rs +++ b/src/lune/scheduler/impl_async.rs @@ -1,7 +1,7 @@ use futures_util::Future; use mlua::prelude::*; -use super::{traits::IntoLuaOwnedThread, SchedulerImpl}; +use super::SchedulerImpl; impl<'lua, 'fut> SchedulerImpl<'fut> where @@ -10,7 +10,7 @@ where /** Schedules a plain future to run whenever the scheduler is available. */ - pub fn schedule_future(&self, fut: F) + pub fn schedule_future(&'lua self, fut: F) where F: 'fut + Future, { @@ -24,15 +24,11 @@ where /** Schedules the given `thread` to run when the given `fut` completes. */ - pub fn schedule_future_thread(&'fut self, thread: T, fut: F) -> LuaResult<()> + pub fn schedule_future_thread(&'lua self, thread: LuaOwnedThread, fut: F) -> LuaResult<()> where - T: IntoLuaOwnedThread, R: IntoLuaMulti<'fut>, F: 'fut + Future>, { - let thread = thread.into_owned_lua_thread(&self.lua)?; - - // FIXME: We use self in the future below, so this doesn't compile... how to fix? self.schedule_future(async move { let rets = fut.await.expect("Failed to receive result"); self.push_back(thread, rets) diff --git a/src/lune/scheduler/impl_runner.rs b/src/lune/scheduler/impl_runner.rs index ff37ca0..878bde7 100644 --- a/src/lune/scheduler/impl_runner.rs +++ b/src/lune/scheduler/impl_runner.rs @@ -4,7 +4,7 @@ use futures_util::StreamExt; use mlua::prelude::*; use tokio::task::LocalSet; -use super::{traits::IntoLuaOwnedThread, SchedulerImpl}; +use super::SchedulerImpl; impl<'lua, 'fut> SchedulerImpl<'fut> where @@ -119,22 +119,4 @@ where ExitCode::SUCCESS } } - - /** - Schedules a new main thread and runs the scheduler until completion. - - See [`Self::run_to_completion`] for more info. - */ - pub async fn run_main( - &'lua self, - main: impl IntoLuaOwnedThread, - args: impl IntoLuaMulti<'lua>, - ) -> ExitCode { - let thread = main - .into_owned_lua_thread(&self.lua) - .expect("Failed to create thread for main"); - self.push_back(thread, args) - .expect("Failed to queue thread for main"); - self.run_to_completion().await - } } diff --git a/src/lune/scheduler/impl_threads.rs b/src/lune/scheduler/impl_threads.rs index da043c0..b7d2b94 100644 --- a/src/lune/scheduler/impl_threads.rs +++ b/src/lune/scheduler/impl_threads.rs @@ -4,7 +4,6 @@ use mlua::prelude::*; use super::{ thread::{SchedulerThread, SchedulerThreadId, SchedulerThreadSender}, - traits::IntoLuaOwnedThread, SchedulerImpl, }; @@ -58,10 +57,9 @@ where */ pub fn push_front( &'lua self, - thread: impl IntoLuaOwnedThread, + thread: LuaOwnedThread, args: impl IntoLuaMulti<'lua>, ) -> LuaResult { - let thread = thread.into_owned_lua_thread(&self.lua)?; let args = args.into_lua_multi(&self.lua)?; let thread = SchedulerThread::new(&self.lua, thread, args)?; @@ -85,10 +83,9 @@ where */ pub fn push_back( &'lua self, - thread: impl IntoLuaOwnedThread, + thread: LuaOwnedThread, args: impl IntoLuaMulti<'lua>, ) -> LuaResult { - let thread = thread.into_owned_lua_thread(&self.lua)?; let args = args.into_lua_multi(&self.lua)?; let thread = SchedulerThread::new(&self.lua, thread, args)?; From dc80b1c28f31ebb3333b1e1099361993d5f94340 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Fri, 18 Aug 2023 09:20:35 -0500 Subject: [PATCH 012/103] Scheduler now manages entire Lua struct, elide lifetimes where possible --- src/lune/mod.rs | 20 ++------ src/lune/scheduler/impl_async.rs | 11 ++-- src/lune/scheduler/impl_runner.rs | 81 ++++++++++++++++++++---------- src/lune/scheduler/impl_threads.rs | 24 ++++----- src/lune/scheduler/mod.rs | 46 +++-------------- src/lune/scheduler/traits.rs | 13 ----- 6 files changed, 81 insertions(+), 114 deletions(-) diff --git a/src/lune/mod.rs b/src/lune/mod.rs index c625719..b46e023 100644 --- a/src/lune/mod.rs +++ b/src/lune/mod.rs @@ -1,6 +1,4 @@ -use std::{process::ExitCode, sync::Arc}; - -use mlua::prelude::*; +use std::process::ExitCode; mod error; mod scheduler; @@ -41,18 +39,8 @@ impl Lune { script_name: impl AsRef, script_contents: impl AsRef<[u8]>, ) -> Result { - let lua = Arc::new(Lua::new()); - let sched = Scheduler::new(Arc::clone(&lua)); - - let main_fn = lua - .load(script_contents.as_ref()) - .set_name(script_name.as_ref()) - .into_function()?; - let main_thread = lua.create_thread(main_fn)?.into_owned(); - - sched - .push_back(main_thread, ()) - .expect("Failed to enqueue thread for main"); - Ok(sched.run_to_completion().await) + Ok(Scheduler::new() + .run_main(script_name, script_contents) + .await) } } diff --git a/src/lune/scheduler/impl_async.rs b/src/lune/scheduler/impl_async.rs index 7e1ca67..65c1970 100644 --- a/src/lune/scheduler/impl_async.rs +++ b/src/lune/scheduler/impl_async.rs @@ -1,16 +1,16 @@ use futures_util::Future; use mlua::prelude::*; -use super::SchedulerImpl; +use super::Scheduler; -impl<'lua, 'fut> SchedulerImpl<'fut> +impl<'lua, 'fut> Scheduler<'fut> where 'lua: 'fut, { /** Schedules a plain future to run whenever the scheduler is available. */ - pub fn schedule_future(&'lua self, fut: F) + pub fn schedule_future(&'fut self, fut: F) where F: 'fut + Future, { @@ -24,10 +24,9 @@ where /** Schedules the given `thread` to run when the given `fut` completes. */ - pub fn schedule_future_thread(&'lua self, thread: LuaOwnedThread, fut: F) -> LuaResult<()> + pub fn schedule_future_thread(&'fut self, thread: LuaOwnedThread, fut: F) -> LuaResult<()> where - R: IntoLuaMulti<'fut>, - F: 'fut + Future>, + F: 'fut + Future>>, { self.schedule_future(async move { let rets = fut.await.expect("Failed to receive result"); diff --git a/src/lune/scheduler/impl_runner.rs b/src/lune/scheduler/impl_runner.rs index 878bde7..ed84b92 100644 --- a/src/lune/scheduler/impl_runner.rs +++ b/src/lune/scheduler/impl_runner.rs @@ -2,11 +2,14 @@ use std::{process::ExitCode, sync::Arc}; use futures_util::StreamExt; use mlua::prelude::*; + use tokio::task::LocalSet; -use super::SchedulerImpl; +use super::{IntoLuaOwnedThread, Scheduler}; -impl<'lua, 'fut> SchedulerImpl<'fut> +const EMPTY_MULTI_VALUE: LuaMultiValue = LuaMultiValue::new(); + +impl<'lua, 'fut> Scheduler<'fut> where 'lua: 'fut, { @@ -15,7 +18,7 @@ where Returns `true` if any thread was resumed, `false` otherwise. */ - fn run_lua_threads(&'lua self) -> bool { + fn run_lua_threads(&self) -> bool { if self.state.has_exit_code() { return false; } @@ -60,7 +63,7 @@ where Returns `true` if any future was resumed, `false` otherwise. */ - async fn run_futures(&'lua self) -> bool { + async fn run_futures(&self) -> bool { let mut resumed_any = false; let mut futs = self @@ -84,32 +87,31 @@ where Will emit lua output and errors to stdout and stderr. */ - pub async fn run_to_completion(&'lua self) -> ExitCode { - let fut = async move { - loop { - // 1. Run lua threads until exit or there are none left, - // if any thread was resumed it may have spawned futures - let resumed_lua = self.run_lua_threads(); + pub async fn run_to_completion(&self) -> ExitCode { + let set = LocalSet::new(); + let _guard = set.enter(); - // 2. If we got a manual exit code from lua we should - // not try to wait for any pending futures to complete - if self.state.has_exit_code() { - break; - } + loop { + // 1. Run lua threads until exit or there are none left, + // if any thread was resumed it may have spawned futures + let resumed_lua = self.run_lua_threads(); - // 3. Keep resuming futures until we get a new lua thread to - // resume, or until we don't have any futures left to wait for - let resumed_fut = self.run_futures().await; - - // 4. If we did not resume any lua threads, and we have no futures - // remaining either, we have now run the scheduler until completion - if !resumed_lua && !resumed_fut { - break; - } + // 2. If we got a manual exit code from lua we should + // not try to wait for any pending futures to complete + if self.state.has_exit_code() { + break; } - }; - LocalSet::new().run_until(fut).await; + // 3. Keep resuming futures until we get a new lua thread to + // resume, or until we don't have any futures left to wait for + let resumed_fut = self.run_futures().await; + + // 4. If we did not resume any lua threads, and we have no futures + // remaining either, we have now run the scheduler until completion + if !resumed_lua && !resumed_fut { + break; + } + } if let Some(code) = self.state.exit_code() { ExitCode::from(code) @@ -119,4 +121,31 @@ where ExitCode::SUCCESS } } + + /** + Runs a script with the given `script_name` and `script_contents` to completion. + + Refer to [`run_to_completion`] for additional details. + */ + pub async fn run_main( + self, + script_name: impl AsRef, + script_contents: impl AsRef<[u8]>, + ) -> ExitCode { + let main_fn = self + .lua + .load(script_contents.as_ref()) + .set_name(script_name.as_ref()) + .into_function() + .expect("Failed to create function for main"); + + let main_thread = main_fn + .into_owned_lua_thread(&self.lua) + .expect("Failed to create thread for main"); + + self.push_back(main_thread, EMPTY_MULTI_VALUE) + .expect("Failed to enqueue thread for main"); + + self.run_to_completion().await + } } diff --git a/src/lune/scheduler/impl_threads.rs b/src/lune/scheduler/impl_threads.rs index b7d2b94..b7dea8a 100644 --- a/src/lune/scheduler/impl_threads.rs +++ b/src/lune/scheduler/impl_threads.rs @@ -4,10 +4,10 @@ use mlua::prelude::*; use super::{ thread::{SchedulerThread, SchedulerThreadId, SchedulerThreadSender}, - SchedulerImpl, + Scheduler, }; -impl<'lua, 'fut> SchedulerImpl<'fut> +impl<'lua, 'fut> Scheduler<'fut> where 'lua: 'fut, { @@ -28,8 +28,8 @@ where Returns `None` if there are no threads left to run. */ pub(super) fn pop_thread( - &'lua self, - ) -> LuaResult, SchedulerThreadSender)>> { + &self, + ) -> LuaResult, SchedulerThreadSender)>> { match self .threads .try_borrow_mut() @@ -56,12 +56,10 @@ where right away, before any other currently scheduled threads. */ pub fn push_front( - &'lua self, + &self, thread: LuaOwnedThread, - args: impl IntoLuaMulti<'lua>, + args: LuaMultiValue<'_>, ) -> LuaResult { - let args = args.into_lua_multi(&self.lua)?; - let thread = SchedulerThread::new(&self.lua, thread, args)?; let thread_id = thread.id(); @@ -82,12 +80,10 @@ where after all other current threads have been resumed. */ pub fn push_back( - &'lua self, + &self, thread: LuaOwnedThread, - args: impl IntoLuaMulti<'lua>, + args: LuaMultiValue<'_>, ) -> LuaResult { - let args = args.into_lua_multi(&self.lua)?; - let thread = SchedulerThread::new(&self.lua, thread, args)?; let thread_id = thread.id(); @@ -107,9 +103,9 @@ where Waits for the given thread to finish running, and returns its result. */ pub async fn wait_for_thread( - &'lua self, + &self, thread_id: SchedulerThreadId, - ) -> LuaResult> { + ) -> LuaResult> { let mut recv = { let senders = self.thread_senders.borrow(); let sender = senders diff --git a/src/lune/scheduler/mod.rs b/src/lune/scheduler/mod.rs index a6937c3..e7edc1b 100644 --- a/src/lune/scheduler/mod.rs +++ b/src/lune/scheduler/mod.rs @@ -1,9 +1,7 @@ use std::{ cell::RefCell, collections::{HashMap, VecDeque}, - ops::Deref, pin::Pin, - sync::Arc, }; use futures_util::{stream::FuturesUnordered, Future}; @@ -28,51 +26,21 @@ use self::{ /** Scheduler for Lua threads. - Can be cheaply cloned, and any clone will refer - to the same underlying scheduler and Lua struct. -*/ -#[derive(Debug, Clone)] -pub(crate) struct Scheduler<'fut> { - inner: Arc>, -} - -impl<'fut> Scheduler<'fut> { - /** - Creates a new scheduler for the given [`Lua`] struct. - */ - pub fn new(lua: Arc) -> Self { - let sched_lua = Arc::clone(&lua); - let sched_impl = SchedulerImpl::new(sched_lua); - - let inner = Arc::new(sched_impl); - - Self { inner } - } -} - -impl<'fut> Deref for Scheduler<'fut> { - type Target = SchedulerImpl<'fut>; - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -/** - Implementation of scheduler for Lua threads. - - Not meant to be used directly, use [`Scheduler`] instead. + This wraps a [`Lua`] struct and exposes it as the `lua` property. */ #[derive(Debug)] -pub(crate) struct SchedulerImpl<'fut> { - lua: Arc, +pub(crate) struct Scheduler<'fut> { + pub(crate) lua: Lua, state: SchedulerState, threads: RefCell>, thread_senders: RefCell>, futures: AsyncMutex + 'fut>>>>, } -impl<'fut> SchedulerImpl<'fut> { - fn new(lua: Arc) -> Self { +impl<'fut> Scheduler<'fut> { + pub fn new() -> Self { + let lua = Lua::new(); + Self { lua, state: SchedulerState::new(), diff --git a/src/lune/scheduler/traits.rs b/src/lune/scheduler/traits.rs index bd24567..3bbd129 100644 --- a/src/lune/scheduler/traits.rs +++ b/src/lune/scheduler/traits.rs @@ -1,5 +1,3 @@ -use std::sync::Arc; - use futures_util::Future; use mlua::prelude::*; @@ -19,11 +17,6 @@ pub trait LuaSchedulerExt<'lua, 'fut> where 'lua: 'fut, { - /** - Creates a new [`Lua`] struct with a [`Scheduler`]. - */ - fn new_with_scheduler() -> Arc; - /** Creates a function callable from Lua that runs an async closure and returns the results of it to the call site. @@ -40,12 +33,6 @@ impl<'lua, 'fut> LuaSchedulerExt<'lua, 'fut> for Lua where 'lua: 'fut, { - fn new_with_scheduler() -> Arc { - let lua = Arc::new(Lua::new()); - lua.set_app_data(Scheduler::new(Arc::clone(&lua))); - lua - } - fn create_async_function(&'lua self, func: F) -> LuaResult> where A: FromLuaMulti<'lua>, From 62e6130223a312477fdc07b904792b061d97b6b4 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Fri, 18 Aug 2023 09:36:08 -0500 Subject: [PATCH 013/103] Relax scheduler method args since lifetimes are now correct --- src/lune/mod.rs | 13 ++++++++++--- src/lune/scheduler/impl_async.rs | 12 ++++++++++-- src/lune/scheduler/impl_runner.rs | 31 +----------------------------- src/lune/scheduler/impl_threads.rs | 24 ++++++++++++++--------- src/lune/scheduler/traits.rs | 4 ++-- 5 files changed, 38 insertions(+), 46 deletions(-) diff --git a/src/lune/mod.rs b/src/lune/mod.rs index b46e023..dd79aac 100644 --- a/src/lune/mod.rs +++ b/src/lune/mod.rs @@ -39,8 +39,15 @@ impl Lune { script_name: impl AsRef, script_contents: impl AsRef<[u8]>, ) -> Result { - Ok(Scheduler::new() - .run_main(script_name, script_contents) - .await) + let scheduler = Scheduler::new(); + + let main = scheduler + .lua + .load(script_contents.as_ref()) + .set_name(script_name.as_ref()); + + scheduler.push_back(main, ())?; + + Ok(scheduler.run_to_completion().await) } } diff --git a/src/lune/scheduler/impl_async.rs b/src/lune/scheduler/impl_async.rs index 65c1970..2754f40 100644 --- a/src/lune/scheduler/impl_async.rs +++ b/src/lune/scheduler/impl_async.rs @@ -24,12 +24,20 @@ where /** Schedules the given `thread` to run when the given `fut` completes. */ - pub fn schedule_future_thread(&'fut self, thread: LuaOwnedThread, fut: F) -> LuaResult<()> + pub fn schedule_future_thread( + &'fut self, + thread: LuaOwnedThread, + fut: F, + ) -> LuaResult<()> where - F: 'fut + Future>>, + FR: IntoLuaMulti<'fut>, + F: 'fut + Future>, { self.schedule_future(async move { let rets = fut.await.expect("Failed to receive result"); + let rets = rets + .into_lua_multi(&self.lua) + .expect("Failed to create return multi value"); self.push_back(thread, rets) .expect("Failed to schedule future thread"); }); diff --git a/src/lune/scheduler/impl_runner.rs b/src/lune/scheduler/impl_runner.rs index ed84b92..2554c4a 100644 --- a/src/lune/scheduler/impl_runner.rs +++ b/src/lune/scheduler/impl_runner.rs @@ -5,9 +5,7 @@ use mlua::prelude::*; use tokio::task::LocalSet; -use super::{IntoLuaOwnedThread, Scheduler}; - -const EMPTY_MULTI_VALUE: LuaMultiValue = LuaMultiValue::new(); +use super::Scheduler; impl<'lua, 'fut> Scheduler<'fut> where @@ -121,31 +119,4 @@ where ExitCode::SUCCESS } } - - /** - Runs a script with the given `script_name` and `script_contents` to completion. - - Refer to [`run_to_completion`] for additional details. - */ - pub async fn run_main( - self, - script_name: impl AsRef, - script_contents: impl AsRef<[u8]>, - ) -> ExitCode { - let main_fn = self - .lua - .load(script_contents.as_ref()) - .set_name(script_name.as_ref()) - .into_function() - .expect("Failed to create function for main"); - - let main_thread = main_fn - .into_owned_lua_thread(&self.lua) - .expect("Failed to create thread for main"); - - self.push_back(main_thread, EMPTY_MULTI_VALUE) - .expect("Failed to enqueue thread for main"); - - self.run_to_completion().await - } } diff --git a/src/lune/scheduler/impl_threads.rs b/src/lune/scheduler/impl_threads.rs index b7dea8a..638416d 100644 --- a/src/lune/scheduler/impl_threads.rs +++ b/src/lune/scheduler/impl_threads.rs @@ -4,7 +4,7 @@ use mlua::prelude::*; use super::{ thread::{SchedulerThread, SchedulerThreadId, SchedulerThreadSender}, - Scheduler, + IntoLuaOwnedThread, Scheduler, }; impl<'lua, 'fut> Scheduler<'fut> @@ -55,11 +55,14 @@ where Schedules the `thread` to be resumed with the given `args` right away, before any other currently scheduled threads. */ - pub fn push_front( - &self, - thread: LuaOwnedThread, - args: LuaMultiValue<'_>, + pub fn push_front<'a>( + &'a self, + thread: impl IntoLuaOwnedThread, + args: impl IntoLuaMulti<'a>, ) -> LuaResult { + let thread = thread.into_owned_lua_thread(&self.lua)?; + let args = args.into_lua_multi(&self.lua)?; + let thread = SchedulerThread::new(&self.lua, thread, args)?; let thread_id = thread.id(); @@ -79,11 +82,14 @@ where Schedules the `thread` to be resumed with the given `args` after all other current threads have been resumed. */ - pub fn push_back( - &self, - thread: LuaOwnedThread, - args: LuaMultiValue<'_>, + pub fn push_back<'a>( + &'a self, + thread: impl IntoLuaOwnedThread, + args: impl IntoLuaMulti<'a>, ) -> LuaResult { + let thread = thread.into_owned_lua_thread(&self.lua)?; + let args = args.into_lua_multi(&self.lua)?; + let thread = SchedulerThread::new(&self.lua, thread, args)?; let thread_id = thread.id(); diff --git a/src/lune/scheduler/traits.rs b/src/lune/scheduler/traits.rs index 3bbd129..9457843 100644 --- a/src/lune/scheduler/traits.rs +++ b/src/lune/scheduler/traits.rs @@ -26,7 +26,7 @@ where A: FromLuaMulti<'lua>, R: IntoLuaMulti<'lua>, F: 'static + Fn(&'lua Lua, A) -> FR, - FR: 'fut + Future>; + FR: 'static + Future>; } impl<'lua, 'fut> LuaSchedulerExt<'lua, 'fut> for Lua @@ -38,7 +38,7 @@ where A: FromLuaMulti<'lua>, R: IntoLuaMulti<'lua>, F: 'static + Fn(&'lua Lua, A) -> FR, - FR: 'fut + Future>, + FR: 'static + Future>, { let async_env = self.create_table_with_capacity(0, 2)?; From e1fa89cf60498224353153f5a876ab6151c754e5 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Fri, 18 Aug 2023 12:23:26 -0500 Subject: [PATCH 014/103] Some lifetime and async arg improvements --- src/lune/scheduler/impl_async.rs | 9 +++++---- src/lune/scheduler/traits.rs | 16 ++++++++-------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/lune/scheduler/impl_async.rs b/src/lune/scheduler/impl_async.rs index 2754f40..eac200f 100644 --- a/src/lune/scheduler/impl_async.rs +++ b/src/lune/scheduler/impl_async.rs @@ -1,7 +1,7 @@ use futures_util::Future; use mlua::prelude::*; -use super::Scheduler; +use super::{IntoLuaOwnedThread, Scheduler}; impl<'lua, 'fut> Scheduler<'fut> where @@ -12,7 +12,7 @@ where */ pub fn schedule_future(&'fut self, fut: F) where - F: 'fut + Future, + F: Future + 'fut, { let futs = self .futures @@ -26,13 +26,14 @@ where */ pub fn schedule_future_thread( &'fut self, - thread: LuaOwnedThread, + thread: impl IntoLuaOwnedThread, fut: F, ) -> LuaResult<()> where FR: IntoLuaMulti<'fut>, - F: 'fut + Future>, + F: Future> + 'fut, { + let thread = thread.into_owned_lua_thread(&self.lua)?; self.schedule_future(async move { let rets = fut.await.expect("Failed to receive result"); let rets = rets diff --git a/src/lune/scheduler/traits.rs b/src/lune/scheduler/traits.rs index 9457843..0bf9ff8 100644 --- a/src/lune/scheduler/traits.rs +++ b/src/lune/scheduler/traits.rs @@ -25,8 +25,8 @@ where where A: FromLuaMulti<'lua>, R: IntoLuaMulti<'lua>, - F: 'static + Fn(&'lua Lua, A) -> FR, - FR: 'static + Future>; + F: Fn(&'lua Lua, A) -> FR + 'static, + FR: Future> + 'fut; } impl<'lua, 'fut> LuaSchedulerExt<'lua, 'fut> for Lua @@ -37,8 +37,8 @@ where where A: FromLuaMulti<'lua>, R: IntoLuaMulti<'lua>, - F: 'static + Fn(&'lua Lua, A) -> FR, - FR: 'static + Future>, + F: Fn(&'lua Lua, A) -> FR + 'static, + FR: Future> + 'fut, { let async_env = self.create_table_with_capacity(0, 2)?; @@ -52,12 +52,12 @@ where async_env.set( "schedule", LuaFunction::wrap(move |lua: &Lua, args: A| { - let _thread = lua.current_thread().into_owned(); - let _future = func(lua, args); - let _sched = lua + let thread = lua.current_thread(); + let future = func(lua, args); + let sched = lua .app_data_ref::<&Scheduler>() .expect("Lua struct is missing scheduler"); - // FIXME: `self` escapes outside of method + // FIXME: `self` escapes outside of method because we are borrowing `func`? // sched.schedule_future_thread(thread, future)?; Ok(()) }), From 1f72033a6a15faff6a922af0829bca9734cd9b2e Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Fri, 18 Aug 2023 13:43:18 -0500 Subject: [PATCH 015/103] Use static lua for now, to make lifetimes work --- src/lune/mod.rs | 26 ++++++++++++++++----- src/lune/scheduler/impl_async.rs | 4 ++-- src/lune/scheduler/impl_threads.rs | 14 ++++++------ src/lune/scheduler/mod.rs | 34 +++++++++++++++------------- src/lune/scheduler/traits.rs | 36 ++++++++++++++---------------- 5 files changed, 65 insertions(+), 49 deletions(-) diff --git a/src/lune/mod.rs b/src/lune/mod.rs index dd79aac..30080cf 100644 --- a/src/lune/mod.rs +++ b/src/lune/mod.rs @@ -6,9 +6,11 @@ mod scheduler; use self::scheduler::Scheduler; pub use error::LuneError; +use mlua::Lua; -#[derive(Clone, Debug, Default)] +#[derive(Debug, Clone)] pub struct Lune { + lua: &'static Lua, args: Vec, } @@ -16,8 +18,12 @@ impl Lune { /** Creates a new Lune script runner. */ + #[allow(clippy::new_without_default)] pub fn new() -> Self { - Self::default() + Self { + lua: Lua::new().into_static(), + args: Vec::new(), + } } /** @@ -39,15 +45,25 @@ impl Lune { script_name: impl AsRef, script_contents: impl AsRef<[u8]>, ) -> Result { - let scheduler = Scheduler::new(); + let scheduler = Scheduler::new(self.lua); + self.lua.set_app_data(scheduler.clone()); - let main = scheduler + let main = self .lua .load(script_contents.as_ref()) .set_name(script_name.as_ref()); scheduler.push_back(main, ())?; - Ok(scheduler.run_to_completion().await) } } + +impl Drop for Lune { + fn drop(&mut self) { + // SAFETY: The scheduler needs the static lifetime reference to lua, + // when dropped nothing outside of here has access to the scheduler + unsafe { + Lua::from_static(self.lua); + } + } +} diff --git a/src/lune/scheduler/impl_async.rs b/src/lune/scheduler/impl_async.rs index eac200f..cf14abd 100644 --- a/src/lune/scheduler/impl_async.rs +++ b/src/lune/scheduler/impl_async.rs @@ -33,11 +33,11 @@ where FR: IntoLuaMulti<'fut>, F: Future> + 'fut, { - let thread = thread.into_owned_lua_thread(&self.lua)?; + let thread = thread.into_owned_lua_thread(self.lua)?; self.schedule_future(async move { let rets = fut.await.expect("Failed to receive result"); let rets = rets - .into_lua_multi(&self.lua) + .into_lua_multi(self.lua) .expect("Failed to create return multi value"); self.push_back(thread, rets) .expect("Failed to schedule future thread"); diff --git a/src/lune/scheduler/impl_threads.rs b/src/lune/scheduler/impl_threads.rs index 638416d..5184576 100644 --- a/src/lune/scheduler/impl_threads.rs +++ b/src/lune/scheduler/impl_threads.rs @@ -39,7 +39,7 @@ where { Some(thread) => { let thread_id = &thread.id(); - let (thread, args) = thread.into_inner(&self.lua); + let (thread, args) = thread.into_inner(self.lua); let sender = self .thread_senders .borrow_mut() @@ -60,10 +60,10 @@ where thread: impl IntoLuaOwnedThread, args: impl IntoLuaMulti<'a>, ) -> LuaResult { - let thread = thread.into_owned_lua_thread(&self.lua)?; - let args = args.into_lua_multi(&self.lua)?; + let thread = thread.into_owned_lua_thread(self.lua)?; + let args = args.into_lua_multi(self.lua)?; - let thread = SchedulerThread::new(&self.lua, thread, args)?; + let thread = SchedulerThread::new(self.lua, thread, args)?; let thread_id = thread.id(); self.threads @@ -87,10 +87,10 @@ where thread: impl IntoLuaOwnedThread, args: impl IntoLuaMulti<'a>, ) -> LuaResult { - let thread = thread.into_owned_lua_thread(&self.lua)?; - let args = args.into_lua_multi(&self.lua)?; + let thread = thread.into_owned_lua_thread(self.lua)?; + let args = args.into_lua_multi(self.lua)?; - let thread = SchedulerThread::new(&self.lua, thread, args)?; + let thread = SchedulerThread::new(self.lua, thread, args)?; let thread_id = thread.id(); self.threads diff --git a/src/lune/scheduler/mod.rs b/src/lune/scheduler/mod.rs index e7edc1b..0231f38 100644 --- a/src/lune/scheduler/mod.rs +++ b/src/lune/scheduler/mod.rs @@ -2,6 +2,7 @@ use std::{ cell::RefCell, collections::{HashMap, VecDeque}, pin::Pin, + sync::Arc, }; use futures_util::{stream::FuturesUnordered, Future}; @@ -23,30 +24,31 @@ use self::{ thread::{SchedulerThread, SchedulerThreadId, SchedulerThreadSender}, }; -/** - Scheduler for Lua threads. +type SchedulerFuture<'fut> = Pin + 'fut>>; - This wraps a [`Lua`] struct and exposes it as the `lua` property. +/** + Scheduler for Lua threads and futures. + + This scheduler can be cheaply cloned and the underlying state + and data will remain unchanged and accessible from all clones. */ -#[derive(Debug)] +#[derive(Debug, Clone)] pub(crate) struct Scheduler<'fut> { - pub(crate) lua: Lua, - state: SchedulerState, - threads: RefCell>, - thread_senders: RefCell>, - futures: AsyncMutex + 'fut>>>>, + lua: &'static Lua, + state: Arc, + threads: Arc>>, + thread_senders: Arc>>, + futures: Arc>>>, } impl<'fut> Scheduler<'fut> { - pub fn new() -> Self { - let lua = Lua::new(); - + pub fn new(lua: &'static Lua) -> Self { Self { lua, - state: SchedulerState::new(), - threads: RefCell::new(VecDeque::new()), - thread_senders: RefCell::new(HashMap::new()), - futures: AsyncMutex::new(FuturesUnordered::new()), + state: Arc::new(SchedulerState::new()), + threads: Arc::new(RefCell::new(VecDeque::new())), + thread_senders: Arc::new(RefCell::new(HashMap::new())), + futures: Arc::new(AsyncMutex::new(FuturesUnordered::new())), } } } diff --git a/src/lune/scheduler/traits.rs b/src/lune/scheduler/traits.rs index 0bf9ff8..b79382b 100644 --- a/src/lune/scheduler/traits.rs +++ b/src/lune/scheduler/traits.rs @@ -13,32 +13,29 @@ return yield() for access to the scheduler without having to import it or handle registry / app data references manually. */ -pub trait LuaSchedulerExt<'lua, 'fut> -where - 'lua: 'fut, -{ +pub trait LuaSchedulerExt { /** Creates a function callable from Lua that runs an async closure and returns the results of it to the call site. */ - fn create_async_function(&'lua self, func: F) -> LuaResult> + fn create_async_function( + &'static self, + func: F, + ) -> LuaResult> where - A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, - F: Fn(&'lua Lua, A) -> FR + 'static, - FR: Future> + 'fut; + A: FromLuaMulti<'static>, + R: IntoLuaMulti<'static>, + F: Fn(&'static Lua, A) -> FR + 'static, + FR: Future> + 'static; } -impl<'lua, 'fut> LuaSchedulerExt<'lua, 'fut> for Lua -where - 'lua: 'fut, -{ - fn create_async_function(&'lua self, func: F) -> LuaResult> +impl LuaSchedulerExt for Lua { + fn create_async_function(&'static self, func: F) -> LuaResult> where - A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, - F: Fn(&'lua Lua, A) -> FR + 'static, - FR: Future> + 'fut, + A: FromLuaMulti<'static>, + R: IntoLuaMulti<'static>, + F: Fn(&'static Lua, A) -> FR + 'static, + FR: Future> + 'static, { let async_env = self.create_table_with_capacity(0, 2)?; @@ -58,7 +55,8 @@ where .app_data_ref::<&Scheduler>() .expect("Lua struct is missing scheduler"); // FIXME: `self` escapes outside of method because we are borrowing `func`? - // sched.schedule_future_thread(thread, future)?; + // For now we solve this by just using a &'static Lua reference everywhere + sched.schedule_future_thread(thread, future)?; Ok(()) }), )?; From b4bbc0630a75673c1601adc304834a7d729512fd Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Fri, 18 Aug 2023 13:50:04 -0500 Subject: [PATCH 016/103] Add table builder util --- src/lune/mod.rs | 1 + src/lune/util/mod.rs | 3 ++ src/lune/util/table_builder.rs | 68 ++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 src/lune/util/mod.rs create mode 100644 src/lune/util/table_builder.rs diff --git a/src/lune/mod.rs b/src/lune/mod.rs index 30080cf..bf41a4b 100644 --- a/src/lune/mod.rs +++ b/src/lune/mod.rs @@ -2,6 +2,7 @@ use std::process::ExitCode; mod error; mod scheduler; +mod util; use self::scheduler::Scheduler; diff --git a/src/lune/util/mod.rs b/src/lune/util/mod.rs new file mode 100644 index 0000000..7de66f3 --- /dev/null +++ b/src/lune/util/mod.rs @@ -0,0 +1,3 @@ +mod table_builder; + +pub use table_builder::TableBuilder; diff --git a/src/lune/util/table_builder.rs b/src/lune/util/table_builder.rs new file mode 100644 index 0000000..9cc6a4e --- /dev/null +++ b/src/lune/util/table_builder.rs @@ -0,0 +1,68 @@ +#![allow(dead_code)] + +use std::future::Future; + +use mlua::prelude::*; + +use crate::lune::scheduler::LuaSchedulerExt; + +pub struct TableBuilder<'lua> { + lua: &'lua Lua, + tab: LuaTable<'lua>, +} + +impl<'lua> TableBuilder<'lua> { + pub fn new(lua: &'lua Lua) -> LuaResult { + let tab = lua.create_table()?; + Ok(Self { lua, tab }) + } + + pub fn with_value(self, key: K, value: V) -> LuaResult + where + K: IntoLua<'lua>, + V: IntoLua<'lua>, + { + self.tab.raw_set(key, value)?; + Ok(self) + } + + pub fn with_function(self, key: K, func: F) -> LuaResult + where + K: IntoLua<'lua>, + A: FromLuaMulti<'lua>, + R: IntoLuaMulti<'lua>, + F: Fn(&'lua Lua, A) -> LuaResult + 'static, + { + let f = self.lua.create_function(func)?; + self.with_value(key, LuaValue::Function(f)) + } + + pub fn with_metatable(self, table: LuaTable) -> LuaResult { + self.tab.set_metatable(Some(table)); + Ok(self) + } + + pub fn build_readonly(self) -> LuaResult> { + self.tab.set_readonly(true); + Ok(self.tab) + } + + pub fn build(self) -> LuaResult> { + Ok(self.tab) + } +} + +// FIXME: Remove static lifetimes here when possible and move into above impl +impl TableBuilder<'static> { + pub fn with_async_function(self, key: K, func: F) -> LuaResult + where + K: IntoLua<'static>, + A: FromLuaMulti<'static>, + R: IntoLuaMulti<'static>, + F: Fn(&'static Lua, A) -> FR + 'static, + FR: Future> + 'static, + { + let f = self.lua.create_async_function(func)?; + self.with_value(key, LuaValue::Function(f)) + } +} From 6e83958653f95d68c7c80a53c976c08548ff6b12 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Fri, 18 Aug 2023 13:55:09 -0500 Subject: [PATCH 017/103] Remove mlua macros feature --- Cargo.lock | 40 ---------------------------------------- Cargo.toml | 1 - 2 files changed, 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fe200cb..2ca13cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1140,7 +1140,6 @@ dependencies = [ "bstr", "erased-serde", "mlua-sys", - "mlua_derive", "num-traits", "once_cell", "rustc-hash", @@ -1160,21 +1159,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "mlua_derive" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f359220f24e6452dd82a3f50d7242d4aab822b5594798048e953d7a9e0314c6" -dependencies = [ - "itertools", - "once_cell", - "proc-macro-error", - "proc-macro2", - "quote", - "regex", - "syn 2.0.28", -] - [[package]] name = "nibble_vec" version = "0.1.0" @@ -1347,30 +1331,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro-hack" version = "0.5.20+deprecated" diff --git a/Cargo.toml b/Cargo.toml index bc43256..61ad355 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,7 +81,6 @@ mlua = { version = "0.9.0", features = [ "luau", "luau-jit", "serialize", - "macros", "unstable", ] } tokio = { version = "1.24", features = ["full"] } From 0dbf4668172789f67785cc7d2f5de38671e7bc23 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Fri, 18 Aug 2023 13:57:31 -0500 Subject: [PATCH 018/103] Add back lua lifetime to scheduler --- src/lune/scheduler/impl_async.rs | 2 +- src/lune/scheduler/impl_runner.rs | 2 +- src/lune/scheduler/impl_threads.rs | 2 +- src/lune/scheduler/mod.rs | 8 ++++---- src/lune/scheduler/traits.rs | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lune/scheduler/impl_async.rs b/src/lune/scheduler/impl_async.rs index cf14abd..9ae53a5 100644 --- a/src/lune/scheduler/impl_async.rs +++ b/src/lune/scheduler/impl_async.rs @@ -3,7 +3,7 @@ use mlua::prelude::*; use super::{IntoLuaOwnedThread, Scheduler}; -impl<'lua, 'fut> Scheduler<'fut> +impl<'lua, 'fut> Scheduler<'lua, 'fut> where 'lua: 'fut, { diff --git a/src/lune/scheduler/impl_runner.rs b/src/lune/scheduler/impl_runner.rs index 2554c4a..088bf23 100644 --- a/src/lune/scheduler/impl_runner.rs +++ b/src/lune/scheduler/impl_runner.rs @@ -7,7 +7,7 @@ use tokio::task::LocalSet; use super::Scheduler; -impl<'lua, 'fut> Scheduler<'fut> +impl<'lua, 'fut> Scheduler<'lua, 'fut> where 'lua: 'fut, { diff --git a/src/lune/scheduler/impl_threads.rs b/src/lune/scheduler/impl_threads.rs index 5184576..2f8969f 100644 --- a/src/lune/scheduler/impl_threads.rs +++ b/src/lune/scheduler/impl_threads.rs @@ -7,7 +7,7 @@ use super::{ IntoLuaOwnedThread, Scheduler, }; -impl<'lua, 'fut> Scheduler<'fut> +impl<'lua, 'fut> Scheduler<'lua, 'fut> where 'lua: 'fut, { diff --git a/src/lune/scheduler/mod.rs b/src/lune/scheduler/mod.rs index 0231f38..2bbb373 100644 --- a/src/lune/scheduler/mod.rs +++ b/src/lune/scheduler/mod.rs @@ -33,16 +33,16 @@ type SchedulerFuture<'fut> = Pin + 'fut>>; and data will remain unchanged and accessible from all clones. */ #[derive(Debug, Clone)] -pub(crate) struct Scheduler<'fut> { - lua: &'static Lua, +pub(crate) struct Scheduler<'lua, 'fut> { + lua: &'lua Lua, state: Arc, threads: Arc>>, thread_senders: Arc>>, futures: Arc>>>, } -impl<'fut> Scheduler<'fut> { - pub fn new(lua: &'static Lua) -> Self { +impl<'lua, 'fut> Scheduler<'lua, 'fut> { + pub fn new(lua: &'lua Lua) -> Self { Self { lua, state: Arc::new(SchedulerState::new()), diff --git a/src/lune/scheduler/traits.rs b/src/lune/scheduler/traits.rs index b79382b..fad4f24 100644 --- a/src/lune/scheduler/traits.rs +++ b/src/lune/scheduler/traits.rs @@ -55,7 +55,7 @@ impl LuaSchedulerExt for Lua { .app_data_ref::<&Scheduler>() .expect("Lua struct is missing scheduler"); // FIXME: `self` escapes outside of method because we are borrowing `func`? - // For now we solve this by just using a &'static Lua reference everywhere + // For now we solve this by using 'static lifetimes for this entire method sched.schedule_future_thread(thread, future)?; Ok(()) }), From 7fe43a969f9fae6e7146a30a379232bade6c048e Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Fri, 18 Aug 2023 14:03:06 -0500 Subject: [PATCH 019/103] Use static lifetime bound instead of spamming actual static lifetime --- src/lune/scheduler/traits.rs | 35 +++++++++++++++++----------------- src/lune/util/table_builder.rs | 17 ++++++++++------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/lune/scheduler/traits.rs b/src/lune/scheduler/traits.rs index fad4f24..3531c7b 100644 --- a/src/lune/scheduler/traits.rs +++ b/src/lune/scheduler/traits.rs @@ -13,29 +13,32 @@ return yield() for access to the scheduler without having to import it or handle registry / app data references manually. */ -pub trait LuaSchedulerExt { +pub trait LuaSchedulerExt<'lua> { /** Creates a function callable from Lua that runs an async closure and returns the results of it to the call site. */ - fn create_async_function( - &'static self, - func: F, - ) -> LuaResult> + fn create_async_function(&'lua self, func: F) -> LuaResult> where - A: FromLuaMulti<'static>, - R: IntoLuaMulti<'static>, - F: Fn(&'static Lua, A) -> FR + 'static, - FR: Future> + 'static; + A: FromLuaMulti<'lua>, + R: IntoLuaMulti<'lua>, + F: Fn(&'lua Lua, A) -> FR + 'lua, + FR: Future> + 'lua; } -impl LuaSchedulerExt for Lua { - fn create_async_function(&'static self, func: F) -> LuaResult> +// FIXME: `self` escapes outside of method because we are borrowing `func` +// when we call `schedule_future_thread` in the lua function body below +// For now we solve this by using the 'static lifetime bound in the impl +impl<'lua> LuaSchedulerExt<'lua> for Lua +where + 'lua: 'static, +{ + fn create_async_function(&'lua self, func: F) -> LuaResult> where - A: FromLuaMulti<'static>, - R: IntoLuaMulti<'static>, - F: Fn(&'static Lua, A) -> FR + 'static, - FR: Future> + 'static, + A: FromLuaMulti<'lua>, + R: IntoLuaMulti<'lua>, + F: Fn(&'lua Lua, A) -> FR + 'lua, + FR: Future> + 'lua, { let async_env = self.create_table_with_capacity(0, 2)?; @@ -54,8 +57,6 @@ impl LuaSchedulerExt for Lua { let sched = lua .app_data_ref::<&Scheduler>() .expect("Lua struct is missing scheduler"); - // FIXME: `self` escapes outside of method because we are borrowing `func`? - // For now we solve this by using 'static lifetimes for this entire method sched.schedule_future_thread(thread, future)?; Ok(()) }), diff --git a/src/lune/util/table_builder.rs b/src/lune/util/table_builder.rs index 9cc6a4e..7db4910 100644 --- a/src/lune/util/table_builder.rs +++ b/src/lune/util/table_builder.rs @@ -52,15 +52,18 @@ impl<'lua> TableBuilder<'lua> { } } -// FIXME: Remove static lifetimes here when possible and move into above impl -impl TableBuilder<'static> { +// FIXME: Remove static lifetime bound here when possible and move into above impl +impl<'lua> TableBuilder<'lua> +where + 'lua: 'static, +{ pub fn with_async_function(self, key: K, func: F) -> LuaResult where - K: IntoLua<'static>, - A: FromLuaMulti<'static>, - R: IntoLuaMulti<'static>, - F: Fn(&'static Lua, A) -> FR + 'static, - FR: Future> + 'static, + K: IntoLua<'lua>, + A: FromLuaMulti<'lua>, + R: IntoLuaMulti<'lua>, + F: Fn(&'lua Lua, A) -> FR + 'lua, + FR: Future> + 'lua, { let f = self.lua.create_async_function(func)?; self.with_value(key, LuaValue::Function(f)) From 7d73601a58f74e14e9406878e894f0a4a820048f Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Fri, 18 Aug 2023 16:35:33 -0500 Subject: [PATCH 020/103] Initial scaffolding to get custom globals and require working --- src/cli/repl.rs | 2 +- src/lune/globals/mod.rs | 18 ++++++++++++ src/lune/globals/require/absolute.rs | 13 +++++++++ src/lune/globals/require/alias.rs | 14 +++++++++ src/lune/globals/require/builtin.rs | 13 +++++++++ src/lune/globals/require/context.rs | 43 ++++++++++++++++++++++++++++ src/lune/globals/require/mod.rs | 41 ++++++++++++++++++++++++++ src/lune/globals/require/relative.rs | 13 +++++++++ src/lune/mod.rs | 31 ++++++++++++++------ src/lune/scheduler/impl_async.rs | 3 ++ src/lune/scheduler/mod.rs | 10 +++++++ src/lune/scheduler/traits.rs | 1 + src/tests.rs | 2 +- 13 files changed, 193 insertions(+), 11 deletions(-) create mode 100644 src/lune/globals/mod.rs create mode 100644 src/lune/globals/require/absolute.rs create mode 100644 src/lune/globals/require/alias.rs create mode 100644 src/lune/globals/require/builtin.rs create mode 100644 src/lune/globals/require/context.rs create mode 100644 src/lune/globals/require/mod.rs create mode 100644 src/lune/globals/require/relative.rs diff --git a/src/cli/repl.rs b/src/cli/repl.rs index 6639424..71b4923 100644 --- a/src/cli/repl.rs +++ b/src/cli/repl.rs @@ -32,7 +32,7 @@ pub async fn show_interface() -> Result { let mut prompt_state = PromptState::Regular; let mut source_code = String::new(); - let lune_instance = Lune::new(); + let mut lune_instance = Lune::new(); loop { let prompt = match prompt_state { diff --git a/src/lune/globals/mod.rs b/src/lune/globals/mod.rs new file mode 100644 index 0000000..fb03d1e --- /dev/null +++ b/src/lune/globals/mod.rs @@ -0,0 +1,18 @@ +use mlua::prelude::*; + +use super::util::TableBuilder; + +mod require; + +pub fn inject_all(lua: &'static Lua) -> LuaResult<()> { + let all = TableBuilder::new(lua)? + .with_value("require", require::create(lua)?)? + .build_readonly()?; + + for res in all.pairs() { + let (key, value): (LuaValue, LuaValue) = res?; + lua.globals().set(key, value)?; + } + + Ok(()) +} diff --git a/src/lune/globals/require/absolute.rs b/src/lune/globals/require/absolute.rs new file mode 100644 index 0000000..08f45ea --- /dev/null +++ b/src/lune/globals/require/absolute.rs @@ -0,0 +1,13 @@ +use mlua::prelude::*; + +use super::context::*; + +pub(super) async fn require<'lua>( + _lua: &'lua Lua, + _ctx: RequireContext, + path: &str, +) -> LuaResult> { + Err(LuaError::runtime(format!( + "TODO: Support require for absolute paths (tried to require '{path}')" + ))) +} diff --git a/src/lune/globals/require/alias.rs b/src/lune/globals/require/alias.rs new file mode 100644 index 0000000..0db3a07 --- /dev/null +++ b/src/lune/globals/require/alias.rs @@ -0,0 +1,14 @@ +use mlua::prelude::*; + +use super::context::*; + +pub(super) async fn require<'lua>( + _lua: &'lua Lua, + _ctx: RequireContext, + alias: &str, + name: &str, +) -> LuaResult> { + Err(LuaError::runtime(format!( + "TODO: Support require for built-in libraries (tried to require '{name}' with alias '{alias}')" + ))) +} diff --git a/src/lune/globals/require/builtin.rs b/src/lune/globals/require/builtin.rs new file mode 100644 index 0000000..377d947 --- /dev/null +++ b/src/lune/globals/require/builtin.rs @@ -0,0 +1,13 @@ +use mlua::prelude::*; + +use super::context::*; + +pub(super) async fn require<'lua>( + _lua: &'lua Lua, + _ctx: RequireContext, + name: &str, +) -> LuaResult> { + Err(LuaError::runtime(format!( + "TODO: Support require for built-in libraries (tried to require '{name}')" + ))) +} diff --git a/src/lune/globals/require/context.rs b/src/lune/globals/require/context.rs new file mode 100644 index 0000000..54fd981 --- /dev/null +++ b/src/lune/globals/require/context.rs @@ -0,0 +1,43 @@ +use mlua::prelude::*; + +const REGISTRY_KEY: &str = "RequireContext"; + +// TODO: Store current file path for each thread in +// this context somehow, as well as built-in libraries +#[derive(Clone)] +pub(super) struct RequireContext { + pub(super) use_absolute_paths: bool, +} + +impl RequireContext { + pub fn new() -> Self { + Self { + // TODO: Set to false by default, load some kind of config + // or env var to check if we should be using absolute paths + use_absolute_paths: true, + } + } + + pub fn from_registry(lua: &Lua) -> Self { + lua.named_registry_value(REGISTRY_KEY) + .expect("Missing require context in lua registry") + } + + pub fn insert_into_registry(self, lua: &Lua) { + lua.set_named_registry_value(REGISTRY_KEY, self) + .expect("Failed to insert RequireContext into registry"); + } +} + +impl LuaUserData for RequireContext {} + +impl<'lua> FromLua<'lua> for RequireContext { + fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { + if let LuaValue::UserData(ud) = value { + if let Ok(ctx) = ud.borrow::() { + return Ok(ctx.clone()); + } + } + unreachable!("RequireContext should only be used from registry") + } +} diff --git a/src/lune/globals/require/mod.rs b/src/lune/globals/require/mod.rs new file mode 100644 index 0000000..8315687 --- /dev/null +++ b/src/lune/globals/require/mod.rs @@ -0,0 +1,41 @@ +use mlua::prelude::*; + +use crate::lune::scheduler::LuaSchedulerExt; + +mod context; +use context::RequireContext; + +mod absolute; +mod alias; +mod builtin; +mod relative; + +pub fn create(lua: &'static Lua) -> LuaResult> { + RequireContext::new().insert_into_registry(lua); + + lua.create_async_function(|lua, path: LuaString| async move { + let context = RequireContext::from_registry(lua); + + let path = path + .to_str() + .into_lua_err() + .context("Failed to parse require path as string")? + .to_string(); + + if let Some(builtin_name) = path + .strip_prefix("@lune/") + .map(|name| name.to_ascii_lowercase()) + { + builtin::require(lua, context, &builtin_name).await + } else if let Some(aliased_path) = path.strip_prefix('@') { + let (alias, name) = aliased_path.split_once('/').ok_or(LuaError::runtime( + "Require with custom alias must contain '/' delimiter", + ))?; + alias::require(lua, context, alias, name).await + } else if context.use_absolute_paths { + absolute::require(lua, context, &path).await + } else { + relative::require(lua, context, &path).await + } + }) +} diff --git a/src/lune/globals/require/relative.rs b/src/lune/globals/require/relative.rs new file mode 100644 index 0000000..08f45ea --- /dev/null +++ b/src/lune/globals/require/relative.rs @@ -0,0 +1,13 @@ +use mlua::prelude::*; + +use super::context::*; + +pub(super) async fn require<'lua>( + _lua: &'lua Lua, + _ctx: RequireContext, + path: &str, +) -> LuaResult> { + Err(LuaError::runtime(format!( + "TODO: Support require for absolute paths (tried to require '{path}')" + ))) +} diff --git a/src/lune/mod.rs b/src/lune/mod.rs index bf41a4b..1b5a602 100644 --- a/src/lune/mod.rs +++ b/src/lune/mod.rs @@ -1,6 +1,7 @@ use std::process::ExitCode; mod error; +mod globals; mod scheduler; mod util; @@ -12,6 +13,7 @@ use mlua::Lua; #[derive(Debug, Clone)] pub struct Lune { lua: &'static Lua, + scheduler: &'static Scheduler<'static, 'static>, args: Vec, } @@ -21,8 +23,19 @@ impl Lune { */ #[allow(clippy::new_without_default)] pub fn new() -> Self { + // FIXME: Leaking these and using a manual drop implementation + // does not feel great... is there any way for us to create a + // scheduler, store it in app data, and guarantee it has + // the same lifetime as Lua without using any unsafe? + let lua = Lua::new().into_static(); + let scheduler = Scheduler::new(lua).into_static(); + + lua.set_app_data(scheduler); + globals::inject_all(lua).expect("Failed to inject lua globals"); + Self { - lua: Lua::new().into_static(), + lua, + scheduler, args: Vec::new(), } } @@ -42,29 +55,29 @@ impl Lune { Runs a Lune script inside of a new Luau VM. */ pub async fn run( - &self, + &mut self, script_name: impl AsRef, script_contents: impl AsRef<[u8]>, ) -> Result { - let scheduler = Scheduler::new(self.lua); - self.lua.set_app_data(scheduler.clone()); - let main = self .lua .load(script_contents.as_ref()) .set_name(script_name.as_ref()); - scheduler.push_back(main, ())?; - Ok(scheduler.run_to_completion().await) + self.scheduler.push_back(main, ())?; + Ok(self.scheduler.run_to_completion().await) } } impl Drop for Lune { fn drop(&mut self) { - // SAFETY: The scheduler needs the static lifetime reference to lua, - // when dropped nothing outside of here has access to the scheduler + // SAFETY: When the Lune struct is dropped, it is guaranteed + // that the Lua and Scheduler structs are no longer being used, + // since all the methods that reference them (eg. `run`) + // take an exclusive / mutable reference unsafe { Lua::from_static(self.lua); + Scheduler::from_static(self.scheduler); } } } diff --git a/src/lune/scheduler/impl_async.rs b/src/lune/scheduler/impl_async.rs index 9ae53a5..fa3bf99 100644 --- a/src/lune/scheduler/impl_async.rs +++ b/src/lune/scheduler/impl_async.rs @@ -23,6 +23,8 @@ where /** Schedules the given `thread` to run when the given `fut` completes. + + If the given future returns a [`LuaError`], that error will be passed to the given `thread`. */ pub fn schedule_future_thread( &'fut self, @@ -35,6 +37,7 @@ where { let thread = thread.into_owned_lua_thread(self.lua)?; self.schedule_future(async move { + // TODO: Throw any error back to lua instead of panicking here let rets = fut.await.expect("Failed to receive result"); let rets = rets .into_lua_multi(self.lua) diff --git a/src/lune/scheduler/mod.rs b/src/lune/scheduler/mod.rs index 2bbb373..8b970a0 100644 --- a/src/lune/scheduler/mod.rs +++ b/src/lune/scheduler/mod.rs @@ -51,4 +51,14 @@ impl<'lua, 'fut> Scheduler<'lua, 'fut> { futures: Arc::new(AsyncMutex::new(FuturesUnordered::new())), } } + + #[doc(hidden)] + pub fn into_static(self) -> &'static Self { + Box::leak(Box::new(self)) + } + + #[doc(hidden)] + pub unsafe fn from_static(lua: &'static Scheduler) -> Self { + *Box::from_raw(lua as *const Scheduler as *mut Scheduler) + } } diff --git a/src/lune/scheduler/traits.rs b/src/lune/scheduler/traits.rs index 3531c7b..9731608 100644 --- a/src/lune/scheduler/traits.rs +++ b/src/lune/scheduler/traits.rs @@ -65,6 +65,7 @@ where let async_func = self .load(ASYNC_IMPL_LUA) .set_name("async") + .set_environment(async_env) .into_function()?; Ok(async_func) } diff --git a/src/tests.rs b/src/tests.rs index d88c047..af56722 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -20,7 +20,7 @@ macro_rules! create_tests { // The rest of the test logic can continue as normal let full_name = format!("tests/{}.luau", $value); let script = read_to_string(&full_name).await?; - let lune = Lune::new().with_args( + let mut lune = Lune::new().with_args( ARGS .clone() .iter() From bcef44e28646c34534f8b29d52f01496fa2361c6 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Sat, 19 Aug 2023 15:31:17 -0500 Subject: [PATCH 021/103] Implement bulk of new require behavior --- Cargo.lock | 7 + Cargo.toml | 1 + src/lune/globals/require/context.rs | 231 ++++++++++++++++++++++++++-- src/lune/globals/require/mod.rs | 6 +- src/lune/scheduler/mod.rs | 3 +- 5 files changed, 232 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2ca13cd..47f3717 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1049,6 +1049,7 @@ dependencies = [ "mlua", "once_cell", "os_str_bytes", + "path-clean", "pin-project", "rand", "rbx_binary", @@ -1267,6 +1268,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "path-clean" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef" + [[package]] name = "percent-encoding" version = "2.3.0" diff --git a/Cargo.toml b/Cargo.toml index 61ad355..255bc92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,6 +71,7 @@ async-trait = "0.1" dialoguer = "0.10" dunce = "1.0" lz4_flex = "0.11" +path-clean = "1.0" pin-project = "1.0" os_str_bytes = "6.4" urlencoding = "2.1" diff --git a/src/lune/globals/require/context.rs b/src/lune/globals/require/context.rs index 54fd981..c2863ae 100644 --- a/src/lune/globals/require/context.rs +++ b/src/lune/globals/require/context.rs @@ -1,31 +1,230 @@ +use std::{collections::HashMap, env, path::PathBuf, sync::Arc}; + use mlua::prelude::*; +use tokio::{fs, sync::Mutex as AsyncMutex}; + +use crate::lune::scheduler::{IntoLuaOwnedThread, Scheduler, SchedulerThreadId}; const REGISTRY_KEY: &str = "RequireContext"; -// TODO: Store current file path for each thread in -// this context somehow, as well as built-in libraries -#[derive(Clone)] +#[derive(Debug, Clone)] pub(super) struct RequireContext { - pub(super) use_absolute_paths: bool, + use_absolute_paths: bool, + working_directory: PathBuf, + cache_results: Arc>>>, + cache_pending: Arc>>, } impl RequireContext { - pub fn new() -> Self { - Self { + /** + Creates a new require context for the given [`Lua`] struct. + + Note that this require context is global and only one require + context should be created per [`Lua`] struct, creating more + than one context may lead to undefined require-behavior. + */ + pub fn create(lua: &Lua) { + let this = Self { // TODO: Set to false by default, load some kind of config // or env var to check if we should be using absolute paths use_absolute_paths: true, + working_directory: env::current_dir().expect("Failed to get current working directory"), + cache_results: Arc::new(AsyncMutex::new(HashMap::new())), + cache_pending: Arc::new(AsyncMutex::new(HashMap::new())), + }; + lua.set_named_registry_value(REGISTRY_KEY, this) + .expect("Failed to insert RequireContext into registry"); + } + + /** + If `require` should use absolute paths or not. + */ + pub fn use_absolute_paths(&self) -> bool { + self.use_absolute_paths + } + + /** + Transforms the path into an absolute path. + + If the given path is already an absolute path, this + will only resolve path segments such as `./`, `../`, ... + + If the given path is not absolute, it first gets transformed into an + absolute path by prepending the path to the current working directory. + */ + fn abs_path(&self, path: impl AsRef) -> PathBuf { + let path = path_clean::clean(path.as_ref()); + if path.is_absolute() { + path + } else { + self.working_directory.join(path) } } - pub fn from_registry(lua: &Lua) -> Self { - lua.named_registry_value(REGISTRY_KEY) - .expect("Missing require context in lua registry") + /** + Checks if the given path has a cached require result. + + The cache uses absolute paths, so any given relative + path will first be transformed into an absolute path. + */ + pub fn is_cached(&self, path: impl AsRef) -> LuaResult { + let path = self.abs_path(path); + let is_cached = self + .cache_results + .try_lock() + .expect("RequireContext may not be used from multiple threads") + .contains_key(&path); + Ok(is_cached) } - pub fn insert_into_registry(self, lua: &Lua) { - lua.set_named_registry_value(REGISTRY_KEY, self) - .expect("Failed to insert RequireContext into registry"); + /** + Checks if the given path is currently being used in `require`. + + The cache uses absolute paths, so any given relative + path will first be transformed into an absolute path. + */ + pub fn is_pending(&self, path: impl AsRef) -> LuaResult { + let path = self.abs_path(path); + let is_pending = self + .cache_pending + .try_lock() + .expect("RequireContext may not be used from multiple threads") + .contains_key(&path); + Ok(is_pending) + } + + /** + Gets the resulting value from the require cache. + + Will panic if the path has not been cached, use [`is_cached`] first. + + The cache uses absolute paths, so any given relative + path will first be transformed into an absolute path. + */ + pub fn get_from_cache<'lua>( + &'lua self, + lua: &'lua Lua, + path: impl AsRef + 'lua, + ) -> LuaResult> { + let path = self.abs_path(path); + + let results = self + .cache_results + .try_lock() + .expect("RequireContext may not be used from multiple threads"); + + let cached = results + .get(&path) + .expect("Path does not exist in results cache"); + match cached { + Err(e) => Err(e.clone()), + Ok(key) => { + let multi_vec = lua + .registry_value::>(key) + .expect("Missing require result in lua registry"); + Ok(LuaMultiValue::from_vec(multi_vec)) + } + } + } + + /** + Waits for the resulting value from the require cache. + + Will panic if the path has not been cached, use [`is_cached`] first. + + The cache uses absolute paths, so any given relative + path will first be transformed into an absolute path. + */ + pub async fn wait_for_cache<'lua>( + &'lua self, + lua: &'lua Lua, + path: impl AsRef + 'lua, + ) -> LuaResult> { + let path = self.abs_path(path); + let sched = lua + .app_data_ref::<&Scheduler>() + .expect("Lua struct is missing scheduler"); + + let thread_id = { + let pending = self + .cache_pending + .try_lock() + .expect("RequireContext may not be used from multiple threads"); + let thread_id = pending + .get(&path) + .expect("Path is not currently pending require"); + *thread_id + }; + + sched.wait_for_thread(thread_id).await + } + + /** + Loads (requires) the file at the given path. + + The cache uses absolute paths, so any given relative + path will first be transformed into an absolute path. + */ + pub async fn load<'lua>( + &'lua self, + lua: &'lua Lua, + path: impl AsRef + 'lua, + ) -> LuaResult> { + let path = self.abs_path(path); + let sched = lua + .app_data_ref::<&Scheduler>() + .expect("Lua struct is missing scheduler"); + + // TODO: Store any fs error in the cache, too + let file_contents = fs::read(&path).await?; + + // TODO: Store any lua loading/parsing error in the cache, too + // TODO: Set chunk name as file name relative to cwd + let file_thread = lua + .load(file_contents) + .into_function()? + .into_owned_lua_thread(lua)?; + + // Schedule the thread to run and store the pending thread id in the require context + let thread_id = { + let thread_id = sched.push_back(file_thread, ())?; + self.cache_pending + .try_lock() + .expect("RequireContext may not be used from multiple threads") + .insert(path.clone(), thread_id); + thread_id + }; + + // Wait for the thread to finish running + let thread_res = sched.wait_for_thread(thread_id).await; + + // Clone the result and store it in the cache, note + // that cloning a [`LuaValue`] will still refer to + // the same underlying lua data and indentity + let result = match thread_res.clone() { + Err(e) => Err(e), + Ok(multi) => { + let multi_vec = multi.into_vec(); + let multi_key = lua + .create_registry_value(multi_vec) + .expect("Failed to store require result in registry"); + Ok(multi_key) + } + }; + + // NOTE: We use the async lock and not try_lock here because + // some other thread may be wanting to insert into the require + // cache at the same time, and that's not an actual error case + self.cache_results.lock().await.insert(path.clone(), result); + + // Remove the pending thread id from the require context + self.cache_pending + .try_lock() + .expect("RequireContext may not be used from multiple threads") + .remove(&path) + .expect("Pending require thread id was unexpectedly removed"); + + thread_res } } @@ -41,3 +240,11 @@ impl<'lua> FromLua<'lua> for RequireContext { unreachable!("RequireContext should only be used from registry") } } + +impl<'lua> From<&'lua Lua> for RequireContext { + fn from(value: &'lua Lua) -> Self { + value + .named_registry_value(REGISTRY_KEY) + .expect("Missing require context in lua registry") + } +} diff --git a/src/lune/globals/require/mod.rs b/src/lune/globals/require/mod.rs index 8315687..7418a85 100644 --- a/src/lune/globals/require/mod.rs +++ b/src/lune/globals/require/mod.rs @@ -11,10 +11,10 @@ mod builtin; mod relative; pub fn create(lua: &'static Lua) -> LuaResult> { - RequireContext::new().insert_into_registry(lua); + RequireContext::create(lua); lua.create_async_function(|lua, path: LuaString| async move { - let context = RequireContext::from_registry(lua); + let context = RequireContext::from(lua); let path = path .to_str() @@ -32,7 +32,7 @@ pub fn create(lua: &'static Lua) -> LuaResult> { "Require with custom alias must contain '/' delimiter", ))?; alias::require(lua, context, alias, name).await - } else if context.use_absolute_paths { + } else if context.use_absolute_paths() { absolute::require(lua, context, &path).await } else { relative::require(lua, context, &path).await diff --git a/src/lune/scheduler/mod.rs b/src/lune/scheduler/mod.rs index 8b970a0..398b1d5 100644 --- a/src/lune/scheduler/mod.rs +++ b/src/lune/scheduler/mod.rs @@ -17,11 +17,12 @@ mod impl_async; mod impl_runner; mod impl_threads; +pub use self::thread::SchedulerThreadId; pub use self::traits::*; use self::{ state::SchedulerState, - thread::{SchedulerThread, SchedulerThreadId, SchedulerThreadSender}, + thread::{SchedulerThread, SchedulerThreadSender}, }; type SchedulerFuture<'fut> = Pin + 'fut>>; From dcb989fd92d97b87476575f48115ad574f26dcb8 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Sat, 19 Aug 2023 16:06:12 -0500 Subject: [PATCH 022/103] Implement basic abs path require, propagate async errors back to lua threads --- src/lune/globals/require/absolute.rs | 21 ++++++++++++++------- src/lune/globals/require/alias.rs | 9 ++++++--- src/lune/globals/require/builtin.rs | 9 ++++++--- src/lune/globals/require/context.rs | 20 +++++++++----------- src/lune/globals/require/mod.rs | 23 +++++++++++++---------- src/lune/globals/require/relative.rs | 9 ++++++--- src/lune/scheduler/impl_async.rs | 21 ++++++++++++++------- src/lune/scheduler/mod.rs | 14 ++++++++++++-- src/lune/scheduler/state.rs | 16 +++++++++++++++- 9 files changed, 95 insertions(+), 47 deletions(-) diff --git a/src/lune/globals/require/absolute.rs b/src/lune/globals/require/absolute.rs index 08f45ea..23b2a2c 100644 --- a/src/lune/globals/require/absolute.rs +++ b/src/lune/globals/require/absolute.rs @@ -2,12 +2,19 @@ use mlua::prelude::*; use super::context::*; -pub(super) async fn require<'lua>( - _lua: &'lua Lua, - _ctx: RequireContext, +pub(super) async fn require<'lua, 'ctx>( + lua: &'lua Lua, + ctx: &'ctx RequireContext, path: &str, -) -> LuaResult> { - Err(LuaError::runtime(format!( - "TODO: Support require for absolute paths (tried to require '{path}')" - ))) +) -> LuaResult> +where + 'lua: 'ctx, +{ + if ctx.is_cached(path)? { + ctx.get_from_cache(lua, path) + } else if ctx.is_pending(path)? { + ctx.wait_for_cache(lua, path).await + } else { + ctx.load(lua, path).await + } } diff --git a/src/lune/globals/require/alias.rs b/src/lune/globals/require/alias.rs index 0db3a07..e0d38eb 100644 --- a/src/lune/globals/require/alias.rs +++ b/src/lune/globals/require/alias.rs @@ -2,12 +2,15 @@ use mlua::prelude::*; use super::context::*; -pub(super) async fn require<'lua>( +pub(super) async fn require<'lua, 'ctx>( _lua: &'lua Lua, - _ctx: RequireContext, + _ctx: &'ctx RequireContext, alias: &str, name: &str, -) -> LuaResult> { +) -> LuaResult> +where + 'lua: 'ctx, +{ Err(LuaError::runtime(format!( "TODO: Support require for built-in libraries (tried to require '{name}' with alias '{alias}')" ))) diff --git a/src/lune/globals/require/builtin.rs b/src/lune/globals/require/builtin.rs index 377d947..e7a5171 100644 --- a/src/lune/globals/require/builtin.rs +++ b/src/lune/globals/require/builtin.rs @@ -2,11 +2,14 @@ use mlua::prelude::*; use super::context::*; -pub(super) async fn require<'lua>( +pub(super) async fn require<'lua, 'ctx>( _lua: &'lua Lua, - _ctx: RequireContext, + _ctx: &'ctx RequireContext, name: &str, -) -> LuaResult> { +) -> LuaResult> +where + 'lua: 'ctx, +{ Err(LuaError::runtime(format!( "TODO: Support require for built-in libraries (tried to require '{name}')" ))) diff --git a/src/lune/globals/require/context.rs b/src/lune/globals/require/context.rs index c2863ae..acf2c1c 100644 --- a/src/lune/globals/require/context.rs +++ b/src/lune/globals/require/context.rs @@ -23,17 +23,15 @@ impl RequireContext { context should be created per [`Lua`] struct, creating more than one context may lead to undefined require-behavior. */ - pub fn create(lua: &Lua) { - let this = Self { + pub fn new() -> Self { + Self { // TODO: Set to false by default, load some kind of config // or env var to check if we should be using absolute paths use_absolute_paths: true, working_directory: env::current_dir().expect("Failed to get current working directory"), cache_results: Arc::new(AsyncMutex::new(HashMap::new())), cache_pending: Arc::new(AsyncMutex::new(HashMap::new())), - }; - lua.set_named_registry_value(REGISTRY_KEY, this) - .expect("Failed to insert RequireContext into registry"); + } } /** @@ -102,9 +100,9 @@ impl RequireContext { path will first be transformed into an absolute path. */ pub fn get_from_cache<'lua>( - &'lua self, + &self, lua: &'lua Lua, - path: impl AsRef + 'lua, + path: impl AsRef, ) -> LuaResult> { let path = self.abs_path(path); @@ -136,9 +134,9 @@ impl RequireContext { path will first be transformed into an absolute path. */ pub async fn wait_for_cache<'lua>( - &'lua self, + &self, lua: &'lua Lua, - path: impl AsRef + 'lua, + path: impl AsRef, ) -> LuaResult> { let path = self.abs_path(path); let sched = lua @@ -166,9 +164,9 @@ impl RequireContext { path will first be transformed into an absolute path. */ pub async fn load<'lua>( - &'lua self, + &self, lua: &'lua Lua, - path: impl AsRef + 'lua, + path: impl AsRef, ) -> LuaResult> { let path = self.abs_path(path); let sched = lua diff --git a/src/lune/globals/require/mod.rs b/src/lune/globals/require/mod.rs index 7418a85..04e7b4a 100644 --- a/src/lune/globals/require/mod.rs +++ b/src/lune/globals/require/mod.rs @@ -11,31 +11,34 @@ mod builtin; mod relative; pub fn create(lua: &'static Lua) -> LuaResult> { - RequireContext::create(lua); - + lua.set_app_data(RequireContext::new()); lua.create_async_function(|lua, path: LuaString| async move { - let context = RequireContext::from(lua); - let path = path .to_str() .into_lua_err() .context("Failed to parse require path as string")? .to_string(); - if let Some(builtin_name) = path + let context = lua + .app_data_ref() + .expect("Failed to get RequireContext from app data"); + + let res = if let Some(builtin_name) = path .strip_prefix("@lune/") .map(|name| name.to_ascii_lowercase()) { - builtin::require(lua, context, &builtin_name).await + builtin::require(lua, &context, &builtin_name).await } else if let Some(aliased_path) = path.strip_prefix('@') { let (alias, name) = aliased_path.split_once('/').ok_or(LuaError::runtime( "Require with custom alias must contain '/' delimiter", ))?; - alias::require(lua, context, alias, name).await + alias::require(lua, &context, alias, name).await } else if context.use_absolute_paths() { - absolute::require(lua, context, &path).await + absolute::require(lua, &context, &path).await } else { - relative::require(lua, context, &path).await - } + relative::require(lua, &context, &path).await + }; + + res.clone() }) } diff --git a/src/lune/globals/require/relative.rs b/src/lune/globals/require/relative.rs index 08f45ea..def0c2f 100644 --- a/src/lune/globals/require/relative.rs +++ b/src/lune/globals/require/relative.rs @@ -2,11 +2,14 @@ use mlua::prelude::*; use super::context::*; -pub(super) async fn require<'lua>( +pub(super) async fn require<'lua, 'ctx>( _lua: &'lua Lua, - _ctx: RequireContext, + _ctx: &'ctx RequireContext, path: &str, -) -> LuaResult> { +) -> LuaResult> +where + 'lua: 'ctx, +{ Err(LuaError::runtime(format!( "TODO: Support require for absolute paths (tried to require '{path}')" ))) diff --git a/src/lune/scheduler/impl_async.rs b/src/lune/scheduler/impl_async.rs index fa3bf99..f57ec3b 100644 --- a/src/lune/scheduler/impl_async.rs +++ b/src/lune/scheduler/impl_async.rs @@ -37,13 +37,20 @@ where { let thread = thread.into_owned_lua_thread(self.lua)?; self.schedule_future(async move { - // TODO: Throw any error back to lua instead of panicking here - let rets = fut.await.expect("Failed to receive result"); - let rets = rets - .into_lua_multi(self.lua) - .expect("Failed to create return multi value"); - self.push_back(thread, rets) - .expect("Failed to schedule future thread"); + match fut.await.and_then(|rets| rets.into_lua_multi(self.lua)) { + Err(e) => { + self.state.set_lua_error(e); + // NOTE: We push the thread to the front of the scheduler + // to ensure that it runs first to be able to catch the + // stored error from within the scheduler lua interrupt + self.push_front(thread, ()) + .expect("Failed to schedule future thread"); + } + Ok(v) => { + self.push_back(thread, v) + .expect("Failed to schedule future thread"); + } + } }); Ok(()) diff --git a/src/lune/scheduler/mod.rs b/src/lune/scheduler/mod.rs index 398b1d5..520a323 100644 --- a/src/lune/scheduler/mod.rs +++ b/src/lune/scheduler/mod.rs @@ -44,13 +44,23 @@ pub(crate) struct Scheduler<'lua, 'fut> { impl<'lua, 'fut> Scheduler<'lua, 'fut> { pub fn new(lua: &'lua Lua) -> Self { - Self { + let this = Self { lua, state: Arc::new(SchedulerState::new()), threads: Arc::new(RefCell::new(VecDeque::new())), thread_senders: Arc::new(RefCell::new(HashMap::new())), futures: Arc::new(AsyncMutex::new(FuturesUnordered::new())), - } + }; + + // HACK: Propagate errors given to the scheduler back to their lua threads + // FUTURE: Do profiling and anything else we need inside of this interrupt + let state = this.state.clone(); + lua.set_interrupt(move |_| match state.get_lua_error() { + Some(e) => Err(e), + None => Ok(LuaVmState::Continue), + }); + + this } #[doc(hidden)] diff --git a/src/lune/scheduler/state.rs b/src/lune/scheduler/state.rs index 25df33b..3a0b12a 100644 --- a/src/lune/scheduler/state.rs +++ b/src/lune/scheduler/state.rs @@ -1,4 +1,9 @@ -use std::sync::atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering}; +use std::{ + cell::RefCell, + sync::atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering}, +}; + +use mlua::Error as LuaError; #[derive(Debug, Default)] pub struct SchedulerState { @@ -6,6 +11,7 @@ pub struct SchedulerState { exit_code: AtomicU8, num_resumptions: AtomicUsize, num_errors: AtomicUsize, + lua_error: RefCell>, } impl SchedulerState { @@ -41,4 +47,12 @@ impl SchedulerState { self.exit_state.store(true, Ordering::SeqCst); self.exit_code.store(code.into(), Ordering::SeqCst); } + + pub fn get_lua_error(&self) -> Option { + self.lua_error.take() + } + + pub fn set_lua_error(&self, e: LuaError) { + self.lua_error.replace(Some(e)); + } } From 2960f960de36e2b556447ea8dc0393c84d4e6a80 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Sat, 19 Aug 2023 16:38:07 -0500 Subject: [PATCH 023/103] More robust scheduler error resumption mechanism --- src/lune/scheduler/impl_async.rs | 8 ++---- src/lune/scheduler/impl_runner.rs | 44 ++++++++++++++++++++---------- src/lune/scheduler/impl_threads.rs | 35 +++++++++++++++--------- src/lune/scheduler/mod.rs | 14 +++++++--- src/lune/scheduler/state.rs | 29 +++++++++++++------- 5 files changed, 83 insertions(+), 47 deletions(-) diff --git a/src/lune/scheduler/impl_async.rs b/src/lune/scheduler/impl_async.rs index f57ec3b..6f9a83e 100644 --- a/src/lune/scheduler/impl_async.rs +++ b/src/lune/scheduler/impl_async.rs @@ -39,12 +39,8 @@ where self.schedule_future(async move { match fut.await.and_then(|rets| rets.into_lua_multi(self.lua)) { Err(e) => { - self.state.set_lua_error(e); - // NOTE: We push the thread to the front of the scheduler - // to ensure that it runs first to be able to catch the - // stored error from within the scheduler lua interrupt - self.push_front(thread, ()) - .expect("Failed to schedule future thread"); + self.push_err(thread, e) + .expect("Failed to schedule future err thread"); } Ok(v) => { self.push_back(thread, v) diff --git a/src/lune/scheduler/impl_runner.rs b/src/lune/scheduler/impl_runner.rs index 088bf23..82617cf 100644 --- a/src/lune/scheduler/impl_runner.rs +++ b/src/lune/scheduler/impl_runner.rs @@ -23,29 +23,45 @@ where let mut resumed_any = false; - while let Some((thread, args, sender)) = self + // Pop threads from the scheduler until there are none left + while let Some(thread) = self .pop_thread() .expect("Failed to pop thread from scheduler") { + // Deconstruct the scheduler thread into its parts + let thread_id = thread.id(); + let (thread, args) = thread.into_inner(self.lua); + + // Resume the thread, ensuring that the schedulers + // current thread id is set correctly for error catching + self.state.set_current_thread_id(Some(thread_id)); let res = thread.resume::<_, LuaMultiValue>(args); - self.state.add_resumption(); + self.state.set_current_thread_id(None); + resumed_any = true; + // If we got any resumption (lua-side) error, increment + // the error count of the scheduler so we can exit with + // a non-zero exit code, and print it out to stderr + // TODO: Pretty print the lua error here if let Err(err) = &res { - self.state.add_error(); - eprint!("{err}"); // TODO: Pretty print the lua error here + self.state.increment_error_count(); + eprint!("{err}"); } - if sender.receiver_count() > 0 { - sender - .send(res.map(|v| { - Arc::new( - self.lua - .create_registry_value(v.into_vec()) - .expect("Failed to store return values in registry"), - ) - })) - .expect("Failed to broadcast return values of thread"); + // Send results of resuming this thread to any listeners + if let Some(sender) = self.thread_senders.borrow_mut().remove(&thread_id) { + if sender.receiver_count() > 0 { + sender + .send(res.map(|v| { + Arc::new( + self.lua + .create_registry_value(v.into_vec()) + .expect("Failed to store return values in registry"), + ) + })) + .expect("Failed to broadcast return values of thread"); + } } if self.state.has_exit_code() { diff --git a/src/lune/scheduler/impl_threads.rs b/src/lune/scheduler/impl_threads.rs index 2f8969f..765bbb7 100644 --- a/src/lune/scheduler/impl_threads.rs +++ b/src/lune/scheduler/impl_threads.rs @@ -27,9 +27,7 @@ where Returns `None` if there are no threads left to run. */ - pub(super) fn pop_thread( - &self, - ) -> LuaResult, SchedulerThreadSender)>> { + pub(super) fn pop_thread(&self) -> LuaResult> { match self .threads .try_borrow_mut() @@ -37,20 +35,31 @@ where .context("Failed to borrow threads vec")? .pop_front() { - Some(thread) => { - let thread_id = &thread.id(); - let (thread, args) = thread.into_inner(self.lua); - let sender = self - .thread_senders - .borrow_mut() - .remove(thread_id) - .expect("Missing thread sender"); - Ok(Some((thread, args, sender))) - } + Some(thread) => Ok(Some(thread)), None => Ok(None), } } + /** + Schedules the `thread` to be resumed with the given [`LuaError`]. + */ + pub fn push_err(&self, thread: impl IntoLuaOwnedThread, err: LuaError) -> LuaResult<()> { + let thread = thread.into_owned_lua_thread(self.lua)?; + let args = LuaMultiValue::new(); // Will be resumed with error, don't need real args + + let thread = SchedulerThread::new(self.lua, thread, args)?; + let thread_id = thread.id(); + + self.state.set_thread_error(thread_id, err); + self.threads + .try_borrow_mut() + .into_lua_err() + .context("Failed to borrow threads vec")? + .push_front(thread); + + Ok(()) + } + /** Schedules the `thread` to be resumed with the given `args` right away, before any other currently scheduled threads. diff --git a/src/lune/scheduler/mod.rs b/src/lune/scheduler/mod.rs index 520a323..6f457d7 100644 --- a/src/lune/scheduler/mod.rs +++ b/src/lune/scheduler/mod.rs @@ -52,12 +52,18 @@ impl<'lua, 'fut> Scheduler<'lua, 'fut> { futures: Arc::new(AsyncMutex::new(FuturesUnordered::new())), }; - // HACK: Propagate errors given to the scheduler back to their lua threads + // Propagate errors given to the scheduler back to their lua threads // FUTURE: Do profiling and anything else we need inside of this interrupt let state = this.state.clone(); - lua.set_interrupt(move |_| match state.get_lua_error() { - Some(e) => Err(e), - None => Ok(LuaVmState::Continue), + lua.set_interrupt(move |_| { + if let Some(id) = state.get_current_thread_id() { + match state.get_thread_error(id) { + Some(e) => Err(e), + None => Ok(LuaVmState::Continue), + } + } else { + Ok(LuaVmState::Continue) + } }); this diff --git a/src/lune/scheduler/state.rs b/src/lune/scheduler/state.rs index 3a0b12a..1c38670 100644 --- a/src/lune/scheduler/state.rs +++ b/src/lune/scheduler/state.rs @@ -1,17 +1,21 @@ use std::{ cell::RefCell, + collections::HashMap, sync::atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering}, }; use mlua::Error as LuaError; +use super::SchedulerThreadId; + #[derive(Debug, Default)] pub struct SchedulerState { exit_state: AtomicBool, exit_code: AtomicU8, num_resumptions: AtomicUsize, num_errors: AtomicUsize, - lua_error: RefCell>, + thread_id: RefCell>, + thread_errors: RefCell>, } impl SchedulerState { @@ -19,11 +23,7 @@ impl SchedulerState { Self::default() } - pub fn add_resumption(&self) { - self.num_resumptions.fetch_add(1, Ordering::Relaxed); - } - - pub fn add_error(&self) { + pub fn increment_error_count(&self) { self.num_errors.fetch_add(1, Ordering::Relaxed); } @@ -48,11 +48,20 @@ impl SchedulerState { self.exit_code.store(code.into(), Ordering::SeqCst); } - pub fn get_lua_error(&self) -> Option { - self.lua_error.take() + pub fn get_current_thread_id(&self) -> Option { + *self.thread_id.borrow() } - pub fn set_lua_error(&self, e: LuaError) { - self.lua_error.replace(Some(e)); + pub fn set_current_thread_id(&self, id: Option) { + self.thread_id.replace(id); + self.num_resumptions.fetch_add(1, Ordering::Relaxed); + } + + pub fn get_thread_error(&self, id: SchedulerThreadId) -> Option { + self.thread_errors.borrow_mut().remove(&id) + } + + pub fn set_thread_error(&self, id: SchedulerThreadId, err: LuaError) { + self.thread_errors.borrow_mut().insert(id, err); } } From b1847bf84ca75cf075d63f4d2948ea92cb8731fe Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Sat, 19 Aug 2023 16:45:46 -0500 Subject: [PATCH 024/103] Add documentation for scheduler state --- src/lune/scheduler/state.rs | 47 +++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/lune/scheduler/state.rs b/src/lune/scheduler/state.rs index 1c38670..6050810 100644 --- a/src/lune/scheduler/state.rs +++ b/src/lune/scheduler/state.rs @@ -14,23 +14,45 @@ pub struct SchedulerState { exit_code: AtomicU8, num_resumptions: AtomicUsize, num_errors: AtomicUsize, + // TODO: Use Arc> to make these thread and borrow safe thread_id: RefCell>, thread_errors: RefCell>, } impl SchedulerState { + /** + Creates a new scheduler state. + + This scheduler state uses atomic operations for everything + except lua resumption errors, and is completely thread safe. + */ pub fn new() -> Self { Self::default() } + /** + Increments the total lua error count for the scheduler. + + This is used to determine if the scheduler should exit with + a non-zero exit code, when no exit code is explicitly set. + */ pub fn increment_error_count(&self) { self.num_errors.fetch_add(1, Ordering::Relaxed); } + /** + Checks if there have been any lua errors. + + This is used to determine if the scheduler should exit with + a non-zero exit code, when no exit code is explicitly set. + */ pub fn has_errored(&self) -> bool { self.num_errors.load(Ordering::SeqCst) > 0 } + /** + Gets the currently set exit code for the scheduler, if any. + */ pub fn exit_code(&self) -> Option { if self.exit_state.load(Ordering::SeqCst) { Some(self.exit_code.load(Ordering::SeqCst)) @@ -39,28 +61,53 @@ impl SchedulerState { } } + /** + Checks if the scheduler has an explicit exit code set. + */ pub fn has_exit_code(&self) -> bool { self.exit_state.load(Ordering::SeqCst) } + /** + Sets the explicit exit code for the scheduler. + */ pub fn set_exit_code(&self, code: impl Into) { self.exit_state.store(true, Ordering::SeqCst); self.exit_code.store(code.into(), Ordering::SeqCst); } + /** + Gets the currently running lua scheduler thread id, if any. + */ pub fn get_current_thread_id(&self) -> Option { *self.thread_id.borrow() } + /** + Sets the currently running lua scheduler thread id. + + This should be set to `Some(id)` just before resuming a lua + thread, and `None` while no lua thread is being resumed. + */ pub fn set_current_thread_id(&self, id: Option) { self.thread_id.replace(id); self.num_resumptions.fetch_add(1, Ordering::Relaxed); } + /** + Gets the [`LuaError`] (if any) for the given `id`. + + Note that this removes the error from the scheduler state completely. + */ pub fn get_thread_error(&self, id: SchedulerThreadId) -> Option { self.thread_errors.borrow_mut().remove(&id) } + /** + Sets a [`LuaError`] for the given `id`. + + Note that this will replace any already existing [`LuaError`]. + */ pub fn set_thread_error(&self, id: SchedulerThreadId, err: LuaError) { self.thread_errors.borrow_mut().insert(id, err); } From 4acc730d3868de9a10580aed75ce8cb85d847321 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Sat, 19 Aug 2023 17:09:13 -0500 Subject: [PATCH 025/103] Ensure thread safety of scheduler state --- src/lune/scheduler/state.rs | 55 +++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/src/lune/scheduler/state.rs b/src/lune/scheduler/state.rs index 6050810..f2df7ee 100644 --- a/src/lune/scheduler/state.rs +++ b/src/lune/scheduler/state.rs @@ -1,30 +1,34 @@ use std::{ - cell::RefCell, collections::HashMap, - sync::atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering}, + sync::{ + atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering}, + Arc, Mutex, + }, }; use mlua::Error as LuaError; use super::SchedulerThreadId; +/** + Internal state for a [`Scheduler`]. + + This scheduler state uses atomic operations for everything + except lua error storage, and is completely thread safe. +*/ #[derive(Debug, Default)] pub struct SchedulerState { exit_state: AtomicBool, exit_code: AtomicU8, num_resumptions: AtomicUsize, num_errors: AtomicUsize, - // TODO: Use Arc> to make these thread and borrow safe - thread_id: RefCell>, - thread_errors: RefCell>, + thread_id: Arc>>, + thread_errors: Arc>>, } impl SchedulerState { /** Creates a new scheduler state. - - This scheduler state uses atomic operations for everything - except lua resumption errors, and is completely thread safe. */ pub fn new() -> Self { Self::default() @@ -80,18 +84,33 @@ impl SchedulerState { Gets the currently running lua scheduler thread id, if any. */ pub fn get_current_thread_id(&self) -> Option { - *self.thread_id.borrow() + *self + .thread_id + .lock() + .expect("Failed to lock current thread id") } /** Sets the currently running lua scheduler thread id. - This should be set to `Some(id)` just before resuming a lua - thread, and `None` while no lua thread is being resumed. + This must be set to `Some(id)` just before resuming a lua thread, + and `None` while no lua thread is being resumed. If set to `Some` + while the current thread id is also `Some`, this will panic. + + Must only be set once per thread id, although this + is not checked at runtime for performance reasons. */ pub fn set_current_thread_id(&self, id: Option) { - self.thread_id.replace(id); self.num_resumptions.fetch_add(1, Ordering::Relaxed); + let mut thread_id = self + .thread_id + .lock() + .expect("Failed to lock current thread id"); + assert!( + id.is_none() || thread_id.is_none(), + "Current thread id can not be overwritten" + ); + *thread_id = id; } /** @@ -100,7 +119,11 @@ impl SchedulerState { Note that this removes the error from the scheduler state completely. */ pub fn get_thread_error(&self, id: SchedulerThreadId) -> Option { - self.thread_errors.borrow_mut().remove(&id) + let mut thread_errors = self + .thread_errors + .lock() + .expect("Failed to lock thread errors"); + thread_errors.remove(&id) } /** @@ -109,6 +132,10 @@ impl SchedulerState { Note that this will replace any already existing [`LuaError`]. */ pub fn set_thread_error(&self, id: SchedulerThreadId, err: LuaError) { - self.thread_errors.borrow_mut().insert(id, err); + let mut thread_errors = self + .thread_errors + .lock() + .expect("Failed to lock thread errors"); + thread_errors.insert(id, err); } } From b69f824b57816f6fb38517583fd8eccd58a4169f Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Sat, 19 Aug 2023 17:15:24 -0500 Subject: [PATCH 026/103] Pretty-print lua errors --- src/lune/error.rs | 9 +++++++++ src/lune/scheduler/impl_runner.rs | 6 ++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/lune/error.rs b/src/lune/error.rs index dcb9222..1999877 100644 --- a/src/lune/error.rs +++ b/src/lune/error.rs @@ -62,6 +62,15 @@ impl From for LuneError { } } +impl From<&LuaError> for LuneError { + fn from(value: &LuaError) -> Self { + Self { + error: value.clone(), + disable_colors: false, + } + } +} + impl Display for LuneError { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { write!(f, "{}", self.error) // TODO: Pretty formatting diff --git a/src/lune/scheduler/impl_runner.rs b/src/lune/scheduler/impl_runner.rs index 82617cf..0ed44a8 100644 --- a/src/lune/scheduler/impl_runner.rs +++ b/src/lune/scheduler/impl_runner.rs @@ -5,6 +5,8 @@ use mlua::prelude::*; use tokio::task::LocalSet; +use crate::LuneError; + use super::Scheduler; impl<'lua, 'fut> Scheduler<'lua, 'fut> @@ -43,10 +45,10 @@ where // If we got any resumption (lua-side) error, increment // the error count of the scheduler so we can exit with // a non-zero exit code, and print it out to stderr - // TODO: Pretty print the lua error here if let Err(err) = &res { self.state.increment_error_count(); - eprint!("{err}"); + // NOTE: LuneError will pretty-format this error + eprint!("{}", LuneError::from(err)); } // Send results of resuming this thread to any listeners From 2762a43dbb1d6ca89e5e6edc567add5ca27e4db0 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Sat, 19 Aug 2023 17:23:40 -0500 Subject: [PATCH 027/103] Add back _G table --- src/lune/globals/g_table.rs | 5 +++++ src/lune/globals/mod.rs | 2 ++ 2 files changed, 7 insertions(+) create mode 100644 src/lune/globals/g_table.rs diff --git a/src/lune/globals/g_table.rs b/src/lune/globals/g_table.rs new file mode 100644 index 0000000..c073bad --- /dev/null +++ b/src/lune/globals/g_table.rs @@ -0,0 +1,5 @@ +use mlua::prelude::*; + +pub fn create(lua: &'static Lua) -> LuaResult> { + lua.create_table() +} diff --git a/src/lune/globals/mod.rs b/src/lune/globals/mod.rs index fb03d1e..18a70c4 100644 --- a/src/lune/globals/mod.rs +++ b/src/lune/globals/mod.rs @@ -2,10 +2,12 @@ use mlua::prelude::*; use super::util::TableBuilder; +mod g_table; mod require; pub fn inject_all(lua: &'static Lua) -> LuaResult<()> { let all = TableBuilder::new(lua)? + .with_value("_G", g_table::create(lua)?)? .with_value("require", require::create(lua)?)? .build_readonly()?; From 24b6498774de61cbd4c6b5a1f48a55c3e4678ca6 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Sat, 19 Aug 2023 17:27:42 -0500 Subject: [PATCH 028/103] Rename version global test --- src/lune/globals/version.rs | 5 +++++ src/tests.rs | 6 ++---- tests/globals/{version.luau => _VERSION.luau} | 0 3 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 src/lune/globals/version.rs rename tests/globals/{version.luau => _VERSION.luau} (100%) diff --git a/src/lune/globals/version.rs b/src/lune/globals/version.rs new file mode 100644 index 0000000..c073bad --- /dev/null +++ b/src/lune/globals/version.rs @@ -0,0 +1,5 @@ +use mlua::prelude::*; + +pub fn create(lua: &'static Lua) -> LuaResult> { + lua.create_table() +} diff --git a/src/tests.rs b/src/tests.rs index af56722..5380e5d 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -79,13 +79,11 @@ create_tests! { require_siblings: "require/tests/siblings", global_g_table: "globals/_G", - // TODO: Uncomment this test, it is commented out right - // now to let CI pass so that we can make a new release - // global_coroutine: "globals/coroutine", + global_version: "globals/_VERSION", + global_coroutine: "globals/coroutine", global_pcall: "globals/pcall", global_type: "globals/type", global_typeof: "globals/typeof", - global_version: "globals/version", serde_compression_files: "serde/compression/files", serde_compression_roundtrip: "serde/compression/roundtrip", diff --git a/tests/globals/version.luau b/tests/globals/_VERSION.luau similarity index 100% rename from tests/globals/version.luau rename to tests/globals/_VERSION.luau From 7a63987cbee519e9853e37f0a1d91a904a511d07 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Sat, 19 Aug 2023 17:34:15 -0500 Subject: [PATCH 029/103] Bring back version global --- src/lune/globals/mod.rs | 2 ++ src/lune/globals/version.rs | 21 ++++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/lune/globals/mod.rs b/src/lune/globals/mod.rs index 18a70c4..dfcb928 100644 --- a/src/lune/globals/mod.rs +++ b/src/lune/globals/mod.rs @@ -4,10 +4,12 @@ use super::util::TableBuilder; mod g_table; mod require; +mod version; pub fn inject_all(lua: &'static Lua) -> LuaResult<()> { let all = TableBuilder::new(lua)? .with_value("_G", g_table::create(lua)?)? + .with_value("_VERSION", version::create(lua)?)? .with_value("require", require::create(lua)?)? .build_readonly()?; diff --git a/src/lune/globals/version.rs b/src/lune/globals/version.rs index c073bad..153350a 100644 --- a/src/lune/globals/version.rs +++ b/src/lune/globals/version.rs @@ -1,5 +1,24 @@ use mlua::prelude::*; pub fn create(lua: &'static Lua) -> LuaResult> { - lua.create_table() + let luau_version_full = lua + .globals() + .get::<_, LuaString>("_VERSION") + .expect("Missing _VERSION global"); + + let luau_version = luau_version_full + .to_str()? + .strip_prefix("Luau 0.") + .expect("_VERSION global is formatted incorrectly") + .trim(); + + if luau_version.is_empty() { + panic!("_VERSION global is missing version number") + } + + lua.create_string(format!( + "Lune {lune}+{luau}", + lune = env!("CARGO_PKG_VERSION"), + luau = luau_version, + )) } From 0757d6f29364aa29d8df9f0acc22a7dbbdb0b7be Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Sat, 19 Aug 2023 18:42:28 -0500 Subject: [PATCH 030/103] Initial implementation of builtin libraries, task library --- src/lune/builtins/mod.rs | 44 ++++++++++++++ src/lune/builtins/task/mod.rs | 93 +++++++++++++++++++++++++++++ src/lune/builtins/task/tof.rs | 30 ++++++++++ src/lune/globals/require/builtin.rs | 9 ++- src/lune/globals/require/context.rs | 62 ++++++++++++++++++- src/lune/mod.rs | 1 + src/lune/util/table_builder.rs | 3 +- 7 files changed, 234 insertions(+), 8 deletions(-) create mode 100644 src/lune/builtins/mod.rs create mode 100644 src/lune/builtins/task/mod.rs create mode 100644 src/lune/builtins/task/tof.rs diff --git a/src/lune/builtins/mod.rs b/src/lune/builtins/mod.rs new file mode 100644 index 0000000..666f71d --- /dev/null +++ b/src/lune/builtins/mod.rs @@ -0,0 +1,44 @@ +use std::str::FromStr; + +use mlua::prelude::*; + +mod task; + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub enum LuneBuiltin { + Task, +} + +impl<'lua> LuneBuiltin +where + 'lua: 'static, // FIXME: Remove static lifetime bound here when builtin libraries no longer need it +{ + pub fn name(&self) -> &'static str { + match self { + Self::Task => "task", + } + } + + pub fn create(&self, lua: &'lua Lua) -> LuaResult> { + let res = match self { + Self::Task => task::create(lua), + }; + match res { + Ok(v) => Ok(v.into_lua_multi(lua)?), + Err(e) => Err(e.context(format!( + "Failed to create builtin library '{}'", + self.name() + ))), + } + } +} + +impl FromStr for LuneBuiltin { + type Err = String; + fn from_str(s: &str) -> Result { + match s.trim().to_ascii_lowercase().as_str() { + "task" => Ok(Self::Task), + _ => Err(format!("Unknown builtin library '{s}'")), + } + } +} diff --git a/src/lune/builtins/task/mod.rs b/src/lune/builtins/task/mod.rs new file mode 100644 index 0000000..1c2ff96 --- /dev/null +++ b/src/lune/builtins/task/mod.rs @@ -0,0 +1,93 @@ +use std::time::Duration; + +use mlua::prelude::*; + +use tokio::time::{self, Instant}; + +use crate::lune::{scheduler::Scheduler, util::TableBuilder}; + +mod tof; +use tof::LuaThreadOrFunction; + +pub fn create(lua: &'static Lua) -> LuaResult> { + TableBuilder::new(lua)? + .with_function("cancel", task_cancel)? + .with_function("defer", task_defer)? + .with_function("delay", task_delay)? + .with_function("spawn", task_spawn)? + .with_async_function("wait", task_wait)? + .build_readonly() +} + +fn task_cancel(lua: &Lua, thread: LuaThread) -> LuaResult<()> { + let close = lua + .globals() + .get::<_, LuaTable>("coroutine")? + .get::<_, LuaFunction>("close")?; + match close.call(thread) { + Err(LuaError::CoroutineInactive) => Ok(()), + Err(e) => Err(e), + Ok(()) => Ok(()), + } +} + +fn task_defer<'lua>( + lua: &'lua Lua, + (tof, args): (LuaThreadOrFunction<'lua>, LuaMultiValue<'_>), +) -> LuaResult> { + let thread = tof.into_thread(lua)?; + let sched = lua + .app_data_ref::<&Scheduler>() + .expect("Lua struct is missing scheduler"); + sched.push_back(thread.clone(), args)?; + Ok(thread) +} + +// FIXME: `self` escapes outside of method because we are borrowing `tof` and +// `args` when we call `schedule_future_thread` in the lua function body below +// For now we solve this by using the 'static lifetime bound in the impl +fn task_delay<'lua>( + lua: &'lua Lua, + (secs, tof, args): (f64, LuaThreadOrFunction<'lua>, LuaMultiValue<'lua>), +) -> LuaResult> +where + 'lua: 'static, +{ + let thread = tof.into_thread(lua)?; + let sched = lua + .app_data_ref::<&Scheduler>() + .expect("Lua struct is missing scheduler"); + + let thread2 = thread.clone(); + sched.schedule_future_thread(thread.clone(), async move { + let duration = Duration::from_secs_f64(secs); + time::sleep(duration).await; + sched.push_back(thread2, args)?; + Ok(()) + })?; + + Ok(thread) +} + +fn task_spawn<'lua>( + lua: &'lua Lua, + (tof, args): (LuaThreadOrFunction<'lua>, LuaMultiValue<'_>), +) -> LuaResult> { + let thread = tof.into_thread(lua)?; + let resume = lua + .globals() + .get::<_, LuaTable>("coroutine")? + .get::<_, LuaFunction>("resume")?; + resume.call((thread.clone(), args))?; + Ok(thread) +} + +async fn task_wait(_: &Lua, secs: Option) -> LuaResult { + let duration = Duration::from_secs_f64(secs.unwrap_or_default()); + + let before = Instant::now(); + time::sleep(duration).await; + let after = Instant::now(); + + Ok((after - before).as_secs_f64()) +} diff --git a/src/lune/builtins/task/tof.rs b/src/lune/builtins/task/tof.rs new file mode 100644 index 0000000..e63cd2b --- /dev/null +++ b/src/lune/builtins/task/tof.rs @@ -0,0 +1,30 @@ +use mlua::prelude::*; + +#[derive(Clone)] +pub(super) enum LuaThreadOrFunction<'lua> { + Thread(LuaThread<'lua>), + Function(LuaFunction<'lua>), +} + +impl<'lua> LuaThreadOrFunction<'lua> { + pub(super) fn into_thread(self, lua: &'lua Lua) -> LuaResult> { + match self { + Self::Thread(t) => Ok(t), + Self::Function(f) => lua.create_thread(f), + } + } +} + +impl<'lua> FromLua<'lua> for LuaThreadOrFunction<'lua> { + fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { + match value { + LuaValue::Thread(t) => Ok(Self::Thread(t)), + LuaValue::Function(f) => Ok(Self::Function(f)), + value => Err(LuaError::FromLuaConversionError { + from: value.type_name(), + to: "LuaThreadOrFunction", + message: Some("Expected thread or function".to_string()), + }), + } + } +} diff --git a/src/lune/globals/require/builtin.rs b/src/lune/globals/require/builtin.rs index e7a5171..914d1c1 100644 --- a/src/lune/globals/require/builtin.rs +++ b/src/lune/globals/require/builtin.rs @@ -3,14 +3,13 @@ use mlua::prelude::*; use super::context::*; pub(super) async fn require<'lua, 'ctx>( - _lua: &'lua Lua, - _ctx: &'ctx RequireContext, + lua: &'lua Lua, + ctx: &'ctx RequireContext, name: &str, ) -> LuaResult> where 'lua: 'ctx, + 'lua: 'static, // FIXME: Remove static lifetime bound here when builtin libraries no longer need it { - Err(LuaError::runtime(format!( - "TODO: Support require for built-in libraries (tried to require '{name}')" - ))) + ctx.load_builtin(lua, name) } diff --git a/src/lune/globals/require/context.rs b/src/lune/globals/require/context.rs index acf2c1c..ceba8f2 100644 --- a/src/lune/globals/require/context.rs +++ b/src/lune/globals/require/context.rs @@ -3,7 +3,10 @@ use std::{collections::HashMap, env, path::PathBuf, sync::Arc}; use mlua::prelude::*; use tokio::{fs, sync::Mutex as AsyncMutex}; -use crate::lune::scheduler::{IntoLuaOwnedThread, Scheduler, SchedulerThreadId}; +use crate::lune::{ + builtins::LuneBuiltin, + scheduler::{IntoLuaOwnedThread, Scheduler, SchedulerThreadId}, +}; const REGISTRY_KEY: &str = "RequireContext"; @@ -11,6 +14,7 @@ const REGISTRY_KEY: &str = "RequireContext"; pub(super) struct RequireContext { use_absolute_paths: bool, working_directory: PathBuf, + cache_builtins: Arc>>>, cache_results: Arc>>>, cache_pending: Arc>>, } @@ -24,11 +28,13 @@ impl RequireContext { than one context may lead to undefined require-behavior. */ pub fn new() -> Self { + let cwd = env::current_dir().expect("Failed to get current working directory"); Self { // TODO: Set to false by default, load some kind of config // or env var to check if we should be using absolute paths use_absolute_paths: true, - working_directory: env::current_dir().expect("Failed to get current working directory"), + working_directory: cwd, + cache_builtins: Arc::new(AsyncMutex::new(HashMap::new())), cache_results: Arc::new(AsyncMutex::new(HashMap::new())), cache_pending: Arc::new(AsyncMutex::new(HashMap::new())), } @@ -224,6 +230,58 @@ impl RequireContext { thread_res } + + /** + Loads (requires) the builtin with the given name. + */ + pub fn load_builtin<'lua>( + &self, + lua: &'lua Lua, + name: impl AsRef, + ) -> LuaResult> + where + 'lua: 'static, // FIXME: Remove static lifetime bound here when builtin libraries no longer need it + { + let builtin: LuneBuiltin = match name.as_ref().parse() { + Err(e) => return Err(LuaError::runtime(e)), + Ok(b) => b, + }; + + let mut cache = self + .cache_builtins + .try_lock() + .expect("RequireContext may not be used from multiple threads"); + + if let Some(res) = cache.get(&builtin) { + return match res { + Err(e) => return Err(e.clone()), + Ok(key) => { + let multi_vec = lua + .registry_value::>(key) + .expect("Missing builtin result in lua registry"); + Ok(LuaMultiValue::from_vec(multi_vec)) + } + }; + }; + + let result = builtin.create(lua); + + cache.insert( + builtin, + match result.clone() { + Err(e) => Err(e), + Ok(multi) => { + let multi_vec = multi.into_vec(); + let multi_key = lua + .create_registry_value(multi_vec) + .expect("Failed to store require result in registry"); + Ok(multi_key) + } + }, + ); + + result + } } impl LuaUserData for RequireContext {} diff --git a/src/lune/mod.rs b/src/lune/mod.rs index 1b5a602..8ac8a88 100644 --- a/src/lune/mod.rs +++ b/src/lune/mod.rs @@ -1,5 +1,6 @@ use std::process::ExitCode; +mod builtins; mod error; mod globals; mod scheduler; diff --git a/src/lune/util/table_builder.rs b/src/lune/util/table_builder.rs index 7db4910..9f022d4 100644 --- a/src/lune/util/table_builder.rs +++ b/src/lune/util/table_builder.rs @@ -52,7 +52,8 @@ impl<'lua> TableBuilder<'lua> { } } -// FIXME: Remove static lifetime bound here when possible and move into above impl +// FIXME: Remove static lifetime bound here when `create_async_function` +// no longer needs it to compile, then move this into the above impl impl<'lua> TableBuilder<'lua> where 'lua: 'static, From e4cf40789c34dae36b10956fc45fe4510f8424fb Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Sat, 19 Aug 2023 18:44:43 -0500 Subject: [PATCH 031/103] Dont try to resume dead threads in the scheduler --- src/lune/scheduler/impl_runner.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lune/scheduler/impl_runner.rs b/src/lune/scheduler/impl_runner.rs index 0ed44a8..918cb6d 100644 --- a/src/lune/scheduler/impl_runner.rs +++ b/src/lune/scheduler/impl_runner.rs @@ -34,6 +34,12 @@ where let thread_id = thread.id(); let (thread, args) = thread.into_inner(self.lua); + // Make sure this thread is still resumable, it might have + // been resumed somewhere else or even have been cancelled + if thread.status() != LuaThreadStatus::Resumable { + continue; + } + // Resume the thread, ensuring that the schedulers // current thread id is set correctly for error catching self.state.set_current_thread_id(Some(thread_id)); From 73361d5a52a5938a6da301756f282945f829c600 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Sat, 19 Aug 2023 19:13:50 -0500 Subject: [PATCH 032/103] Add back stdio builtin and pretty error formatting --- src/lune/builtins/mod.rs | 7 +- src/lune/builtins/stdio/mod.rs | 108 +++++++ src/lune/builtins/stdio/prompt.rs | 192 ++++++++++++ src/lune/builtins/task/mod.rs | 2 +- src/lune/error.rs | 8 +- src/lune/scheduler/impl_runner.rs | 2 +- src/lune/util/formatting.rs | 466 ++++++++++++++++++++++++++++++ src/lune/util/mod.rs | 2 + 8 files changed, 783 insertions(+), 4 deletions(-) create mode 100644 src/lune/builtins/stdio/mod.rs create mode 100644 src/lune/builtins/stdio/prompt.rs create mode 100644 src/lune/util/formatting.rs diff --git a/src/lune/builtins/mod.rs b/src/lune/builtins/mod.rs index 666f71d..51cdb55 100644 --- a/src/lune/builtins/mod.rs +++ b/src/lune/builtins/mod.rs @@ -2,11 +2,13 @@ use std::str::FromStr; use mlua::prelude::*; +mod stdio; mod task; #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub enum LuneBuiltin { Task, + Stdio, } impl<'lua> LuneBuiltin @@ -16,15 +18,17 @@ where pub fn name(&self) -> &'static str { match self { Self::Task => "task", + Self::Stdio => "stdio", } } pub fn create(&self, lua: &'lua Lua) -> LuaResult> { let res = match self { Self::Task => task::create(lua), + Self::Stdio => stdio::create(lua), }; match res { - Ok(v) => Ok(v.into_lua_multi(lua)?), + Ok(v) => v.into_lua_multi(lua), Err(e) => Err(e.context(format!( "Failed to create builtin library '{}'", self.name() @@ -38,6 +42,7 @@ impl FromStr for LuneBuiltin { fn from_str(s: &str) -> Result { match s.trim().to_ascii_lowercase().as_str() { "task" => Ok(Self::Task), + "stdio" => Ok(Self::Stdio), _ => Err(format!("Unknown builtin library '{s}'")), } } diff --git a/src/lune/builtins/stdio/mod.rs b/src/lune/builtins/stdio/mod.rs new file mode 100644 index 0000000..139a643 --- /dev/null +++ b/src/lune/builtins/stdio/mod.rs @@ -0,0 +1,108 @@ +use mlua::prelude::*; + +use dialoguer::{theme::ColorfulTheme, Confirm, Input, MultiSelect, Select}; +use tokio::{ + io::{self, AsyncWriteExt}, + task, +}; + +use crate::lune::util::{ + formatting::{ + format_style, pretty_format_multi_value, style_from_color_str, style_from_style_str, + }, + TableBuilder, +}; + +mod prompt; +use prompt::{PromptKind, PromptOptions, PromptResult}; + +pub fn create(lua: &'static Lua) -> LuaResult> { + TableBuilder::new(lua)? + .with_function("color", stdio_color)? + .with_function("style", stdio_style)? + .with_function("format", stdio_format)? + .with_async_function("write", stdio_write)? + .with_async_function("ewrite", stdio_ewrite)? + .with_async_function("prompt", stdio_prompt)? + .build_readonly() +} + +fn stdio_color(_: &Lua, color: String) -> LuaResult { + let ansi_string = format_style(style_from_color_str(&color)?); + Ok(ansi_string) +} + +fn stdio_style(_: &Lua, color: String) -> LuaResult { + let ansi_string = format_style(style_from_style_str(&color)?); + Ok(ansi_string) +} + +fn stdio_format(_: &Lua, args: LuaMultiValue) -> LuaResult { + pretty_format_multi_value(&args) +} + +async fn stdio_write(_: &Lua, s: LuaString<'_>) -> LuaResult<()> { + let mut stdout = io::stdout(); + stdout.write_all(s.as_bytes()).await?; + stdout.flush().await?; + Ok(()) +} + +async fn stdio_ewrite(_: &Lua, s: LuaString<'_>) -> LuaResult<()> { + let mut stderr = io::stderr(); + stderr.write_all(s.as_bytes()).await?; + stderr.flush().await?; + Ok(()) +} + +async fn stdio_prompt(_: &Lua, options: PromptOptions) -> LuaResult { + task::spawn_blocking(move || prompt(options)) + .await + .into_lua_err()? +} + +fn prompt(options: PromptOptions) -> LuaResult { + let theme = ColorfulTheme::default(); + match options.kind { + PromptKind::Text => { + let input: String = Input::with_theme(&theme) + .allow_empty(true) + .with_prompt(options.text.unwrap_or_default()) + .with_initial_text(options.default_string.unwrap_or_default()) + .interact_text()?; + Ok(PromptResult::String(input)) + } + PromptKind::Confirm => { + let mut prompt = Confirm::with_theme(&theme); + if let Some(b) = options.default_bool { + prompt.default(b); + }; + let result = prompt + .with_prompt(&options.text.expect("Missing text in prompt options")) + .interact()?; + Ok(PromptResult::Boolean(result)) + } + PromptKind::Select => { + let chosen = Select::with_theme(&theme) + .with_prompt(&options.text.unwrap_or_default()) + .items(&options.options.expect("Missing options in prompt options")) + .interact_opt()?; + Ok(match chosen { + Some(idx) => PromptResult::Index(idx + 1), + None => PromptResult::None, + }) + } + PromptKind::MultiSelect => { + let chosen = MultiSelect::with_theme(&theme) + .with_prompt(&options.text.unwrap_or_default()) + .items(&options.options.expect("Missing options in prompt options")) + .interact_opt()?; + Ok(match chosen { + None => PromptResult::None, + Some(indices) => { + PromptResult::Indices(indices.iter().map(|idx| *idx + 1).collect()) + } + }) + } + } +} diff --git a/src/lune/builtins/stdio/prompt.rs b/src/lune/builtins/stdio/prompt.rs new file mode 100644 index 0000000..9cdd899 --- /dev/null +++ b/src/lune/builtins/stdio/prompt.rs @@ -0,0 +1,192 @@ +use std::fmt; + +use mlua::prelude::*; + +#[derive(Debug, Clone, Copy)] +pub enum PromptKind { + Text, + Confirm, + Select, + MultiSelect, +} + +impl PromptKind { + fn get_all() -> Vec { + vec![Self::Text, Self::Confirm, Self::Select, Self::MultiSelect] + } +} + +impl Default for PromptKind { + fn default() -> Self { + Self::Text + } +} + +impl fmt::Display for PromptKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match self { + Self::Text => "Text", + Self::Confirm => "Confirm", + Self::Select => "Select", + Self::MultiSelect => "MultiSelect", + } + ) + } +} + +impl<'lua> FromLua<'lua> for PromptKind { + fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { + if let LuaValue::Nil = value { + Ok(Self::default()) + } else if let LuaValue::String(s) = value { + let s = s.to_str()?; + /* + If the user only typed the prompt kind slightly wrong, meaning + it has some kind of space in it, a weird character, or an uppercase + character, we should try to be permissive as possible and still work + + Not everyone is using an IDE with proper Luau type definitions + installed, and Luau is still a permissive scripting language + even though it has a strict (but optional) type system + */ + let s = s + .chars() + .filter_map(|c| { + if c.is_ascii_alphabetic() { + Some(c.to_ascii_lowercase()) + } else { + None + } + }) + .collect::(); + // If the prompt kind is still invalid we will + // show the user a descriptive error message + match s.as_ref() { + "text" => Ok(Self::Text), + "confirm" => Ok(Self::Confirm), + "select" => Ok(Self::Select), + "multiselect" => Ok(Self::MultiSelect), + s => Err(LuaError::FromLuaConversionError { + from: "string", + to: "PromptKind", + message: Some(format!( + "Invalid prompt kind '{s}', valid kinds are:\n{}", + PromptKind::get_all() + .iter() + .map(ToString::to_string) + .collect::>() + .join(", ") + )), + }), + } + } else { + Err(LuaError::FromLuaConversionError { + from: "nil", + to: "PromptKind", + message: None, + }) + } + } +} + +pub struct PromptOptions { + pub kind: PromptKind, + pub text: Option, + pub default_string: Option, + pub default_bool: Option, + pub options: Option>, +} + +impl<'lua> FromLuaMulti<'lua> for PromptOptions { + fn from_lua_multi(mut values: LuaMultiValue<'lua>, lua: &'lua Lua) -> LuaResult { + // Argument #1 - prompt kind (optional) + let kind = values + .pop_front() + .map(|value| PromptKind::from_lua(value, lua)) + .transpose()? + .unwrap_or_default(); + // Argument #2 - prompt text (optional) + let text = values + .pop_front() + .map(|text| String::from_lua(text, lua)) + .transpose()?; + // Argument #3 - default value / options, + // this is different per each prompt kind + let (default_bool, default_string, options) = match values.pop_front() { + None => (None, None, None), + Some(options) => match options { + LuaValue::Nil => (None, None, None), + LuaValue::Boolean(b) => (Some(b), None, None), + LuaValue::String(s) => ( + None, + Some(String::from_lua(LuaValue::String(s), lua)?), + None, + ), + LuaValue::Table(t) => ( + None, + None, + Some(Vec::::from_lua(LuaValue::Table(t), lua)?), + ), + value => { + return Err(LuaError::FromLuaConversionError { + from: value.type_name(), + to: "PromptOptions", + message: Some("Argument #3 must be a boolean, table, or nil".to_string()), + }) + } + }, + }; + /* + Make sure we got the required values for the specific prompt kind: + + - "Confirm" requires a message to be present so the user knows what they are confirming + - "Select" and "MultiSelect" both require a table of options to choose from + */ + if matches!(kind, PromptKind::Confirm) && text.is_none() { + return Err(LuaError::FromLuaConversionError { + from: "nil", + to: "PromptOptions", + message: Some("Argument #2 missing or nil".to_string()), + }); + } + if matches!(kind, PromptKind::Select | PromptKind::MultiSelect) && options.is_none() { + return Err(LuaError::FromLuaConversionError { + from: "nil", + to: "PromptOptions", + message: Some("Argument #3 missing or nil".to_string()), + }); + } + // All good, return the prompt options + Ok(Self { + kind, + text, + default_bool, + default_string, + options, + }) + } +} + +#[derive(Debug, Clone)] +pub enum PromptResult { + String(String), + Boolean(bool), + Index(usize), + Indices(Vec), + None, +} + +impl<'lua> IntoLua<'lua> for PromptResult { + fn into_lua(self, lua: &'lua Lua) -> LuaResult> { + Ok(match self { + Self::String(s) => LuaValue::String(lua.create_string(&s)?), + Self::Boolean(b) => LuaValue::Boolean(b), + Self::Index(i) => LuaValue::Number(i as f64), + Self::Indices(v) => v.into_lua(lua)?, + Self::None => LuaValue::Nil, + }) + } +} diff --git a/src/lune/builtins/task/mod.rs b/src/lune/builtins/task/mod.rs index 1c2ff96..2bba0f8 100644 --- a/src/lune/builtins/task/mod.rs +++ b/src/lune/builtins/task/mod.rs @@ -9,7 +9,7 @@ use crate::lune::{scheduler::Scheduler, util::TableBuilder}; mod tof; use tof::LuaThreadOrFunction; -pub fn create(lua: &'static Lua) -> LuaResult> { +pub fn create(lua: &'static Lua) -> LuaResult> { TableBuilder::new(lua)? .with_function("cancel", task_cancel)? .with_function("defer", task_defer)? diff --git a/src/lune/error.rs b/src/lune/error.rs index 1999877..c83fad1 100644 --- a/src/lune/error.rs +++ b/src/lune/error.rs @@ -5,6 +5,8 @@ use std::{ use mlua::prelude::*; +use crate::lune::util::formatting::pretty_format_luau_error; + /** An opaque error type for formatted lua errors. */ @@ -73,7 +75,11 @@ impl From<&LuaError> for LuneError { impl Display for LuneError { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - write!(f, "{}", self.error) // TODO: Pretty formatting + write!( + f, + "{}", + pretty_format_luau_error(&self.error, !self.disable_colors) + ) } } diff --git a/src/lune/scheduler/impl_runner.rs b/src/lune/scheduler/impl_runner.rs index 918cb6d..f013c9a 100644 --- a/src/lune/scheduler/impl_runner.rs +++ b/src/lune/scheduler/impl_runner.rs @@ -54,7 +54,7 @@ where if let Err(err) = &res { self.state.increment_error_count(); // NOTE: LuneError will pretty-format this error - eprint!("{}", LuneError::from(err)); + eprintln!("{}", LuneError::from(err)); } // Send results of resuming this thread to any listeners diff --git a/src/lune/util/formatting.rs b/src/lune/util/formatting.rs new file mode 100644 index 0000000..165ceb4 --- /dev/null +++ b/src/lune/util/formatting.rs @@ -0,0 +1,466 @@ +use std::fmt::Write; + +use console::{colors_enabled, set_colors_enabled, style, Style}; +use mlua::prelude::*; +use once_cell::sync::Lazy; + +const MAX_FORMAT_DEPTH: usize = 4; + +const INDENT: &str = " "; + +pub const STYLE_RESET_STR: &str = "\x1b[0m"; + +// Colors +pub static COLOR_BLACK: Lazy