mirror of
https://github.com/lune-org/lune.git
synced 2025-01-19 09:18:06 +00:00
Remove old lune lib
This commit is contained in:
parent
90fce384d0
commit
ebdc8a6261
53 changed files with 0 additions and 5919 deletions
|
@ -1,123 +0,0 @@
|
||||||
use std::io::ErrorKind as IoErrorKind;
|
|
||||||
use std::path::{PathBuf, MAIN_SEPARATOR};
|
|
||||||
|
|
||||||
use mlua::prelude::*;
|
|
||||||
use tokio::fs;
|
|
||||||
|
|
||||||
use crate::lune_temp::lua::{
|
|
||||||
fs::{copy, FsMetadata, FsWriteOptions},
|
|
||||||
table::TableBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
|
|
||||||
TableBuilder::new(lua)?
|
|
||||||
.with_async_function("readFile", fs_read_file)?
|
|
||||||
.with_async_function("readDir", fs_read_dir)?
|
|
||||||
.with_async_function("writeFile", fs_write_file)?
|
|
||||||
.with_async_function("writeDir", fs_write_dir)?
|
|
||||||
.with_async_function("removeFile", fs_remove_file)?
|
|
||||||
.with_async_function("removeDir", fs_remove_dir)?
|
|
||||||
.with_async_function("metadata", fs_metadata)?
|
|
||||||
.with_async_function("isFile", fs_is_file)?
|
|
||||||
.with_async_function("isDir", fs_is_dir)?
|
|
||||||
.with_async_function("move", fs_move)?
|
|
||||||
.with_async_function("copy", fs_copy)?
|
|
||||||
.build_readonly()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fs_read_file(lua: &Lua, path: String) -> LuaResult<LuaString> {
|
|
||||||
let bytes = fs::read(&path).await.into_lua_err()?;
|
|
||||||
lua.create_string(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fs_read_dir(_: &Lua, path: String) -> LuaResult<Vec<String>> {
|
|
||||||
let mut dir_strings = Vec::new();
|
|
||||||
let mut dir = fs::read_dir(&path).await.into_lua_err()?;
|
|
||||||
while let Some(dir_entry) = dir.next_entry().await.into_lua_err()? {
|
|
||||||
if let Some(dir_path_str) = dir_entry.path().to_str() {
|
|
||||||
dir_strings.push(dir_path_str.to_owned());
|
|
||||||
} else {
|
|
||||||
return Err(LuaError::RuntimeError(format!(
|
|
||||||
"File path could not be converted into a string: '{}'",
|
|
||||||
dir_entry.path().display()
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut dir_string_prefix = path;
|
|
||||||
if !dir_string_prefix.ends_with(MAIN_SEPARATOR) {
|
|
||||||
dir_string_prefix.push(MAIN_SEPARATOR);
|
|
||||||
}
|
|
||||||
let dir_strings_no_prefix = dir_strings
|
|
||||||
.iter()
|
|
||||||
.map(|inner_path| {
|
|
||||||
inner_path
|
|
||||||
.trim()
|
|
||||||
.trim_start_matches(&dir_string_prefix)
|
|
||||||
.to_owned()
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
Ok(dir_strings_no_prefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fs_write_file(_: &Lua, (path, contents): (String, LuaString<'_>)) -> LuaResult<()> {
|
|
||||||
fs::write(&path, &contents.as_bytes()).await.into_lua_err()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fs_write_dir(_: &Lua, path: String) -> LuaResult<()> {
|
|
||||||
fs::create_dir_all(&path).await.into_lua_err()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fs_remove_file(_: &Lua, path: String) -> LuaResult<()> {
|
|
||||||
fs::remove_file(&path).await.into_lua_err()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fs_remove_dir(_: &Lua, path: String) -> LuaResult<()> {
|
|
||||||
fs::remove_dir_all(&path).await.into_lua_err()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fs_metadata(_: &Lua, path: String) -> LuaResult<FsMetadata> {
|
|
||||||
match fs::metadata(path).await {
|
|
||||||
Err(e) if e.kind() == IoErrorKind::NotFound => Ok(FsMetadata::not_found()),
|
|
||||||
Ok(meta) => Ok(FsMetadata::from(meta)),
|
|
||||||
Err(e) => Err(e.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fs_is_file(_: &Lua, path: String) -> LuaResult<bool> {
|
|
||||||
match fs::metadata(path).await {
|
|
||||||
Err(e) if e.kind() == IoErrorKind::NotFound => Ok(false),
|
|
||||||
Ok(meta) => Ok(meta.is_file()),
|
|
||||||
Err(e) => Err(e.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fs_is_dir(_: &Lua, path: String) -> LuaResult<bool> {
|
|
||||||
match fs::metadata(path).await {
|
|
||||||
Err(e) if e.kind() == IoErrorKind::NotFound => Ok(false),
|
|
||||||
Ok(meta) => Ok(meta.is_dir()),
|
|
||||||
Err(e) => Err(e.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fs_move(_: &Lua, (from, to, options): (String, String, FsWriteOptions)) -> LuaResult<()> {
|
|
||||||
let path_from = PathBuf::from(from);
|
|
||||||
if !path_from.exists() {
|
|
||||||
return Err(LuaError::RuntimeError(format!(
|
|
||||||
"No file or directory exists at the path '{}'",
|
|
||||||
path_from.display()
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
let path_to = PathBuf::from(to);
|
|
||||||
if !options.overwrite && path_to.exists() {
|
|
||||||
return Err(LuaError::RuntimeError(format!(
|
|
||||||
"A file or directory already exists at the path '{}'",
|
|
||||||
path_to.display()
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
fs::rename(path_from, path_to).await.into_lua_err()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fs_copy(_: &Lua, (from, to, options): (String, String, FsWriteOptions)) -> LuaResult<()> {
|
|
||||||
copy(from, to, options).await
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
use crate::lune_temp::lua::{
|
|
||||||
luau::{LuauCompileOptions, LuauLoadOptions},
|
|
||||||
table::TableBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
const BYTECODE_ERROR_BYTE: u8 = 0;
|
|
||||||
|
|
||||||
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
|
|
||||||
TableBuilder::new(lua)?
|
|
||||||
.with_function("compile", compile_source)?
|
|
||||||
.with_function("load", load_source)?
|
|
||||||
.build_readonly()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compile_source<'lua>(
|
|
||||||
lua: &'lua Lua,
|
|
||||||
(source, options): (LuaString<'lua>, LuauCompileOptions),
|
|
||||||
) -> LuaResult<LuaString<'lua>> {
|
|
||||||
let bytecode = options.into_compiler().compile(source);
|
|
||||||
|
|
||||||
match bytecode.first() {
|
|
||||||
Some(&BYTECODE_ERROR_BYTE) => Err(LuaError::RuntimeError(
|
|
||||||
String::from_utf8_lossy(&bytecode).into_owned(),
|
|
||||||
)),
|
|
||||||
Some(_) => lua.create_string(bytecode),
|
|
||||||
None => panic!("Compiling resulted in empty bytecode"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_source<'lua>(
|
|
||||||
lua: &'lua Lua,
|
|
||||||
(source, options): (LuaString<'lua>, LuauLoadOptions),
|
|
||||||
) -> LuaResult<LuaFunction<'lua>> {
|
|
||||||
lua.load(source.as_bytes())
|
|
||||||
.set_name(options.debug_name)
|
|
||||||
.into_function()
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
pub mod fs;
|
|
||||||
pub mod luau;
|
|
||||||
pub mod net;
|
|
||||||
pub mod process;
|
|
||||||
pub mod serde;
|
|
||||||
pub mod stdio;
|
|
||||||
pub mod task;
|
|
||||||
pub mod top_level;
|
|
||||||
|
|
||||||
#[cfg(feature = "roblox")]
|
|
||||||
pub mod roblox;
|
|
|
@ -1,216 +0,0 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
use console::style;
|
|
||||||
use hyper::{
|
|
||||||
header::{CONTENT_ENCODING, CONTENT_LENGTH},
|
|
||||||
Server,
|
|
||||||
};
|
|
||||||
use tokio::{sync::mpsc, task};
|
|
||||||
|
|
||||||
use crate::lune_temp::lua::{
|
|
||||||
net::{
|
|
||||||
NetClient, NetClientBuilder, NetLocalExec, NetService, NetWebSocket, RequestConfig,
|
|
||||||
ServeConfig,
|
|
||||||
},
|
|
||||||
serde::{decompress, CompressDecompressFormat, EncodeDecodeConfig, EncodeDecodeFormat},
|
|
||||||
table::TableBuilder,
|
|
||||||
task::{TaskScheduler, TaskSchedulerAsyncExt},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
|
|
||||||
// Create a reusable client for performing our
|
|
||||||
// web requests and store it in the lua registry,
|
|
||||||
// allowing us to reuse headers and internal structs
|
|
||||||
let client = NetClientBuilder::new()
|
|
||||||
.headers(&[("User-Agent", create_user_agent_header())])?
|
|
||||||
.build()?;
|
|
||||||
lua.set_named_registry_value("net.client", client)?;
|
|
||||||
// Create the global table for net
|
|
||||||
TableBuilder::new(lua)?
|
|
||||||
.with_function("jsonEncode", net_json_encode)?
|
|
||||||
.with_function("jsonDecode", net_json_decode)?
|
|
||||||
.with_async_function("request", net_request)?
|
|
||||||
.with_async_function("socket", net_socket)?
|
|
||||||
.with_async_function("serve", net_serve)?
|
|
||||||
.with_function("urlEncode", net_url_encode)?
|
|
||||||
.with_function("urlDecode", net_url_decode)?
|
|
||||||
.build_readonly()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_user_agent_header() -> String {
|
|
||||||
let (github_owner, github_repo) = env!("CARGO_PKG_REPOSITORY")
|
|
||||||
.trim_start_matches("https://github.com/")
|
|
||||||
.split_once('/')
|
|
||||||
.unwrap();
|
|
||||||
format!("{github_owner}-{github_repo}-cli")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn net_json_encode<'lua>(
|
|
||||||
lua: &'lua Lua,
|
|
||||||
(val, pretty): (LuaValue<'lua>, Option<bool>),
|
|
||||||
) -> LuaResult<LuaString<'lua>> {
|
|
||||||
EncodeDecodeConfig::from((EncodeDecodeFormat::Json, pretty.unwrap_or_default()))
|
|
||||||
.serialize_to_string(lua, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn net_json_decode<'lua>(lua: &'lua Lua, json: LuaString<'lua>) -> LuaResult<LuaValue<'lua>> {
|
|
||||||
EncodeDecodeConfig::from(EncodeDecodeFormat::Json).deserialize_from_string(lua, json)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn net_request<'lua>(
|
|
||||||
lua: &'static Lua,
|
|
||||||
config: RequestConfig<'lua>,
|
|
||||||
) -> LuaResult<LuaTable<'lua>> {
|
|
||||||
// Create and send the request
|
|
||||||
let client: LuaUserDataRef<NetClient> = lua.named_registry_value("net.client")?;
|
|
||||||
let mut request = client.request(config.method, &config.url);
|
|
||||||
for (query, value) in config.query {
|
|
||||||
request = request.query(&[(query.to_str()?, value.to_str()?)]);
|
|
||||||
}
|
|
||||||
for (header, value) in config.headers {
|
|
||||||
request = request.header(header.to_str()?, value.to_str()?);
|
|
||||||
}
|
|
||||||
let res = request
|
|
||||||
.body(config.body.unwrap_or_default())
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.into_lua_err()?;
|
|
||||||
// Extract status, headers
|
|
||||||
let res_status = res.status().as_u16();
|
|
||||||
let res_status_text = res.status().canonical_reason();
|
|
||||||
let mut res_headers = res
|
|
||||||
.headers()
|
|
||||||
.iter()
|
|
||||||
.map(|(name, value)| {
|
|
||||||
(
|
|
||||||
name.as_str().to_string(),
|
|
||||||
value.to_str().unwrap().to_owned(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<HashMap<String, String>>();
|
|
||||||
// Read response bytes
|
|
||||||
let mut res_bytes = res.bytes().await.into_lua_err()?.to_vec();
|
|
||||||
// Check for extra options, decompression
|
|
||||||
if config.options.decompress {
|
|
||||||
// NOTE: Header names are guaranteed to be lowercase because of the above
|
|
||||||
// transformations of them into the hashmap, so we can compare directly
|
|
||||||
let format = res_headers.iter().find_map(|(name, val)| {
|
|
||||||
if name == CONTENT_ENCODING.as_str() {
|
|
||||||
CompressDecompressFormat::detect_from_header_str(val)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if let Some(format) = format {
|
|
||||||
res_bytes = decompress(format, res_bytes).await?;
|
|
||||||
let content_encoding_header_str = CONTENT_ENCODING.as_str();
|
|
||||||
let content_length_header_str = CONTENT_LENGTH.as_str();
|
|
||||||
res_headers.retain(|name, _| {
|
|
||||||
name != content_encoding_header_str && name != content_length_header_str
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Construct and return a readonly lua table with results
|
|
||||||
TableBuilder::new(lua)?
|
|
||||||
.with_value("ok", (200..300).contains(&res_status))?
|
|
||||||
.with_value("statusCode", res_status)?
|
|
||||||
.with_value("statusMessage", res_status_text)?
|
|
||||||
.with_value("headers", res_headers)?
|
|
||||||
.with_value("body", lua.create_string(&res_bytes)?)?
|
|
||||||
.build_readonly()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn net_socket<'lua>(lua: &'static Lua, url: String) -> LuaResult<LuaTable> {
|
|
||||||
let (ws, _) = tokio_tungstenite::connect_async(url).await.into_lua_err()?;
|
|
||||||
NetWebSocket::new(ws).into_lua_table(lua)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn net_serve<'lua>(
|
|
||||||
lua: &'static Lua,
|
|
||||||
(port, config): (u16, ServeConfig<'lua>),
|
|
||||||
) -> LuaResult<LuaTable<'lua>> {
|
|
||||||
// Note that we need to use a mpsc here and not
|
|
||||||
// a oneshot channel since we move the sender
|
|
||||||
// into our table with the stop function
|
|
||||||
let (shutdown_tx, mut shutdown_rx) = mpsc::channel::<()>(1);
|
|
||||||
let server_request_callback = lua.create_registry_value(config.handle_request)?;
|
|
||||||
let server_websocket_callback = config.handle_web_socket.map(|handler| {
|
|
||||||
lua.create_registry_value(handler)
|
|
||||||
.expect("Failed to store websocket handler")
|
|
||||||
});
|
|
||||||
let sched = lua
|
|
||||||
.app_data_ref::<&TaskScheduler>()
|
|
||||||
.expect("Missing task scheduler - make sure it is added as a lua app data before the first scheduler resumption");
|
|
||||||
// Bind first to make sure that we can bind to this address
|
|
||||||
let bound = match Server::try_bind(&([127, 0, 0, 1], port).into()) {
|
|
||||||
Err(e) => {
|
|
||||||
return Err(LuaError::external(format!(
|
|
||||||
"Failed to bind to localhost on port {port}\n{}",
|
|
||||||
format!("{e}").replace(
|
|
||||||
"error creating server listener: ",
|
|
||||||
&format!("{}", style("> ").dim())
|
|
||||||
)
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
Ok(bound) => bound,
|
|
||||||
};
|
|
||||||
// Register a background task to prevent the task scheduler from
|
|
||||||
// exiting early and start up our web server on the bound address
|
|
||||||
let task = sched.register_background_task();
|
|
||||||
let server = bound
|
|
||||||
.http1_only(true) // Web sockets can only use http1
|
|
||||||
.http1_keepalive(true) // Web sockets must be kept alive
|
|
||||||
.executor(NetLocalExec)
|
|
||||||
.serve(NetService::new(
|
|
||||||
lua,
|
|
||||||
server_request_callback,
|
|
||||||
server_websocket_callback,
|
|
||||||
))
|
|
||||||
.with_graceful_shutdown(async move {
|
|
||||||
task.unregister(Ok(()));
|
|
||||||
shutdown_rx
|
|
||||||
.recv()
|
|
||||||
.await
|
|
||||||
.expect("Server was stopped instantly");
|
|
||||||
shutdown_rx.close();
|
|
||||||
});
|
|
||||||
// Spawn a new tokio task so we don't block
|
|
||||||
task::spawn_local(server);
|
|
||||||
// Create a new read-only table that contains methods
|
|
||||||
// for manipulating server behavior and shutting it down
|
|
||||||
let handle_stop = move |_, _: ()| match shutdown_tx.try_send(()) {
|
|
||||||
Ok(_) => Ok(()),
|
|
||||||
Err(_) => Err(LuaError::RuntimeError(
|
|
||||||
"Server has already been stopped".to_string(),
|
|
||||||
)),
|
|
||||||
};
|
|
||||||
TableBuilder::new(lua)?
|
|
||||||
.with_function("stop", handle_stop)?
|
|
||||||
.build_readonly()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn net_url_encode<'lua>(
|
|
||||||
lua: &'lua Lua,
|
|
||||||
(lua_string, as_binary): (LuaString<'lua>, Option<bool>),
|
|
||||||
) -> LuaResult<LuaValue<'lua>> {
|
|
||||||
if matches!(as_binary, Some(true)) {
|
|
||||||
urlencoding::encode_binary(lua_string.as_bytes()).into_lua(lua)
|
|
||||||
} else {
|
|
||||||
urlencoding::encode(lua_string.to_str()?).into_lua(lua)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn net_url_decode<'lua>(
|
|
||||||
lua: &'lua Lua,
|
|
||||||
(lua_string, as_binary): (LuaString<'lua>, Option<bool>),
|
|
||||||
) -> LuaResult<LuaValue<'lua>> {
|
|
||||||
if matches!(as_binary, Some(true)) {
|
|
||||||
urlencoding::decode_binary(lua_string.as_bytes()).into_lua(lua)
|
|
||||||
} else {
|
|
||||||
urlencoding::decode(lua_string.to_str()?)
|
|
||||||
.map_err(|e| LuaError::RuntimeError(format!("Encountered invalid encoding - {e}")))?
|
|
||||||
.into_lua(lua)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,296 +0,0 @@
|
||||||
use std::{
|
|
||||||
collections::HashMap,
|
|
||||||
env::{self, consts},
|
|
||||||
path::{self, PathBuf},
|
|
||||||
process::{ExitCode, Stdio},
|
|
||||||
};
|
|
||||||
|
|
||||||
use directories::UserDirs;
|
|
||||||
use dunce::canonicalize;
|
|
||||||
use mlua::prelude::*;
|
|
||||||
use os_str_bytes::RawOsString;
|
|
||||||
use tokio::process::Command;
|
|
||||||
|
|
||||||
use crate::lune_temp::lua::{
|
|
||||||
process::pipe_and_inherit_child_process_stdio, table::TableBuilder, task::TaskScheduler,
|
|
||||||
};
|
|
||||||
|
|
||||||
const PROCESS_EXIT_IMPL_LUA: &str = r#"
|
|
||||||
exit(...)
|
|
||||||
yield()
|
|
||||||
"#;
|
|
||||||
|
|
||||||
pub fn create(lua: &'static Lua, args_vec: Vec<String>) -> LuaResult<LuaTable> {
|
|
||||||
let cwd_str = {
|
|
||||||
let cwd = canonicalize(env::current_dir()?)?;
|
|
||||||
let cwd_str = cwd.to_string_lossy().to_string();
|
|
||||||
if !cwd_str.ends_with(path::MAIN_SEPARATOR) {
|
|
||||||
format!("{cwd_str}{}", path::MAIN_SEPARATOR)
|
|
||||||
} else {
|
|
||||||
cwd_str
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Create constants for OS & processor architecture
|
|
||||||
let os = lua.create_string(&consts::OS.to_lowercase())?;
|
|
||||||
let arch = lua.create_string(&consts::ARCH.to_lowercase())?;
|
|
||||||
// Create readonly args array
|
|
||||||
let args_tab = TableBuilder::new(lua)?
|
|
||||||
.with_sequential_values(args_vec)?
|
|
||||||
.build_readonly()?;
|
|
||||||
// Create proxied table for env that gets & sets real env vars
|
|
||||||
let env_tab = TableBuilder::new(lua)?
|
|
||||||
.with_metatable(
|
|
||||||
TableBuilder::new(lua)?
|
|
||||||
.with_function(LuaMetaMethod::Index.name(), process_env_get)?
|
|
||||||
.with_function(LuaMetaMethod::NewIndex.name(), process_env_set)?
|
|
||||||
.with_function(LuaMetaMethod::Iter.name(), process_env_iter)?
|
|
||||||
.build_readonly()?,
|
|
||||||
)?
|
|
||||||
.build_readonly()?;
|
|
||||||
// Create our process exit function, this is a bit involved since
|
|
||||||
// we have no way to yield from c / rust, we need to load a lua
|
|
||||||
// chunk that will set the exit code and yield for us instead
|
|
||||||
let process_exit_env_yield: LuaFunction = lua.named_registry_value("co.yield")?;
|
|
||||||
let process_exit_env_exit: LuaFunction = lua.create_function(|lua, code: Option<u8>| {
|
|
||||||
let exit_code = code.map_or(ExitCode::SUCCESS, ExitCode::from);
|
|
||||||
let sched = lua
|
|
||||||
.app_data_ref::<&TaskScheduler>()
|
|
||||||
.expect("Missing task scheduler - make sure it is added as a lua app data before the first scheduler resumption");
|
|
||||||
sched.set_exit_code(exit_code);
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
let process_exit = lua
|
|
||||||
.load(PROCESS_EXIT_IMPL_LUA)
|
|
||||||
.set_name("=process.exit")
|
|
||||||
.set_environment(
|
|
||||||
TableBuilder::new(lua)?
|
|
||||||
.with_value("yield", process_exit_env_yield)?
|
|
||||||
.with_value("exit", process_exit_env_exit)?
|
|
||||||
.build_readonly()?,
|
|
||||||
)
|
|
||||||
.into_function()?;
|
|
||||||
// Create the full process table
|
|
||||||
TableBuilder::new(lua)?
|
|
||||||
.with_value("os", os)?
|
|
||||||
.with_value("arch", arch)?
|
|
||||||
.with_value("args", args_tab)?
|
|
||||||
.with_value("cwd", cwd_str)?
|
|
||||||
.with_value("env", env_tab)?
|
|
||||||
.with_value("exit", process_exit)?
|
|
||||||
.with_async_function("spawn", process_spawn)?
|
|
||||||
.build_readonly()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_env_get<'lua>(
|
|
||||||
lua: &'lua Lua,
|
|
||||||
(_, key): (LuaValue<'lua>, String),
|
|
||||||
) -> LuaResult<LuaValue<'lua>> {
|
|
||||||
match env::var_os(key) {
|
|
||||||
Some(value) => {
|
|
||||||
let raw_value = RawOsString::new(value);
|
|
||||||
Ok(LuaValue::String(
|
|
||||||
lua.create_string(raw_value.as_raw_bytes())?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
None => Ok(LuaValue::Nil),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_env_set<'lua>(
|
|
||||||
_: &'lua Lua,
|
|
||||||
(_, key, value): (LuaValue<'lua>, String, Option<String>),
|
|
||||||
) -> LuaResult<()> {
|
|
||||||
// Make sure key is valid, otherwise set_var will panic
|
|
||||||
if key.is_empty() {
|
|
||||||
Err(LuaError::RuntimeError("Key must not be empty".to_string()))
|
|
||||||
} else if key.contains('=') {
|
|
||||||
Err(LuaError::RuntimeError(
|
|
||||||
"Key must not contain the equals character '='".to_string(),
|
|
||||||
))
|
|
||||||
} else if key.contains('\0') {
|
|
||||||
Err(LuaError::RuntimeError(
|
|
||||||
"Key must not contain the NUL character".to_string(),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
match value {
|
|
||||||
Some(value) => {
|
|
||||||
// Make sure value is valid, otherwise set_var will panic
|
|
||||||
if value.contains('\0') {
|
|
||||||
Err(LuaError::RuntimeError(
|
|
||||||
"Value must not contain the NUL character".to_string(),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
env::set_var(&key, &value);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
env::remove_var(&key);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_env_iter<'lua>(
|
|
||||||
lua: &'lua Lua,
|
|
||||||
(_, _): (LuaValue<'lua>, ()),
|
|
||||||
) -> LuaResult<LuaFunction<'lua>> {
|
|
||||||
let mut vars = env::vars_os().collect::<Vec<_>>().into_iter();
|
|
||||||
lua.create_function_mut(move |lua, _: ()| match vars.next() {
|
|
||||||
Some((key, value)) => {
|
|
||||||
let raw_key = RawOsString::new(key);
|
|
||||||
let raw_value = RawOsString::new(value);
|
|
||||||
Ok((
|
|
||||||
LuaValue::String(lua.create_string(raw_key.as_raw_bytes())?),
|
|
||||||
LuaValue::String(lua.create_string(raw_value.as_raw_bytes())?),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
None => Ok((LuaValue::Nil, LuaValue::Nil)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn process_spawn<'lua>(
|
|
||||||
lua: &'static Lua,
|
|
||||||
(mut program, args, options): (String, Option<Vec<String>>, Option<LuaTable<'lua>>),
|
|
||||||
) -> LuaResult<LuaTable<'lua>> {
|
|
||||||
// Parse any given options or create defaults
|
|
||||||
let (child_cwd, child_envs, child_shell, child_stdio_inherit) = match options {
|
|
||||||
Some(options) => {
|
|
||||||
let mut cwd = env::current_dir()?;
|
|
||||||
let mut envs = HashMap::new();
|
|
||||||
let mut shell = None;
|
|
||||||
let mut inherit = false;
|
|
||||||
match options.raw_get("cwd")? {
|
|
||||||
LuaValue::Nil => {}
|
|
||||||
LuaValue::String(s) => {
|
|
||||||
cwd = PathBuf::from(s.to_string_lossy().to_string());
|
|
||||||
// Substitute leading tilde (~) for the actual home dir
|
|
||||||
if cwd.starts_with("~") {
|
|
||||||
if let Some(user_dirs) = UserDirs::new() {
|
|
||||||
cwd = user_dirs.home_dir().join(cwd.strip_prefix("~").unwrap())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if !cwd.exists() {
|
|
||||||
return Err(LuaError::RuntimeError(
|
|
||||||
"Invalid value for option 'cwd' - path does not exist".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
value => {
|
|
||||||
return Err(LuaError::RuntimeError(format!(
|
|
||||||
"Invalid type for option 'cwd' - expected 'string', got '{}'",
|
|
||||||
value.type_name()
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match options.raw_get("env")? {
|
|
||||||
LuaValue::Nil => {}
|
|
||||||
LuaValue::Table(t) => {
|
|
||||||
for pair in t.pairs::<String, String>() {
|
|
||||||
let (k, v) = pair?;
|
|
||||||
envs.insert(k, v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
value => {
|
|
||||||
return Err(LuaError::RuntimeError(format!(
|
|
||||||
"Invalid type for option 'env' - expected 'table', got '{}'",
|
|
||||||
value.type_name()
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match options.raw_get("shell")? {
|
|
||||||
LuaValue::Nil => {}
|
|
||||||
LuaValue::String(s) => shell = Some(s.to_string_lossy().to_string()),
|
|
||||||
LuaValue::Boolean(true) => {
|
|
||||||
shell = match env::consts::FAMILY {
|
|
||||||
"unix" => Some("/bin/sh".to_string()),
|
|
||||||
"windows" => Some("/bin/sh".to_string()),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
value => {
|
|
||||||
return Err(LuaError::RuntimeError(format!(
|
|
||||||
"Invalid type for option 'shell' - expected 'true' or 'string', got '{}'",
|
|
||||||
value.type_name()
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match options.raw_get("stdio")? {
|
|
||||||
LuaValue::Nil => {}
|
|
||||||
LuaValue::String(s) => {
|
|
||||||
match s.to_str()? {
|
|
||||||
"inherit" => {
|
|
||||||
inherit = true;
|
|
||||||
},
|
|
||||||
"default" => {
|
|
||||||
inherit = false;
|
|
||||||
}
|
|
||||||
_ => return Err(LuaError::RuntimeError(
|
|
||||||
format!("Invalid value for option 'stdio' - expected 'inherit' or 'default', got '{}'", s.to_string_lossy()),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
value => {
|
|
||||||
return Err(LuaError::RuntimeError(format!(
|
|
||||||
"Invalid type for option 'stdio' - expected 'string', got '{}'",
|
|
||||||
value.type_name()
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok::<_, LuaError>((cwd, envs, shell, inherit))
|
|
||||||
}
|
|
||||||
None => Ok((env::current_dir()?, HashMap::new(), None, false)),
|
|
||||||
}?;
|
|
||||||
// Run a shell using the command param if wanted
|
|
||||||
let child_args = if let Some(shell) = child_shell {
|
|
||||||
let shell_args = match args {
|
|
||||||
Some(args) => vec!["-c".to_string(), format!("{} {}", program, args.join(" "))],
|
|
||||||
None => vec!["-c".to_string(), program],
|
|
||||||
};
|
|
||||||
program = shell;
|
|
||||||
Some(shell_args)
|
|
||||||
} else {
|
|
||||||
args
|
|
||||||
};
|
|
||||||
// Create command with the wanted options
|
|
||||||
let mut cmd = match child_args {
|
|
||||||
None => Command::new(program),
|
|
||||||
Some(args) => {
|
|
||||||
let mut cmd = Command::new(program);
|
|
||||||
cmd.args(args);
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Set dir to run in and env variables
|
|
||||||
cmd.current_dir(child_cwd);
|
|
||||||
cmd.envs(child_envs);
|
|
||||||
// Spawn the child process
|
|
||||||
let child = cmd
|
|
||||||
.stdin(Stdio::null())
|
|
||||||
.stdout(Stdio::piped())
|
|
||||||
.stderr(Stdio::piped())
|
|
||||||
.spawn()?;
|
|
||||||
// Inherit the output and stderr if wanted
|
|
||||||
let result = if child_stdio_inherit {
|
|
||||||
pipe_and_inherit_child_process_stdio(child).await
|
|
||||||
} else {
|
|
||||||
let output = child.wait_with_output().await?;
|
|
||||||
Ok((output.status, output.stdout, output.stderr))
|
|
||||||
};
|
|
||||||
// Extract result
|
|
||||||
let (status, stdout, stderr) = result?;
|
|
||||||
// NOTE: If an exit code was not given by the child process,
|
|
||||||
// we default to 1 if it yielded any error output, otherwise 0
|
|
||||||
let code = status.code().unwrap_or(match stderr.is_empty() {
|
|
||||||
true => 0,
|
|
||||||
false => 1,
|
|
||||||
});
|
|
||||||
// Construct and return a readonly lua table with results
|
|
||||||
TableBuilder::new(lua)?
|
|
||||||
.with_value("ok", code == 0)?
|
|
||||||
.with_value("code", code)?
|
|
||||||
.with_value("stdout", lua.create_string(&stdout)?)?
|
|
||||||
.with_value("stderr", lua.create_string(&stderr)?)?
|
|
||||||
.build_readonly()
|
|
||||||
}
|
|
|
@ -1,104 +0,0 @@
|
||||||
use mlua::prelude::*;
|
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
|
|
||||||
use crate::roblox::{
|
|
||||||
self,
|
|
||||||
document::{Document, DocumentError, DocumentFormat, DocumentKind},
|
|
||||||
instance::Instance,
|
|
||||||
reflection::Database as ReflectionDatabase,
|
|
||||||
};
|
|
||||||
|
|
||||||
use tokio::task;
|
|
||||||
|
|
||||||
use crate::lune_temp::lua::table::TableBuilder;
|
|
||||||
|
|
||||||
static REFLECTION_DATABASE: OnceCell<ReflectionDatabase> = OnceCell::new();
|
|
||||||
|
|
||||||
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
|
|
||||||
let mut roblox_constants = Vec::new();
|
|
||||||
let roblox_module = roblox::module(lua)?;
|
|
||||||
for pair in roblox_module.pairs::<LuaValue, LuaValue>() {
|
|
||||||
roblox_constants.push(pair?);
|
|
||||||
}
|
|
||||||
TableBuilder::new(lua)?
|
|
||||||
.with_values(roblox_constants)?
|
|
||||||
.with_async_function("deserializePlace", deserialize_place)?
|
|
||||||
.with_async_function("deserializeModel", deserialize_model)?
|
|
||||||
.with_async_function("serializePlace", serialize_place)?
|
|
||||||
.with_async_function("serializeModel", serialize_model)?
|
|
||||||
.with_function("getAuthCookie", get_auth_cookie)?
|
|
||||||
.with_function("getReflectionDatabase", get_reflection_database)?
|
|
||||||
.build_readonly()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn deserialize_place<'lua>(
|
|
||||||
lua: &'lua Lua,
|
|
||||||
contents: LuaString<'lua>,
|
|
||||||
) -> LuaResult<LuaValue<'lua>> {
|
|
||||||
let bytes = contents.as_bytes().to_vec();
|
|
||||||
let fut = task::spawn_blocking(move || {
|
|
||||||
let doc = Document::from_bytes(bytes, DocumentKind::Place)?;
|
|
||||||
let data_model = doc.into_data_model_instance()?;
|
|
||||||
Ok::<_, DocumentError>(data_model)
|
|
||||||
});
|
|
||||||
fut.await.into_lua_err()??.into_lua(lua)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn deserialize_model<'lua>(
|
|
||||||
lua: &'lua Lua,
|
|
||||||
contents: LuaString<'lua>,
|
|
||||||
) -> LuaResult<LuaValue<'lua>> {
|
|
||||||
let bytes = contents.as_bytes().to_vec();
|
|
||||||
let fut = task::spawn_blocking(move || {
|
|
||||||
let doc = Document::from_bytes(bytes, DocumentKind::Model)?;
|
|
||||||
let instance_array = doc.into_instance_array()?;
|
|
||||||
Ok::<_, DocumentError>(instance_array)
|
|
||||||
});
|
|
||||||
fut.await.into_lua_err()??.into_lua(lua)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn serialize_place<'lua>(
|
|
||||||
lua: &'lua Lua,
|
|
||||||
(data_model, as_xml): (LuaUserDataRef<'lua, Instance>, Option<bool>),
|
|
||||||
) -> LuaResult<LuaString<'lua>> {
|
|
||||||
let data_model = (*data_model).clone();
|
|
||||||
let fut = task::spawn_blocking(move || {
|
|
||||||
let doc = Document::from_data_model_instance(data_model)?;
|
|
||||||
let bytes = doc.to_bytes_with_format(match as_xml {
|
|
||||||
Some(true) => DocumentFormat::Xml,
|
|
||||||
_ => DocumentFormat::Binary,
|
|
||||||
})?;
|
|
||||||
Ok::<_, DocumentError>(bytes)
|
|
||||||
});
|
|
||||||
let bytes = fut.await.into_lua_err()??;
|
|
||||||
lua.create_string(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn serialize_model<'lua>(
|
|
||||||
lua: &'lua Lua,
|
|
||||||
(instances, as_xml): (Vec<LuaUserDataRef<'lua, Instance>>, Option<bool>),
|
|
||||||
) -> LuaResult<LuaString<'lua>> {
|
|
||||||
let instances = instances.iter().map(|i| (*i).clone()).collect();
|
|
||||||
let fut = task::spawn_blocking(move || {
|
|
||||||
let doc = Document::from_instance_array(instances)?;
|
|
||||||
let bytes = doc.to_bytes_with_format(match as_xml {
|
|
||||||
Some(true) => DocumentFormat::Xml,
|
|
||||||
_ => DocumentFormat::Binary,
|
|
||||||
})?;
|
|
||||||
Ok::<_, DocumentError>(bytes)
|
|
||||||
});
|
|
||||||
let bytes = fut.await.into_lua_err()??;
|
|
||||||
lua.create_string(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_auth_cookie(_: &Lua, raw: Option<bool>) -> LuaResult<Option<String>> {
|
|
||||||
if matches!(raw, Some(true)) {
|
|
||||||
Ok(rbx_cookie::get_value())
|
|
||||||
} else {
|
|
||||||
Ok(rbx_cookie::get())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_reflection_database(_: &Lua, _: ()) -> LuaResult<ReflectionDatabase> {
|
|
||||||
Ok(*REFLECTION_DATABASE.get_or_init(ReflectionDatabase::new))
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
use crate::lune_temp::lua::{
|
|
||||||
serde::{
|
|
||||||
compress, decompress, CompressDecompressFormat, EncodeDecodeConfig, EncodeDecodeFormat,
|
|
||||||
},
|
|
||||||
table::TableBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
|
|
||||||
TableBuilder::new(lua)?
|
|
||||||
.with_function("encode", serde_encode)?
|
|
||||||
.with_function("decode", serde_decode)?
|
|
||||||
.with_async_function("compress", serde_compress)?
|
|
||||||
.with_async_function("decompress", serde_decompress)?
|
|
||||||
.build_readonly()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn serde_encode<'lua>(
|
|
||||||
lua: &'lua Lua,
|
|
||||||
(format, val, pretty): (EncodeDecodeFormat, LuaValue<'lua>, Option<bool>),
|
|
||||||
) -> LuaResult<LuaString<'lua>> {
|
|
||||||
let config = EncodeDecodeConfig::from((format, pretty.unwrap_or_default()));
|
|
||||||
config.serialize_to_string(lua, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn serde_decode<'lua>(
|
|
||||||
lua: &'lua Lua,
|
|
||||||
(format, str): (EncodeDecodeFormat, LuaString<'lua>),
|
|
||||||
) -> LuaResult<LuaValue<'lua>> {
|
|
||||||
let config = EncodeDecodeConfig::from(format);
|
|
||||||
config.deserialize_from_string(lua, str)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn serde_compress<'lua>(
|
|
||||||
lua: &'lua Lua,
|
|
||||||
(format, str): (CompressDecompressFormat, LuaString<'lua>),
|
|
||||||
) -> LuaResult<LuaString<'lua>> {
|
|
||||||
let bytes = compress(format, str).await?;
|
|
||||||
lua.create_string(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn serde_decompress<'lua>(
|
|
||||||
lua: &'lua Lua,
|
|
||||||
(format, str): (CompressDecompressFormat, LuaString<'lua>),
|
|
||||||
) -> LuaResult<LuaString<'lua>> {
|
|
||||||
let bytes = decompress(format, str).await?;
|
|
||||||
lua.create_string(bytes)
|
|
||||||
}
|
|
|
@ -1,99 +0,0 @@
|
||||||
use dialoguer::{theme::ColorfulTheme, Confirm, Input, MultiSelect, Select};
|
|
||||||
use mlua::prelude::*;
|
|
||||||
use tokio::{
|
|
||||||
io::{self, AsyncWriteExt},
|
|
||||||
task,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::lune_temp::lua::{
|
|
||||||
stdio::{
|
|
||||||
formatting::{
|
|
||||||
format_style, pretty_format_multi_value, style_from_color_str, style_from_style_str,
|
|
||||||
},
|
|
||||||
prompt::{PromptKind, PromptOptions, PromptResult},
|
|
||||||
},
|
|
||||||
table::TableBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
|
|
||||||
TableBuilder::new(lua)?
|
|
||||||
.with_function("color", |_, color: String| {
|
|
||||||
let ansi_string = format_style(style_from_color_str(&color)?);
|
|
||||||
Ok(ansi_string)
|
|
||||||
})?
|
|
||||||
.with_function("style", |_, style: String| {
|
|
||||||
let ansi_string = format_style(style_from_style_str(&style)?);
|
|
||||||
Ok(ansi_string)
|
|
||||||
})?
|
|
||||||
.with_function("format", |_, args: LuaMultiValue| {
|
|
||||||
pretty_format_multi_value(&args)
|
|
||||||
})?
|
|
||||||
.with_async_function("write", |_, s: LuaString| async move {
|
|
||||||
let mut stdout = io::stdout();
|
|
||||||
stdout.write_all(s.as_bytes()).await?;
|
|
||||||
stdout.flush().await?;
|
|
||||||
Ok(())
|
|
||||||
})?
|
|
||||||
.with_async_function("ewrite", |_, s: LuaString| async move {
|
|
||||||
let mut stderr = io::stderr();
|
|
||||||
stderr.write_all(s.as_bytes()).await?;
|
|
||||||
stderr.flush().await?;
|
|
||||||
Ok(())
|
|
||||||
})?
|
|
||||||
.with_async_function("prompt", |_, options: PromptOptions| async move {
|
|
||||||
task::spawn_blocking(move || prompt(options))
|
|
||||||
.await
|
|
||||||
.into_lua_err()?
|
|
||||||
})?
|
|
||||||
.build_readonly()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prompt_theme() -> ColorfulTheme {
|
|
||||||
ColorfulTheme::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prompt(options: PromptOptions) -> LuaResult<PromptResult> {
|
|
||||||
let theme = prompt_theme();
|
|
||||||
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(&prompt_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(&prompt_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())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,168 +0,0 @@
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
use crate::lune_temp::lua::{
|
|
||||||
async_ext::LuaAsyncExt,
|
|
||||||
table::TableBuilder,
|
|
||||||
task::{
|
|
||||||
LuaThreadOrFunction, LuaThreadOrTaskReference, TaskKind, TaskReference, TaskScheduler,
|
|
||||||
TaskSchedulerScheduleExt,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const SPAWN_IMPL_LUA: &str = r#"
|
|
||||||
scheduleNext(thread())
|
|
||||||
local task = scheduleNext(...)
|
|
||||||
yield()
|
|
||||||
return task
|
|
||||||
"#;
|
|
||||||
|
|
||||||
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable<'static>> {
|
|
||||||
lua.app_data_ref::<&TaskScheduler>()
|
|
||||||
.expect("Missing task scheduler in app data");
|
|
||||||
/*
|
|
||||||
1. Schedule the current thread at the front
|
|
||||||
2. Schedule the wanted task arg at the front,
|
|
||||||
the previous schedule now comes right after
|
|
||||||
3. Give control over to the scheduler, which will
|
|
||||||
resume the above tasks in order when its ready
|
|
||||||
|
|
||||||
The spawn function needs special treatment,
|
|
||||||
we need to yield right away to allow the
|
|
||||||
spawned task to run until first yield
|
|
||||||
*/
|
|
||||||
let task_spawn_env_yield: LuaFunction = lua.named_registry_value("co.yield")?;
|
|
||||||
let task_spawn = lua
|
|
||||||
.load(SPAWN_IMPL_LUA)
|
|
||||||
.set_name("task.spawn")
|
|
||||||
.set_environment(
|
|
||||||
TableBuilder::new(lua)?
|
|
||||||
.with_function("thread", |lua, _: ()| Ok(lua.current_thread()))?
|
|
||||||
.with_value("yield", task_spawn_env_yield)?
|
|
||||||
.with_function(
|
|
||||||
"scheduleNext",
|
|
||||||
|lua, (tof, args): (LuaThreadOrFunction, LuaMultiValue)| {
|
|
||||||
let sched = lua.app_data_ref::<&TaskScheduler>().unwrap();
|
|
||||||
sched.schedule_blocking(tof.into_thread(lua)?, args)
|
|
||||||
},
|
|
||||||
)?
|
|
||||||
.build_readonly()?,
|
|
||||||
)
|
|
||||||
.into_function()?;
|
|
||||||
// Functions in the built-in coroutine library also need to be
|
|
||||||
// replaced, these are a bit different than the ones above because
|
|
||||||
// calling resume or the function that wrap returns must return
|
|
||||||
// whatever lua value(s) that the thread or task yielded back
|
|
||||||
let globals = lua.globals();
|
|
||||||
let coroutine = globals.get::<_, LuaTable>("coroutine")?;
|
|
||||||
coroutine.set("status", lua.create_function(coroutine_status)?)?;
|
|
||||||
coroutine.set("resume", lua.create_function(coroutine_resume)?)?;
|
|
||||||
coroutine.set("wrap", lua.create_function(coroutine_wrap)?)?;
|
|
||||||
// All good, return the task scheduler lib
|
|
||||||
TableBuilder::new(lua)?
|
|
||||||
.with_value("wait", lua.create_waiter_function()?)?
|
|
||||||
.with_value("spawn", task_spawn)?
|
|
||||||
.with_function("cancel", task_cancel)?
|
|
||||||
.with_function("defer", task_defer)?
|
|
||||||
.with_function("delay", task_delay)?
|
|
||||||
.build_readonly()
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Basic task functions
|
|
||||||
*/
|
|
||||||
|
|
||||||
fn task_cancel(lua: &Lua, task: LuaUserDataRef<TaskReference>) -> LuaResult<()> {
|
|
||||||
let sched = lua.app_data_ref::<&TaskScheduler>().unwrap();
|
|
||||||
sched.remove_task(*task)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn task_defer(
|
|
||||||
lua: &Lua,
|
|
||||||
(tof, args): (LuaThreadOrFunction, LuaMultiValue),
|
|
||||||
) -> LuaResult<TaskReference> {
|
|
||||||
let sched = lua.app_data_ref::<&TaskScheduler>().unwrap();
|
|
||||||
sched.schedule_blocking_deferred(tof.into_thread(lua)?, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn task_delay(
|
|
||||||
lua: &Lua,
|
|
||||||
(secs, tof, args): (f64, LuaThreadOrFunction, LuaMultiValue),
|
|
||||||
) -> LuaResult<TaskReference> {
|
|
||||||
let sched = lua.app_data_ref::<&TaskScheduler>().unwrap();
|
|
||||||
sched.schedule_blocking_after_seconds(secs, tof.into_thread(lua)?, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Coroutine library overrides for compat with task scheduler
|
|
||||||
*/
|
|
||||||
|
|
||||||
fn coroutine_status<'a>(
|
|
||||||
lua: &'a Lua,
|
|
||||||
value: LuaThreadOrTaskReference<'a>,
|
|
||||||
) -> LuaResult<LuaString<'a>> {
|
|
||||||
Ok(match value {
|
|
||||||
LuaThreadOrTaskReference::Thread(thread) => {
|
|
||||||
let get_status: LuaFunction = lua.named_registry_value("co.status")?;
|
|
||||||
get_status.call(thread)?
|
|
||||||
}
|
|
||||||
LuaThreadOrTaskReference::TaskReference(task) => {
|
|
||||||
let sched = lua.app_data_ref::<&TaskScheduler>().unwrap();
|
|
||||||
sched
|
|
||||||
.get_task_status(task)
|
|
||||||
.unwrap_or_else(|| lua.create_string("dead").unwrap())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn coroutine_resume<'lua>(
|
|
||||||
lua: &'lua Lua,
|
|
||||||
(value, arguments): (LuaThreadOrTaskReference, LuaMultiValue<'lua>),
|
|
||||||
) -> LuaResult<(bool, LuaMultiValue<'lua>)> {
|
|
||||||
let sched = lua.app_data_ref::<&TaskScheduler>().unwrap();
|
|
||||||
if sched.current_task().is_none() {
|
|
||||||
return Err(LuaError::RuntimeError(
|
|
||||||
"No current task to inherit".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let current = sched.current_task().unwrap();
|
|
||||||
let result = match value {
|
|
||||||
LuaThreadOrTaskReference::Thread(t) => {
|
|
||||||
let task = sched.create_task(TaskKind::Instant, t, Some(arguments), true)?;
|
|
||||||
sched.resume_task(task, None)
|
|
||||||
}
|
|
||||||
LuaThreadOrTaskReference::TaskReference(t) => sched.resume_task(t, Some(Ok(arguments))),
|
|
||||||
};
|
|
||||||
sched.force_set_current_task(Some(current));
|
|
||||||
match result {
|
|
||||||
Ok(rets) => Ok((true, rets.1)),
|
|
||||||
Err(e) => Ok((false, e.into_lua_multi(lua)?)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn coroutine_wrap<'lua>(lua: &'lua Lua, func: LuaFunction) -> LuaResult<LuaFunction<'lua>> {
|
|
||||||
let task = lua.app_data_ref::<&TaskScheduler>().unwrap().create_task(
|
|
||||||
TaskKind::Instant,
|
|
||||||
lua.create_thread(func)?,
|
|
||||||
None,
|
|
||||||
false,
|
|
||||||
)?;
|
|
||||||
lua.create_function(move |lua, args: LuaMultiValue| {
|
|
||||||
let sched = lua.app_data_ref::<&TaskScheduler>().unwrap();
|
|
||||||
if sched.current_task().is_none() {
|
|
||||||
return Err(LuaError::RuntimeError(
|
|
||||||
"No current task to inherit".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let current = sched.current_task().unwrap();
|
|
||||||
let result = lua
|
|
||||||
.app_data_ref::<&TaskScheduler>()
|
|
||||||
.unwrap()
|
|
||||||
.resume_task(task, Some(Ok(args)));
|
|
||||||
sched.force_set_current_task(Some(current));
|
|
||||||
match result {
|
|
||||||
Ok(rets) => Ok(rets.1),
|
|
||||||
Err(e) => Err(e),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
use mlua::prelude::*;
|
|
||||||
use std::io::{self, Write as _};
|
|
||||||
|
|
||||||
#[cfg(feature = "roblox")]
|
|
||||||
use crate::roblox::datatypes::extension::RobloxUserdataTypenameExt;
|
|
||||||
|
|
||||||
use crate::lune_temp::lua::{
|
|
||||||
stdio::formatting::{format_label, pretty_format_multi_value},
|
|
||||||
task::TaskReference,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn print(_: &Lua, args: LuaMultiValue) -> LuaResult<()> {
|
|
||||||
let formatted = format!("{}\n", pretty_format_multi_value(&args)?);
|
|
||||||
let mut stdout = io::stdout();
|
|
||||||
stdout.write_all(formatted.as_bytes())?;
|
|
||||||
stdout.flush()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn warn(_: &Lua, args: LuaMultiValue) -> LuaResult<()> {
|
|
||||||
let formatted = format!(
|
|
||||||
"{}\n{}",
|
|
||||||
format_label("warn"),
|
|
||||||
pretty_format_multi_value(&args)?
|
|
||||||
);
|
|
||||||
let mut stdout = io::stdout();
|
|
||||||
stdout.write_all(formatted.as_bytes())?;
|
|
||||||
stdout.flush()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// HACK: We need to preserve the default behavior of
|
|
||||||
// the lua error function, for pcall and such, which
|
|
||||||
// is really tricky to do from scratch so we will
|
|
||||||
// just proxy the default function here instead
|
|
||||||
|
|
||||||
pub fn error(lua: &Lua, (arg, level): (LuaValue, Option<u32>)) -> LuaResult<()> {
|
|
||||||
let error: LuaFunction = lua.named_registry_value("error")?;
|
|
||||||
let trace: LuaFunction = lua.named_registry_value("dbg.trace")?;
|
|
||||||
error.call((
|
|
||||||
LuaError::CallbackError {
|
|
||||||
traceback: format!("override traceback:{}", trace.call::<_, String>(())?),
|
|
||||||
cause: LuaError::external(format!(
|
|
||||||
"{}\n{}",
|
|
||||||
format_label("error"),
|
|
||||||
pretty_format_multi_value(&arg.into_lua_multi(lua)?)?
|
|
||||||
))
|
|
||||||
.into(),
|
|
||||||
},
|
|
||||||
level,
|
|
||||||
))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn proxy_type<'lua>(lua: &'lua Lua, value: LuaValue<'lua>) -> LuaResult<LuaString<'lua>> {
|
|
||||||
if let LuaValue::UserData(u) = &value {
|
|
||||||
if u.is::<TaskReference>() {
|
|
||||||
return lua.create_string("thread");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lua.named_registry_value::<LuaFunction>("type")?.call(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn proxy_typeof<'lua>(lua: &'lua Lua, value: LuaValue<'lua>) -> LuaResult<LuaString<'lua>> {
|
|
||||||
if let LuaValue::UserData(u) = &value {
|
|
||||||
if u.is::<TaskReference>() {
|
|
||||||
return lua.create_string("thread");
|
|
||||||
}
|
|
||||||
#[cfg(feature = "roblox")]
|
|
||||||
{
|
|
||||||
if let Some(type_name) = u.roblox_type_name() {
|
|
||||||
return lua.create_string(type_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lua.named_registry_value::<LuaFunction>("typeof")?
|
|
||||||
.call(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Add an override for tostring that formats errors in a nicer way
|
|
|
@ -1,83 +0,0 @@
|
||||||
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<LuaError> 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)
|
|
||||||
// }
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
mod require;
|
|
||||||
mod require_waker;
|
|
||||||
|
|
||||||
use crate::lune_temp::builtins::{self, top_level};
|
|
||||||
|
|
||||||
pub fn create(lua: &'static Lua, args: Vec<String>) -> LuaResult<()> {
|
|
||||||
// Create all builtins
|
|
||||||
let builtins = vec![
|
|
||||||
("fs", builtins::fs::create(lua)?),
|
|
||||||
("net", builtins::net::create(lua)?),
|
|
||||||
("process", builtins::process::create(lua, args)?),
|
|
||||||
("serde", builtins::serde::create(lua)?),
|
|
||||||
("stdio", builtins::stdio::create(lua)?),
|
|
||||||
("task", builtins::task::create(lua)?),
|
|
||||||
("luau", builtins::luau::create(lua)?),
|
|
||||||
#[cfg(feature = "roblox")]
|
|
||||||
("roblox", builtins::roblox::create(lua)?),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Create our importer (require) with builtins
|
|
||||||
let require_fn = require::create(lua, builtins)?;
|
|
||||||
|
|
||||||
// Create all top-level globals
|
|
||||||
let globals = vec![
|
|
||||||
("require", require_fn),
|
|
||||||
("print", lua.create_function(top_level::print)?),
|
|
||||||
("warn", lua.create_function(top_level::warn)?),
|
|
||||||
("error", lua.create_function(top_level::error)?),
|
|
||||||
("type", lua.create_function(top_level::proxy_type)?),
|
|
||||||
("typeof", lua.create_function(top_level::proxy_typeof)?),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Set top-level globals
|
|
||||||
let lua_globals = lua.globals();
|
|
||||||
for (name, global) in globals {
|
|
||||||
lua_globals.set(name, global)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,319 +0,0 @@
|
||||||
use std::{
|
|
||||||
cell::RefCell,
|
|
||||||
collections::{HashMap, HashSet},
|
|
||||||
env::current_dir,
|
|
||||||
path::{self, PathBuf},
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mlua::prelude::*;
|
|
||||||
use tokio::fs;
|
|
||||||
use tokio::sync::Mutex as AsyncMutex;
|
|
||||||
|
|
||||||
use crate::lune_temp::lua::{
|
|
||||||
table::TableBuilder,
|
|
||||||
task::{TaskScheduler, TaskSchedulerScheduleExt},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::require_waker::{RequireWakerFuture, RequireWakerState};
|
|
||||||
|
|
||||||
const REQUIRE_IMPL_LUA: &str = r#"
|
|
||||||
local source = info(1, "s")
|
|
||||||
if source == '[string "require"]' then
|
|
||||||
source = info(2, "s")
|
|
||||||
end
|
|
||||||
load(context, source, ...)
|
|
||||||
return yield()
|
|
||||||
"#;
|
|
||||||
|
|
||||||
type RequireWakersVec<'lua> = Vec<Arc<AsyncMutex<RequireWakerState<'lua>>>>;
|
|
||||||
|
|
||||||
fn append_extension_and_canonicalize(
|
|
||||||
path: impl Into<PathBuf>,
|
|
||||||
ext: &'static str,
|
|
||||||
) -> Result<PathBuf, std::io::Error> {
|
|
||||||
let mut new = path.into();
|
|
||||||
match new.extension() {
|
|
||||||
// FUTURE: There's probably a better way to do this than converting to a lossy string
|
|
||||||
Some(e) => new.set_extension(format!("{}.{ext}", e.to_string_lossy())),
|
|
||||||
None => new.set_extension(ext),
|
|
||||||
};
|
|
||||||
dunce::canonicalize(new)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
|
||||||
struct RequireContext<'lua> {
|
|
||||||
// NOTE: We need to use arc here so that mlua clones
|
|
||||||
// the reference and not the entire inner value(s)
|
|
||||||
builtins: Arc<HashMap<String, LuaMultiValue<'lua>>>,
|
|
||||||
cached: Arc<RefCell<HashMap<String, LuaResult<LuaMultiValue<'lua>>>>>,
|
|
||||||
wakers: Arc<RefCell<HashMap<String, RequireWakersVec<'lua>>>>,
|
|
||||||
locks: Arc<RefCell<HashSet<String>>>,
|
|
||||||
pwd: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> RequireContext<'lua> {
|
|
||||||
pub fn new<K, V>(lua: &'lua Lua, builtins_vec: Vec<(K, V)>) -> LuaResult<Self>
|
|
||||||
where
|
|
||||||
K: Into<String>,
|
|
||||||
V: IntoLua<'lua>,
|
|
||||||
{
|
|
||||||
let mut pwd = current_dir()
|
|
||||||
.expect("Failed to access current working directory")
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string();
|
|
||||||
if !pwd.ends_with(path::MAIN_SEPARATOR) {
|
|
||||||
pwd = format!("{pwd}{}", path::MAIN_SEPARATOR)
|
|
||||||
}
|
|
||||||
let mut builtins = HashMap::new();
|
|
||||||
for (key, value) in builtins_vec {
|
|
||||||
builtins.insert(key.into(), value.into_lua_multi(lua)?);
|
|
||||||
}
|
|
||||||
Ok(Self {
|
|
||||||
pwd,
|
|
||||||
builtins: Arc::new(builtins),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_locked(&self, absolute_path: &str) -> bool {
|
|
||||||
self.locks.borrow().contains(absolute_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_locked(&self, absolute_path: &str) -> bool {
|
|
||||||
self.locks.borrow_mut().insert(absolute_path.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_unlocked(&self, absolute_path: &str) -> bool {
|
|
||||||
self.locks.borrow_mut().remove(absolute_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_acquire_lock_sync(&self, absolute_path: &str) -> bool {
|
|
||||||
if self.is_locked(absolute_path) {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
self.set_locked(absolute_path);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_cached(&self, absolute_path: &str, result: &LuaResult<LuaMultiValue<'lua>>) {
|
|
||||||
self.cached
|
|
||||||
.borrow_mut()
|
|
||||||
.insert(absolute_path.to_string(), result.clone());
|
|
||||||
if let Some(wakers) = self.wakers.borrow_mut().remove(absolute_path) {
|
|
||||||
for waker in wakers {
|
|
||||||
waker
|
|
||||||
.try_lock()
|
|
||||||
.expect("Failed to lock waker")
|
|
||||||
.finalize(result.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wait_for_cache(self, absolute_path: &str) -> RequireWakerFuture<'lua> {
|
|
||||||
let state = RequireWakerState::new();
|
|
||||||
let fut = RequireWakerFuture::new(&state);
|
|
||||||
self.wakers
|
|
||||||
.borrow_mut()
|
|
||||||
.entry(absolute_path.to_string())
|
|
||||||
.or_insert_with(Vec::new)
|
|
||||||
.push(Arc::clone(&state));
|
|
||||||
fut
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_paths(
|
|
||||||
&self,
|
|
||||||
require_source: String,
|
|
||||||
require_path: String,
|
|
||||||
) -> LuaResult<(String, String)> {
|
|
||||||
if require_path.starts_with('@') {
|
|
||||||
return Ok((require_path.clone(), require_path));
|
|
||||||
}
|
|
||||||
let path_relative_to_pwd = PathBuf::from(
|
|
||||||
&require_source
|
|
||||||
.trim_start_matches("[string \"")
|
|
||||||
.trim_end_matches("\"]"),
|
|
||||||
)
|
|
||||||
.parent()
|
|
||||||
.unwrap()
|
|
||||||
.join(&require_path);
|
|
||||||
// Try to normalize and resolve relative path segments such as './' and '../'
|
|
||||||
let file_path = match (
|
|
||||||
append_extension_and_canonicalize(&path_relative_to_pwd, "luau"),
|
|
||||||
append_extension_and_canonicalize(&path_relative_to_pwd, "lua"),
|
|
||||||
) {
|
|
||||||
(Ok(luau), _) => luau,
|
|
||||||
(_, Ok(lua)) => lua,
|
|
||||||
// If we did not find a luau/lua file at the wanted path,
|
|
||||||
// we should also look for "init" files in directories
|
|
||||||
_ => {
|
|
||||||
let init_dir_path = path_relative_to_pwd.join("init");
|
|
||||||
match (
|
|
||||||
append_extension_and_canonicalize(&init_dir_path, "luau"),
|
|
||||||
append_extension_and_canonicalize(&init_dir_path, "lua"),
|
|
||||||
) {
|
|
||||||
(Ok(luau), _) => luau,
|
|
||||||
(_, Ok(lua)) => lua,
|
|
||||||
_ => {
|
|
||||||
return Err(LuaError::RuntimeError(format!(
|
|
||||||
"File does not exist at path '{require_path}'"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let absolute = file_path.to_string_lossy().to_string();
|
|
||||||
let relative = absolute.trim_start_matches(&self.pwd).to_string();
|
|
||||||
Ok((absolute, relative))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> LuaUserData for RequireContext<'lua> {}
|
|
||||||
|
|
||||||
fn load_builtin<'lua>(
|
|
||||||
_lua: &'lua Lua,
|
|
||||||
context: RequireContext<'lua>,
|
|
||||||
module_name: String,
|
|
||||||
_has_acquired_lock: bool,
|
|
||||||
) -> LuaResult<LuaMultiValue<'lua>> {
|
|
||||||
match context.builtins.get(&module_name) {
|
|
||||||
Some(module) => Ok(module.clone()),
|
|
||||||
None => Err(LuaError::RuntimeError(format!(
|
|
||||||
"No builtin module exists with the name '{}'",
|
|
||||||
module_name
|
|
||||||
))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn load_file<'lua>(
|
|
||||||
lua: &'lua Lua,
|
|
||||||
context: RequireContext<'lua>,
|
|
||||||
absolute_path: String,
|
|
||||||
relative_path: String,
|
|
||||||
has_acquired_lock: bool,
|
|
||||||
) -> LuaResult<LuaMultiValue<'lua>> {
|
|
||||||
let cached = { context.cached.borrow().get(&absolute_path).cloned() };
|
|
||||||
match cached {
|
|
||||||
Some(cached) => cached,
|
|
||||||
None => {
|
|
||||||
if !has_acquired_lock {
|
|
||||||
return context.wait_for_cache(&absolute_path).await;
|
|
||||||
}
|
|
||||||
// Try to read the wanted file, note that we use bytes instead of reading
|
|
||||||
// to a string since lua scripts are not necessarily valid utf-8 strings
|
|
||||||
let contents = fs::read(&absolute_path).await.into_lua_err()?;
|
|
||||||
// Use a name without extensions for loading the chunk, some
|
|
||||||
// other code assumes the require path is without extensions
|
|
||||||
let path_relative_no_extension = relative_path
|
|
||||||
.trim_end_matches(".lua")
|
|
||||||
.trim_end_matches(".luau");
|
|
||||||
// Load the file into a thread
|
|
||||||
let loaded_func = lua
|
|
||||||
.load(contents)
|
|
||||||
.set_name(path_relative_no_extension)
|
|
||||||
.into_function()?;
|
|
||||||
let loaded_thread = lua.create_thread(loaded_func)?;
|
|
||||||
// Run the thread and wait for completion using the native task scheduler waker
|
|
||||||
let task_fut = {
|
|
||||||
let sched = lua.app_data_ref::<&TaskScheduler>().unwrap();
|
|
||||||
let task = sched.schedule_blocking(loaded_thread, LuaMultiValue::new())?;
|
|
||||||
sched.wait_for_task_completion(task)
|
|
||||||
};
|
|
||||||
// Wait for the thread to finish running, cache + return our result,
|
|
||||||
// notify any other threads that are also waiting on this to finish
|
|
||||||
let rets = task_fut.await;
|
|
||||||
context.set_cached(&absolute_path, &rets);
|
|
||||||
rets
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn load<'lua>(
|
|
||||||
lua: &'lua Lua,
|
|
||||||
context: LuaUserDataRef<'lua, RequireContext<'lua>>,
|
|
||||||
absolute_path: String,
|
|
||||||
relative_path: String,
|
|
||||||
has_acquired_lock: bool,
|
|
||||||
) -> LuaResult<LuaMultiValue<'lua>> {
|
|
||||||
let result = if absolute_path == relative_path && absolute_path.starts_with('@') {
|
|
||||||
if let Some(module_name) = absolute_path.strip_prefix("@lune/") {
|
|
||||||
load_builtin(
|
|
||||||
lua,
|
|
||||||
context.clone(),
|
|
||||||
module_name.to_string(),
|
|
||||||
has_acquired_lock,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// FUTURE: '@' can be used a special prefix for users to set their own
|
|
||||||
// paths relative to a project file, similar to typescript paths config
|
|
||||||
// https://www.typescriptlang.org/tsconfig#paths
|
|
||||||
Err(LuaError::RuntimeError(
|
|
||||||
"Require paths prefixed by '@' are not yet supported".to_string(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
load_file(
|
|
||||||
lua,
|
|
||||||
context.clone(),
|
|
||||||
absolute_path.to_string(),
|
|
||||||
relative_path,
|
|
||||||
has_acquired_lock,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
};
|
|
||||||
if has_acquired_lock {
|
|
||||||
context.set_unlocked(&absolute_path);
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create<K, V>(lua: &'static Lua, builtins: Vec<(K, V)>) -> LuaResult<LuaFunction>
|
|
||||||
where
|
|
||||||
K: Clone + Into<String>,
|
|
||||||
V: Clone + IntoLua<'static>,
|
|
||||||
{
|
|
||||||
let require_context = RequireContext::new(lua, builtins)?;
|
|
||||||
let require_yield: LuaFunction = lua.named_registry_value("co.yield")?;
|
|
||||||
let require_info: LuaFunction = lua.named_registry_value("dbg.info")?;
|
|
||||||
let require_print: LuaFunction = lua.named_registry_value("print")?;
|
|
||||||
|
|
||||||
let require_env = TableBuilder::new(lua)?
|
|
||||||
.with_value("context", require_context)?
|
|
||||||
.with_value("yield", require_yield)?
|
|
||||||
.with_value("info", require_info)?
|
|
||||||
.with_value("print", require_print)?
|
|
||||||
.with_function(
|
|
||||||
"load",
|
|
||||||
|lua,
|
|
||||||
(context, require_source, require_path): (
|
|
||||||
LuaUserDataRef<RequireContext>,
|
|
||||||
String,
|
|
||||||
String,
|
|
||||||
)| {
|
|
||||||
let (absolute_path, relative_path) =
|
|
||||||
context.get_paths(require_source, require_path)?;
|
|
||||||
// NOTE: We can not acquire the lock in the async part of the require
|
|
||||||
// load process since several requires may have happened for the
|
|
||||||
// same path before the async load task even gets a chance to run
|
|
||||||
let has_lock = context.try_acquire_lock_sync(&absolute_path);
|
|
||||||
let fut = load(lua, context, absolute_path, relative_path, has_lock);
|
|
||||||
let sched = lua
|
|
||||||
.app_data_ref::<&TaskScheduler>()
|
|
||||||
.expect("Missing task scheduler as a lua app data");
|
|
||||||
sched.queue_async_task_inherited(lua.current_thread(), None, async {
|
|
||||||
let rets = fut.await?;
|
|
||||||
let mult = rets.into_lua_multi(lua)?;
|
|
||||||
Ok(Some(mult))
|
|
||||||
})
|
|
||||||
},
|
|
||||||
)?
|
|
||||||
.build_readonly()?;
|
|
||||||
|
|
||||||
let require_fn_lua = lua
|
|
||||||
.load(REQUIRE_IMPL_LUA)
|
|
||||||
.set_name("require")
|
|
||||||
.set_environment(require_env)
|
|
||||||
.into_function()?;
|
|
||||||
Ok(require_fn_lua)
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
use std::{
|
|
||||||
future::Future,
|
|
||||||
pin::Pin,
|
|
||||||
sync::Arc,
|
|
||||||
task::{Context, Poll, Waker},
|
|
||||||
};
|
|
||||||
|
|
||||||
use tokio::sync::Mutex as AsyncMutex;
|
|
||||||
|
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub(super) struct RequireWakerState<'lua> {
|
|
||||||
rets: Option<LuaResult<LuaMultiValue<'lua>>>,
|
|
||||||
waker: Option<Waker>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> RequireWakerState<'lua> {
|
|
||||||
pub fn new() -> Arc<AsyncMutex<Self>> {
|
|
||||||
Arc::new(AsyncMutex::new(RequireWakerState {
|
|
||||||
rets: None,
|
|
||||||
waker: None,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn finalize(&mut self, rets: LuaResult<LuaMultiValue<'lua>>) {
|
|
||||||
self.rets = Some(rets);
|
|
||||||
if let Some(waker) = self.waker.take() {
|
|
||||||
waker.wake();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(super) struct RequireWakerFuture<'lua> {
|
|
||||||
state: Arc<AsyncMutex<RequireWakerState<'lua>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> RequireWakerFuture<'lua> {
|
|
||||||
pub fn new(state: &Arc<AsyncMutex<RequireWakerState<'lua>>>) -> Self {
|
|
||||||
Self {
|
|
||||||
state: Arc::clone(state),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> Clone for RequireWakerFuture<'lua> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
state: Arc::clone(&self.state),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> Future for RequireWakerFuture<'lua> {
|
|
||||||
type Output = LuaResult<LuaMultiValue<'lua>>;
|
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
|
||||||
let mut shared_state = self.state.try_lock().unwrap();
|
|
||||||
if let Some(rets) = shared_state.rets.clone() {
|
|
||||||
Poll::Ready(rets)
|
|
||||||
} else {
|
|
||||||
shared_state.waker = Some(cx.waker().clone());
|
|
||||||
Poll::Pending
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,93 +0,0 @@
|
||||||
use async_trait::async_trait;
|
|
||||||
use futures_util::Future;
|
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
use crate::lune_temp::{lua::table::TableBuilder, lua::task::TaskScheduler};
|
|
||||||
|
|
||||||
use super::task::TaskSchedulerAsyncExt;
|
|
||||||
|
|
||||||
const ASYNC_IMPL_LUA: &str = r#"
|
|
||||||
resumeAsync(...)
|
|
||||||
return yield()
|
|
||||||
"#;
|
|
||||||
|
|
||||||
const WAIT_IMPL_LUA: &str = r#"
|
|
||||||
resumeAfter(...)
|
|
||||||
return yield()
|
|
||||||
"#;
|
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
|
||||||
pub trait LuaAsyncExt {
|
|
||||||
fn create_async_function<'lua, A, R, F, FR>(self, func: F) -> LuaResult<LuaFunction<'lua>>
|
|
||||||
where
|
|
||||||
A: FromLuaMulti<'static>,
|
|
||||||
R: IntoLuaMulti<'static>,
|
|
||||||
F: 'static + Fn(&'lua Lua, A) -> FR,
|
|
||||||
FR: 'static + Future<Output = LuaResult<R>>;
|
|
||||||
|
|
||||||
fn create_waiter_function<'lua>(self) -> LuaResult<LuaFunction<'lua>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LuaAsyncExt for &'static 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<'lua, A, R, F, FR>(self, func: F) -> LuaResult<LuaFunction<'lua>>
|
|
||||||
where
|
|
||||||
A: FromLuaMulti<'static>,
|
|
||||||
R: IntoLuaMulti<'static>,
|
|
||||||
F: 'static + Fn(&'lua Lua, A) -> FR,
|
|
||||||
FR: 'static + Future<Output = LuaResult<R>>,
|
|
||||||
{
|
|
||||||
let async_env_yield: LuaFunction = self.named_registry_value("co.yield")?;
|
|
||||||
let async_env = TableBuilder::new(self)?
|
|
||||||
.with_value("yield", async_env_yield)?
|
|
||||||
.with_function("resumeAsync", move |lua: &Lua, args: A| {
|
|
||||||
let thread = lua.current_thread();
|
|
||||||
let fut = func(lua, args);
|
|
||||||
let sched = lua
|
|
||||||
.app_data_ref::<&TaskScheduler>()
|
|
||||||
.expect("Missing task scheduler as a lua app data");
|
|
||||||
sched.queue_async_task(thread, None, async {
|
|
||||||
let rets = fut.await?;
|
|
||||||
let mult = rets.into_lua_multi(lua)?;
|
|
||||||
Ok(Some(mult))
|
|
||||||
})
|
|
||||||
})?
|
|
||||||
.build_readonly()?;
|
|
||||||
let async_func = self
|
|
||||||
.load(ASYNC_IMPL_LUA)
|
|
||||||
.set_name("async")
|
|
||||||
.set_environment(async_env)
|
|
||||||
.into_function()?;
|
|
||||||
Ok(async_func)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Creates a special async function that waits the
|
|
||||||
desired amount of time, inheriting the guid of the
|
|
||||||
current thread / task for proper cancellation.
|
|
||||||
|
|
||||||
This will yield the lua thread calling the function until the
|
|
||||||
desired time has passed and the scheduler resumes the thread.
|
|
||||||
*/
|
|
||||||
fn create_waiter_function<'lua>(self) -> LuaResult<LuaFunction<'lua>> {
|
|
||||||
let async_env_yield: LuaFunction = self.named_registry_value("co.yield")?;
|
|
||||||
let async_env = TableBuilder::new(self)?
|
|
||||||
.with_value("yield", async_env_yield)?
|
|
||||||
.with_function("resumeAfter", move |lua: &Lua, duration: Option<f64>| {
|
|
||||||
let sched = lua
|
|
||||||
.app_data_ref::<&TaskScheduler>()
|
|
||||||
.expect("Missing task scheduler as a lua app data");
|
|
||||||
sched.schedule_wait(lua.current_thread(), duration)
|
|
||||||
})?
|
|
||||||
.build_readonly()?;
|
|
||||||
let async_func = self
|
|
||||||
.load(WAIT_IMPL_LUA)
|
|
||||||
.set_name("wait")
|
|
||||||
.set_environment(async_env)
|
|
||||||
.into_function()?;
|
|
||||||
Ok(async_func)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,184 +0,0 @@
|
||||||
use mlua::{prelude::*, Compiler as LuaCompiler};
|
|
||||||
|
|
||||||
/*
|
|
||||||
- Level 0 is the call to info
|
|
||||||
- Level 1 is the load call in create() below where we load this into a function
|
|
||||||
- Level 2 is the call to the trace, which we also want to skip, so start at 3
|
|
||||||
|
|
||||||
Also note that we must match the mlua traceback format here so that we
|
|
||||||
can pattern match and beautify it properly later on when outputting it
|
|
||||||
*/
|
|
||||||
const TRACE_IMPL_LUA: &str = r#"
|
|
||||||
local lines = {}
|
|
||||||
for level = 3, 16 do
|
|
||||||
local parts = {}
|
|
||||||
local source, line, name = info(level, "sln")
|
|
||||||
if source then
|
|
||||||
push(parts, source)
|
|
||||||
else
|
|
||||||
break
|
|
||||||
end
|
|
||||||
if line == -1 then
|
|
||||||
line = nil
|
|
||||||
end
|
|
||||||
if name and #name <= 0 then
|
|
||||||
name = nil
|
|
||||||
end
|
|
||||||
if line then
|
|
||||||
push(parts, format("%d", line))
|
|
||||||
end
|
|
||||||
if name and #parts > 1 then
|
|
||||||
push(parts, format(" in function '%s'", name))
|
|
||||||
elseif name then
|
|
||||||
push(parts, format("in function '%s'", name))
|
|
||||||
end
|
|
||||||
if #parts > 0 then
|
|
||||||
push(lines, concat(parts, ":"))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if #lines > 0 then
|
|
||||||
return concat(lines, "\n")
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
"#;
|
|
||||||
|
|
||||||
/**
|
|
||||||
Stores the following globals in the Lua registry:
|
|
||||||
|
|
||||||
| Registry Name | Global |
|
|
||||||
|-----------------|-------------------|
|
|
||||||
| `"print"` | `print` |
|
|
||||||
| `"error"` | `error` |
|
|
||||||
| `"type"` | `type` |
|
|
||||||
| `"typeof"` | `typeof` |
|
|
||||||
| `"pcall"` | `pcall` |
|
|
||||||
| `"xpcall"` | `xpcall` |
|
|
||||||
| `"tostring"` | `tostring` |
|
|
||||||
| `"tonumber"` | `tonumber` |
|
|
||||||
| `"co.yield"` | `coroutine.yield` |
|
|
||||||
| `"co.close"` | `coroutine.close` |
|
|
||||||
| `"tab.pack"` | `table.pack` |
|
|
||||||
| `"tab.unpack"` | `table.unpack` |
|
|
||||||
| `"tab.freeze"` | `table.freeze` |
|
|
||||||
| `"tab.getmeta"` | `getmetatable` |
|
|
||||||
| `"tab.setmeta"` | `setmetatable` |
|
|
||||||
| `"dbg.info"` | `debug.info` |
|
|
||||||
| `"dbg.trace"` | `debug.traceback` |
|
|
||||||
|
|
||||||
These globals can then be modified safely from other runtime code.
|
|
||||||
*/
|
|
||||||
fn store_globals_in_registry(lua: &Lua) -> LuaResult<()> {
|
|
||||||
// Extract some global tables that we will extract
|
|
||||||
// built-in functions from and store in the registry
|
|
||||||
let globals = lua.globals();
|
|
||||||
let debug: LuaTable = globals.get("debug")?;
|
|
||||||
let string: LuaTable = globals.get("string")?;
|
|
||||||
let table: LuaTable = globals.get("table")?;
|
|
||||||
let coroutine: LuaTable = globals.get("coroutine")?;
|
|
||||||
|
|
||||||
// Store original lua global functions in the registry so we can use
|
|
||||||
// them later without passing them around and dealing with lifetimes
|
|
||||||
lua.set_named_registry_value("print", globals.get::<_, LuaFunction>("print")?)?;
|
|
||||||
lua.set_named_registry_value("error", globals.get::<_, LuaFunction>("error")?)?;
|
|
||||||
lua.set_named_registry_value("type", globals.get::<_, LuaFunction>("type")?)?;
|
|
||||||
lua.set_named_registry_value("typeof", globals.get::<_, LuaFunction>("typeof")?)?;
|
|
||||||
lua.set_named_registry_value("xpcall", globals.get::<_, LuaFunction>("xpcall")?)?;
|
|
||||||
lua.set_named_registry_value("pcall", globals.get::<_, LuaFunction>("pcall")?)?;
|
|
||||||
lua.set_named_registry_value("tostring", globals.get::<_, LuaFunction>("tostring")?)?;
|
|
||||||
lua.set_named_registry_value("tonumber", globals.get::<_, LuaFunction>("tonumber")?)?;
|
|
||||||
lua.set_named_registry_value("co.status", coroutine.get::<_, LuaFunction>("status")?)?;
|
|
||||||
lua.set_named_registry_value("co.yield", coroutine.get::<_, LuaFunction>("yield")?)?;
|
|
||||||
lua.set_named_registry_value("co.close", coroutine.get::<_, LuaFunction>("close")?)?;
|
|
||||||
lua.set_named_registry_value("dbg.info", debug.get::<_, LuaFunction>("info")?)?;
|
|
||||||
lua.set_named_registry_value("tab.pack", table.get::<_, LuaFunction>("pack")?)?;
|
|
||||||
lua.set_named_registry_value("tab.unpack", table.get::<_, LuaFunction>("unpack")?)?;
|
|
||||||
lua.set_named_registry_value("tab.freeze", table.get::<_, LuaFunction>("freeze")?)?;
|
|
||||||
lua.set_named_registry_value(
|
|
||||||
"tab.getmeta",
|
|
||||||
globals.get::<_, LuaFunction>("getmetatable")?,
|
|
||||||
)?;
|
|
||||||
lua.set_named_registry_value(
|
|
||||||
"tab.setmeta",
|
|
||||||
globals.get::<_, LuaFunction>("setmetatable")?,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Create a trace function that can be called to obtain a full stack trace from
|
|
||||||
// lua, this is not possible to do from rust when using our manual scheduler
|
|
||||||
let dbg_trace_env = lua.create_table_with_capacity(0, 1)?;
|
|
||||||
dbg_trace_env.set("info", debug.get::<_, LuaFunction>("info")?)?;
|
|
||||||
dbg_trace_env.set("push", table.get::<_, LuaFunction>("insert")?)?;
|
|
||||||
dbg_trace_env.set("concat", table.get::<_, LuaFunction>("concat")?)?;
|
|
||||||
dbg_trace_env.set("format", string.get::<_, LuaFunction>("format")?)?;
|
|
||||||
|
|
||||||
let dbg_trace_fn = lua
|
|
||||||
.load(TRACE_IMPL_LUA)
|
|
||||||
.set_name("=dbg.trace")
|
|
||||||
.set_environment(dbg_trace_env)
|
|
||||||
.into_function()?;
|
|
||||||
lua.set_named_registry_value("dbg.trace", dbg_trace_fn)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Sets the `_VERSION` global to a value matching the string `Lune x.y.z+w` where
|
|
||||||
`x.y.z` is the current Lune runtime version and `w` is the current Luau version
|
|
||||||
*/
|
|
||||||
fn set_global_version(lua: &Lua) -> LuaResult<()> {
|
|
||||||
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.globals().set(
|
|
||||||
"_VERSION",
|
|
||||||
lua.create_string(&format!(
|
|
||||||
"Lune {lune}+{luau}",
|
|
||||||
lune = env!("CARGO_PKG_VERSION"),
|
|
||||||
luau = luau_version,
|
|
||||||
))?,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Creates a _G table that is separate from our built-in globals
|
|
||||||
*/
|
|
||||||
fn set_global_table(lua: &Lua) -> LuaResult<()> {
|
|
||||||
lua.globals().set("_G", lua.create_table()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Enables JIT and sets default compiler settings for the Lua struct.
|
|
||||||
*/
|
|
||||||
fn init_compiler_settings(lua: &Lua) {
|
|
||||||
lua.enable_jit(true);
|
|
||||||
lua.set_compiler(
|
|
||||||
LuaCompiler::default()
|
|
||||||
.set_coverage_level(0)
|
|
||||||
.set_debug_level(1)
|
|
||||||
.set_optimization_level(1),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Creates a new [`mlua::Lua`] struct with compiler,
|
|
||||||
registry, and globals customized for the Lune runtime.
|
|
||||||
|
|
||||||
Refer to the source code for additional details and specifics.
|
|
||||||
*/
|
|
||||||
pub fn create() -> LuaResult<Lua> {
|
|
||||||
let lua = Lua::new();
|
|
||||||
init_compiler_settings(&lua);
|
|
||||||
store_globals_in_registry(&lua)?;
|
|
||||||
set_global_version(&lua)?;
|
|
||||||
set_global_table(&lua)?;
|
|
||||||
Ok(lua)
|
|
||||||
}
|
|
|
@ -1,155 +0,0 @@
|
||||||
use std::collections::VecDeque;
|
|
||||||
use std::io::ErrorKind;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
use mlua::prelude::*;
|
|
||||||
use tokio::fs;
|
|
||||||
|
|
||||||
use super::FsWriteOptions;
|
|
||||||
|
|
||||||
pub struct CopyContents {
|
|
||||||
// Vec<(relative depth, path)>
|
|
||||||
pub dirs: Vec<(usize, PathBuf)>,
|
|
||||||
pub files: Vec<(usize, PathBuf)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_contents_at(root: PathBuf, _options: FsWriteOptions) -> LuaResult<CopyContents> {
|
|
||||||
let mut dirs = Vec::new();
|
|
||||||
let mut files = Vec::new();
|
|
||||||
|
|
||||||
let mut queue = VecDeque::new();
|
|
||||||
|
|
||||||
let normalized_root = fs::canonicalize(&root).await.map_err(|e| {
|
|
||||||
LuaError::RuntimeError(format!("Failed to canonicalize root directory path\n{e}"))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Push initial children of the root path into the queue
|
|
||||||
let mut entries = fs::read_dir(&normalized_root).await?;
|
|
||||||
while let Some(entry) = entries.next_entry().await? {
|
|
||||||
queue.push_back((1, entry.path()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Go through the current queue, pushing to it
|
|
||||||
// when we find any new descendant directories
|
|
||||||
// FUTURE: Try to do async reading here concurrently to speed it up a bit
|
|
||||||
while let Some((current_depth, current_path)) = queue.pop_front() {
|
|
||||||
let meta = fs::metadata(¤t_path).await?;
|
|
||||||
if meta.is_symlink() {
|
|
||||||
return Err(LuaError::RuntimeError(format!(
|
|
||||||
"Symlinks are not yet supported, encountered at path '{}'",
|
|
||||||
current_path.display()
|
|
||||||
)));
|
|
||||||
} else if meta.is_dir() {
|
|
||||||
// FUTURE: Add an option in FsWriteOptions for max depth and limit it here
|
|
||||||
let mut entries = fs::read_dir(¤t_path).await?;
|
|
||||||
while let Some(entry) = entries.next_entry().await? {
|
|
||||||
queue.push_back((current_depth + 1, entry.path()));
|
|
||||||
}
|
|
||||||
dirs.push((current_depth, current_path));
|
|
||||||
} else {
|
|
||||||
files.push((current_depth, current_path));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that all directory and file paths are relative to the root path
|
|
||||||
// SAFETY: Since we only ever push dirs and files relative to the root, unwrap is safe
|
|
||||||
for (_, dir) in dirs.iter_mut() {
|
|
||||||
*dir = dir.strip_prefix(&normalized_root).unwrap().to_path_buf()
|
|
||||||
}
|
|
||||||
for (_, file) in files.iter_mut() {
|
|
||||||
*file = file.strip_prefix(&normalized_root).unwrap().to_path_buf()
|
|
||||||
}
|
|
||||||
|
|
||||||
// FUTURE: Deduplicate paths such that these directories:
|
|
||||||
// - foo/
|
|
||||||
// - foo/bar/
|
|
||||||
// - foo/bar/baz/
|
|
||||||
// turn into a single foo/bar/baz/ and let create_dir_all do the heavy lifting
|
|
||||||
|
|
||||||
Ok(CopyContents { dirs, files })
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn ensure_no_dir_exists(path: impl AsRef<Path>) -> LuaResult<()> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
match fs::metadata(&path).await {
|
|
||||||
Ok(meta) if meta.is_dir() => Err(LuaError::RuntimeError(format!(
|
|
||||||
"A directory already exists at the path '{}'",
|
|
||||||
path.display()
|
|
||||||
))),
|
|
||||||
_ => Ok(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn ensure_no_file_exists(path: impl AsRef<Path>) -> LuaResult<()> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
match fs::metadata(&path).await {
|
|
||||||
Ok(meta) if meta.is_file() => Err(LuaError::RuntimeError(format!(
|
|
||||||
"A file already exists at the path '{}'",
|
|
||||||
path.display()
|
|
||||||
))),
|
|
||||||
_ => Ok(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn copy(
|
|
||||||
source: impl AsRef<Path>,
|
|
||||||
target: impl AsRef<Path>,
|
|
||||||
options: FsWriteOptions,
|
|
||||||
) -> LuaResult<()> {
|
|
||||||
let source = source.as_ref();
|
|
||||||
let target = target.as_ref();
|
|
||||||
|
|
||||||
// Check if we got a file or directory - we will handle them differently below
|
|
||||||
let (is_dir, is_file) = match fs::metadata(&source).await {
|
|
||||||
Ok(meta) => (meta.is_dir(), meta.is_file()),
|
|
||||||
Err(e) if e.kind() == ErrorKind::NotFound => {
|
|
||||||
return Err(LuaError::RuntimeError(format!(
|
|
||||||
"No file or directory exists at the path '{}'",
|
|
||||||
source.display()
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
Err(e) => return Err(e.into()),
|
|
||||||
};
|
|
||||||
if !is_file && !is_dir {
|
|
||||||
return Err(LuaError::RuntimeError(format!(
|
|
||||||
"The given path '{}' is not a file or a directory",
|
|
||||||
source.display()
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform copying:
|
|
||||||
//
|
|
||||||
// 1. If we are not allowed to overwrite, make sure nothing exists at the target path
|
|
||||||
// 2. If we are allowed to overwrite, remove any previous entry at the path
|
|
||||||
// 3. Write all directories first
|
|
||||||
// 4. Write all files
|
|
||||||
|
|
||||||
if !options.overwrite {
|
|
||||||
if is_file {
|
|
||||||
ensure_no_file_exists(target).await?;
|
|
||||||
} else if is_dir {
|
|
||||||
ensure_no_dir_exists(target).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_file {
|
|
||||||
fs::copy(source, target).await?;
|
|
||||||
} else if is_dir {
|
|
||||||
let contents = get_contents_at(source.to_path_buf(), options).await?;
|
|
||||||
|
|
||||||
if options.overwrite {
|
|
||||||
fs::remove_dir_all(target).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FUTURE: Write dirs / files concurrently
|
|
||||||
// to potentially speed these operations up
|
|
||||||
for (_, dir) in &contents.dirs {
|
|
||||||
fs::create_dir_all(target.join(dir)).await?;
|
|
||||||
}
|
|
||||||
for (_, file) in &contents.files {
|
|
||||||
fs::copy(source.join(file), target.join(file)).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,153 +0,0 @@
|
||||||
use std::{
|
|
||||||
fmt,
|
|
||||||
fs::{FileType as StdFileType, Metadata as StdMetadata, Permissions as StdPermissions},
|
|
||||||
io::Result as IoResult,
|
|
||||||
str::FromStr,
|
|
||||||
time::SystemTime,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum FsMetadataKind {
|
|
||||||
None,
|
|
||||||
File,
|
|
||||||
Dir,
|
|
||||||
Symlink,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for FsMetadataKind {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{}",
|
|
||||||
match self {
|
|
||||||
Self::None => "none",
|
|
||||||
Self::File => "file",
|
|
||||||
Self::Dir => "dir",
|
|
||||||
Self::Symlink => "symlink",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for FsMetadataKind {
|
|
||||||
type Err = &'static str;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s.trim().to_ascii_lowercase().as_ref() {
|
|
||||||
"none" => Ok(Self::None),
|
|
||||||
"file" => Ok(Self::File),
|
|
||||||
"dir" => Ok(Self::Dir),
|
|
||||||
"symlink" => Ok(Self::Symlink),
|
|
||||||
_ => Err("Invalid metadata kind"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<StdFileType> for FsMetadataKind {
|
|
||||||
fn from(value: StdFileType) -> Self {
|
|
||||||
if value.is_file() {
|
|
||||||
Self::File
|
|
||||||
} else if value.is_dir() {
|
|
||||||
Self::Dir
|
|
||||||
} else if value.is_symlink() {
|
|
||||||
Self::Symlink
|
|
||||||
} else {
|
|
||||||
panic!("Encountered unknown filesystem filetype")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> IntoLua<'lua> for FsMetadataKind {
|
|
||||||
fn into_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
|
|
||||||
if self == Self::None {
|
|
||||||
Ok(LuaValue::Nil)
|
|
||||||
} else {
|
|
||||||
self.to_string().into_lua(lua)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct FsPermissions {
|
|
||||||
pub(crate) read_only: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<StdPermissions> for FsPermissions {
|
|
||||||
fn from(value: StdPermissions) -> Self {
|
|
||||||
Self {
|
|
||||||
read_only: value.readonly(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> IntoLua<'lua> for FsPermissions {
|
|
||||||
fn into_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
|
|
||||||
let tab = lua.create_table_with_capacity(0, 1)?;
|
|
||||||
tab.set("readOnly", self.read_only)?;
|
|
||||||
tab.set_readonly(true);
|
|
||||||
Ok(LuaValue::Table(tab))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct FsMetadata {
|
|
||||||
pub(crate) kind: FsMetadataKind,
|
|
||||||
pub(crate) exists: bool,
|
|
||||||
pub(crate) created_at: Option<f64>,
|
|
||||||
pub(crate) modified_at: Option<f64>,
|
|
||||||
pub(crate) accessed_at: Option<f64>,
|
|
||||||
pub(crate) permissions: Option<FsPermissions>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FsMetadata {
|
|
||||||
pub fn not_found() -> Self {
|
|
||||||
Self {
|
|
||||||
kind: FsMetadataKind::None,
|
|
||||||
exists: false,
|
|
||||||
created_at: None,
|
|
||||||
modified_at: None,
|
|
||||||
accessed_at: None,
|
|
||||||
permissions: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> IntoLua<'lua> for FsMetadata {
|
|
||||||
fn into_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
|
|
||||||
let tab = lua.create_table_with_capacity(0, 5)?;
|
|
||||||
tab.set("kind", self.kind)?;
|
|
||||||
tab.set("exists", self.exists)?;
|
|
||||||
tab.set("createdAt", self.created_at)?;
|
|
||||||
tab.set("modifiedAt", self.modified_at)?;
|
|
||||||
tab.set("accessedAt", self.accessed_at)?;
|
|
||||||
tab.set("permissions", self.permissions)?;
|
|
||||||
tab.set_readonly(true);
|
|
||||||
Ok(LuaValue::Table(tab))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<StdMetadata> for FsMetadata {
|
|
||||||
fn from(value: StdMetadata) -> Self {
|
|
||||||
Self {
|
|
||||||
kind: value.file_type().into(),
|
|
||||||
exists: true,
|
|
||||||
// FUTURE: Turn these into DateTime structs instead when that's implemented
|
|
||||||
created_at: system_time_to_timestamp(value.created()),
|
|
||||||
modified_at: system_time_to_timestamp(value.modified()),
|
|
||||||
accessed_at: system_time_to_timestamp(value.accessed()),
|
|
||||||
permissions: Some(FsPermissions::from(value.permissions())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn system_time_to_timestamp(res: IoResult<SystemTime>) -> Option<f64> {
|
|
||||||
match res {
|
|
||||||
Ok(t) => match t.duration_since(SystemTime::UNIX_EPOCH) {
|
|
||||||
Ok(d) => Some(d.as_secs_f64()),
|
|
||||||
Err(_) => None,
|
|
||||||
},
|
|
||||||
Err(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
mod copy;
|
|
||||||
mod metadata;
|
|
||||||
mod options;
|
|
||||||
|
|
||||||
pub use copy::copy;
|
|
||||||
pub use metadata::FsMetadata;
|
|
||||||
pub use options::FsWriteOptions;
|
|
|
@ -1,31 +0,0 @@
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct FsWriteOptions {
|
|
||||||
pub(crate) overwrite: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> FromLua<'lua> for FsWriteOptions {
|
|
||||||
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
|
||||||
Ok(match value {
|
|
||||||
LuaValue::Nil => Self { overwrite: false },
|
|
||||||
LuaValue::Boolean(b) => Self { overwrite: b },
|
|
||||||
LuaValue::Table(t) => {
|
|
||||||
let overwrite: Option<bool> = t.get("overwrite")?;
|
|
||||||
Self {
|
|
||||||
overwrite: overwrite.unwrap_or(false),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(LuaError::FromLuaConversionError {
|
|
||||||
from: value.type_name(),
|
|
||||||
to: "FsWriteOptions",
|
|
||||||
message: Some(format!(
|
|
||||||
"Invalid write options - expected boolean or table, got {}",
|
|
||||||
value.type_name()
|
|
||||||
)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
mod options;
|
|
||||||
|
|
||||||
pub use options::{LuauCompileOptions, LuauLoadOptions};
|
|
|
@ -1,116 +0,0 @@
|
||||||
use mlua::prelude::*;
|
|
||||||
use mlua::Compiler as LuaCompiler;
|
|
||||||
|
|
||||||
const DEFAULT_DEBUG_NAME: &str = "luau.load(...)";
|
|
||||||
|
|
||||||
pub struct LuauCompileOptions {
|
|
||||||
pub(crate) optimization_level: u8,
|
|
||||||
pub(crate) coverage_level: u8,
|
|
||||||
pub(crate) debug_level: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LuauCompileOptions {
|
|
||||||
pub fn into_compiler(self) -> LuaCompiler {
|
|
||||||
LuaCompiler::default()
|
|
||||||
.set_optimization_level(self.optimization_level)
|
|
||||||
.set_coverage_level(self.coverage_level)
|
|
||||||
.set_debug_level(self.debug_level)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for LuauCompileOptions {
|
|
||||||
fn default() -> Self {
|
|
||||||
// NOTE: This is the same as LuaCompiler::default() values, but they are
|
|
||||||
// not accessible from outside of mlua so we need to recreate them here.
|
|
||||||
Self {
|
|
||||||
optimization_level: 1,
|
|
||||||
coverage_level: 0,
|
|
||||||
debug_level: 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> FromLua<'lua> for LuauCompileOptions {
|
|
||||||
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
|
||||||
Ok(match value {
|
|
||||||
LuaValue::Nil => Self::default(),
|
|
||||||
LuaValue::Table(t) => {
|
|
||||||
let mut options = Self::default();
|
|
||||||
|
|
||||||
let get_and_check = |name: &'static str| -> LuaResult<Option<u8>> {
|
|
||||||
match t.get(name)? {
|
|
||||||
Some(n @ (0 | 1 | 2)) => Ok(Some(n)),
|
|
||||||
Some(n) => Err(LuaError::runtime(format!(
|
|
||||||
"'{name}' must be one of: 0, 1, or 2 - got {n}"
|
|
||||||
))),
|
|
||||||
None => Ok(None),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(optimization_level) = get_and_check("optimizationLevel")? {
|
|
||||||
options.optimization_level = optimization_level;
|
|
||||||
}
|
|
||||||
if let Some(coverage_level) = get_and_check("coverageLevel")? {
|
|
||||||
options.coverage_level = coverage_level;
|
|
||||||
}
|
|
||||||
if let Some(debug_level) = get_and_check("debugLevel")? {
|
|
||||||
options.debug_level = debug_level;
|
|
||||||
}
|
|
||||||
|
|
||||||
options
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(LuaError::FromLuaConversionError {
|
|
||||||
from: value.type_name(),
|
|
||||||
to: "CompileOptions",
|
|
||||||
message: Some(format!(
|
|
||||||
"Invalid compile options - expected table, got {}",
|
|
||||||
value.type_name()
|
|
||||||
)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct LuauLoadOptions {
|
|
||||||
pub(crate) debug_name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for LuauLoadOptions {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
debug_name: DEFAULT_DEBUG_NAME.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> FromLua<'lua> for LuauLoadOptions {
|
|
||||||
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
|
||||||
Ok(match value {
|
|
||||||
LuaValue::Nil => Self::default(),
|
|
||||||
LuaValue::Table(t) => {
|
|
||||||
let mut options = Self::default();
|
|
||||||
|
|
||||||
if let Some(debug_name) = t.get("debugName")? {
|
|
||||||
options.debug_name = debug_name;
|
|
||||||
}
|
|
||||||
|
|
||||||
options
|
|
||||||
}
|
|
||||||
LuaValue::String(s) => Self {
|
|
||||||
debug_name: s.to_string_lossy().to_string(),
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
return Err(LuaError::FromLuaConversionError {
|
|
||||||
from: value.type_name(),
|
|
||||||
to: "LoadOptions",
|
|
||||||
message: Some(format!(
|
|
||||||
"Invalid load options - expected string or table, got {}",
|
|
||||||
value.type_name()
|
|
||||||
)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
mod create;
|
|
||||||
|
|
||||||
pub mod async_ext;
|
|
||||||
pub mod fs;
|
|
||||||
pub mod luau;
|
|
||||||
pub mod net;
|
|
||||||
pub mod process;
|
|
||||||
pub mod serde;
|
|
||||||
pub mod stdio;
|
|
||||||
pub mod table;
|
|
||||||
pub mod task;
|
|
||||||
|
|
||||||
pub use create::create as create_lune_lua;
|
|
|
@ -1,49 +0,0 @@
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
use hyper::{header::HeaderName, http::HeaderValue, HeaderMap};
|
|
||||||
use reqwest::{IntoUrl, Method, RequestBuilder};
|
|
||||||
|
|
||||||
pub struct NetClientBuilder {
|
|
||||||
builder: reqwest::ClientBuilder,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NetClientBuilder {
|
|
||||||
pub fn new() -> NetClientBuilder {
|
|
||||||
Self {
|
|
||||||
builder: reqwest::ClientBuilder::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn headers<K, V>(mut self, headers: &[(K, V)]) -> LuaResult<Self>
|
|
||||||
where
|
|
||||||
K: AsRef<str>,
|
|
||||||
V: AsRef<[u8]>,
|
|
||||||
{
|
|
||||||
let mut map = HeaderMap::new();
|
|
||||||
for (key, val) in headers {
|
|
||||||
let hkey = HeaderName::from_str(key.as_ref()).into_lua_err()?;
|
|
||||||
let hval = HeaderValue::from_bytes(val.as_ref()).into_lua_err()?;
|
|
||||||
map.insert(hkey, hval);
|
|
||||||
}
|
|
||||||
self.builder = self.builder.default_headers(map);
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build(self) -> LuaResult<NetClient> {
|
|
||||||
let client = self.builder.build().into_lua_err()?;
|
|
||||||
Ok(NetClient(client))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct NetClient(reqwest::Client);
|
|
||||||
|
|
||||||
impl NetClient {
|
|
||||||
pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder {
|
|
||||||
self.0.request(method, url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LuaUserData for NetClient {}
|
|
|
@ -1,204 +0,0 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
use reqwest::Method;
|
|
||||||
|
|
||||||
// Net request config
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct RequestConfigOptions {
|
|
||||||
pub decompress: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for RequestConfigOptions {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self { decompress: true }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> FromLua<'lua> for RequestConfigOptions {
|
|
||||||
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
|
||||||
// Nil means default options, table means custom options
|
|
||||||
if let LuaValue::Nil = value {
|
|
||||||
return Ok(Self::default());
|
|
||||||
} else if let LuaValue::Table(tab) = value {
|
|
||||||
// Extract flags
|
|
||||||
let decompress = match tab.raw_get::<_, Option<bool>>("decompress") {
|
|
||||||
Ok(decomp) => Ok(decomp.unwrap_or(true)),
|
|
||||||
Err(_) => Err(LuaError::RuntimeError(
|
|
||||||
"Invalid option value for 'decompress' in request config options".to_string(),
|
|
||||||
)),
|
|
||||||
}?;
|
|
||||||
return Ok(Self { decompress });
|
|
||||||
}
|
|
||||||
// Anything else is invalid
|
|
||||||
Err(LuaError::FromLuaConversionError {
|
|
||||||
from: value.type_name(),
|
|
||||||
to: "RequestConfigOptions",
|
|
||||||
message: Some(format!(
|
|
||||||
"Invalid request config options - expected table or nil, got {}",
|
|
||||||
value.type_name()
|
|
||||||
)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct RequestConfig<'a> {
|
|
||||||
pub url: String,
|
|
||||||
pub method: Method,
|
|
||||||
pub query: HashMap<LuaString<'a>, LuaString<'a>>,
|
|
||||||
pub headers: HashMap<LuaString<'a>, LuaString<'a>>,
|
|
||||||
pub body: Option<Vec<u8>>,
|
|
||||||
pub options: RequestConfigOptions,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> FromLua<'lua> for RequestConfig<'lua> {
|
|
||||||
fn from_lua(value: LuaValue<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
|
|
||||||
// If we just got a string we assume its a GET request to a given url
|
|
||||||
if let LuaValue::String(s) = value {
|
|
||||||
return Ok(Self {
|
|
||||||
url: s.to_string_lossy().to_string(),
|
|
||||||
method: Method::GET,
|
|
||||||
query: HashMap::new(),
|
|
||||||
headers: HashMap::new(),
|
|
||||||
body: None,
|
|
||||||
options: Default::default(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// If we got a table we are able to configure the entire request
|
|
||||||
if let LuaValue::Table(tab) = value {
|
|
||||||
// Extract url
|
|
||||||
let url = match tab.raw_get::<_, LuaString>("url") {
|
|
||||||
Ok(config_url) => Ok(config_url.to_string_lossy().to_string()),
|
|
||||||
Err(_) => Err(LuaError::RuntimeError(
|
|
||||||
"Missing 'url' in request config".to_string(),
|
|
||||||
)),
|
|
||||||
}?;
|
|
||||||
// Extract method
|
|
||||||
let method = match tab.raw_get::<_, LuaString>("method") {
|
|
||||||
Ok(config_method) => config_method.to_string_lossy().trim().to_ascii_uppercase(),
|
|
||||||
Err(_) => "GET".to_string(),
|
|
||||||
};
|
|
||||||
// Extract query
|
|
||||||
let query = match tab.raw_get::<_, LuaTable>("query") {
|
|
||||||
Ok(config_headers) => {
|
|
||||||
let mut lua_headers = HashMap::new();
|
|
||||||
for pair in config_headers.pairs::<LuaString, LuaString>() {
|
|
||||||
let (key, value) = pair?.to_owned();
|
|
||||||
lua_headers.insert(key, value);
|
|
||||||
}
|
|
||||||
lua_headers
|
|
||||||
}
|
|
||||||
Err(_) => HashMap::new(),
|
|
||||||
};
|
|
||||||
// Extract headers
|
|
||||||
let headers = match tab.raw_get::<_, LuaTable>("headers") {
|
|
||||||
Ok(config_headers) => {
|
|
||||||
let mut lua_headers = HashMap::new();
|
|
||||||
for pair in config_headers.pairs::<LuaString, LuaString>() {
|
|
||||||
let (key, value) = pair?.to_owned();
|
|
||||||
lua_headers.insert(key, value);
|
|
||||||
}
|
|
||||||
lua_headers
|
|
||||||
}
|
|
||||||
Err(_) => HashMap::new(),
|
|
||||||
};
|
|
||||||
// Extract body
|
|
||||||
let body = match tab.raw_get::<_, LuaString>("body") {
|
|
||||||
Ok(config_body) => Some(config_body.as_bytes().to_owned()),
|
|
||||||
Err(_) => None,
|
|
||||||
};
|
|
||||||
// Convert method string into proper enum
|
|
||||||
let method = method.trim().to_ascii_uppercase();
|
|
||||||
let method = match method.as_ref() {
|
|
||||||
"GET" => Ok(Method::GET),
|
|
||||||
"POST" => Ok(Method::POST),
|
|
||||||
"PUT" => Ok(Method::PUT),
|
|
||||||
"DELETE" => Ok(Method::DELETE),
|
|
||||||
"HEAD" => Ok(Method::HEAD),
|
|
||||||
"OPTIONS" => Ok(Method::OPTIONS),
|
|
||||||
"PATCH" => Ok(Method::PATCH),
|
|
||||||
_ => Err(LuaError::RuntimeError(format!(
|
|
||||||
"Invalid request config method '{}'",
|
|
||||||
&method
|
|
||||||
))),
|
|
||||||
}?;
|
|
||||||
// Parse any extra options given
|
|
||||||
let options = match tab.raw_get::<_, LuaValue>("options") {
|
|
||||||
Ok(opts) => RequestConfigOptions::from_lua(opts, lua)?,
|
|
||||||
Err(_) => RequestConfigOptions::default(),
|
|
||||||
};
|
|
||||||
// All good, validated and we got what we need
|
|
||||||
return Ok(Self {
|
|
||||||
url,
|
|
||||||
method,
|
|
||||||
query,
|
|
||||||
headers,
|
|
||||||
body,
|
|
||||||
options,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
// Anything else is invalid
|
|
||||||
Err(LuaError::FromLuaConversionError {
|
|
||||||
from: value.type_name(),
|
|
||||||
to: "RequestConfig",
|
|
||||||
message: Some(format!(
|
|
||||||
"Invalid request config - expected string or table, got {}",
|
|
||||||
value.type_name()
|
|
||||||
)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Net serve config
|
|
||||||
|
|
||||||
pub struct ServeConfig<'a> {
|
|
||||||
pub handle_request: LuaFunction<'a>,
|
|
||||||
pub handle_web_socket: Option<LuaFunction<'a>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> FromLua<'lua> for ServeConfig<'lua> {
|
|
||||||
fn from_lua(value: LuaValue<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
|
|
||||||
let message = match &value {
|
|
||||||
LuaValue::Function(f) => {
|
|
||||||
return Ok(ServeConfig {
|
|
||||||
handle_request: f.clone(),
|
|
||||||
handle_web_socket: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
LuaValue::Table(t) => {
|
|
||||||
let handle_request: Option<LuaFunction> = t.raw_get("handleRequest")?;
|
|
||||||
let handle_web_socket: Option<LuaFunction> = t.raw_get("handleWebSocket")?;
|
|
||||||
if handle_request.is_some() || handle_web_socket.is_some() {
|
|
||||||
return Ok(ServeConfig {
|
|
||||||
handle_request: handle_request.unwrap_or_else(|| {
|
|
||||||
let chunk = r#"
|
|
||||||
return {
|
|
||||||
status = 426,
|
|
||||||
body = "Upgrade Required",
|
|
||||||
headers = {
|
|
||||||
Upgrade = "websocket",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
"#;
|
|
||||||
lua.load(chunk)
|
|
||||||
.into_function()
|
|
||||||
.expect("Failed to create default http responder function")
|
|
||||||
}),
|
|
||||||
handle_web_socket,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Some("Missing handleRequest and / or handleWebSocket".to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
Err(LuaError::FromLuaConversionError {
|
|
||||||
from: value.type_name(),
|
|
||||||
to: "ServeConfig",
|
|
||||||
message,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
mod client;
|
|
||||||
mod config;
|
|
||||||
mod response;
|
|
||||||
mod server;
|
|
||||||
mod websocket;
|
|
||||||
|
|
||||||
pub use client::{NetClient, NetClientBuilder};
|
|
||||||
pub use config::{RequestConfig, ServeConfig};
|
|
||||||
pub use response::{NetServeResponse, NetServeResponseKind};
|
|
||||||
pub use server::{NetLocalExec, NetService};
|
|
||||||
pub use websocket::NetWebSocket;
|
|
|
@ -1,106 +0,0 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use hyper::{Body, Response};
|
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub enum NetServeResponseKind {
|
|
||||||
PlainText,
|
|
||||||
Table,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct NetServeResponse {
|
|
||||||
kind: NetServeResponseKind,
|
|
||||||
status: u16,
|
|
||||||
headers: HashMap<String, Vec<u8>>,
|
|
||||||
body: Option<Vec<u8>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NetServeResponse {
|
|
||||||
pub fn into_response(self) -> LuaResult<Response<Body>> {
|
|
||||||
Ok(match self.kind {
|
|
||||||
NetServeResponseKind::PlainText => Response::builder()
|
|
||||||
.status(200)
|
|
||||||
.header("Content-Type", "text/plain")
|
|
||||||
.body(Body::from(self.body.unwrap()))
|
|
||||||
.into_lua_err()?,
|
|
||||||
NetServeResponseKind::Table => {
|
|
||||||
let mut response = Response::builder();
|
|
||||||
for (key, value) in self.headers {
|
|
||||||
response = response.header(&key, value);
|
|
||||||
}
|
|
||||||
response
|
|
||||||
.status(self.status)
|
|
||||||
.body(Body::from(self.body.unwrap_or_default()))
|
|
||||||
.into_lua_err()?
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> FromLua<'lua> for NetServeResponse {
|
|
||||||
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
|
||||||
match value {
|
|
||||||
// Plain strings from the handler are plaintext responses
|
|
||||||
LuaValue::String(s) => Ok(Self {
|
|
||||||
kind: NetServeResponseKind::PlainText,
|
|
||||||
status: 200,
|
|
||||||
headers: HashMap::new(),
|
|
||||||
body: Some(s.as_bytes().to_vec()),
|
|
||||||
}),
|
|
||||||
// Tables are more detailed responses with potential status, headers, body
|
|
||||||
LuaValue::Table(t) => {
|
|
||||||
let status: Option<u16> = t.get("status")?;
|
|
||||||
let headers: Option<LuaTable> = t.get("headers")?;
|
|
||||||
let body: Option<LuaString> = t.get("body")?;
|
|
||||||
|
|
||||||
let mut headers_map = HashMap::new();
|
|
||||||
if let Some(headers) = headers {
|
|
||||||
for pair in headers.pairs::<String, LuaString>() {
|
|
||||||
let (h, v) = pair?;
|
|
||||||
headers_map.insert(h, v.as_bytes().to_vec());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let body_bytes = body.map(|s| s.as_bytes().to_vec());
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
kind: NetServeResponseKind::Table,
|
|
||||||
status: status.unwrap_or(200),
|
|
||||||
headers: headers_map,
|
|
||||||
body: body_bytes,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// Anything else is an error
|
|
||||||
value => Err(LuaError::FromLuaConversionError {
|
|
||||||
from: value.type_name(),
|
|
||||||
to: "NetServeResponse",
|
|
||||||
message: None,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> IntoLua<'lua> for NetServeResponse {
|
|
||||||
fn into_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
|
|
||||||
if self.headers.len() > i32::MAX as usize {
|
|
||||||
return Err(LuaError::ToLuaConversionError {
|
|
||||||
from: "NetServeResponse",
|
|
||||||
to: "table",
|
|
||||||
message: Some("Too many header values".to_string()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let body = self.body.map(|b| lua.create_string(b)).transpose()?;
|
|
||||||
let headers = lua.create_table_with_capacity(0, self.headers.len())?;
|
|
||||||
for (key, value) in self.headers {
|
|
||||||
headers.set(key, lua.create_string(&value)?)?;
|
|
||||||
}
|
|
||||||
let table = lua.create_table_with_capacity(0, 3)?;
|
|
||||||
table.set("status", self.status)?;
|
|
||||||
table.set("headers", headers)?;
|
|
||||||
table.set("body", body)?;
|
|
||||||
table.set_readonly(true);
|
|
||||||
Ok(LuaValue::Table(table))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,178 +0,0 @@
|
||||||
use std::{
|
|
||||||
future::Future,
|
|
||||||
pin::Pin,
|
|
||||||
sync::Arc,
|
|
||||||
task::{Context, Poll},
|
|
||||||
};
|
|
||||||
|
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
use hyper::{body::to_bytes, server::conn::AddrStream, service::Service};
|
|
||||||
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_temp::{
|
|
||||||
lua::table::TableBuilder,
|
|
||||||
lua::task::{TaskScheduler, TaskSchedulerAsyncExt, TaskSchedulerScheduleExt},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{NetServeResponse, NetWebSocket};
|
|
||||||
|
|
||||||
// Hyper service implementation for net, lots of boilerplate here
|
|
||||||
// but make_svc and make_svc_function do not work for what we need
|
|
||||||
|
|
||||||
pub struct NetServiceInner(
|
|
||||||
&'static Lua,
|
|
||||||
Arc<LuaRegistryKey>,
|
|
||||||
Arc<Option<LuaRegistryKey>>,
|
|
||||||
);
|
|
||||||
|
|
||||||
impl Service<Request<Body>> for NetServiceInner {
|
|
||||||
type Response = Response<Body>;
|
|
||||||
type Error = LuaError;
|
|
||||||
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
|
|
||||||
|
|
||||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
|
||||||
Poll::Ready(Ok(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(&mut self, mut req: Request<Body>) -> Self::Future {
|
|
||||||
let lua = self.0;
|
|
||||||
if self.2.is_some() && is_ws_upgrade_request(&req) {
|
|
||||||
// Websocket upgrade request + websocket handler exists,
|
|
||||||
// we should now upgrade this connection to a websocket
|
|
||||||
// and then call our handler with a new socket object
|
|
||||||
let kopt = self.2.clone();
|
|
||||||
let key = kopt.as_ref().as_ref().unwrap();
|
|
||||||
let handler: LuaFunction = lua.registry_value(key).expect("Missing websocket handler");
|
|
||||||
let (response, ws) = ws_upgrade(&mut req, None).expect("Failed to upgrade websocket");
|
|
||||||
// This should be spawned as a registered task, otherwise
|
|
||||||
// the scheduler may exit early and cancel this even though what
|
|
||||||
// we want here is a long-running task that keeps the program alive
|
|
||||||
let sched = lua
|
|
||||||
.app_data_ref::<&TaskScheduler>()
|
|
||||||
.expect("Missing task scheduler");
|
|
||||||
let task = sched.register_background_task();
|
|
||||||
task::spawn_local(async move {
|
|
||||||
// Create our new full websocket object, then
|
|
||||||
// schedule our handler to get called asap
|
|
||||||
let ws = ws.await.into_lua_err()?;
|
|
||||||
let sock = NetWebSocket::new(ws).into_lua_table(lua)?;
|
|
||||||
let sched = lua
|
|
||||||
.app_data_ref::<&TaskScheduler>()
|
|
||||||
.expect("Missing task scheduler");
|
|
||||||
let result = sched.schedule_blocking(
|
|
||||||
lua.create_thread(handler)?,
|
|
||||||
LuaMultiValue::from_vec(vec![LuaValue::Table(sock)]),
|
|
||||||
);
|
|
||||||
task.unregister(Ok(()));
|
|
||||||
result
|
|
||||||
});
|
|
||||||
Box::pin(async move { Ok(response) })
|
|
||||||
} else {
|
|
||||||
// Got a normal http request or no websocket handler
|
|
||||||
// exists, just call the http request handler
|
|
||||||
let key = self.1.clone();
|
|
||||||
let (parts, body) = req.into_parts();
|
|
||||||
Box::pin(async move {
|
|
||||||
// Convert request body into bytes, extract handler
|
|
||||||
let bytes = to_bytes(body).await.into_lua_err()?;
|
|
||||||
let handler: LuaFunction = lua.registry_value(&key)?;
|
|
||||||
// Create a readonly table for the request query params
|
|
||||||
let query_params = TableBuilder::new(lua)?
|
|
||||||
.with_values(
|
|
||||||
parts
|
|
||||||
.uri
|
|
||||||
.query()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.split('&')
|
|
||||||
.filter_map(|q| q.split_once('='))
|
|
||||||
.collect(),
|
|
||||||
)?
|
|
||||||
.build_readonly()?;
|
|
||||||
// Do the same for headers
|
|
||||||
let header_map = TableBuilder::new(lua)?
|
|
||||||
.with_values(
|
|
||||||
parts
|
|
||||||
.headers
|
|
||||||
.iter()
|
|
||||||
.map(|(name, value)| {
|
|
||||||
(name.to_string(), value.to_str().unwrap().to_string())
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
)?
|
|
||||||
.build_readonly()?;
|
|
||||||
// Create a readonly table with request info to pass to the handler
|
|
||||||
let request = TableBuilder::new(lua)?
|
|
||||||
.with_value("path", parts.uri.path())?
|
|
||||||
.with_value("query", query_params)?
|
|
||||||
.with_value("method", parts.method.as_str())?
|
|
||||||
.with_value("headers", header_map)?
|
|
||||||
.with_value("body", lua.create_string(&bytes)?)?
|
|
||||||
.build_readonly()?;
|
|
||||||
let response: LuaResult<NetServeResponse> = handler.call(request);
|
|
||||||
// Send below errors to task scheduler so that they can emit properly
|
|
||||||
let lua_error = match response {
|
|
||||||
Ok(r) => match r.into_response() {
|
|
||||||
Ok(res) => return Ok(res),
|
|
||||||
Err(err) => err,
|
|
||||||
},
|
|
||||||
Err(err) => err,
|
|
||||||
};
|
|
||||||
lua.app_data_ref::<&TaskScheduler>()
|
|
||||||
.expect("Missing task scheduler")
|
|
||||||
.forward_lua_error(lua_error);
|
|
||||||
Ok(Response::builder()
|
|
||||||
.status(500)
|
|
||||||
.body(Body::from("Internal Server Error"))
|
|
||||||
.unwrap())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct NetService(
|
|
||||||
&'static Lua,
|
|
||||||
Arc<LuaRegistryKey>,
|
|
||||||
Arc<Option<LuaRegistryKey>>,
|
|
||||||
);
|
|
||||||
|
|
||||||
impl NetService {
|
|
||||||
pub fn new(
|
|
||||||
lua: &'static Lua,
|
|
||||||
callback_http: LuaRegistryKey,
|
|
||||||
callback_websocket: Option<LuaRegistryKey>,
|
|
||||||
) -> Self {
|
|
||||||
Self(lua, Arc::new(callback_http), Arc::new(callback_websocket))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Service<&AddrStream> for NetService {
|
|
||||||
type Response = NetServiceInner;
|
|
||||||
type Error = hyper::Error;
|
|
||||||
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
|
|
||||||
|
|
||||||
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
|
|
||||||
Poll::Ready(Ok(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(&mut self, _: &AddrStream) -> Self::Future {
|
|
||||||
let lua = self.0;
|
|
||||||
let key1 = self.1.clone();
|
|
||||||
let key2 = self.2.clone();
|
|
||||||
Box::pin(async move { Ok(NetServiceInner(lua, key1, key2)) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub struct NetLocalExec;
|
|
||||||
|
|
||||||
impl<F> hyper::rt::Executor<F> for NetLocalExec
|
|
||||||
where
|
|
||||||
F: std::future::Future + 'static, // not requiring `Send`
|
|
||||||
{
|
|
||||||
fn execute(&self, fut: F) {
|
|
||||||
task::spawn_local(fut);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,229 +0,0 @@
|
||||||
use std::{cell::Cell, sync::Arc};
|
|
||||||
|
|
||||||
use hyper::upgrade::Upgraded;
|
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
use futures_util::{
|
|
||||||
stream::{SplitSink, SplitStream},
|
|
||||||
SinkExt, StreamExt,
|
|
||||||
};
|
|
||||||
use tokio::{
|
|
||||||
io::{AsyncRead, AsyncWrite},
|
|
||||||
net::TcpStream,
|
|
||||||
sync::Mutex as AsyncMutex,
|
|
||||||
};
|
|
||||||
|
|
||||||
use hyper_tungstenite::{
|
|
||||||
tungstenite::{
|
|
||||||
protocol::{frame::coding::CloseCode as WsCloseCode, CloseFrame as WsCloseFrame},
|
|
||||||
Message as WsMessage,
|
|
||||||
},
|
|
||||||
WebSocketStream,
|
|
||||||
};
|
|
||||||
use tokio_tungstenite::MaybeTlsStream;
|
|
||||||
|
|
||||||
use crate::lune_temp::lua::table::TableBuilder;
|
|
||||||
|
|
||||||
const WEB_SOCKET_IMPL_LUA: &str = r#"
|
|
||||||
return freeze(setmetatable({
|
|
||||||
close = function(...)
|
|
||||||
return close(websocket, ...)
|
|
||||||
end,
|
|
||||||
send = function(...)
|
|
||||||
return send(websocket, ...)
|
|
||||||
end,
|
|
||||||
next = function(...)
|
|
||||||
return next(websocket, ...)
|
|
||||||
end,
|
|
||||||
}, {
|
|
||||||
__index = function(self, key)
|
|
||||||
if key == "closeCode" then
|
|
||||||
return close_code(websocket)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
}))
|
|
||||||
"#;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct NetWebSocket<T> {
|
|
||||||
close_code: Arc<Cell<Option<u16>>>,
|
|
||||||
read_stream: Arc<AsyncMutex<SplitStream<WebSocketStream<T>>>>,
|
|
||||||
write_stream: Arc<AsyncMutex<SplitSink<WebSocketStream<T>, WsMessage>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Clone for NetWebSocket<T> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
close_code: Arc::clone(&self.close_code),
|
|
||||||
read_stream: Arc::clone(&self.read_stream),
|
|
||||||
write_stream: Arc::clone(&self.write_stream),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> NetWebSocket<T>
|
|
||||||
where
|
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
|
||||||
{
|
|
||||||
pub fn new(value: WebSocketStream<T>) -> Self {
|
|
||||||
let (write, read) = value.split();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
close_code: Arc::new(Cell::new(None)),
|
|
||||||
read_stream: Arc::new(AsyncMutex::new(read)),
|
|
||||||
write_stream: Arc::new(AsyncMutex::new(write)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_lua_table_with_env<'lua>(
|
|
||||||
lua: &'lua Lua,
|
|
||||||
env: LuaTable<'lua>,
|
|
||||||
) -> LuaResult<LuaTable<'lua>> {
|
|
||||||
lua.load(WEB_SOCKET_IMPL_LUA)
|
|
||||||
.set_name("websocket")
|
|
||||||
.set_environment(env)
|
|
||||||
.eval()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type NetWebSocketStreamClient = MaybeTlsStream<TcpStream>;
|
|
||||||
impl NetWebSocket<NetWebSocketStreamClient> {
|
|
||||||
pub fn into_lua_table(self, lua: &'static Lua) -> LuaResult<LuaTable> {
|
|
||||||
let socket_env = TableBuilder::new(lua)?
|
|
||||||
.with_value("websocket", self)?
|
|
||||||
.with_function("close_code", close_code::<NetWebSocketStreamClient>)?
|
|
||||||
.with_async_function("close", close::<NetWebSocketStreamClient>)?
|
|
||||||
.with_async_function("send", send::<NetWebSocketStreamClient>)?
|
|
||||||
.with_async_function("next", next::<NetWebSocketStreamClient>)?
|
|
||||||
.with_value(
|
|
||||||
"setmetatable",
|
|
||||||
lua.named_registry_value::<LuaFunction>("tab.setmeta")?,
|
|
||||||
)?
|
|
||||||
.with_value(
|
|
||||||
"freeze",
|
|
||||||
lua.named_registry_value::<LuaFunction>("tab.freeze")?,
|
|
||||||
)?
|
|
||||||
.build_readonly()?;
|
|
||||||
Self::into_lua_table_with_env(lua, socket_env)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type NetWebSocketStreamServer = Upgraded;
|
|
||||||
impl NetWebSocket<NetWebSocketStreamServer> {
|
|
||||||
pub fn into_lua_table(self, lua: &'static Lua) -> LuaResult<LuaTable> {
|
|
||||||
let socket_env = TableBuilder::new(lua)?
|
|
||||||
.with_value("websocket", self)?
|
|
||||||
.with_function("close_code", close_code::<NetWebSocketStreamServer>)?
|
|
||||||
.with_async_function("close", close::<NetWebSocketStreamServer>)?
|
|
||||||
.with_async_function("send", send::<NetWebSocketStreamServer>)?
|
|
||||||
.with_async_function("next", next::<NetWebSocketStreamServer>)?
|
|
||||||
.with_value(
|
|
||||||
"setmetatable",
|
|
||||||
lua.named_registry_value::<LuaFunction>("tab.setmeta")?,
|
|
||||||
)?
|
|
||||||
.with_value(
|
|
||||||
"freeze",
|
|
||||||
lua.named_registry_value::<LuaFunction>("tab.freeze")?,
|
|
||||||
)?
|
|
||||||
.build_readonly()?;
|
|
||||||
Self::into_lua_table_with_env(lua, socket_env)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> LuaUserData for NetWebSocket<T> {}
|
|
||||||
|
|
||||||
fn close_code<'lua, T>(
|
|
||||||
_lua: &'lua Lua,
|
|
||||||
socket: LuaUserDataRef<'lua, NetWebSocket<T>>,
|
|
||||||
) -> LuaResult<LuaValue<'lua>>
|
|
||||||
where
|
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
|
||||||
{
|
|
||||||
Ok(match socket.close_code.get() {
|
|
||||||
Some(code) => LuaValue::Number(code as f64),
|
|
||||||
None => LuaValue::Nil,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn close<'lua, T>(
|
|
||||||
_lua: &'lua Lua,
|
|
||||||
(socket, code): (LuaUserDataRef<'lua, NetWebSocket<T>>, Option<u16>),
|
|
||||||
) -> LuaResult<()>
|
|
||||||
where
|
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
|
||||||
{
|
|
||||||
let mut ws = socket.write_stream.lock().await;
|
|
||||||
|
|
||||||
ws.send(WsMessage::Close(Some(WsCloseFrame {
|
|
||||||
code: match code {
|
|
||||||
Some(code) if (1000..=4999).contains(&code) => WsCloseCode::from(code),
|
|
||||||
Some(code) => {
|
|
||||||
return Err(LuaError::RuntimeError(format!(
|
|
||||||
"Close code must be between 1000 and 4999, got {code}"
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
None => WsCloseCode::Normal,
|
|
||||||
},
|
|
||||||
reason: "".into(),
|
|
||||||
})))
|
|
||||||
.await
|
|
||||||
.into_lua_err()?;
|
|
||||||
|
|
||||||
let res = ws.close();
|
|
||||||
res.await.into_lua_err()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send<'lua, T>(
|
|
||||||
_lua: &'lua Lua,
|
|
||||||
(socket, string, as_binary): (
|
|
||||||
LuaUserDataRef<'lua, NetWebSocket<T>>,
|
|
||||||
LuaString<'lua>,
|
|
||||||
Option<bool>,
|
|
||||||
),
|
|
||||||
) -> LuaResult<()>
|
|
||||||
where
|
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
|
||||||
{
|
|
||||||
let msg = if matches!(as_binary, Some(true)) {
|
|
||||||
WsMessage::Binary(string.as_bytes().to_vec())
|
|
||||||
} else {
|
|
||||||
let s = string.to_str().into_lua_err()?;
|
|
||||||
WsMessage::Text(s.to_string())
|
|
||||||
};
|
|
||||||
let mut ws = socket.write_stream.lock().await;
|
|
||||||
ws.send(msg).await.into_lua_err()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn next<'lua, T>(
|
|
||||||
lua: &'lua Lua,
|
|
||||||
socket: LuaUserDataRef<'lua, NetWebSocket<T>>,
|
|
||||||
) -> LuaResult<LuaValue<'lua>>
|
|
||||||
where
|
|
||||||
T: AsyncRead + AsyncWrite + Unpin,
|
|
||||||
{
|
|
||||||
let mut ws = socket.read_stream.lock().await;
|
|
||||||
let item = ws.next().await.transpose().into_lua_err();
|
|
||||||
let msg = match item {
|
|
||||||
Ok(Some(WsMessage::Close(msg))) => {
|
|
||||||
if let Some(msg) = &msg {
|
|
||||||
socket.close_code.replace(Some(msg.code.into()));
|
|
||||||
}
|
|
||||||
Ok(Some(WsMessage::Close(msg)))
|
|
||||||
}
|
|
||||||
val => val,
|
|
||||||
}?;
|
|
||||||
while let Some(msg) = &msg {
|
|
||||||
let msg_string_opt = match msg {
|
|
||||||
WsMessage::Binary(bin) => Some(lua.create_string(bin)?),
|
|
||||||
WsMessage::Text(txt) => Some(lua.create_string(txt)?),
|
|
||||||
// Stop waiting for next message if we get a close message
|
|
||||||
WsMessage::Close(_) => return Ok(LuaValue::Nil),
|
|
||||||
// Ignore ping/pong/frame messages, they are handled by tungstenite
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
if let Some(msg_string) = msg_string_opt {
|
|
||||||
return Ok(LuaValue::String(msg_string));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(LuaValue::Nil)
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
use std::process::ExitStatus;
|
|
||||||
|
|
||||||
use mlua::prelude::*;
|
|
||||||
use tokio::{io, process::Child, task};
|
|
||||||
|
|
||||||
mod tee_writer;
|
|
||||||
use tee_writer::AsyncTeeWriter;
|
|
||||||
|
|
||||||
pub async fn pipe_and_inherit_child_process_stdio(
|
|
||||||
mut child: Child,
|
|
||||||
) -> LuaResult<(ExitStatus, Vec<u8>, Vec<u8>)> {
|
|
||||||
let mut child_stdout = child.stdout.take().unwrap();
|
|
||||||
let mut child_stderr = child.stderr.take().unwrap();
|
|
||||||
|
|
||||||
/*
|
|
||||||
NOTE: We do not need to register these
|
|
||||||
independent tasks spawning in the scheduler
|
|
||||||
|
|
||||||
This function is only used by `process.spawn` which in
|
|
||||||
turn registers a task with the scheduler that awaits this
|
|
||||||
*/
|
|
||||||
|
|
||||||
let stdout_thread = task::spawn(async move {
|
|
||||||
let mut stdout = io::stdout();
|
|
||||||
let mut tee = AsyncTeeWriter::new(&mut stdout);
|
|
||||||
|
|
||||||
io::copy(&mut child_stdout, &mut tee).await.into_lua_err()?;
|
|
||||||
|
|
||||||
Ok::<_, LuaError>(tee.into_vec())
|
|
||||||
});
|
|
||||||
|
|
||||||
let stderr_thread = task::spawn(async move {
|
|
||||||
let mut stderr = io::stderr();
|
|
||||||
let mut tee = AsyncTeeWriter::new(&mut stderr);
|
|
||||||
|
|
||||||
io::copy(&mut child_stderr, &mut tee).await.into_lua_err()?;
|
|
||||||
|
|
||||||
Ok::<_, LuaError>(tee.into_vec())
|
|
||||||
});
|
|
||||||
|
|
||||||
let status = child.wait().await.expect("Child process failed to start");
|
|
||||||
|
|
||||||
let stdout_buffer = stdout_thread.await.expect("Tee writer for stdout errored");
|
|
||||||
let stderr_buffer = stderr_thread.await.expect("Tee writer for stderr errored");
|
|
||||||
|
|
||||||
Ok::<_, LuaError>((status, stdout_buffer?, stderr_buffer?))
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
use std::{
|
|
||||||
io::Write,
|
|
||||||
pin::Pin,
|
|
||||||
task::{Context, Poll},
|
|
||||||
};
|
|
||||||
|
|
||||||
use pin_project::pin_project;
|
|
||||||
use tokio::io::{self, AsyncWrite};
|
|
||||||
|
|
||||||
#[pin_project]
|
|
||||||
pub struct AsyncTeeWriter<'a, W>
|
|
||||||
where
|
|
||||||
W: AsyncWrite + Unpin,
|
|
||||||
{
|
|
||||||
#[pin]
|
|
||||||
writer: &'a mut W,
|
|
||||||
buffer: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, W> AsyncTeeWriter<'a, W>
|
|
||||||
where
|
|
||||||
W: AsyncWrite + Unpin,
|
|
||||||
{
|
|
||||||
pub fn new(writer: &'a mut W) -> Self {
|
|
||||||
Self {
|
|
||||||
writer,
|
|
||||||
buffer: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_vec(self) -> Vec<u8> {
|
|
||||||
self.buffer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, W> AsyncWrite for AsyncTeeWriter<'a, W>
|
|
||||||
where
|
|
||||||
W: AsyncWrite + Unpin,
|
|
||||||
{
|
|
||||||
fn poll_write(
|
|
||||||
self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
buf: &[u8],
|
|
||||||
) -> Poll<io::Result<usize>> {
|
|
||||||
let mut this = self.project();
|
|
||||||
match this.writer.as_mut().poll_write(cx, buf) {
|
|
||||||
Poll::Ready(res) => {
|
|
||||||
this.buffer
|
|
||||||
.write_all(buf)
|
|
||||||
.expect("Failed to write to internal tee buffer");
|
|
||||||
Poll::Ready(res)
|
|
||||||
}
|
|
||||||
Poll::Pending => Poll::Pending,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
||||||
self.project().writer.as_mut().poll_flush(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
||||||
self.project().writer.as_mut().poll_shutdown(cx)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,162 +0,0 @@
|
||||||
use lz4_flex::{compress_prepend_size, decompress_size_prepended};
|
|
||||||
use mlua::prelude::*;
|
|
||||||
use tokio::{
|
|
||||||
io::{copy, BufReader},
|
|
||||||
task,
|
|
||||||
};
|
|
||||||
|
|
||||||
use async_compression::{
|
|
||||||
tokio::bufread::{
|
|
||||||
BrotliDecoder, BrotliEncoder, GzipDecoder, GzipEncoder, ZlibDecoder, ZlibEncoder,
|
|
||||||
},
|
|
||||||
Level::Best as CompressionQuality,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub enum CompressDecompressFormat {
|
|
||||||
Brotli,
|
|
||||||
GZip,
|
|
||||||
LZ4,
|
|
||||||
ZLib,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
impl CompressDecompressFormat {
|
|
||||||
pub fn detect_from_bytes(bytes: impl AsRef<[u8]>) -> Option<Self> {
|
|
||||||
match bytes.as_ref() {
|
|
||||||
// https://github.com/PSeitz/lz4_flex/blob/main/src/frame/header.rs#L28
|
|
||||||
b if b.len() >= 4
|
|
||||||
&& matches!(
|
|
||||||
u32::from_le_bytes(b[0..4].try_into().unwrap()),
|
|
||||||
0x184D2204 | 0x184C2102
|
|
||||||
) =>
|
|
||||||
{
|
|
||||||
Some(Self::LZ4)
|
|
||||||
}
|
|
||||||
// https://github.com/dropbox/rust-brotli/blob/master/src/enc/brotli_bit_stream.rs#L2805
|
|
||||||
b if b.len() >= 4
|
|
||||||
&& matches!(
|
|
||||||
b[0..3],
|
|
||||||
[0xE1, 0x97, 0x81] | [0xE1, 0x97, 0x82] | [0xE1, 0x97, 0x80]
|
|
||||||
) =>
|
|
||||||
{
|
|
||||||
Some(Self::Brotli)
|
|
||||||
}
|
|
||||||
// https://github.com/rust-lang/flate2-rs/blob/main/src/gz/mod.rs#L135
|
|
||||||
b if b.len() >= 3 && matches!(b[0..3], [0x1F, 0x8B, 0x08]) => Some(Self::GZip),
|
|
||||||
// https://stackoverflow.com/a/43170354
|
|
||||||
b if b.len() >= 2
|
|
||||||
&& matches!(
|
|
||||||
b[0..2],
|
|
||||||
[0x78, 0x01] | [0x78, 0x5E] | [0x78, 0x9C] | [0x78, 0xDA]
|
|
||||||
) =>
|
|
||||||
{
|
|
||||||
Some(Self::ZLib)
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn detect_from_header_str(header: impl AsRef<str>) -> Option<Self> {
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding#directives
|
|
||||||
match header.as_ref().to_ascii_lowercase().trim() {
|
|
||||||
"br" | "brotli" => Some(Self::Brotli),
|
|
||||||
"deflate" => Some(Self::ZLib),
|
|
||||||
"gz" | "gzip" => Some(Self::GZip),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> FromLua<'lua> for CompressDecompressFormat {
|
|
||||||
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
|
||||||
if let LuaValue::String(s) = &value {
|
|
||||||
match s.to_string_lossy().to_ascii_lowercase().trim() {
|
|
||||||
"brotli" => Ok(Self::Brotli),
|
|
||||||
"gzip" => Ok(Self::GZip),
|
|
||||||
"lz4" => Ok(Self::LZ4),
|
|
||||||
"zlib" => Ok(Self::ZLib),
|
|
||||||
kind => Err(LuaError::FromLuaConversionError {
|
|
||||||
from: value.type_name(),
|
|
||||||
to: "CompressDecompressFormat",
|
|
||||||
message: Some(format!(
|
|
||||||
"Invalid format '{kind}', valid formats are: brotli, gzip, lz4, zlib"
|
|
||||||
)),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(LuaError::FromLuaConversionError {
|
|
||||||
from: value.type_name(),
|
|
||||||
to: "CompressDecompressFormat",
|
|
||||||
message: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn compress<'lua>(
|
|
||||||
format: CompressDecompressFormat,
|
|
||||||
source: impl AsRef<[u8]>,
|
|
||||||
) -> LuaResult<Vec<u8>> {
|
|
||||||
if let CompressDecompressFormat::LZ4 = format {
|
|
||||||
let source = source.as_ref().to_vec();
|
|
||||||
return task::spawn_blocking(move || compress_prepend_size(&source))
|
|
||||||
.await
|
|
||||||
.into_lua_err();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut bytes = Vec::new();
|
|
||||||
let reader = BufReader::new(source.as_ref());
|
|
||||||
|
|
||||||
match format {
|
|
||||||
CompressDecompressFormat::Brotli => {
|
|
||||||
let mut encoder = BrotliEncoder::with_quality(reader, CompressionQuality);
|
|
||||||
copy(&mut encoder, &mut bytes).await?;
|
|
||||||
}
|
|
||||||
CompressDecompressFormat::GZip => {
|
|
||||||
let mut encoder = GzipEncoder::with_quality(reader, CompressionQuality);
|
|
||||||
copy(&mut encoder, &mut bytes).await?;
|
|
||||||
}
|
|
||||||
CompressDecompressFormat::ZLib => {
|
|
||||||
let mut encoder = ZlibEncoder::with_quality(reader, CompressionQuality);
|
|
||||||
copy(&mut encoder, &mut bytes).await?;
|
|
||||||
}
|
|
||||||
CompressDecompressFormat::LZ4 => unreachable!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn decompress<'lua>(
|
|
||||||
format: CompressDecompressFormat,
|
|
||||||
source: impl AsRef<[u8]>,
|
|
||||||
) -> LuaResult<Vec<u8>> {
|
|
||||||
if let CompressDecompressFormat::LZ4 = format {
|
|
||||||
let source = source.as_ref().to_vec();
|
|
||||||
return task::spawn_blocking(move || decompress_size_prepended(&source))
|
|
||||||
.await
|
|
||||||
.into_lua_err()?
|
|
||||||
.into_lua_err();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut bytes = Vec::new();
|
|
||||||
let reader = BufReader::new(source.as_ref());
|
|
||||||
|
|
||||||
match format {
|
|
||||||
CompressDecompressFormat::Brotli => {
|
|
||||||
let mut decoder = BrotliDecoder::new(reader);
|
|
||||||
copy(&mut decoder, &mut bytes).await?;
|
|
||||||
}
|
|
||||||
CompressDecompressFormat::GZip => {
|
|
||||||
let mut decoder = GzipDecoder::new(reader);
|
|
||||||
copy(&mut decoder, &mut bytes).await?;
|
|
||||||
}
|
|
||||||
CompressDecompressFormat::ZLib => {
|
|
||||||
let mut decoder = ZlibDecoder::new(reader);
|
|
||||||
copy(&mut decoder, &mut bytes).await?;
|
|
||||||
}
|
|
||||||
CompressDecompressFormat::LZ4 => unreachable!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(bytes)
|
|
||||||
}
|
|
|
@ -1,133 +0,0 @@
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
use serde_json::Value as JsonValue;
|
|
||||||
use serde_yaml::Value as YamlValue;
|
|
||||||
use toml::Value as TomlValue;
|
|
||||||
|
|
||||||
const LUA_SERIALIZE_OPTIONS: LuaSerializeOptions = LuaSerializeOptions::new()
|
|
||||||
.set_array_metatable(false)
|
|
||||||
.serialize_none_to_null(false)
|
|
||||||
.serialize_unit_to_null(false);
|
|
||||||
|
|
||||||
const LUA_DESERIALIZE_OPTIONS: LuaDeserializeOptions = LuaDeserializeOptions::new()
|
|
||||||
.deny_recursive_tables(false)
|
|
||||||
.deny_unsupported_types(true);
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub enum EncodeDecodeFormat {
|
|
||||||
Json,
|
|
||||||
Yaml,
|
|
||||||
Toml,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> FromLua<'lua> for EncodeDecodeFormat {
|
|
||||||
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
|
||||||
if let LuaValue::String(s) = &value {
|
|
||||||
match s.to_string_lossy().to_ascii_lowercase().trim() {
|
|
||||||
"json" => Ok(Self::Json),
|
|
||||||
"yaml" => Ok(Self::Yaml),
|
|
||||||
"toml" => Ok(Self::Toml),
|
|
||||||
kind => Err(LuaError::FromLuaConversionError {
|
|
||||||
from: value.type_name(),
|
|
||||||
to: "EncodeDecodeFormat",
|
|
||||||
message: Some(format!(
|
|
||||||
"Invalid format '{kind}', valid formats are: json, yaml, toml"
|
|
||||||
)),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(LuaError::FromLuaConversionError {
|
|
||||||
from: value.type_name(),
|
|
||||||
to: "EncodeDecodeFormat",
|
|
||||||
message: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct EncodeDecodeConfig {
|
|
||||||
pub format: EncodeDecodeFormat,
|
|
||||||
pub pretty: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EncodeDecodeConfig {
|
|
||||||
pub fn serialize_to_string<'lua>(
|
|
||||||
self,
|
|
||||||
lua: &'lua Lua,
|
|
||||||
value: LuaValue<'lua>,
|
|
||||||
) -> LuaResult<LuaString<'lua>> {
|
|
||||||
let bytes = match self.format {
|
|
||||||
EncodeDecodeFormat::Json => {
|
|
||||||
let serialized: JsonValue = lua.from_value_with(value, LUA_DESERIALIZE_OPTIONS)?;
|
|
||||||
if self.pretty {
|
|
||||||
serde_json::to_vec_pretty(&serialized).into_lua_err()?
|
|
||||||
} else {
|
|
||||||
serde_json::to_vec(&serialized).into_lua_err()?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EncodeDecodeFormat::Yaml => {
|
|
||||||
let serialized: YamlValue = lua.from_value_with(value, LUA_DESERIALIZE_OPTIONS)?;
|
|
||||||
let mut writer = Vec::with_capacity(128);
|
|
||||||
serde_yaml::to_writer(&mut writer, &serialized).into_lua_err()?;
|
|
||||||
writer
|
|
||||||
}
|
|
||||||
EncodeDecodeFormat::Toml => {
|
|
||||||
let serialized: TomlValue = lua.from_value_with(value, LUA_DESERIALIZE_OPTIONS)?;
|
|
||||||
let s = if self.pretty {
|
|
||||||
toml::to_string_pretty(&serialized).into_lua_err()?
|
|
||||||
} else {
|
|
||||||
toml::to_string(&serialized).into_lua_err()?
|
|
||||||
};
|
|
||||||
s.as_bytes().to_vec()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
lua.create_string(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deserialize_from_string<'lua>(
|
|
||||||
self,
|
|
||||||
lua: &'lua Lua,
|
|
||||||
string: LuaString<'lua>,
|
|
||||||
) -> LuaResult<LuaValue<'lua>> {
|
|
||||||
let bytes = string.as_bytes();
|
|
||||||
match self.format {
|
|
||||||
EncodeDecodeFormat::Json => {
|
|
||||||
let value: JsonValue = serde_json::from_slice(bytes).into_lua_err()?;
|
|
||||||
lua.to_value_with(&value, LUA_SERIALIZE_OPTIONS)
|
|
||||||
}
|
|
||||||
EncodeDecodeFormat::Yaml => {
|
|
||||||
let value: YamlValue = serde_yaml::from_slice(bytes).into_lua_err()?;
|
|
||||||
lua.to_value_with(&value, LUA_SERIALIZE_OPTIONS)
|
|
||||||
}
|
|
||||||
EncodeDecodeFormat::Toml => {
|
|
||||||
if let Ok(s) = string.to_str() {
|
|
||||||
let value: TomlValue = toml::from_str(s).into_lua_err()?;
|
|
||||||
lua.to_value_with(&value, LUA_SERIALIZE_OPTIONS)
|
|
||||||
} else {
|
|
||||||
Err(LuaError::RuntimeError(
|
|
||||||
"TOML must be valid utf-8".to_string(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<EncodeDecodeFormat> for EncodeDecodeConfig {
|
|
||||||
fn from(format: EncodeDecodeFormat) -> Self {
|
|
||||||
Self {
|
|
||||||
format,
|
|
||||||
pretty: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<(EncodeDecodeFormat, bool)> for EncodeDecodeConfig {
|
|
||||||
fn from(value: (EncodeDecodeFormat, bool)) -> Self {
|
|
||||||
Self {
|
|
||||||
format: value.0,
|
|
||||||
pretty: value.1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
mod compress_decompress;
|
|
||||||
mod encode_decode;
|
|
||||||
|
|
||||||
pub use compress_decompress::{compress, decompress, CompressDecompressFormat};
|
|
||||||
pub use encode_decode::{EncodeDecodeConfig, EncodeDecodeFormat};
|
|
|
@ -1,473 +0,0 @@
|
||||||
use std::fmt::Write;
|
|
||||||
|
|
||||||
use console::{colors_enabled, set_colors_enabled, style, Style};
|
|
||||||
use mlua::prelude::*;
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
|
|
||||||
use crate::lune_temp::lua::task::TaskReference;
|
|
||||||
|
|
||||||
const MAX_FORMAT_DEPTH: usize = 4;
|
|
||||||
|
|
||||||
const INDENT: &str = " ";
|
|
||||||
|
|
||||||
pub const STYLE_RESET_STR: &str = "\x1b[0m";
|
|
||||||
|
|
||||||
// Colors
|
|
||||||
pub static COLOR_BLACK: Lazy<Style> = Lazy::new(|| Style::new().black());
|
|
||||||
pub static COLOR_RED: Lazy<Style> = Lazy::new(|| Style::new().red());
|
|
||||||
pub static COLOR_GREEN: Lazy<Style> = Lazy::new(|| Style::new().green());
|
|
||||||
pub static COLOR_YELLOW: Lazy<Style> = Lazy::new(|| Style::new().yellow());
|
|
||||||
pub static COLOR_BLUE: Lazy<Style> = Lazy::new(|| Style::new().blue());
|
|
||||||
pub static COLOR_PURPLE: Lazy<Style> = Lazy::new(|| Style::new().magenta());
|
|
||||||
pub static COLOR_CYAN: Lazy<Style> = Lazy::new(|| Style::new().cyan());
|
|
||||||
pub static COLOR_WHITE: Lazy<Style> = Lazy::new(|| Style::new().white());
|
|
||||||
|
|
||||||
// Styles
|
|
||||||
pub static STYLE_BOLD: Lazy<Style> = Lazy::new(|| Style::new().bold());
|
|
||||||
pub static STYLE_DIM: Lazy<Style> = Lazy::new(|| Style::new().dim());
|
|
||||||
|
|
||||||
fn can_be_plain_lua_table_key(s: &LuaString) -> bool {
|
|
||||||
let str = s.to_string_lossy().to_string();
|
|
||||||
let first_char = str.chars().next().unwrap();
|
|
||||||
if first_char.is_alphabetic() {
|
|
||||||
str.chars().all(|c| c == '_' || c.is_alphanumeric())
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn format_label<S: AsRef<str>>(s: S) -> String {
|
|
||||||
format!(
|
|
||||||
"{}{}{} ",
|
|
||||||
style("[").dim(),
|
|
||||||
match s.as_ref().to_ascii_lowercase().as_str() {
|
|
||||||
"info" => style("INFO").blue(),
|
|
||||||
"warn" => style("WARN").yellow(),
|
|
||||||
"error" => style("ERROR").red(),
|
|
||||||
_ => style(""),
|
|
||||||
},
|
|
||||||
style("]").dim()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn format_style(style: Option<&'static Style>) -> String {
|
|
||||||
if cfg!(test) {
|
|
||||||
"".to_string()
|
|
||||||
} else if let Some(style) = style {
|
|
||||||
// HACK: We have no direct way of referencing the ansi color code
|
|
||||||
// of the style that console::Style provides, and we also know for
|
|
||||||
// sure that styles always include the reset sequence at the end,
|
|
||||||
// unless we are in a CI environment on non-interactive terminal
|
|
||||||
style
|
|
||||||
.apply_to("")
|
|
||||||
.to_string()
|
|
||||||
.trim_end_matches(STYLE_RESET_STR)
|
|
||||||
.to_string()
|
|
||||||
} else {
|
|
||||||
STYLE_RESET_STR.to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn style_from_color_str<S: AsRef<str>>(s: S) -> LuaResult<Option<&'static Style>> {
|
|
||||||
Ok(match s.as_ref() {
|
|
||||||
"reset" => None,
|
|
||||||
"black" => Some(&COLOR_BLACK),
|
|
||||||
"red" => Some(&COLOR_RED),
|
|
||||||
"green" => Some(&COLOR_GREEN),
|
|
||||||
"yellow" => Some(&COLOR_YELLOW),
|
|
||||||
"blue" => Some(&COLOR_BLUE),
|
|
||||||
"purple" => Some(&COLOR_PURPLE),
|
|
||||||
"cyan" => Some(&COLOR_CYAN),
|
|
||||||
"white" => Some(&COLOR_WHITE),
|
|
||||||
_ => {
|
|
||||||
return Err(LuaError::RuntimeError(format!(
|
|
||||||
"The color '{}' is not a valid color name",
|
|
||||||
s.as_ref()
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn style_from_style_str<S: AsRef<str>>(s: S) -> LuaResult<Option<&'static Style>> {
|
|
||||||
Ok(match s.as_ref() {
|
|
||||||
"reset" => None,
|
|
||||||
"bold" => Some(&STYLE_BOLD),
|
|
||||||
"dim" => Some(&STYLE_DIM),
|
|
||||||
_ => {
|
|
||||||
return Err(LuaError::RuntimeError(format!(
|
|
||||||
"The style '{}' is not a valid style name",
|
|
||||||
s.as_ref()
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pretty_format_value(
|
|
||||||
buffer: &mut String,
|
|
||||||
value: &LuaValue,
|
|
||||||
depth: usize,
|
|
||||||
) -> std::fmt::Result {
|
|
||||||
// TODO: Handle tables with cyclic references
|
|
||||||
match &value {
|
|
||||||
LuaValue::Nil => write!(buffer, "nil")?,
|
|
||||||
LuaValue::Boolean(true) => write!(buffer, "{}", COLOR_YELLOW.apply_to("true"))?,
|
|
||||||
LuaValue::Boolean(false) => write!(buffer, "{}", COLOR_YELLOW.apply_to("false"))?,
|
|
||||||
LuaValue::Number(n) => write!(buffer, "{}", COLOR_CYAN.apply_to(format!("{n}")))?,
|
|
||||||
LuaValue::Integer(i) => write!(buffer, "{}", COLOR_CYAN.apply_to(format!("{i}")))?,
|
|
||||||
LuaValue::String(s) => write!(
|
|
||||||
buffer,
|
|
||||||
"\"{}\"",
|
|
||||||
COLOR_GREEN.apply_to(
|
|
||||||
s.to_string_lossy()
|
|
||||||
.replace('"', r#"\""#)
|
|
||||||
.replace('\r', r#"\r"#)
|
|
||||||
.replace('\n', r#"\n"#)
|
|
||||||
)
|
|
||||||
)?,
|
|
||||||
LuaValue::Table(ref tab) => {
|
|
||||||
if depth >= MAX_FORMAT_DEPTH {
|
|
||||||
write!(buffer, "{}", STYLE_DIM.apply_to("{ ... }"))?;
|
|
||||||
} else if let Some(s) = call_table_tostring_metamethod(tab) {
|
|
||||||
write!(buffer, "{s}")?;
|
|
||||||
} else {
|
|
||||||
let mut is_empty = false;
|
|
||||||
let depth_indent = INDENT.repeat(depth);
|
|
||||||
write!(buffer, "{}", STYLE_DIM.apply_to("{"))?;
|
|
||||||
for pair in tab.clone().pairs::<LuaValue, LuaValue>() {
|
|
||||||
let (key, value) = pair.unwrap();
|
|
||||||
match &key {
|
|
||||||
LuaValue::String(s) if can_be_plain_lua_table_key(s) => write!(
|
|
||||||
buffer,
|
|
||||||
"\n{}{}{} {} ",
|
|
||||||
depth_indent,
|
|
||||||
INDENT,
|
|
||||||
s.to_string_lossy(),
|
|
||||||
STYLE_DIM.apply_to("=")
|
|
||||||
)?,
|
|
||||||
_ => {
|
|
||||||
write!(buffer, "\n{depth_indent}{INDENT}[")?;
|
|
||||||
pretty_format_value(buffer, &key, depth)?;
|
|
||||||
write!(buffer, "] {} ", STYLE_DIM.apply_to("="))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pretty_format_value(buffer, &value, depth + 1)?;
|
|
||||||
write!(buffer, "{}", STYLE_DIM.apply_to(","))?;
|
|
||||||
is_empty = false;
|
|
||||||
}
|
|
||||||
if is_empty {
|
|
||||||
write!(buffer, "{}", STYLE_DIM.apply_to(" }"))?;
|
|
||||||
} else {
|
|
||||||
write!(buffer, "\n{depth_indent}{}", STYLE_DIM.apply_to("}"))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LuaValue::Vector(v) => write!(
|
|
||||||
buffer,
|
|
||||||
"{}",
|
|
||||||
COLOR_PURPLE.apply_to(format!(
|
|
||||||
"<vector({x}, {y}, {z})>",
|
|
||||||
x = v.x(),
|
|
||||||
y = v.y(),
|
|
||||||
z = v.z()
|
|
||||||
))
|
|
||||||
)?,
|
|
||||||
LuaValue::Thread(_) => write!(buffer, "{}", COLOR_PURPLE.apply_to("<thread>"))?,
|
|
||||||
LuaValue::Function(_) => write!(buffer, "{}", COLOR_PURPLE.apply_to("<function>"))?,
|
|
||||||
LuaValue::UserData(u) => {
|
|
||||||
if u.is::<TaskReference>() {
|
|
||||||
// Task references must be transparent
|
|
||||||
// to lua and pretend to be normal lua
|
|
||||||
// threads for compatibility purposes
|
|
||||||
write!(buffer, "{}", COLOR_PURPLE.apply_to("<thread>"))?
|
|
||||||
} else if let Some(s) = call_userdata_tostring_metamethod(u) {
|
|
||||||
write!(buffer, "{s}")?
|
|
||||||
} else {
|
|
||||||
write!(buffer, "{}", COLOR_PURPLE.apply_to("<userdata>"))?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LuaValue::LightUserData(_) => write!(buffer, "{}", COLOR_PURPLE.apply_to("<userdata>"))?,
|
|
||||||
LuaValue::Error(e) => write!(buffer, "{}", pretty_format_luau_error(e, false),)?,
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pretty_format_multi_value(multi: &LuaMultiValue) -> LuaResult<String> {
|
|
||||||
let mut buffer = String::new();
|
|
||||||
let mut counter = 0;
|
|
||||||
for value in multi {
|
|
||||||
counter += 1;
|
|
||||||
if let LuaValue::String(s) = value {
|
|
||||||
write!(buffer, "{}", s.to_string_lossy()).into_lua_err()?;
|
|
||||||
} else {
|
|
||||||
pretty_format_value(&mut buffer, value, 0).into_lua_err()?;
|
|
||||||
}
|
|
||||||
if counter < multi.len() {
|
|
||||||
write!(&mut buffer, " ").into_lua_err()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pretty_format_luau_error(e: &LuaError, colorized: bool) -> String {
|
|
||||||
let previous_colors_enabled = if !colorized {
|
|
||||||
set_colors_enabled(false);
|
|
||||||
Some(colors_enabled())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let stack_begin = format!("[{}]", COLOR_BLUE.apply_to("Stack Begin"));
|
|
||||||
let stack_end = format!("[{}]", COLOR_BLUE.apply_to("Stack End"));
|
|
||||||
let err_string = match e {
|
|
||||||
LuaError::RuntimeError(e) => {
|
|
||||||
// Remove unnecessary prefix
|
|
||||||
let mut err_string = e.to_string();
|
|
||||||
if let Some(no_prefix) = err_string.strip_prefix("runtime error: ") {
|
|
||||||
err_string = no_prefix.to_string();
|
|
||||||
}
|
|
||||||
// Add "Stack Begin" instead of default stack traceback string
|
|
||||||
let mut err_lines = err_string
|
|
||||||
.lines()
|
|
||||||
.map(|s| s.to_string())
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
let mut found_stack_begin = false;
|
|
||||||
for (index, line) in err_lines.clone().iter().enumerate().rev() {
|
|
||||||
if *line == "stack traceback:" {
|
|
||||||
err_lines[index] = stack_begin.clone();
|
|
||||||
found_stack_begin = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add "Stack End" to the very end of the stack trace for symmetry
|
|
||||||
if found_stack_begin {
|
|
||||||
err_lines.push(stack_end.clone());
|
|
||||||
}
|
|
||||||
err_lines.join("\n")
|
|
||||||
}
|
|
||||||
LuaError::CallbackError { traceback, cause } => {
|
|
||||||
// Find the best traceback (most lines) and the root error message
|
|
||||||
// The traceback may also start with "override traceback:" which
|
|
||||||
// means it was passed from somewhere that wants a custom trace,
|
|
||||||
// so we should then respect that and get the best override instead
|
|
||||||
let mut full_trace = traceback.to_string();
|
|
||||||
let mut root_cause = cause.as_ref();
|
|
||||||
let mut trace_override = false;
|
|
||||||
while let LuaError::CallbackError { cause, traceback } = root_cause {
|
|
||||||
let is_override = traceback.starts_with("override traceback:");
|
|
||||||
if is_override {
|
|
||||||
if !trace_override || traceback.lines().count() > full_trace.len() {
|
|
||||||
full_trace = traceback
|
|
||||||
.trim_start_matches("override traceback:")
|
|
||||||
.to_string();
|
|
||||||
trace_override = true;
|
|
||||||
}
|
|
||||||
} else if !trace_override {
|
|
||||||
full_trace = format!("{traceback}\n{full_trace}");
|
|
||||||
}
|
|
||||||
root_cause = cause;
|
|
||||||
}
|
|
||||||
// If we got a runtime error with an embedded traceback, we should
|
|
||||||
// use that instead since it generally contains more information
|
|
||||||
if matches!(root_cause, LuaError::RuntimeError(e) if e.contains("stack traceback:")) {
|
|
||||||
pretty_format_luau_error(root_cause, colorized)
|
|
||||||
} else {
|
|
||||||
// Otherwise we format whatever root error we got using
|
|
||||||
// the same error formatting as for above runtime errors
|
|
||||||
format!(
|
|
||||||
"{}\n{}\n{}\n{}",
|
|
||||||
pretty_format_luau_error(root_cause, colorized),
|
|
||||||
stack_begin,
|
|
||||||
full_trace.trim_start_matches("stack traceback:\n"),
|
|
||||||
stack_end
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LuaError::BadArgument { pos, cause, .. } => match cause.as_ref() {
|
|
||||||
// TODO: Add more detail to this error message
|
|
||||||
LuaError::FromLuaConversionError { from, to, .. } => {
|
|
||||||
format!("Argument #{pos} must be of type '{to}', got '{from}'")
|
|
||||||
}
|
|
||||||
c => format!(
|
|
||||||
"Bad argument #{pos}\n{}",
|
|
||||||
pretty_format_luau_error(c, colorized)
|
|
||||||
),
|
|
||||||
},
|
|
||||||
e => format!("{e}"),
|
|
||||||
};
|
|
||||||
// Re-enable colors if they were previously enabled
|
|
||||||
if let Some(true) = previous_colors_enabled {
|
|
||||||
set_colors_enabled(true)
|
|
||||||
}
|
|
||||||
// Remove the script path from the error message
|
|
||||||
// itself, it can be found in the stack trace
|
|
||||||
let mut err_lines = err_string.lines().collect::<Vec<_>>();
|
|
||||||
if let Some(first_line) = err_lines.first() {
|
|
||||||
if first_line.starts_with("[string \"") {
|
|
||||||
if let Some(closing_bracket) = first_line.find("]:") {
|
|
||||||
let after_closing_bracket = &first_line[closing_bracket + 2..first_line.len()];
|
|
||||||
if let Some(last_colon) = after_closing_bracket.find(": ") {
|
|
||||||
err_lines[0] = &after_closing_bracket
|
|
||||||
[last_colon + 2..first_line.len() - closing_bracket - 2];
|
|
||||||
} else {
|
|
||||||
err_lines[0] = after_closing_bracket
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Find where the stack trace stars and ends
|
|
||||||
let stack_begin_idx =
|
|
||||||
err_lines.iter().enumerate().find_map(
|
|
||||||
|(i, line)| {
|
|
||||||
if *line == stack_begin {
|
|
||||||
Some(i)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
let stack_end_idx =
|
|
||||||
err_lines.iter().enumerate().find_map(
|
|
||||||
|(i, line)| {
|
|
||||||
if *line == stack_end {
|
|
||||||
Some(i)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
// If we have a stack trace, we should transform the formatting from the
|
|
||||||
// default mlua formatting into something more friendly, similar to Roblox
|
|
||||||
if let (Some(idx_start), Some(idx_end)) = (stack_begin_idx, stack_end_idx) {
|
|
||||||
let stack_lines = err_lines
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
// Filter out stack lines
|
|
||||||
.filter_map(|(idx, line)| {
|
|
||||||
if idx > idx_start && idx < idx_end {
|
|
||||||
Some(*line)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// Transform from mlua format into friendly format, while also
|
|
||||||
// ensuring that leading whitespace / indentation is consistent
|
|
||||||
.map(transform_stack_line)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
fix_error_nitpicks(format!(
|
|
||||||
"{}\n{}\n{}\n{}",
|
|
||||||
err_lines
|
|
||||||
.iter()
|
|
||||||
.take(idx_start)
|
|
||||||
.copied()
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("\n"),
|
|
||||||
stack_begin,
|
|
||||||
stack_lines.join("\n"),
|
|
||||||
stack_end,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
fix_error_nitpicks(err_string)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn transform_stack_line(line: &str) -> String {
|
|
||||||
match (line.find('['), line.find(']')) {
|
|
||||||
(Some(idx_start), Some(idx_end)) => {
|
|
||||||
let name = line[idx_start..idx_end + 1]
|
|
||||||
.trim_start_matches('[')
|
|
||||||
.trim_start_matches("string ")
|
|
||||||
.trim_start_matches('"')
|
|
||||||
.trim_end_matches(']')
|
|
||||||
.trim_end_matches('"');
|
|
||||||
let after_name = &line[idx_end + 1..];
|
|
||||||
let line_num = match after_name.find(':') {
|
|
||||||
Some(lineno_start) => match after_name[lineno_start + 1..].find(':') {
|
|
||||||
Some(lineno_end) => &after_name[lineno_start + 1..lineno_end + 1],
|
|
||||||
None => match after_name.contains("in function") || after_name.contains("in ?")
|
|
||||||
{
|
|
||||||
false => &after_name[lineno_start + 1..],
|
|
||||||
true => "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
None => "",
|
|
||||||
};
|
|
||||||
let func_name = match after_name.find("in function ") {
|
|
||||||
Some(func_start) => after_name[func_start + 12..]
|
|
||||||
.trim()
|
|
||||||
.trim_end_matches('\'')
|
|
||||||
.trim_start_matches('\'')
|
|
||||||
.trim_start_matches("_G."),
|
|
||||||
None => "",
|
|
||||||
};
|
|
||||||
let mut result = String::new();
|
|
||||||
write!(
|
|
||||||
result,
|
|
||||||
" Script '{}'",
|
|
||||||
match name {
|
|
||||||
"C" => "[C]",
|
|
||||||
name => name,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
if !line_num.is_empty() {
|
|
||||||
write!(result, ", Line {line_num}").unwrap();
|
|
||||||
}
|
|
||||||
if !func_name.is_empty() {
|
|
||||||
write!(result, " - function {func_name}").unwrap();
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
(_, _) => line.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fix_error_nitpicks(full_message: String) -> String {
|
|
||||||
full_message
|
|
||||||
// Hacky fix for our custom require appearing as a normal script
|
|
||||||
// TODO: It's probably better to pull in the regex crate here ..
|
|
||||||
.replace("'require', Line 5", "'[C]' - function require")
|
|
||||||
.replace("'require', Line 7", "'[C]' - function require")
|
|
||||||
.replace("'require', Line 8", "'[C]' - function require")
|
|
||||||
// Same thing here for our async script
|
|
||||||
.replace("'async', Line 2", "'[C]'")
|
|
||||||
.replace("'async', Line 3", "'[C]'")
|
|
||||||
// Fix error calls in custom script chunks coming through
|
|
||||||
.replace(
|
|
||||||
"'[C]' - function error\n Script '[C]' - function require",
|
|
||||||
"'[C]' - function require",
|
|
||||||
)
|
|
||||||
// Fix strange double require
|
|
||||||
.replace(
|
|
||||||
"'[C]' - function require - function require",
|
|
||||||
"'[C]' - function require",
|
|
||||||
)
|
|
||||||
// Fix strange double C
|
|
||||||
.replace("'[C]'\n Script '[C]'", "'[C]'")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call_table_tostring_metamethod<'a>(tab: &'a LuaTable<'a>) -> Option<String> {
|
|
||||||
let f = match tab.get_metatable() {
|
|
||||||
None => None,
|
|
||||||
Some(meta) => match meta.get::<_, LuaFunction>(LuaMetaMethod::ToString.name()) {
|
|
||||||
Ok(method) => Some(method),
|
|
||||||
Err(_) => None,
|
|
||||||
},
|
|
||||||
}?;
|
|
||||||
match f.call::<_, String>(()) {
|
|
||||||
Ok(res) => Some(res),
|
|
||||||
Err(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call_userdata_tostring_metamethod<'a>(tab: &'a LuaAnyUserData<'a>) -> Option<String> {
|
|
||||||
let f = match tab.get_metatable() {
|
|
||||||
Err(_) => None,
|
|
||||||
Ok(meta) => match meta.get::<LuaFunction>(LuaMetaMethod::ToString.name()) {
|
|
||||||
Ok(method) => Some(method),
|
|
||||||
Err(_) => None,
|
|
||||||
},
|
|
||||||
}?;
|
|
||||||
match f.call::<_, String>(()) {
|
|
||||||
Ok(res) => Some(res),
|
|
||||||
Err(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
pub mod formatting;
|
|
||||||
pub mod prompt;
|
|
|
@ -1,192 +0,0 @@
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub enum PromptKind {
|
|
||||||
Text,
|
|
||||||
Confirm,
|
|
||||||
Select,
|
|
||||||
MultiSelect,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PromptKind {
|
|
||||||
fn get_all() -> Vec<Self> {
|
|
||||||
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<Self> {
|
|
||||||
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::<String>();
|
|
||||||
// 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::<Vec<_>>()
|
|
||||||
.join(", ")
|
|
||||||
)),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(LuaError::FromLuaConversionError {
|
|
||||||
from: "nil",
|
|
||||||
to: "PromptKind",
|
|
||||||
message: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PromptOptions {
|
|
||||||
pub kind: PromptKind,
|
|
||||||
pub text: Option<String>,
|
|
||||||
pub default_string: Option<String>,
|
|
||||||
pub default_bool: Option<bool>,
|
|
||||||
pub options: Option<Vec<String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> FromLuaMulti<'lua> for PromptOptions {
|
|
||||||
fn from_lua_multi(mut values: LuaMultiValue<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
|
|
||||||
// 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::<String>::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<usize>),
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> IntoLua<'lua> for PromptResult {
|
|
||||||
fn into_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
|
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,93 +0,0 @@
|
||||||
use std::future::Future;
|
|
||||||
|
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
use crate::lune_temp::lua::async_ext::LuaAsyncExt;
|
|
||||||
|
|
||||||
pub struct TableBuilder {
|
|
||||||
lua: &'static Lua,
|
|
||||||
tab: LuaTable<'static>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
impl TableBuilder {
|
|
||||||
pub fn new(lua: &'static Lua) -> LuaResult<Self> {
|
|
||||||
let tab = lua.create_table()?;
|
|
||||||
Ok(Self { lua, tab })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_value<K, V>(self, key: K, value: V) -> LuaResult<Self>
|
|
||||||
where
|
|
||||||
K: IntoLua<'static>,
|
|
||||||
V: IntoLua<'static>,
|
|
||||||
{
|
|
||||||
self.tab.raw_set(key, value)?;
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_values<K, V>(self, values: Vec<(K, V)>) -> LuaResult<Self>
|
|
||||||
where
|
|
||||||
K: IntoLua<'static>,
|
|
||||||
V: IntoLua<'static>,
|
|
||||||
{
|
|
||||||
for (key, value) in values {
|
|
||||||
self.tab.raw_set(key, value)?;
|
|
||||||
}
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_sequential_value<V>(self, value: V) -> LuaResult<Self>
|
|
||||||
where
|
|
||||||
V: IntoLua<'static>,
|
|
||||||
{
|
|
||||||
self.tab.raw_push(value)?;
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_sequential_values<V>(self, values: Vec<V>) -> LuaResult<Self>
|
|
||||||
where
|
|
||||||
V: IntoLua<'static>,
|
|
||||||
{
|
|
||||||
for value in values {
|
|
||||||
self.tab.raw_push(value)?;
|
|
||||||
}
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_metatable(self, table: LuaTable) -> LuaResult<Self> {
|
|
||||||
self.tab.set_metatable(Some(table));
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_function<K, A, R, F>(self, key: K, func: F) -> LuaResult<Self>
|
|
||||||
where
|
|
||||||
K: IntoLua<'static>,
|
|
||||||
A: FromLuaMulti<'static>,
|
|
||||||
R: IntoLuaMulti<'static>,
|
|
||||||
F: 'static + Fn(&'static Lua, A) -> LuaResult<R>,
|
|
||||||
{
|
|
||||||
let f = self.lua.create_function(func)?;
|
|
||||||
self.with_value(key, LuaValue::Function(f))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_async_function<K, A, R, F, FR>(self, key: K, func: F) -> LuaResult<Self>
|
|
||||||
where
|
|
||||||
K: IntoLua<'static>,
|
|
||||||
A: FromLuaMulti<'static>,
|
|
||||||
R: IntoLuaMulti<'static>,
|
|
||||||
F: 'static + Fn(&'static Lua, A) -> FR,
|
|
||||||
FR: 'static + Future<Output = LuaResult<R>>,
|
|
||||||
{
|
|
||||||
let f = self.lua.create_async_function(func)?;
|
|
||||||
self.with_value(key, LuaValue::Function(f))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build_readonly(self) -> LuaResult<LuaTable<'static>> {
|
|
||||||
self.tab.set_readonly(true);
|
|
||||||
Ok(self.tab)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build(self) -> LuaResult<LuaTable<'static>> {
|
|
||||||
Ok(self.tab)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
mod builder;
|
|
||||||
|
|
||||||
pub use builder::TableBuilder;
|
|
|
@ -1,135 +0,0 @@
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
|
||||||
|
|
||||||
use futures_util::Future;
|
|
||||||
use mlua::prelude::*;
|
|
||||||
use tokio::time::{sleep, Instant};
|
|
||||||
|
|
||||||
use crate::lune_temp::lua::task::TaskKind;
|
|
||||||
|
|
||||||
use super::super::{
|
|
||||||
scheduler::TaskReference, scheduler::TaskScheduler, scheduler_handle::TaskSchedulerAsyncHandle,
|
|
||||||
scheduler_message::TaskSchedulerMessage,
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
──────────────────────────────────────────────────────────
|
|
||||||
Trait definition - same as the implementation, ignore this
|
|
||||||
|
|
||||||
We use traits here to prevent misuse of certain scheduler
|
|
||||||
APIs, making importing of them as intentional as possible
|
|
||||||
──────────────────────────────────────────────────────────
|
|
||||||
*/
|
|
||||||
#[async_trait(?Send)]
|
|
||||||
pub trait TaskSchedulerAsyncExt<'fut> {
|
|
||||||
fn register_background_task(&self) -> TaskSchedulerAsyncHandle;
|
|
||||||
|
|
||||||
fn schedule_async<'sched, R, F, FR>(
|
|
||||||
&'sched self,
|
|
||||||
thread: LuaThread<'_>,
|
|
||||||
func: F,
|
|
||||||
) -> LuaResult<TaskReference>
|
|
||||||
where
|
|
||||||
'sched: 'fut,
|
|
||||||
R: IntoLuaMulti<'static>,
|
|
||||||
F: 'static + Fn(&'static Lua) -> FR,
|
|
||||||
FR: 'static + Future<Output = LuaResult<R>>;
|
|
||||||
|
|
||||||
fn schedule_wait(
|
|
||||||
&'fut self,
|
|
||||||
reference: LuaThread<'_>,
|
|
||||||
duration: Option<f64>,
|
|
||||||
) -> LuaResult<TaskReference>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
────────────────────
|
|
||||||
Trait implementation
|
|
||||||
────────────────────
|
|
||||||
*/
|
|
||||||
#[async_trait(?Send)]
|
|
||||||
impl<'fut> TaskSchedulerAsyncExt<'fut> for TaskScheduler<'fut> {
|
|
||||||
/**
|
|
||||||
Registers a new background task with the task scheduler.
|
|
||||||
|
|
||||||
The returned [`TaskSchedulerAsyncHandle`] must have its
|
|
||||||
[`TaskSchedulerAsyncHandle::unregister`] method called
|
|
||||||
upon completion of the background task to prevent
|
|
||||||
the task scheduler from running indefinitely.
|
|
||||||
*/
|
|
||||||
fn register_background_task(&self) -> TaskSchedulerAsyncHandle {
|
|
||||||
let sender = self.futures_tx.clone();
|
|
||||||
sender
|
|
||||||
.send(TaskSchedulerMessage::Spawned)
|
|
||||||
.unwrap_or_else(|e| {
|
|
||||||
panic!(
|
|
||||||
"\
|
|
||||||
\nFailed to unregister background task - this is an internal error! \
|
|
||||||
\nPlease report it at {} \
|
|
||||||
\nDetails: {e} \
|
|
||||||
",
|
|
||||||
env!("CARGO_PKG_REPOSITORY")
|
|
||||||
)
|
|
||||||
});
|
|
||||||
TaskSchedulerAsyncHandle::new(sender)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Schedules a lua thread or function
|
|
||||||
to be resumed after running a future.
|
|
||||||
|
|
||||||
The given lua thread or function will be resumed
|
|
||||||
using the optional arguments returned by the future.
|
|
||||||
*/
|
|
||||||
fn schedule_async<'sched, R, F, FR>(
|
|
||||||
&'sched self,
|
|
||||||
thread: LuaThread<'_>,
|
|
||||||
func: F,
|
|
||||||
) -> LuaResult<TaskReference>
|
|
||||||
where
|
|
||||||
'sched: 'fut, // Scheduler must live at least as long as the future
|
|
||||||
R: IntoLuaMulti<'static>,
|
|
||||||
F: 'static + Fn(&'static Lua) -> FR,
|
|
||||||
FR: 'static + Future<Output = LuaResult<R>>,
|
|
||||||
{
|
|
||||||
self.queue_async_task(thread, None, async move {
|
|
||||||
match func(self.lua).await {
|
|
||||||
Ok(res) => match res.into_lua_multi(self.lua) {
|
|
||||||
Ok(multi) => Ok(Some(multi)),
|
|
||||||
Err(e) => Err(e),
|
|
||||||
},
|
|
||||||
Err(e) => Err(e),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Schedules a task reference to be resumed after a certain amount of time.
|
|
||||||
|
|
||||||
The given task will be resumed with the elapsed time as its one and only argument.
|
|
||||||
*/
|
|
||||||
fn schedule_wait(
|
|
||||||
&'fut self,
|
|
||||||
thread: LuaThread<'_>,
|
|
||||||
duration: Option<f64>,
|
|
||||||
) -> LuaResult<TaskReference> {
|
|
||||||
let reference = self.create_task(TaskKind::Future, thread, None, true)?;
|
|
||||||
// Insert the future
|
|
||||||
let futs = self
|
|
||||||
.futures
|
|
||||||
.try_lock()
|
|
||||||
.expect("Tried to add future to queue during futures resumption");
|
|
||||||
futs.push(Box::pin(async move {
|
|
||||||
let before = Instant::now();
|
|
||||||
sleep(Duration::from_secs_f64(
|
|
||||||
duration.unwrap_or_default().max(0.0),
|
|
||||||
))
|
|
||||||
.await;
|
|
||||||
let elapsed_secs = before.elapsed().as_secs_f64();
|
|
||||||
let args = elapsed_secs.into_lua_multi(self.lua).unwrap();
|
|
||||||
(Some(reference), Ok(Some(args)))
|
|
||||||
}));
|
|
||||||
Ok(reference)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
mod async_ext;
|
|
||||||
mod resume_ext;
|
|
||||||
mod schedule_ext;
|
|
||||||
|
|
||||||
pub use async_ext::TaskSchedulerAsyncExt;
|
|
||||||
pub use resume_ext::TaskSchedulerResumeExt;
|
|
||||||
pub use schedule_ext::TaskSchedulerScheduleExt;
|
|
|
@ -1,180 +0,0 @@
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
|
||||||
|
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
use futures_util::StreamExt;
|
|
||||||
use tokio::time::sleep;
|
|
||||||
|
|
||||||
use super::super::{
|
|
||||||
scheduler_message::TaskSchedulerMessage, scheduler_state::TaskSchedulerState, TaskScheduler,
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
──────────────────────────────────────────────────────────
|
|
||||||
Trait definition - same as the implementation, ignore this
|
|
||||||
|
|
||||||
We use traits here to prevent misuse of certain scheduler
|
|
||||||
APIs, making importing of them as intentional as possible
|
|
||||||
──────────────────────────────────────────────────────────
|
|
||||||
*/
|
|
||||||
#[async_trait(?Send)]
|
|
||||||
pub trait TaskSchedulerResumeExt {
|
|
||||||
async fn resume_queue(&self) -> TaskSchedulerState;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
────────────────────
|
|
||||||
Trait implementation
|
|
||||||
────────────────────
|
|
||||||
*/
|
|
||||||
#[async_trait(?Send)]
|
|
||||||
impl TaskSchedulerResumeExt for TaskScheduler<'_> {
|
|
||||||
/**
|
|
||||||
Resumes the task scheduler queue.
|
|
||||||
|
|
||||||
This will run any spawned or deferred Lua tasks in a blocking manner.
|
|
||||||
|
|
||||||
Once all spawned and / or deferred Lua tasks have finished running,
|
|
||||||
this will process delayed tasks, waiting tasks, and native Rust
|
|
||||||
futures concurrently, awaiting the first one to be ready for resumption.
|
|
||||||
*/
|
|
||||||
async fn resume_queue(&self) -> TaskSchedulerState {
|
|
||||||
let current = TaskSchedulerState::new(self);
|
|
||||||
if current.num_blocking > 0 {
|
|
||||||
// 1. Blocking tasks
|
|
||||||
resume_next_blocking_task(self, None)
|
|
||||||
} else if current.num_futures > 0 || current.num_background > 0 {
|
|
||||||
// 2. Async and/or background tasks
|
|
||||||
tokio::select! {
|
|
||||||
result = resume_next_async_task(self) => result,
|
|
||||||
result = receive_next_message(self) => result,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 3. No tasks left, here we sleep one millisecond in case
|
|
||||||
// the caller of resume_queue accidentally calls this in
|
|
||||||
// a busy loop to prevent cpu usage from going to 100%
|
|
||||||
sleep(Duration::from_millis(1)).await;
|
|
||||||
TaskSchedulerState::new(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
────────────────────────────────────────────────────────────────
|
|
||||||
Private functions for the trait that operate on the task scheduler
|
|
||||||
|
|
||||||
These could be implemented as normal methods but if we put them in the
|
|
||||||
trait they become public, and putting them in the task scheduler's
|
|
||||||
own implementation block will clutter that up unnecessarily
|
|
||||||
────────────────────────────────────────────────────────────────
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
Resumes the next queued Lua task, if one exists, blocking
|
|
||||||
the current thread until it either yields or finishes.
|
|
||||||
*/
|
|
||||||
fn resume_next_blocking_task<'sched, 'args>(
|
|
||||||
scheduler: &TaskScheduler<'sched>,
|
|
||||||
override_args: Option<LuaResult<LuaMultiValue<'args>>>,
|
|
||||||
) -> TaskSchedulerState
|
|
||||||
where
|
|
||||||
'args: 'sched,
|
|
||||||
{
|
|
||||||
match {
|
|
||||||
let mut queue_guard = scheduler.tasks_queue_blocking.borrow_mut();
|
|
||||||
let task = queue_guard.pop_front();
|
|
||||||
drop(queue_guard);
|
|
||||||
task
|
|
||||||
} {
|
|
||||||
None => TaskSchedulerState::new(scheduler),
|
|
||||||
Some(task) => match scheduler.resume_task(task, override_args) {
|
|
||||||
Err(task_err) => {
|
|
||||||
scheduler.wake_completed_task(task, Err(task_err.clone()));
|
|
||||||
TaskSchedulerState::err(scheduler, task_err)
|
|
||||||
}
|
|
||||||
Ok(rets) if rets.0 == LuaThreadStatus::Unresumable => {
|
|
||||||
scheduler.wake_completed_task(task, Ok(rets.1));
|
|
||||||
TaskSchedulerState::new(scheduler)
|
|
||||||
}
|
|
||||||
Ok(_) => TaskSchedulerState::new(scheduler),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Awaits the first available queued future, and resumes its associated
|
|
||||||
Lua task which will be ready for resumption when that future wakes.
|
|
||||||
|
|
||||||
Panics if there are no futures currently queued.
|
|
||||||
|
|
||||||
Use [`TaskScheduler::next_queue_future_exists`]
|
|
||||||
to check if there are any queued futures.
|
|
||||||
*/
|
|
||||||
async fn resume_next_async_task(scheduler: &TaskScheduler<'_>) -> TaskSchedulerState {
|
|
||||||
let (task, result) = {
|
|
||||||
let mut futs = scheduler
|
|
||||||
.futures
|
|
||||||
.try_lock()
|
|
||||||
.expect("Tried to resume next queued future while already resuming or modifying");
|
|
||||||
futs.next()
|
|
||||||
.await
|
|
||||||
.expect("Tried to resume next queued future but none are queued")
|
|
||||||
};
|
|
||||||
// The future might not return a reference that it wants to resume
|
|
||||||
if let Some(task) = task {
|
|
||||||
// Promote this future task to a blocking task and resume it
|
|
||||||
// right away, also taking care to not borrow mutably twice
|
|
||||||
// by dropping this guard before trying to resume it
|
|
||||||
let mut queue_guard = scheduler.tasks_queue_blocking.borrow_mut();
|
|
||||||
queue_guard.push_front(task);
|
|
||||||
drop(queue_guard);
|
|
||||||
}
|
|
||||||
resume_next_blocking_task(scheduler, result.transpose())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Awaits the next background task registration
|
|
||||||
message, if any messages exist in the queue.
|
|
||||||
|
|
||||||
This is a no-op if there are no background tasks left running
|
|
||||||
and / or the background task messages channel was closed.
|
|
||||||
*/
|
|
||||||
async fn receive_next_message(scheduler: &TaskScheduler<'_>) -> TaskSchedulerState {
|
|
||||||
let message_opt = {
|
|
||||||
let mut rx = scheduler.futures_rx.lock().await;
|
|
||||||
rx.recv().await
|
|
||||||
};
|
|
||||||
if let Some(message) = message_opt {
|
|
||||||
match message {
|
|
||||||
TaskSchedulerMessage::NewBlockingTaskReady => TaskSchedulerState::new(scheduler),
|
|
||||||
TaskSchedulerMessage::NewLuaErrorReady(err) => TaskSchedulerState::err(scheduler, err),
|
|
||||||
TaskSchedulerMessage::Spawned => {
|
|
||||||
let prev = scheduler.futures_background_count.get();
|
|
||||||
scheduler.futures_background_count.set(prev + 1);
|
|
||||||
TaskSchedulerState::new(scheduler)
|
|
||||||
}
|
|
||||||
TaskSchedulerMessage::Terminated(result) => {
|
|
||||||
let prev = scheduler.futures_background_count.get();
|
|
||||||
scheduler.futures_background_count.set(prev - 1);
|
|
||||||
if prev == 0 {
|
|
||||||
panic!(
|
|
||||||
r#"
|
|
||||||
Terminated a background task without it running - this is an internal error!
|
|
||||||
Please report it at {}
|
|
||||||
"#,
|
|
||||||
env!("CARGO_PKG_REPOSITORY")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if let Err(e) = result {
|
|
||||||
TaskSchedulerState::err(scheduler, e)
|
|
||||||
} else {
|
|
||||||
TaskSchedulerState::new(scheduler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
TaskSchedulerState::new(scheduler)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use mlua::prelude::*;
|
|
||||||
use tokio::time::sleep;
|
|
||||||
|
|
||||||
use super::super::{scheduler::TaskKind, scheduler::TaskReference, scheduler::TaskScheduler};
|
|
||||||
|
|
||||||
/*
|
|
||||||
──────────────────────────────────────────────────────────
|
|
||||||
Trait definition - same as the implementation, ignore this
|
|
||||||
|
|
||||||
We use traits here to prevent misuse of certain scheduler
|
|
||||||
APIs, making importing of them as intentional as possible
|
|
||||||
──────────────────────────────────────────────────────────
|
|
||||||
*/
|
|
||||||
pub trait TaskSchedulerScheduleExt {
|
|
||||||
fn schedule_blocking(
|
|
||||||
&self,
|
|
||||||
thread: LuaThread<'_>,
|
|
||||||
thread_args: LuaMultiValue<'_>,
|
|
||||||
) -> LuaResult<TaskReference>;
|
|
||||||
|
|
||||||
fn schedule_blocking_deferred(
|
|
||||||
&self,
|
|
||||||
thread: LuaThread<'_>,
|
|
||||||
thread_args: LuaMultiValue<'_>,
|
|
||||||
) -> LuaResult<TaskReference>;
|
|
||||||
|
|
||||||
fn schedule_blocking_after_seconds(
|
|
||||||
&self,
|
|
||||||
after_secs: f64,
|
|
||||||
thread: LuaThread<'_>,
|
|
||||||
thread_args: LuaMultiValue<'_>,
|
|
||||||
) -> LuaResult<TaskReference>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
────────────────────
|
|
||||||
Trait implementation
|
|
||||||
────────────────────
|
|
||||||
*/
|
|
||||||
impl TaskSchedulerScheduleExt for TaskScheduler<'_> {
|
|
||||||
/**
|
|
||||||
Schedules a lua thread or function to resume ***first*** during this
|
|
||||||
resumption point, ***skipping ahead*** of any other currently queued tasks.
|
|
||||||
|
|
||||||
The given lua thread or function will be resumed
|
|
||||||
using the given `thread_args` as its argument(s).
|
|
||||||
*/
|
|
||||||
fn schedule_blocking(
|
|
||||||
&self,
|
|
||||||
thread: LuaThread<'_>,
|
|
||||||
thread_args: LuaMultiValue<'_>,
|
|
||||||
) -> LuaResult<TaskReference> {
|
|
||||||
self.queue_blocking_task(TaskKind::Instant, thread, Some(thread_args))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Schedules a lua thread or function to resume ***after all***
|
|
||||||
currently resuming tasks, during this resumption point.
|
|
||||||
|
|
||||||
The given lua thread or function will be resumed
|
|
||||||
using the given `thread_args` as its argument(s).
|
|
||||||
*/
|
|
||||||
fn schedule_blocking_deferred(
|
|
||||||
&self,
|
|
||||||
thread: LuaThread<'_>,
|
|
||||||
thread_args: LuaMultiValue<'_>,
|
|
||||||
) -> LuaResult<TaskReference> {
|
|
||||||
self.queue_blocking_task(TaskKind::Deferred, thread, Some(thread_args))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Schedules a lua thread or function to
|
|
||||||
be resumed after waiting asynchronously.
|
|
||||||
|
|
||||||
The given lua thread or function will be resumed
|
|
||||||
using the given `thread_args` as its argument(s).
|
|
||||||
*/
|
|
||||||
fn schedule_blocking_after_seconds(
|
|
||||||
&self,
|
|
||||||
after_secs: f64,
|
|
||||||
thread: LuaThread<'_>,
|
|
||||||
thread_args: LuaMultiValue<'_>,
|
|
||||||
) -> LuaResult<TaskReference> {
|
|
||||||
self.queue_async_task(thread, Some(thread_args), async move {
|
|
||||||
sleep(Duration::from_secs_f64(after_secs)).await;
|
|
||||||
Ok(None)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
mod ext;
|
|
||||||
mod proxy;
|
|
||||||
mod scheduler;
|
|
||||||
mod scheduler_handle;
|
|
||||||
mod scheduler_message;
|
|
||||||
mod scheduler_state;
|
|
||||||
mod task_kind;
|
|
||||||
mod task_reference;
|
|
||||||
mod task_waiter;
|
|
||||||
|
|
||||||
pub use ext::*;
|
|
||||||
pub use proxy::*;
|
|
||||||
pub use scheduler::*;
|
|
||||||
pub use scheduler_handle::*;
|
|
||||||
pub use scheduler_state::*;
|
|
|
@ -1,118 +0,0 @@
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
use super::TaskReference;
|
|
||||||
|
|
||||||
/*
|
|
||||||
Proxy enum to deal with both threads & functions
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum LuaThreadOrFunction<'lua> {
|
|
||||||
Thread(LuaThread<'lua>),
|
|
||||||
Function(LuaFunction<'lua>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> LuaThreadOrFunction<'lua> {
|
|
||||||
pub fn into_thread(self, lua: &'lua Lua) -> LuaResult<LuaThread<'lua>> {
|
|
||||||
match self {
|
|
||||||
Self::Thread(t) => Ok(t),
|
|
||||||
Self::Function(f) => lua.create_thread(f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> From<LuaThread<'lua>> for LuaThreadOrFunction<'lua> {
|
|
||||||
fn from(value: LuaThread<'lua>) -> Self {
|
|
||||||
Self::Thread(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> From<LuaFunction<'lua>> for LuaThreadOrFunction<'lua> {
|
|
||||||
fn from(value: LuaFunction<'lua>) -> Self {
|
|
||||||
Self::Function(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> FromLua<'lua> for LuaThreadOrFunction<'lua> {
|
|
||||||
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
|
||||||
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(format!(
|
|
||||||
"Expected thread or function, got '{}'",
|
|
||||||
value.type_name()
|
|
||||||
)),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> IntoLua<'lua> for LuaThreadOrFunction<'lua> {
|
|
||||||
fn into_lua(self, _: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
|
|
||||||
match self {
|
|
||||||
Self::Thread(t) => Ok(LuaValue::Thread(t)),
|
|
||||||
Self::Function(f) => Ok(LuaValue::Function(f)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Proxy enum to deal with both threads & task scheduler task references
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum LuaThreadOrTaskReference<'lua> {
|
|
||||||
Thread(LuaThread<'lua>),
|
|
||||||
TaskReference(TaskReference),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> From<LuaThread<'lua>> for LuaThreadOrTaskReference<'lua> {
|
|
||||||
fn from(value: LuaThread<'lua>) -> Self {
|
|
||||||
Self::Thread(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> From<TaskReference> for LuaThreadOrTaskReference<'lua> {
|
|
||||||
fn from(value: TaskReference) -> Self {
|
|
||||||
Self::TaskReference(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> FromLua<'lua> for LuaThreadOrTaskReference<'lua> {
|
|
||||||
fn from_lua(value: LuaValue<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
|
|
||||||
let tname = value.type_name();
|
|
||||||
match value {
|
|
||||||
LuaValue::Thread(t) => Ok(Self::Thread(t)),
|
|
||||||
LuaValue::UserData(u) => {
|
|
||||||
if let Ok(task) =
|
|
||||||
LuaUserDataRef::<TaskReference>::from_lua(LuaValue::UserData(u), lua)
|
|
||||||
{
|
|
||||||
Ok(Self::TaskReference(*task))
|
|
||||||
} else {
|
|
||||||
Err(LuaError::FromLuaConversionError {
|
|
||||||
from: tname,
|
|
||||||
to: "thread",
|
|
||||||
message: Some(format!("Expected thread, got '{tname}'")),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => Err(LuaError::FromLuaConversionError {
|
|
||||||
from: tname,
|
|
||||||
to: "thread",
|
|
||||||
message: Some(format!("Expected thread, got '{tname}'")),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> IntoLua<'lua> for LuaThreadOrTaskReference<'lua> {
|
|
||||||
fn into_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
|
|
||||||
match self {
|
|
||||||
Self::TaskReference(t) => t.into_lua(lua),
|
|
||||||
Self::Thread(t) => Ok(LuaValue::Thread(t)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,480 +0,0 @@
|
||||||
use core::panic;
|
|
||||||
use std::{
|
|
||||||
cell::{Cell, RefCell},
|
|
||||||
collections::{HashMap, VecDeque},
|
|
||||||
process::ExitCode,
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use futures_util::{future::LocalBoxFuture, stream::FuturesUnordered, Future};
|
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
use tokio::sync::{mpsc, Mutex as AsyncMutex};
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
scheduler_message::TaskSchedulerMessage,
|
|
||||||
task_waiter::{TaskWaiterFuture, TaskWaiterState},
|
|
||||||
};
|
|
||||||
pub use super::{task_kind::TaskKind, task_reference::TaskReference};
|
|
||||||
|
|
||||||
type TaskFutureRets<'fut> = LuaResult<Option<LuaMultiValue<'fut>>>;
|
|
||||||
type TaskFuture<'fut> = LocalBoxFuture<'fut, (Option<TaskReference>, TaskFutureRets<'fut>)>;
|
|
||||||
|
|
||||||
/// A struct representing a task contained in the task scheduler
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Task {
|
|
||||||
kind: TaskKind,
|
|
||||||
thread: LuaRegistryKey,
|
|
||||||
args: LuaRegistryKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A task scheduler that implements task queues
|
|
||||||
/// with instant, deferred, and delayed tasks
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct TaskScheduler<'fut> {
|
|
||||||
/*
|
|
||||||
Lots of cell and refcell here, however we need full interior mutability and never outer
|
|
||||||
since the scheduler struct may be accessed from lua more than once at the same time.
|
|
||||||
|
|
||||||
An example of this is the implementation of coroutine.resume, which instantly resumes the given
|
|
||||||
task, where the task getting resumed may also create new scheduler tasks during its resumption.
|
|
||||||
|
|
||||||
The same goes for values used during resumption of futures (`futures` and `futures_rx`)
|
|
||||||
which must use async-aware mutexes to be cancellation safe across await points.
|
|
||||||
*/
|
|
||||||
// Internal state & flags
|
|
||||||
pub(super) lua: &'static Lua,
|
|
||||||
pub(super) guid: Cell<usize>,
|
|
||||||
pub(super) exit_code: Cell<Option<ExitCode>>,
|
|
||||||
// Blocking tasks
|
|
||||||
pub(super) tasks: RefCell<HashMap<TaskReference, Task>>,
|
|
||||||
pub(super) tasks_count: Cell<usize>,
|
|
||||||
pub(super) tasks_current: Cell<Option<TaskReference>>,
|
|
||||||
pub(super) tasks_queue_blocking: RefCell<VecDeque<TaskReference>>,
|
|
||||||
pub(super) tasks_waiter_states:
|
|
||||||
RefCell<HashMap<TaskReference, Vec<Arc<AsyncMutex<TaskWaiterState<'fut>>>>>>,
|
|
||||||
pub(super) tasks_current_lua_error: Arc<AsyncMutex<Option<LuaError>>>,
|
|
||||||
// Future tasks & objects for waking
|
|
||||||
pub(super) futures: AsyncMutex<FuturesUnordered<TaskFuture<'fut>>>,
|
|
||||||
pub(super) futures_count: Cell<usize>,
|
|
||||||
pub(super) futures_background_count: Cell<usize>,
|
|
||||||
pub(super) futures_tx: mpsc::UnboundedSender<TaskSchedulerMessage>,
|
|
||||||
pub(super) futures_rx: AsyncMutex<mpsc::UnboundedReceiver<TaskSchedulerMessage>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'fut> TaskScheduler<'fut> {
|
|
||||||
/**
|
|
||||||
Creates a new task scheduler.
|
|
||||||
*/
|
|
||||||
pub fn new(lua: &'static Lua) -> LuaResult<Self> {
|
|
||||||
let (tx, rx) = mpsc::unbounded_channel();
|
|
||||||
let tasks_current_lua_error = Arc::new(AsyncMutex::new(None));
|
|
||||||
let tasks_current_lua_error_inner = tasks_current_lua_error.clone();
|
|
||||||
lua.set_interrupt(move |_| {
|
|
||||||
match tasks_current_lua_error_inner.try_lock().unwrap().take() {
|
|
||||||
Some(err) => Err(err),
|
|
||||||
None => Ok(LuaVmState::Continue),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Ok(Self {
|
|
||||||
lua,
|
|
||||||
guid: Cell::new(0),
|
|
||||||
exit_code: Cell::new(None),
|
|
||||||
tasks: RefCell::new(HashMap::new()),
|
|
||||||
tasks_count: Cell::new(0),
|
|
||||||
tasks_current: Cell::new(None),
|
|
||||||
tasks_queue_blocking: RefCell::new(VecDeque::new()),
|
|
||||||
tasks_waiter_states: RefCell::new(HashMap::new()),
|
|
||||||
tasks_current_lua_error,
|
|
||||||
futures: AsyncMutex::new(FuturesUnordered::new()),
|
|
||||||
futures_tx: tx,
|
|
||||||
futures_rx: AsyncMutex::new(rx),
|
|
||||||
futures_count: Cell::new(0),
|
|
||||||
futures_background_count: Cell::new(0),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Consumes and leaks the task scheduler,
|
|
||||||
returning a static reference `&'static TaskScheduler`.
|
|
||||||
|
|
||||||
This function is useful when the task scheduler object is
|
|
||||||
supposed to live for the remainder of the program's life.
|
|
||||||
|
|
||||||
Note that dropping the returned reference will cause a memory leak.
|
|
||||||
*/
|
|
||||||
pub fn into_static(self) -> &'static Self {
|
|
||||||
Box::leak(Box::new(self))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Stores the exit code for the task scheduler.
|
|
||||||
|
|
||||||
This will be passed back to the Rust thread that is running the task scheduler,
|
|
||||||
in the [`TaskSchedulerState`] returned on resumption of the task scheduler queue.
|
|
||||||
|
|
||||||
Setting this exit code will signal to that thread that it
|
|
||||||
should stop resuming tasks, and gracefully terminate the program.
|
|
||||||
*/
|
|
||||||
pub fn set_exit_code(&self, code: ExitCode) {
|
|
||||||
self.exit_code.set(Some(code));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Forwards a lua error to be emitted as soon as possible,
|
|
||||||
after any current blocking / queued tasks have been resumed.
|
|
||||||
|
|
||||||
Useful when an async function may call into Lua and get a
|
|
||||||
result back, without erroring out of the entire async block.
|
|
||||||
*/
|
|
||||||
pub fn forward_lua_error(&self, err: LuaError) {
|
|
||||||
let sender = self.futures_tx.clone();
|
|
||||||
sender
|
|
||||||
.send(TaskSchedulerMessage::NewLuaErrorReady(err))
|
|
||||||
.unwrap_or_else(|e| {
|
|
||||||
panic!(
|
|
||||||
"\
|
|
||||||
\nFailed to forward lua error - this is an internal error! \
|
|
||||||
\nPlease report it at {} \
|
|
||||||
\nDetails: {e} \
|
|
||||||
",
|
|
||||||
env!("CARGO_PKG_REPOSITORY")
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Forces the current task to be set to the given reference.
|
|
||||||
|
|
||||||
Useful if a task is to be resumed externally but full
|
|
||||||
compatibility with the task scheduler is still necessary.
|
|
||||||
*/
|
|
||||||
pub(crate) fn force_set_current_task(&self, reference: Option<TaskReference>) {
|
|
||||||
self.tasks_current.set(reference);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Checks if a task still exists in the scheduler.
|
|
||||||
|
|
||||||
A task may no longer exist in the scheduler if it has been manually
|
|
||||||
cancelled and removed by calling [`TaskScheduler::cancel_task()`].
|
|
||||||
*/
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn contains_task(&self, reference: TaskReference) -> bool {
|
|
||||||
self.tasks.borrow().contains_key(&reference)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Returns the currently running task, if any.
|
|
||||||
*/
|
|
||||||
pub fn current_task(&self) -> Option<TaskReference> {
|
|
||||||
self.tasks_current.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Returns the status of a specific task, if it exists in the scheduler.
|
|
||||||
*/
|
|
||||||
pub fn get_task_status(&self, reference: TaskReference) -> Option<LuaString> {
|
|
||||||
self.tasks.borrow().get(&reference).map(|task| {
|
|
||||||
let status: LuaFunction = self
|
|
||||||
.lua
|
|
||||||
.named_registry_value("co.status")
|
|
||||||
.expect("Missing coroutine status function in registry");
|
|
||||||
let thread: LuaThread = self
|
|
||||||
.lua
|
|
||||||
.registry_value(&task.thread)
|
|
||||||
.expect("Task thread missing from registry");
|
|
||||||
status
|
|
||||||
.call(thread)
|
|
||||||
.expect("Task thread failed to call status")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Creates a new task, storing a new Lua thread
|
|
||||||
for it, as well as the arguments to give the
|
|
||||||
thread on resumption, in the Lua registry.
|
|
||||||
|
|
||||||
Note that this task will ***not*** resume on its
|
|
||||||
own, it needs to be used together with either the
|
|
||||||
scheduling functions or [`TaskScheduler::resume_task`].
|
|
||||||
*/
|
|
||||||
pub fn create_task(
|
|
||||||
&self,
|
|
||||||
kind: TaskKind,
|
|
||||||
thread: LuaThread<'_>,
|
|
||||||
thread_args: Option<LuaMultiValue<'_>>,
|
|
||||||
inherit_current_guid: bool,
|
|
||||||
) -> LuaResult<TaskReference> {
|
|
||||||
// Store the thread and its arguments in the registry
|
|
||||||
// NOTE: We must convert to a vec since multis
|
|
||||||
// can't be stored in the registry directly
|
|
||||||
let task_args_vec: Option<Vec<LuaValue>> = thread_args.map(|opt| opt.into_vec());
|
|
||||||
let task_args_key: LuaRegistryKey = self.lua.create_registry_value(task_args_vec)?;
|
|
||||||
let task_thread_key: LuaRegistryKey = self.lua.create_registry_value(thread)?;
|
|
||||||
// Create the full task struct
|
|
||||||
let task = Task {
|
|
||||||
kind,
|
|
||||||
thread: task_thread_key,
|
|
||||||
args: task_args_key,
|
|
||||||
};
|
|
||||||
// Create the task ref to use
|
|
||||||
let guid = if inherit_current_guid {
|
|
||||||
self.current_task()
|
|
||||||
.ok_or_else(|| LuaError::RuntimeError("No current guid to inherit".to_string()))?
|
|
||||||
.id()
|
|
||||||
} else {
|
|
||||||
let guid = self.guid.get();
|
|
||||||
self.guid.set(guid + 1);
|
|
||||||
guid
|
|
||||||
};
|
|
||||||
let reference = TaskReference::new(kind, guid);
|
|
||||||
// Increment the corresponding task counter
|
|
||||||
match kind {
|
|
||||||
TaskKind::Future => self.futures_count.set(self.futures_count.get() + 1),
|
|
||||||
_ => self.tasks_count.set(self.tasks_count.get() + 1),
|
|
||||||
}
|
|
||||||
// Add the task to the scheduler
|
|
||||||
{
|
|
||||||
let mut tasks = self.tasks.borrow_mut();
|
|
||||||
tasks.insert(reference, task);
|
|
||||||
}
|
|
||||||
Ok(reference)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Cancels a task, if the task still exists in the scheduler.
|
|
||||||
|
|
||||||
It is possible to hold one or more task references that point
|
|
||||||
to a task that no longer exists in the scheduler, and calling
|
|
||||||
this method with one of those references will return `false`.
|
|
||||||
*/
|
|
||||||
pub fn remove_task(&self, reference: TaskReference) -> LuaResult<bool> {
|
|
||||||
/*
|
|
||||||
Remove the task from the task list and the Lua registry
|
|
||||||
|
|
||||||
This is all we need to do since resume_task will always
|
|
||||||
ignore resumption of any task that no longer exists there
|
|
||||||
|
|
||||||
This does lead to having some amount of "junk" futures that will
|
|
||||||
build up in the queue but these will get cleaned up and not block
|
|
||||||
the program from exiting since the scheduler only runs until there
|
|
||||||
are no tasks left in the task list, the futures do not matter there
|
|
||||||
*/
|
|
||||||
let mut found = false;
|
|
||||||
let mut tasks = self.tasks.borrow_mut();
|
|
||||||
// Unfortunately we have to loop through to find which task
|
|
||||||
// references to remove instead of removing directly since
|
|
||||||
// tasks can switch kinds between instant, deferred, future
|
|
||||||
let tasks_to_remove: Vec<_> = tasks
|
|
||||||
.keys()
|
|
||||||
.filter(|task_ref| task_ref.id() == reference.id())
|
|
||||||
.copied()
|
|
||||||
.collect();
|
|
||||||
for task_ref in &tasks_to_remove {
|
|
||||||
if let Some(task) = tasks.remove(task_ref) {
|
|
||||||
// Decrement the corresponding task counter
|
|
||||||
match task.kind {
|
|
||||||
TaskKind::Future => self.futures_count.set(self.futures_count.get() - 1),
|
|
||||||
_ => self.tasks_count.set(self.tasks_count.get() - 1),
|
|
||||||
}
|
|
||||||
// NOTE: We need to close the thread here to
|
|
||||||
// make 100% sure that nothing can resume it
|
|
||||||
let close: LuaFunction = self.lua.named_registry_value("co.close")?;
|
|
||||||
let thread: LuaThread = self.lua.registry_value(&task.thread)?;
|
|
||||||
close.call(thread)?;
|
|
||||||
self.lua.remove_registry_value(task.thread)?;
|
|
||||||
self.lua.remove_registry_value(task.args)?;
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(found)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Resumes a task, if the task still exists in the scheduler.
|
|
||||||
|
|
||||||
A task may no longer exist in the scheduler if it has been manually
|
|
||||||
cancelled and removed by calling [`TaskScheduler::cancel_task()`].
|
|
||||||
|
|
||||||
This will be a no-op if the task no longer exists.
|
|
||||||
*/
|
|
||||||
pub fn resume_task<'a, 'r>(
|
|
||||||
&self,
|
|
||||||
reference: TaskReference,
|
|
||||||
override_args: Option<LuaResult<LuaMultiValue<'a>>>,
|
|
||||||
) -> LuaResult<(LuaThreadStatus, LuaMultiValue<'r>)>
|
|
||||||
where
|
|
||||||
'a: 'r,
|
|
||||||
{
|
|
||||||
// Fetch and check if the task was removed, if it got
|
|
||||||
// removed it means it was intentionally cancelled
|
|
||||||
let task = {
|
|
||||||
let mut tasks = self.tasks.borrow_mut();
|
|
||||||
match tasks.remove(&reference) {
|
|
||||||
Some(task) => task,
|
|
||||||
None => return Ok((LuaThreadStatus::Unresumable, LuaMultiValue::new())),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Decrement the corresponding task counter
|
|
||||||
match task.kind {
|
|
||||||
TaskKind::Future => self.futures_count.set(self.futures_count.get() - 1),
|
|
||||||
_ => self.tasks_count.set(self.tasks_count.get() - 1),
|
|
||||||
}
|
|
||||||
// Fetch and remove the thread to resume + its arguments
|
|
||||||
let thread: LuaThread = self.lua.registry_value(&task.thread)?;
|
|
||||||
let thread_args: Option<LuaMultiValue> = {
|
|
||||||
self.lua
|
|
||||||
.registry_value::<Option<Vec<LuaValue>>>(&task.args)
|
|
||||||
.expect("Failed to get stored args for task")
|
|
||||||
.map(LuaMultiValue::from_vec)
|
|
||||||
};
|
|
||||||
self.lua.remove_registry_value(task.thread)?;
|
|
||||||
self.lua.remove_registry_value(task.args)?;
|
|
||||||
// We got everything we need and our references
|
|
||||||
// were cleaned up properly, resume the thread
|
|
||||||
self.tasks_current.set(Some(reference));
|
|
||||||
let rets = match override_args {
|
|
||||||
Some(override_res) => match override_res {
|
|
||||||
Ok(args) => thread.resume(args),
|
|
||||||
Err(e) => {
|
|
||||||
// NOTE: Setting this error here means that when the thread
|
|
||||||
// is resumed it will error instantly, so we don't need
|
|
||||||
// to call it with proper args, empty args is fine
|
|
||||||
self.tasks_current_lua_error.try_lock().unwrap().replace(e);
|
|
||||||
thread.resume(())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => match thread_args {
|
|
||||||
Some(args) => thread.resume(args),
|
|
||||||
None => thread.resume(()),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
self.tasks_current.set(None);
|
|
||||||
match rets {
|
|
||||||
Ok(rets) => Ok((thread.status(), rets)),
|
|
||||||
Err(e) => Err(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Queues a new blocking task to run on the task scheduler.
|
|
||||||
*/
|
|
||||||
pub(crate) fn queue_blocking_task(
|
|
||||||
&self,
|
|
||||||
kind: TaskKind,
|
|
||||||
thread: LuaThread<'_>,
|
|
||||||
thread_args: Option<LuaMultiValue<'_>>,
|
|
||||||
) -> LuaResult<TaskReference> {
|
|
||||||
if kind == TaskKind::Future {
|
|
||||||
panic!("Tried to schedule future using normal task schedule method")
|
|
||||||
}
|
|
||||||
let task_ref = self.create_task(kind, thread, thread_args, false)?;
|
|
||||||
// Add the task to the front of the queue, unless it
|
|
||||||
// should be deferred, in that case add it to the back
|
|
||||||
let mut queue = self.tasks_queue_blocking.borrow_mut();
|
|
||||||
let num_prev_blocking_tasks = queue.len();
|
|
||||||
if kind == TaskKind::Deferred {
|
|
||||||
queue.push_back(task_ref);
|
|
||||||
} else {
|
|
||||||
queue.push_front(task_ref);
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
If we had any previous task and are currently async
|
|
||||||
waiting on tasks, we should send a signal to wake up
|
|
||||||
and run the new blocking task that was just queued
|
|
||||||
|
|
||||||
This can happen in cases such as an async http
|
|
||||||
server waking up from a connection and then wanting to
|
|
||||||
run a lua callback in response, to create the.. response
|
|
||||||
*/
|
|
||||||
if num_prev_blocking_tasks == 0 {
|
|
||||||
self.futures_tx
|
|
||||||
.send(TaskSchedulerMessage::NewBlockingTaskReady)
|
|
||||||
.expect("Futures waker channel was closed")
|
|
||||||
}
|
|
||||||
Ok(task_ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Queues a new future to run on the task scheduler.
|
|
||||||
*/
|
|
||||||
pub(crate) fn queue_async_task(
|
|
||||||
&self,
|
|
||||||
thread: LuaThread<'_>,
|
|
||||||
thread_args: Option<LuaMultiValue<'_>>,
|
|
||||||
fut: impl Future<Output = TaskFutureRets<'fut>> + 'fut,
|
|
||||||
) -> LuaResult<TaskReference> {
|
|
||||||
let task_ref = self.create_task(TaskKind::Future, thread, thread_args, false)?;
|
|
||||||
let futs = self
|
|
||||||
.futures
|
|
||||||
.try_lock()
|
|
||||||
.expect("Tried to add future to queue during futures resumption");
|
|
||||||
futs.push(Box::pin(async move {
|
|
||||||
let result = fut.await;
|
|
||||||
(Some(task_ref), result)
|
|
||||||
}));
|
|
||||||
Ok(task_ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Queues a new future to run on the task scheduler,
|
|
||||||
inheriting the task id of the currently running task.
|
|
||||||
*/
|
|
||||||
pub(crate) fn queue_async_task_inherited(
|
|
||||||
&self,
|
|
||||||
thread: LuaThread<'_>,
|
|
||||||
thread_args: Option<LuaMultiValue<'_>>,
|
|
||||||
fut: impl Future<Output = TaskFutureRets<'fut>> + 'fut,
|
|
||||||
) -> LuaResult<TaskReference> {
|
|
||||||
let task_ref = self.create_task(TaskKind::Future, thread, thread_args, true)?;
|
|
||||||
let futs = self
|
|
||||||
.futures
|
|
||||||
.try_lock()
|
|
||||||
.expect("Tried to add future to queue during futures resumption");
|
|
||||||
futs.push(Box::pin(async move {
|
|
||||||
let result = fut.await;
|
|
||||||
(Some(task_ref), result)
|
|
||||||
}));
|
|
||||||
Ok(task_ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Waits for a task to complete.
|
|
||||||
|
|
||||||
Panics if the task is not currently in the scheduler.
|
|
||||||
*/
|
|
||||||
pub(crate) async fn wait_for_task_completion(
|
|
||||||
&self,
|
|
||||||
reference: TaskReference,
|
|
||||||
) -> LuaResult<LuaMultiValue> {
|
|
||||||
if !self.tasks.borrow().contains_key(&reference) {
|
|
||||||
panic!("Task does not exist in scheduler")
|
|
||||||
}
|
|
||||||
let state = TaskWaiterState::new();
|
|
||||||
{
|
|
||||||
let mut all_states = self.tasks_waiter_states.borrow_mut();
|
|
||||||
all_states
|
|
||||||
.entry(reference)
|
|
||||||
.or_insert_with(Vec::new)
|
|
||||||
.push(Arc::clone(&state));
|
|
||||||
}
|
|
||||||
TaskWaiterFuture::new(&state).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Wakes a task that has been completed and may have external code
|
|
||||||
waiting on it using [`TaskScheduler::wait_for_task_completion`].
|
|
||||||
*/
|
|
||||||
pub(super) fn wake_completed_task(
|
|
||||||
&self,
|
|
||||||
reference: TaskReference,
|
|
||||||
result: LuaResult<LuaMultiValue<'fut>>,
|
|
||||||
) {
|
|
||||||
if let Some(waiter_states) = self.tasks_waiter_states.borrow_mut().remove(&reference) {
|
|
||||||
for waiter_state in waiter_states {
|
|
||||||
waiter_state.try_lock().unwrap().finalize(result.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
use core::panic;
|
|
||||||
|
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
use tokio::sync::mpsc;
|
|
||||||
|
|
||||||
use super::scheduler_message::TaskSchedulerMessage;
|
|
||||||
|
|
||||||
/**
|
|
||||||
A handle to a registered asynchronous background task.
|
|
||||||
|
|
||||||
[`TaskSchedulerAsyncHandle::unregister`] must be
|
|
||||||
called upon completion of the background task to
|
|
||||||
prevent the task scheduler from running indefinitely.
|
|
||||||
*/
|
|
||||||
#[must_use = "Background tasks must be unregistered"]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct TaskSchedulerAsyncHandle {
|
|
||||||
unregistered: bool,
|
|
||||||
sender: mpsc::UnboundedSender<TaskSchedulerMessage>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TaskSchedulerAsyncHandle {
|
|
||||||
pub fn new(sender: mpsc::UnboundedSender<TaskSchedulerMessage>) -> Self {
|
|
||||||
Self {
|
|
||||||
unregistered: false,
|
|
||||||
sender,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unregister(mut self, result: LuaResult<()>) {
|
|
||||||
self.unregistered = true;
|
|
||||||
self.sender
|
|
||||||
.send(TaskSchedulerMessage::Terminated(result))
|
|
||||||
.unwrap_or_else(|_| {
|
|
||||||
panic!(
|
|
||||||
"\
|
|
||||||
\nFailed to unregister background task - this is an internal error! \
|
|
||||||
\nPlease report it at {} \
|
|
||||||
\nDetails: Manual \
|
|
||||||
",
|
|
||||||
env!("CARGO_PKG_REPOSITORY")
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
/// Internal message enum for the task scheduler, used to notify
|
|
||||||
/// futures to wake up and schedule their respective blocking tasks
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum TaskSchedulerMessage {
|
|
||||||
NewBlockingTaskReady,
|
|
||||||
NewLuaErrorReady(LuaError),
|
|
||||||
Spawned,
|
|
||||||
Terminated(LuaResult<()>),
|
|
||||||
}
|
|
|
@ -1,172 +0,0 @@
|
||||||
use std::{fmt, process::ExitCode};
|
|
||||||
|
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
use super::scheduler::TaskScheduler;
|
|
||||||
|
|
||||||
/// Struct representing the current state of the task scheduler
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
#[must_use = "Scheduler state must be checked after every resumption"]
|
|
||||||
pub struct TaskSchedulerState {
|
|
||||||
pub(super) lua_error: Option<LuaError>,
|
|
||||||
pub(super) exit_code: Option<ExitCode>,
|
|
||||||
pub(super) num_blocking: usize,
|
|
||||||
pub(super) num_futures: usize,
|
|
||||||
pub(super) num_background: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TaskSchedulerState {
|
|
||||||
pub(super) fn new(sched: &TaskScheduler) -> Self {
|
|
||||||
Self {
|
|
||||||
lua_error: None,
|
|
||||||
exit_code: sched.exit_code.get(),
|
|
||||||
num_blocking: sched.tasks_count.get(),
|
|
||||||
num_futures: sched.futures_count.get(),
|
|
||||||
num_background: sched.futures_background_count.get(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn err(sched: &TaskScheduler, err: LuaError) -> Self {
|
|
||||||
let mut this = Self::new(sched);
|
|
||||||
this.lua_error = Some(err);
|
|
||||||
this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Returns a clone of the error from
|
|
||||||
this task scheduler result, if any.
|
|
||||||
*/
|
|
||||||
pub fn get_lua_error(&self) -> Option<LuaError> {
|
|
||||||
self.lua_error.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Returns a clone of the exit code from
|
|
||||||
this task scheduler result, if any.
|
|
||||||
*/
|
|
||||||
pub fn get_exit_code(&self) -> Option<ExitCode> {
|
|
||||||
self.exit_code
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Returns `true` if the task scheduler still
|
|
||||||
has blocking lua threads left to run.
|
|
||||||
*/
|
|
||||||
pub fn is_blocking(&self) -> bool {
|
|
||||||
self.num_blocking > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Returns `true` if the task scheduler has finished all
|
|
||||||
blocking lua tasks, but still has yielding tasks running.
|
|
||||||
*/
|
|
||||||
pub fn is_yielding(&self) -> bool {
|
|
||||||
self.num_blocking == 0 && self.num_futures > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Returns `true` if the task scheduler has finished all
|
|
||||||
lua threads, but still has background tasks running.
|
|
||||||
*/
|
|
||||||
pub fn is_background(&self) -> bool {
|
|
||||||
self.num_blocking == 0 && self.num_futures == 0 && self.num_background > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Returns `true` if the task scheduler is done,
|
|
||||||
meaning it has no lua threads left to run, and
|
|
||||||
no spawned tasks are running in the background.
|
|
||||||
|
|
||||||
Also returns `true` if a task has requested to exit the process.
|
|
||||||
*/
|
|
||||||
pub fn is_done(&self) -> bool {
|
|
||||||
self.exit_code.is_some()
|
|
||||||
|| (self.num_blocking == 0 && self.num_futures == 0 && self.num_background == 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for TaskSchedulerState {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
let status = if self.is_blocking() {
|
|
||||||
"Busy"
|
|
||||||
} else if self.is_yielding() {
|
|
||||||
"Yielding"
|
|
||||||
} else if self.is_background() {
|
|
||||||
"Background"
|
|
||||||
} else {
|
|
||||||
"Done"
|
|
||||||
};
|
|
||||||
let code = match self.get_exit_code() {
|
|
||||||
Some(code) => format!("{code:?}"),
|
|
||||||
None => "-".to_string(),
|
|
||||||
};
|
|
||||||
let err = match self.get_lua_error() {
|
|
||||||
Some(e) => format!("{e:?}")
|
|
||||||
.as_bytes()
|
|
||||||
.chunks(42) // Kinda arbitrary but should fit in most terminals
|
|
||||||
.enumerate()
|
|
||||||
.map(|(idx, buf)| {
|
|
||||||
format!(
|
|
||||||
"{}{}{}{}{}",
|
|
||||||
if idx == 0 { "" } else { "\n│ " },
|
|
||||||
if idx == 0 {
|
|
||||||
"".to_string()
|
|
||||||
} else {
|
|
||||||
" ".repeat(16)
|
|
||||||
},
|
|
||||||
if idx == 0 { "" } else { " │ " },
|
|
||||||
String::from_utf8_lossy(buf),
|
|
||||||
if buf.len() == 42 { " │" } else { "" },
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<String>(),
|
|
||||||
None => "-".to_string(),
|
|
||||||
};
|
|
||||||
let parts = vec![
|
|
||||||
format!("Status │ {status}"),
|
|
||||||
format!("Tasks active │ {}", self.num_blocking),
|
|
||||||
format!("Tasks background │ {}", self.num_background),
|
|
||||||
format!("Status code │ {code}"),
|
|
||||||
format!("Lua error │ {err}"),
|
|
||||||
];
|
|
||||||
let lengths = parts
|
|
||||||
.iter()
|
|
||||||
.map(|part| {
|
|
||||||
part.lines()
|
|
||||||
.next()
|
|
||||||
.unwrap()
|
|
||||||
.trim_end_matches(" │")
|
|
||||||
.chars()
|
|
||||||
.count()
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let longest = &parts
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.fold(0, |acc, (index, _)| acc.max(lengths[index]));
|
|
||||||
let sep = "─".repeat(longest + 2);
|
|
||||||
writeln!(f, "┌{}┐", &sep)?;
|
|
||||||
for (index, part) in parts.iter().enumerate() {
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"│ {}{} │",
|
|
||||||
part.trim_end_matches(" │"),
|
|
||||||
" ".repeat(
|
|
||||||
longest
|
|
||||||
- part
|
|
||||||
.lines()
|
|
||||||
.last()
|
|
||||||
.unwrap()
|
|
||||||
.trim_end_matches(" │")
|
|
||||||
.chars()
|
|
||||||
.count()
|
|
||||||
)
|
|
||||||
)?;
|
|
||||||
if index < parts.len() - 1 {
|
|
||||||
writeln!(f, "┝{}┥", &sep)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
write!(f, "└{}┘", &sep)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
/// Enum representing different kinds of tasks
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub enum TaskKind {
|
|
||||||
Instant,
|
|
||||||
Deferred,
|
|
||||||
Future,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
impl TaskKind {
|
|
||||||
pub fn is_instant(&self) -> bool {
|
|
||||||
*self == Self::Instant
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_deferred(&self) -> bool {
|
|
||||||
*self == Self::Deferred
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_blocking(&self) -> bool {
|
|
||||||
*self != Self::Future
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_future(&self) -> bool {
|
|
||||||
*self == Self::Future
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for TaskKind {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
let name: &'static str = match self {
|
|
||||||
TaskKind::Instant => "Instant",
|
|
||||||
TaskKind::Deferred => "Deferred",
|
|
||||||
TaskKind::Future => "Future",
|
|
||||||
};
|
|
||||||
write!(f, "{name}")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
use std::{
|
|
||||||
fmt,
|
|
||||||
hash::{Hash, Hasher},
|
|
||||||
};
|
|
||||||
|
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
use super::task_kind::TaskKind;
|
|
||||||
|
|
||||||
/// A lightweight, copyable struct that represents a
|
|
||||||
/// task in the scheduler and is accessible from Lua
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct TaskReference {
|
|
||||||
kind: TaskKind,
|
|
||||||
guid: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TaskReference {
|
|
||||||
pub const fn new(kind: TaskKind, guid: usize) -> Self {
|
|
||||||
Self { kind, guid }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn id(&self) -> usize {
|
|
||||||
self.guid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for TaskReference {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
if self.guid == 0 {
|
|
||||||
write!(f, "TaskReference(MAIN)")
|
|
||||||
} else {
|
|
||||||
write!(f, "TaskReference({} - {})", self.kind, self.guid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for TaskReference {}
|
|
||||||
impl PartialEq for TaskReference {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.guid == other.guid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hash for TaskReference {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
self.guid.hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LuaUserData for TaskReference {}
|
|
|
@ -1,66 +0,0 @@
|
||||||
use std::{
|
|
||||||
future::Future,
|
|
||||||
pin::Pin,
|
|
||||||
sync::Arc,
|
|
||||||
task::{Context, Poll, Waker},
|
|
||||||
};
|
|
||||||
|
|
||||||
use tokio::sync::Mutex as AsyncMutex;
|
|
||||||
|
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub(super) struct TaskWaiterState<'fut> {
|
|
||||||
rets: Option<LuaResult<LuaMultiValue<'fut>>>,
|
|
||||||
waker: Option<Waker>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'fut> TaskWaiterState<'fut> {
|
|
||||||
pub fn new() -> Arc<AsyncMutex<Self>> {
|
|
||||||
Arc::new(AsyncMutex::new(TaskWaiterState {
|
|
||||||
rets: None,
|
|
||||||
waker: None,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn finalize(&mut self, rets: LuaResult<LuaMultiValue<'fut>>) {
|
|
||||||
self.rets = Some(rets);
|
|
||||||
if let Some(waker) = self.waker.take() {
|
|
||||||
waker.wake();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(super) struct TaskWaiterFuture<'fut> {
|
|
||||||
state: Arc<AsyncMutex<TaskWaiterState<'fut>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'fut> TaskWaiterFuture<'fut> {
|
|
||||||
pub fn new(state: &Arc<AsyncMutex<TaskWaiterState<'fut>>>) -> Self {
|
|
||||||
Self {
|
|
||||||
state: Arc::clone(state),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'fut> Clone for TaskWaiterFuture<'fut> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
state: Arc::clone(&self.state),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'fut> Future for TaskWaiterFuture<'fut> {
|
|
||||||
type Output = LuaResult<LuaMultiValue<'fut>>;
|
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
|
||||||
let mut shared_state = self.state.try_lock().unwrap();
|
|
||||||
if let Some(rets) = shared_state.rets.clone() {
|
|
||||||
Poll::Ready(rets)
|
|
||||||
} else {
|
|
||||||
shared_state.waker = Some(cx.waker().clone());
|
|
||||||
Poll::Pending
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
107
src/old/mod.rs
107
src/old/mod.rs
|
@ -1,107 +0,0 @@
|
||||||
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<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<V>(mut self, args: V) -> Self
|
|
||||||
where
|
|
||||||
V: Into<Vec<String>>,
|
|
||||||
{
|
|
||||||
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<str>,
|
|
||||||
script_contents: impl AsRef<[u8]>,
|
|
||||||
) -> Result<ExitCode, LuneError> {
|
|
||||||
self.run_inner(script_name, script_contents)
|
|
||||||
.await
|
|
||||||
.map_err(LuneError::from)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run_inner(
|
|
||||||
&self,
|
|
||||||
script_name: impl AsRef<str>,
|
|
||||||
script_contents: impl AsRef<[u8]>,
|
|
||||||
) -> Result<ExitCode, LuaError> {
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue