mirror of
https://github.com/lune-org/lune.git
synced 2024-12-13 13:30:38 +00:00
Implement functional but blocking task lib in rust
This commit is contained in:
parent
6b14bc3dc0
commit
706368a462
7 changed files with 115 additions and 162 deletions
|
@ -5,7 +5,7 @@ use crate::utils::{
|
||||||
table_builder::ReadonlyTableBuilder,
|
table_builder::ReadonlyTableBuilder,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn create(lua: Lua) -> Result<Lua> {
|
pub async fn create(lua: &Lua) -> Result<()> {
|
||||||
let print = |args: &MultiValue, throw: bool| -> Result<()> {
|
let print = |args: &MultiValue, throw: bool| -> Result<()> {
|
||||||
let s = pretty_format_multi_value(args)?;
|
let s = pretty_format_multi_value(args)?;
|
||||||
if throw {
|
if throw {
|
||||||
|
@ -18,7 +18,7 @@ pub async fn create(lua: Lua) -> Result<Lua> {
|
||||||
};
|
};
|
||||||
lua.globals().raw_set(
|
lua.globals().raw_set(
|
||||||
"console",
|
"console",
|
||||||
ReadonlyTableBuilder::new(&lua)?
|
ReadonlyTableBuilder::new(lua)?
|
||||||
.with_function("resetColor", |_, _: ()| print_color("reset"))?
|
.with_function("resetColor", |_, _: ()| print_color("reset"))?
|
||||||
.with_function("setColor", |_, color: String| print_color(color))?
|
.with_function("setColor", |_, color: String| print_color(color))?
|
||||||
.with_function("resetStyle", |_, _: ()| print_style("reset"))?
|
.with_function("resetStyle", |_, _: ()| print_style("reset"))?
|
||||||
|
@ -40,6 +40,5 @@ pub async fn create(lua: Lua) -> Result<Lua> {
|
||||||
print(&args, true)
|
print(&args, true)
|
||||||
})?
|
})?
|
||||||
.build()?,
|
.build()?,
|
||||||
)?;
|
)
|
||||||
Ok(lua)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,10 @@ use tokio::fs;
|
||||||
|
|
||||||
use crate::utils::table_builder::ReadonlyTableBuilder;
|
use crate::utils::table_builder::ReadonlyTableBuilder;
|
||||||
|
|
||||||
pub async fn create(lua: Lua) -> Result<Lua> {
|
pub async fn create(lua: &Lua) -> Result<()> {
|
||||||
lua.globals().raw_set(
|
lua.globals().raw_set(
|
||||||
"fs",
|
"fs",
|
||||||
ReadonlyTableBuilder::new(&lua)?
|
ReadonlyTableBuilder::new(lua)?
|
||||||
.with_async_function("readFile", fs_read_file)?
|
.with_async_function("readFile", fs_read_file)?
|
||||||
.with_async_function("readDir", fs_read_dir)?
|
.with_async_function("readDir", fs_read_dir)?
|
||||||
.with_async_function("writeFile", fs_write_file)?
|
.with_async_function("writeFile", fs_write_file)?
|
||||||
|
@ -18,8 +18,7 @@ pub async fn create(lua: Lua) -> Result<Lua> {
|
||||||
.with_async_function("isFile", fs_is_file)?
|
.with_async_function("isFile", fs_is_file)?
|
||||||
.with_async_function("isDir", fs_is_dir)?
|
.with_async_function("isDir", fs_is_dir)?
|
||||||
.build()?,
|
.build()?,
|
||||||
)?;
|
)
|
||||||
Ok(lua)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fs_read_file(_: &Lua, path: String) -> Result<String> {
|
async fn fs_read_file(_: &Lua, path: String) -> Result<String> {
|
||||||
|
|
|
@ -8,16 +8,15 @@ use reqwest::{
|
||||||
|
|
||||||
use crate::utils::{net::get_request_user_agent_header, table_builder::ReadonlyTableBuilder};
|
use crate::utils::{net::get_request_user_agent_header, table_builder::ReadonlyTableBuilder};
|
||||||
|
|
||||||
pub async fn create(lua: Lua) -> Result<Lua> {
|
pub async fn create(lua: &Lua) -> Result<()> {
|
||||||
lua.globals().raw_set(
|
lua.globals().raw_set(
|
||||||
"net",
|
"net",
|
||||||
ReadonlyTableBuilder::new(&lua)?
|
ReadonlyTableBuilder::new(lua)?
|
||||||
.with_function("jsonEncode", net_json_encode)?
|
.with_function("jsonEncode", net_json_encode)?
|
||||||
.with_function("jsonDecode", net_json_decode)?
|
.with_function("jsonDecode", net_json_decode)?
|
||||||
.with_async_function("request", net_request)?
|
.with_async_function("request", net_request)?
|
||||||
.build()?,
|
.build()?,
|
||||||
)?;
|
)
|
||||||
Ok(lua)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn net_json_encode(_: &Lua, (val, pretty): (Value, Option<bool>)) -> Result<String> {
|
fn net_json_encode(_: &Lua, (val, pretty): (Value, Option<bool>)) -> Result<String> {
|
||||||
|
|
|
@ -9,7 +9,7 @@ use tokio::process::Command;
|
||||||
|
|
||||||
use crate::utils::table_builder::ReadonlyTableBuilder;
|
use crate::utils::table_builder::ReadonlyTableBuilder;
|
||||||
|
|
||||||
pub async fn create(lua: Lua, args_vec: Vec<String>) -> Result<Lua> {
|
pub async fn create(lua: &Lua, args_vec: Vec<String>) -> Result<()> {
|
||||||
// Create readonly args array
|
// Create readonly args array
|
||||||
let inner_args = lua.create_table()?;
|
let inner_args = lua.create_table()?;
|
||||||
for arg in &args_vec {
|
for arg in &args_vec {
|
||||||
|
@ -38,14 +38,13 @@ pub async fn create(lua: Lua, args_vec: Vec<String>) -> Result<Lua> {
|
||||||
// Create the full process table
|
// Create the full process table
|
||||||
lua.globals().raw_set(
|
lua.globals().raw_set(
|
||||||
"process",
|
"process",
|
||||||
ReadonlyTableBuilder::new(&lua)?
|
ReadonlyTableBuilder::new(lua)?
|
||||||
.with_table("args", inner_args)?
|
.with_table("args", inner_args)?
|
||||||
.with_table("env", inner_env)?
|
.with_table("env", inner_env)?
|
||||||
.with_function("exit", process_exit)?
|
.with_function("exit", process_exit)?
|
||||||
.with_async_function("spawn", process_spawn)?
|
.with_async_function("spawn", process_spawn)?
|
||||||
.build()?,
|
.build()?,
|
||||||
)?;
|
)
|
||||||
Ok(lua)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_env_get<'lua>(lua: &'lua Lua, (_, key): (Value<'lua>, String)) -> Result<Value<'lua>> {
|
fn process_env_get<'lua>(lua: &'lua Lua, (_, key): (Value<'lua>, String)) -> Result<Value<'lua>> {
|
||||||
|
|
|
@ -1,27 +1,80 @@
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use mlua::{Lua, Result};
|
use mlua::{Error, Function, Lua, Result, Table, Thread, Value, Variadic};
|
||||||
use tokio::time;
|
use tokio::time::{self, Instant};
|
||||||
|
|
||||||
use crate::utils::table_builder::ReadonlyTableBuilder;
|
use crate::utils::table_builder::ReadonlyTableBuilder;
|
||||||
|
|
||||||
const DEFAULT_SLEEP_DURATION: f32 = 1.0 / 60.0;
|
pub async fn create(lua: &Lua) -> Result<()> {
|
||||||
|
|
||||||
pub async fn create(lua: Lua) -> Result<Lua> {
|
|
||||||
lua.globals().raw_set(
|
lua.globals().raw_set(
|
||||||
"task",
|
"task",
|
||||||
ReadonlyTableBuilder::new(&lua)?
|
ReadonlyTableBuilder::new(lua)?
|
||||||
|
.with_async_function("cancel", task_cancel)?
|
||||||
|
.with_async_function("defer", task_defer)?
|
||||||
|
.with_async_function("delay", task_delay)?
|
||||||
|
.with_async_function("spawn", task_spawn)?
|
||||||
.with_async_function("wait", task_wait)?
|
.with_async_function("wait", task_wait)?
|
||||||
.build()?,
|
.build()?,
|
||||||
)?;
|
)
|
||||||
Ok(lua)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: It does seem possible to properly make an async wait
|
fn get_thread_from_arg<'a>(lua: &'a Lua, thread_or_function_arg: Value<'a>) -> Result<Thread<'a>> {
|
||||||
// function with mlua right now, something breaks when using
|
Ok(match thread_or_function_arg {
|
||||||
// async wait functions inside of coroutines
|
Value::Thread(thread) => thread,
|
||||||
async fn task_wait(_: &Lua, duration: Option<f32>) -> Result<f32> {
|
Value::Function(func) => lua.create_thread(func)?,
|
||||||
let secs = duration.unwrap_or(DEFAULT_SLEEP_DURATION);
|
val => {
|
||||||
time::sleep(Duration::from_secs_f32(secs)).await;
|
return Err(Error::RuntimeError(format!(
|
||||||
Ok(secs)
|
"Expected type thread or function, got {}",
|
||||||
|
val.type_name()
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn task_cancel(lua: &Lua, thread: Thread<'_>) -> Result<()> {
|
||||||
|
let coroutine: Table = lua.globals().raw_get("coroutine")?;
|
||||||
|
let close: Function = coroutine.raw_get("close")?;
|
||||||
|
close.call_async(thread).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn task_defer<'a>(lua: &Lua, (tof, args): (Value<'a>, Variadic<Value<'a>>)) -> Result<()> {
|
||||||
|
task_wait(lua, None).await?;
|
||||||
|
get_thread_from_arg(lua, tof)?
|
||||||
|
.into_async::<_, Variadic<Value<'_>>>(args)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn task_delay<'a>(
|
||||||
|
lua: &Lua,
|
||||||
|
(delay, tof, args): (Option<f32>, Value<'a>, Variadic<Value<'a>>),
|
||||||
|
) -> Result<()> {
|
||||||
|
task_wait(lua, delay).await?;
|
||||||
|
get_thread_from_arg(lua, tof)?
|
||||||
|
.into_async::<_, Variadic<Value<'_>>>(args)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn task_spawn<'a>(lua: &Lua, (tof, args): (Value<'a>, Variadic<Value<'a>>)) -> Result<()> {
|
||||||
|
get_thread_from_arg(lua, tof)?
|
||||||
|
.into_async::<_, Variadic<Value<'_>>>(args)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: It doesn't seem possible to properly make an async wait
|
||||||
|
// function with mlua right now, something breaks when using
|
||||||
|
// the async wait function inside of a coroutine
|
||||||
|
async fn task_wait(_: &Lua, duration: Option<f32>) -> Result<f32> {
|
||||||
|
let start = Instant::now();
|
||||||
|
time::sleep(
|
||||||
|
duration
|
||||||
|
.map(Duration::from_secs_f32)
|
||||||
|
.unwrap_or(Duration::ZERO),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let end = Instant::now();
|
||||||
|
Ok((end - start).as_secs_f32())
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ use std::collections::HashSet;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use mlua::Lua;
|
use mlua::Lua;
|
||||||
|
use tokio::task;
|
||||||
|
|
||||||
pub mod globals;
|
pub mod globals;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
@ -61,17 +62,27 @@ impl Lune {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(&self, name: &str, chunk: &str) -> Result<()> {
|
pub async fn run(&self, name: &str, chunk: &str) -> Result<()> {
|
||||||
let mut lua = Lua::new();
|
let run_name = name.to_owned();
|
||||||
for global in &self.globals {
|
let run_chunk = chunk.to_owned();
|
||||||
lua = match &global {
|
let run_globals = self.globals.to_owned();
|
||||||
LuneGlobal::Console => create_console(lua).await?,
|
let run_args = self.args.to_owned();
|
||||||
LuneGlobal::Fs => create_fs(lua).await?,
|
// Spawn a thread-local task so that we can then spawn
|
||||||
LuneGlobal::Net => create_net(lua).await?,
|
// more tasks in our globals without the Send requirement
|
||||||
LuneGlobal::Process => create_process(lua, self.args.clone()).await?,
|
let local = task::LocalSet::new();
|
||||||
LuneGlobal::Task => create_task(lua).await?,
|
local
|
||||||
|
.run_until(async move {
|
||||||
|
task::spawn_local(async move {
|
||||||
|
let lua = Lua::new();
|
||||||
|
for global in &run_globals {
|
||||||
|
match &global {
|
||||||
|
LuneGlobal::Console => create_console(&lua).await?,
|
||||||
|
LuneGlobal::Fs => create_fs(&lua).await?,
|
||||||
|
LuneGlobal::Net => create_net(&lua).await?,
|
||||||
|
LuneGlobal::Process => create_process(&lua, run_args.clone()).await?,
|
||||||
|
LuneGlobal::Task => create_task(&lua).await?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let result = lua.load(chunk).set_name(name)?.exec_async().await;
|
let result = lua.load(&run_chunk).set_name(&run_name)?.exec_async().await;
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(e) => bail!(
|
Err(e) => bail!(
|
||||||
|
@ -80,6 +91,11 @@ impl Lune {
|
||||||
pretty_format_luau_error(&e)
|
pretty_format_luau_error(&e)
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,112 +0,0 @@
|
||||||
local MINIMUM_DELAY_TIME = 1 / 100
|
|
||||||
|
|
||||||
type ThreadOrFunction<A..., R...> = thread | (A...) -> R...
|
|
||||||
type AnyThreadOrFunction = ThreadOrFunction<...any, ...any>
|
|
||||||
|
|
||||||
type WaitingThreadKind = "Normal" | "Deferred" | "Delayed"
|
|
||||||
type WaitingThread = {
|
|
||||||
idx: number,
|
|
||||||
kind: WaitingThreadKind,
|
|
||||||
thread: thread,
|
|
||||||
args: { [number]: any, n: number },
|
|
||||||
}
|
|
||||||
|
|
||||||
local waitingThreadCounter = 0
|
|
||||||
local waitingThreads: { WaitingThread } = {}
|
|
||||||
|
|
||||||
local function scheduleWaitingThreads()
|
|
||||||
-- Grab currently waiting threads and clear the queue but keep capacity
|
|
||||||
local threadsToResume: { WaitingThread } = table.clone(waitingThreads)
|
|
||||||
table.clear(waitingThreads)
|
|
||||||
table.sort(threadsToResume, function(t0, t1)
|
|
||||||
local k0: WaitingThreadKind = t0.kind
|
|
||||||
local k1: WaitingThreadKind = t1.kind
|
|
||||||
if k0 == k1 then
|
|
||||||
return t0.idx < t1.idx
|
|
||||||
end
|
|
||||||
if k0 == "Normal" then
|
|
||||||
return true
|
|
||||||
elseif k1 == "Normal" then
|
|
||||||
return false
|
|
||||||
elseif k0 == "Deferred" then
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
-- Resume threads in order, giving args & waiting if necessary
|
|
||||||
for _, waitingThread in threadsToResume do
|
|
||||||
coroutine.resume(
|
|
||||||
waitingThread.thread,
|
|
||||||
table.unpack(waitingThread.args, 1, waitingThread.args.n)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function insertWaitingThread(kind: WaitingThreadKind, tof: AnyThreadOrFunction, ...: any)
|
|
||||||
if typeof(tof) ~= "thread" and typeof(tof) ~= "function" then
|
|
||||||
if tof == nil then
|
|
||||||
error("Expected thread or function, got nil", 3)
|
|
||||||
end
|
|
||||||
error(
|
|
||||||
string.format("Expected thread or function, got %s %s", typeof(tof), tostring(tof)),
|
|
||||||
3
|
|
||||||
)
|
|
||||||
end
|
|
||||||
local thread = if type(tof) == "function" then coroutine.create(tof) else tof
|
|
||||||
waitingThreadCounter += 1
|
|
||||||
local waitingThread: WaitingThread = {
|
|
||||||
idx = waitingThreadCounter,
|
|
||||||
kind = kind,
|
|
||||||
thread = thread,
|
|
||||||
args = table.pack(...),
|
|
||||||
}
|
|
||||||
table.insert(waitingThreads, waitingThread)
|
|
||||||
return waitingThread
|
|
||||||
end
|
|
||||||
|
|
||||||
local function cancel(thread: unknown)
|
|
||||||
if typeof(thread) ~= "thread" then
|
|
||||||
if thread == nil then
|
|
||||||
error("Expected thread, got nil", 2)
|
|
||||||
end
|
|
||||||
error(string.format("Expected thread, got %s %s", typeof(thread), tostring(thread)), 2)
|
|
||||||
else
|
|
||||||
coroutine.close(thread)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function defer(tof: AnyThreadOrFunction, ...: any): thread
|
|
||||||
local waiting = insertWaitingThread("Deferred", tof, ...)
|
|
||||||
local original = waiting.thread
|
|
||||||
waiting.thread = coroutine.create(function(...)
|
|
||||||
task.wait(1 / 1_000_000)
|
|
||||||
coroutine.resume(original, ...)
|
|
||||||
end)
|
|
||||||
scheduleWaitingThreads()
|
|
||||||
return waiting.thread
|
|
||||||
end
|
|
||||||
|
|
||||||
local function delay(delay: number?, tof: AnyThreadOrFunction, ...: any): thread
|
|
||||||
local waiting = insertWaitingThread("Delayed", tof, ...)
|
|
||||||
local original = waiting.thread
|
|
||||||
waiting.thread = coroutine.create(function(...)
|
|
||||||
task.wait(math.max(MINIMUM_DELAY_TIME, delay or 0))
|
|
||||||
coroutine.resume(original, ...)
|
|
||||||
end)
|
|
||||||
scheduleWaitingThreads()
|
|
||||||
return waiting.thread
|
|
||||||
end
|
|
||||||
|
|
||||||
local function spawn(tof: AnyThreadOrFunction, ...: any): thread
|
|
||||||
local waiting = insertWaitingThread("Normal", tof, ...)
|
|
||||||
scheduleWaitingThreads()
|
|
||||||
return waiting.thread
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
cancel = cancel,
|
|
||||||
defer = defer,
|
|
||||||
delay = delay,
|
|
||||||
spawn = spawn,
|
|
||||||
}
|
|
Loading…
Reference in a new issue