diff --git a/src/lib/globals/console.rs b/src/lib/globals/console.rs
index 7322f9e..e030a29 100644
--- a/src/lib/globals/console.rs
+++ b/src/lib/globals/console.rs
@@ -5,7 +5,7 @@ use crate::utils::{
table_builder::ReadonlyTableBuilder,
};
-pub fn new(lua: &Lua) -> Result
{
+pub async fn new(lua: &Lua) -> Result {
let print = |args: &MultiValue, throw: bool| -> Result<()> {
let s = pretty_format_multi_value(args)?;
if throw {
diff --git a/src/lib/globals/fs.rs b/src/lib/globals/fs.rs
index 88580e0..9afd785 100644
--- a/src/lib/globals/fs.rs
+++ b/src/lib/globals/fs.rs
@@ -5,7 +5,7 @@ use tokio::fs;
use crate::utils::table_builder::ReadonlyTableBuilder;
-pub fn new(lua: &Lua) -> Result {
+pub async fn new(lua: &Lua) -> Result {
ReadonlyTableBuilder::new(lua)?
.with_async_function("readFile", fs_read_file)?
.with_async_function("readDir", fs_read_dir)?
diff --git a/src/lib/globals/mod.rs b/src/lib/globals/mod.rs
index 18515f9..4ecc1c5 100644
--- a/src/lib/globals/mod.rs
+++ b/src/lib/globals/mod.rs
@@ -9,5 +9,3 @@ pub use fs::new as new_fs;
pub use net::new as new_net;
pub use process::new as new_process;
pub use task::new as new_task;
-
-pub use task::WaitingThread as WaitingTaskThread;
diff --git a/src/lib/globals/net.rs b/src/lib/globals/net.rs
index 40ea446..2fea6e3 100644
--- a/src/lib/globals/net.rs
+++ b/src/lib/globals/net.rs
@@ -8,7 +8,7 @@ use reqwest::{
use crate::utils::{net::get_request_user_agent_header, table_builder::ReadonlyTableBuilder};
-pub fn new(lua: &Lua) -> Result {
+pub async fn new(lua: &Lua) -> Result {
ReadonlyTableBuilder::new(lua)?
.with_function("jsonEncode", net_json_encode)?
.with_function("jsonDecode", net_json_decode)?
diff --git a/src/lib/globals/process.rs b/src/lib/globals/process.rs
index 268d13c..314c190 100644
--- a/src/lib/globals/process.rs
+++ b/src/lib/globals/process.rs
@@ -9,7 +9,7 @@ use tokio::process::Command;
use crate::utils::table_builder::ReadonlyTableBuilder;
-pub fn new(lua: &Lua, args_vec: Vec) -> Result {
+pub async fn new(lua: &Lua, args_vec: Vec) -> Result {
// Create readonly args array
let inner_args = lua.create_table()?;
for arg in &args_vec {
diff --git a/src/lib/globals/task.rs b/src/lib/globals/task.rs
index d0c0e57..8acda69 100644
--- a/src/lib/globals/task.rs
+++ b/src/lib/globals/task.rs
@@ -1,60 +1,40 @@
-use std::{
- sync::{Arc, Mutex},
- time::Duration,
-};
+use std::{thread::sleep, time::Duration};
-use mlua::{Function, Lua, Result, Table, Thread, Value, Variadic};
-use tokio::time;
+use mlua::{Function, Lua, Result, Table, Value};
use crate::utils::table_builder::ReadonlyTableBuilder;
const DEFAULT_SLEEP_DURATION: f32 = 1.0 / 60.0;
-#[allow(dead_code)]
-pub struct WaitingThread<'a> {
- is_delayed_for: Option,
- is_deferred: Option,
- thread: Thread<'a>,
- args: Variadic>,
-}
+const TASK_LIB_LUAU: &str = include_str!("../luau/task.luau");
-pub fn new<'a>(lua: &'a Lua, _threads: &Arc>>>) -> Result> {
- // TODO: Figure out how to insert into threads vec
+pub async fn new(lua: &Lua) -> Result {
+ let task_lib: Table = lua
+ .load(TASK_LIB_LUAU)
+ .set_name("task")?
+ .eval_async()
+ .await?;
+ // FUTURE: Properly implementing the task library in async rust is
+ // very complicated but should be done at some point, for now we will
+ // fall back to implementing only task.wait and doing the rest in lua
+ let task_cancel: Function = task_lib.raw_get("cancel")?;
+ let task_defer: Function = task_lib.raw_get("defer")?;
+ let task_delay: Function = task_lib.raw_get("delay")?;
+ let task_spawn: Function = task_lib.raw_get("spawn")?;
ReadonlyTableBuilder::new(lua)?
- .with_function("cancel", |lua, thread: Thread| {
- thread.reset(lua.create_function(|_, _: ()| Ok(()))?)?;
- Ok(())
- })?
- .with_async_function(
- "defer",
- |lua, (func, args): (Function, Variadic)| async move {
- let thread = lua.create_thread(func)?;
- thread.into_async(args).await?;
- Ok(())
- },
- )?
- .with_async_function(
- "delay",
- |lua, (duration, func, args): (Option, Function, Variadic)| async move {
- let secs = duration.unwrap_or(DEFAULT_SLEEP_DURATION);
- time::sleep(Duration::from_secs_f32(secs)).await;
- let thread = lua.create_thread(func)?;
- thread.into_async(args).await?;
- Ok(())
- },
- )?
- .with_async_function(
- "spawn",
- |lua, (func, args): (Function, Variadic)| async move {
- let thread = lua.create_thread(func)?;
- thread.into_async(args).await?;
- Ok(())
- },
- )?
- .with_async_function("wait", |_, duration: Option| async move {
- let secs = duration.unwrap_or(DEFAULT_SLEEP_DURATION);
- time::sleep(Duration::from_secs_f32(secs)).await;
- Ok(secs)
- })?
+ .with_value("cancel", Value::Function(task_cancel))?
+ .with_value("defer", Value::Function(task_defer))?
+ .with_value("delay", Value::Function(task_delay))?
+ .with_value("spawn", Value::Function(task_spawn))?
+ .with_function("wait", wait)?
.build()
}
+
+// FIXME: It does seem possible to properly make an async wait
+// function with mlua right now, something breaks when using
+// async wait functions inside of coroutines
+fn wait(_: &Lua, duration: Option) -> Result {
+ let secs = duration.unwrap_or(DEFAULT_SLEEP_DURATION);
+ sleep(Duration::from_secs_f32(secs));
+ Ok(secs)
+}
diff --git a/src/lib/lib.rs b/src/lib/lib.rs
index 46c4a92..4318a40 100644
--- a/src/lib/lib.rs
+++ b/src/lib/lib.rs
@@ -1,5 +1,3 @@
-use std::sync::{Arc, Mutex};
-
use anyhow::{bail, Result};
use mlua::Lua;
@@ -13,16 +11,15 @@ use crate::{
pub async fn run_lune(name: &str, chunk: &str, args: Vec) -> Result<()> {
let lua = Lua::new();
- let threads = Arc::new(Mutex::new(Vec::new()));
lua.sandbox(true)?;
// Add in all globals
{
let globals = lua.globals();
- globals.raw_set("console", new_console(&lua)?)?;
- globals.raw_set("fs", new_fs(&lua)?)?;
- globals.raw_set("net", new_net(&lua)?)?;
- globals.raw_set("process", new_process(&lua, args.clone())?)?;
- globals.raw_set("task", new_task(&lua, &threads)?)?;
+ globals.raw_set("console", new_console(&lua).await?)?;
+ globals.raw_set("fs", new_fs(&lua).await?)?;
+ globals.raw_set("net", new_net(&lua).await?)?;
+ globals.raw_set("process", new_process(&lua, args.clone()).await?)?;
+ globals.raw_set("task", new_task(&lua).await?)?;
globals.set_readonly(true);
}
// Run the requested chunk asynchronously
diff --git a/src/lib/luau/task.luau b/src/lib/luau/task.luau
new file mode 100644
index 0000000..b598086
--- /dev/null
+++ b/src/lib/luau/task.luau
@@ -0,0 +1,112 @@
+local MINIMUM_DELAY_TIME = 1 / 100
+
+type ThreadOrFunction = 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,
+}
diff --git a/src/lib/utils/table_builder.rs b/src/lib/utils/table_builder.rs
index 8fb3a45..9bbfb8d 100644
--- a/src/lib/utils/table_builder.rs
+++ b/src/lib/utils/table_builder.rs
@@ -18,9 +18,8 @@ impl<'lua> ReadonlyTableBuilder<'lua> {
Ok(self)
}
- pub fn with_table(self, key: &'static str, value: Table) -> Result {
- self.tab.raw_set(key, value)?;
- Ok(self)
+ pub fn with_table(self, key: &'static str, table: Table) -> Result {
+ self.with_value(key, Value::Table(table))
}
pub fn with_function(self, key: &'static str, func: F) -> Result
@@ -29,9 +28,8 @@ impl<'lua> ReadonlyTableBuilder<'lua> {
R: ToLuaMulti<'lua>,
F: 'static + Fn(&'lua Lua, A) -> Result,
{
- let value = self.lua.create_function(func)?;
- self.tab.raw_set(key, value)?;
- Ok(self)
+ let f = self.lua.create_function(func)?;
+ self.with_value(key, Value::Function(f))
}
pub fn with_async_function(self, key: &'static str, func: F) -> Result
@@ -41,9 +39,8 @@ impl<'lua> ReadonlyTableBuilder<'lua> {
F: 'static + Fn(&'lua Lua, A) -> FR,
FR: 'lua + Future