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