2023-02-16 22:23:52 +00:00
|
|
|
use std::time::Duration;
|
|
|
|
|
2023-01-23 02:31:55 +00:00
|
|
|
use mlua::prelude::*;
|
2023-02-16 22:23:52 +00:00
|
|
|
use tokio::time::{sleep, Instant};
|
2023-01-23 07:38:32 +00:00
|
|
|
|
2023-02-13 14:28:18 +00:00
|
|
|
use crate::{
|
2023-02-15 12:22:13 +00:00
|
|
|
lua::task::{TaskKind, TaskReference, TaskScheduler, TaskSchedulerScheduleExt},
|
2023-02-13 14:28:18 +00:00
|
|
|
utils::table::TableBuilder,
|
2023-01-23 23:52:31 +00:00
|
|
|
};
|
2023-01-23 07:38:32 +00:00
|
|
|
|
2023-02-15 12:22:13 +00:00
|
|
|
const ERR_MISSING_SCHEDULER: &str = "Missing task scheduler - make sure it is added as a lua app data before the first scheduler resumption";
|
|
|
|
|
2023-02-13 20:29:05 +00:00
|
|
|
const TASK_SPAWN_IMPL_LUA: &str = r#"
|
2023-02-15 11:18:25 +00:00
|
|
|
-- Schedule the current thread at the front
|
|
|
|
scheduleNext(thread())
|
2023-02-15 12:22:13 +00:00
|
|
|
-- Schedule the wanted task arg at the front,
|
2023-02-15 11:18:25 +00:00
|
|
|
-- the previous schedule now comes right after
|
|
|
|
local task = scheduleNext(...)
|
|
|
|
-- Give control over to the scheduler, which will
|
|
|
|
-- resume the above tasks in order when its ready
|
2023-02-13 20:29:05 +00:00
|
|
|
yield()
|
|
|
|
return task
|
|
|
|
"#;
|
|
|
|
|
|
|
|
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable<'static>> {
|
2023-02-16 22:23:52 +00:00
|
|
|
// The spawn function needs special treatment,
|
2023-02-13 20:29:05 +00:00
|
|
|
// we need to yield right away to allow the
|
|
|
|
// spawned task to run until first yield
|
|
|
|
let task_spawn_env_thread: LuaFunction = lua.named_registry_value("co.thread")?;
|
|
|
|
let task_spawn_env_yield: LuaFunction = lua.named_registry_value("co.yield")?;
|
|
|
|
let task_spawn = lua
|
|
|
|
.load(TASK_SPAWN_IMPL_LUA)
|
2023-02-16 22:23:52 +00:00
|
|
|
.set_name("=task.spawn")?
|
2023-02-13 20:29:05 +00:00
|
|
|
.set_environment(
|
|
|
|
TableBuilder::new(lua)?
|
|
|
|
.with_value("thread", task_spawn_env_thread)?
|
|
|
|
.with_value("yield", task_spawn_env_yield)?
|
|
|
|
.with_function(
|
2023-02-15 11:18:25 +00:00
|
|
|
"scheduleNext",
|
2023-02-13 20:29:05 +00:00
|
|
|
|lua, (tof, args): (LuaValue, LuaMultiValue)| {
|
2023-02-15 12:22:13 +00:00
|
|
|
let sched = lua
|
|
|
|
.app_data_ref::<&TaskScheduler>()
|
|
|
|
.expect(ERR_MISSING_SCHEDULER);
|
|
|
|
sched.schedule_blocking(tof, args)
|
2023-02-13 14:28:18 +00:00
|
|
|
},
|
|
|
|
)?
|
|
|
|
.build_readonly()?,
|
|
|
|
)?
|
|
|
|
.into_function()?;
|
|
|
|
// We want the task scheduler to be transparent,
|
|
|
|
// but it does not return real lua threads, so
|
|
|
|
// we need to override some globals to fake it
|
2023-02-16 15:19:58 +00:00
|
|
|
let type_original: LuaFunction = lua.named_registry_value("type")?;
|
2023-02-13 14:28:18 +00:00
|
|
|
let type_proxy = lua.create_function(move |_, value: LuaValue| {
|
|
|
|
if let LuaValue::UserData(u) = &value {
|
|
|
|
if u.is::<TaskReference>() {
|
|
|
|
return Ok(LuaValue::String(lua.create_string("thread")?));
|
|
|
|
}
|
2023-02-12 18:07:15 +00:00
|
|
|
}
|
2023-02-13 14:28:18 +00:00
|
|
|
type_original.call(value)
|
|
|
|
})?;
|
2023-02-16 15:19:58 +00:00
|
|
|
let typeof_original: LuaFunction = lua.named_registry_value("typeof")?;
|
2023-02-13 14:28:18 +00:00
|
|
|
let typeof_proxy = lua.create_function(move |_, value: LuaValue| {
|
|
|
|
if let LuaValue::UserData(u) = &value {
|
|
|
|
if u.is::<TaskReference>() {
|
|
|
|
return Ok(LuaValue::String(lua.create_string("thread")?));
|
|
|
|
}
|
2023-01-23 23:52:31 +00:00
|
|
|
}
|
2023-02-13 14:28:18 +00:00
|
|
|
typeof_original.call(value)
|
|
|
|
})?;
|
2023-02-16 15:19:58 +00:00
|
|
|
let globals = lua.globals();
|
2023-02-13 14:28:18 +00:00
|
|
|
globals.set("type", type_proxy)?;
|
|
|
|
globals.set("typeof", typeof_proxy)?;
|
2023-02-14 22:17:03 +00:00
|
|
|
// 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 coroutine = globals.get::<_, LuaTable>("coroutine")?;
|
|
|
|
coroutine.set(
|
|
|
|
"resume",
|
|
|
|
lua.create_function(|lua, value: LuaValue| {
|
2023-02-15 12:22:13 +00:00
|
|
|
let tname = value.type_name();
|
2023-02-14 22:17:03 +00:00
|
|
|
if let LuaValue::Thread(thread) = value {
|
2023-02-15 12:22:13 +00:00
|
|
|
let sched = lua
|
|
|
|
.app_data_ref::<&TaskScheduler>()
|
|
|
|
.expect(ERR_MISSING_SCHEDULER);
|
2023-02-14 22:17:03 +00:00
|
|
|
let task =
|
|
|
|
sched.create_task(TaskKind::Instant, LuaValue::Thread(thread), None, None)?;
|
|
|
|
sched.resume_task(task, None)
|
|
|
|
} else if let Ok(task) = TaskReference::from_lua(value, lua) {
|
2023-02-15 12:22:13 +00:00
|
|
|
lua.app_data_ref::<&TaskScheduler>()
|
|
|
|
.expect(ERR_MISSING_SCHEDULER)
|
|
|
|
.resume_task(task, None)
|
2023-02-14 22:17:03 +00:00
|
|
|
} else {
|
2023-02-15 12:22:13 +00:00
|
|
|
Err(LuaError::RuntimeError(format!(
|
|
|
|
"Argument #1 must be a thread, got {tname}",
|
|
|
|
)))
|
2023-02-14 22:17:03 +00:00
|
|
|
}
|
|
|
|
})?,
|
|
|
|
)?;
|
|
|
|
coroutine.set(
|
|
|
|
"wrap",
|
|
|
|
lua.create_function(|lua, func: LuaFunction| {
|
2023-02-15 12:22:13 +00:00
|
|
|
let sched = lua
|
|
|
|
.app_data_ref::<&TaskScheduler>()
|
|
|
|
.expect(ERR_MISSING_SCHEDULER);
|
2023-02-14 22:17:03 +00:00
|
|
|
let task =
|
|
|
|
sched.create_task(TaskKind::Instant, LuaValue::Function(func), None, None)?;
|
|
|
|
lua.create_function(move |lua, args: LuaMultiValue| {
|
2023-02-15 12:22:13 +00:00
|
|
|
let sched = lua
|
|
|
|
.app_data_ref::<&TaskScheduler>()
|
|
|
|
.expect(ERR_MISSING_SCHEDULER);
|
2023-02-15 11:18:25 +00:00
|
|
|
sched.resume_task(task, Some(Ok(args)))
|
2023-02-14 22:17:03 +00:00
|
|
|
})
|
|
|
|
})?,
|
|
|
|
)?;
|
2023-02-13 14:28:18 +00:00
|
|
|
// All good, return the task scheduler lib
|
|
|
|
TableBuilder::new(lua)?
|
|
|
|
.with_value("spawn", task_spawn)?
|
2023-02-16 22:23:52 +00:00
|
|
|
.with_function("cancel", task_cancel)?
|
|
|
|
.with_function("defer", task_defer)?
|
|
|
|
.with_function("delay", task_delay)?
|
|
|
|
.with_async_function("wait", task_wait)?
|
2023-02-13 14:28:18 +00:00
|
|
|
.build_readonly()
|
2023-01-23 07:38:32 +00:00
|
|
|
}
|
2023-02-16 22:23:52 +00:00
|
|
|
|
|
|
|
fn task_cancel(lua: &Lua, task: TaskReference) -> LuaResult<()> {
|
|
|
|
let sched = lua
|
|
|
|
.app_data_ref::<&TaskScheduler>()
|
|
|
|
.expect(ERR_MISSING_SCHEDULER);
|
|
|
|
sched.remove_task(task)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn task_defer(lua: &Lua, (tof, args): (LuaValue, LuaMultiValue)) -> LuaResult<TaskReference> {
|
|
|
|
let sched = lua
|
|
|
|
.app_data_ref::<&TaskScheduler>()
|
|
|
|
.expect(ERR_MISSING_SCHEDULER);
|
|
|
|
sched.schedule_blocking_deferred(tof, args)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn task_delay(
|
|
|
|
lua: &Lua,
|
|
|
|
(secs, tof, args): (f64, LuaValue, LuaMultiValue),
|
|
|
|
) -> LuaResult<TaskReference> {
|
|
|
|
let sched = lua
|
|
|
|
.app_data_ref::<&TaskScheduler>()
|
|
|
|
.expect(ERR_MISSING_SCHEDULER);
|
|
|
|
sched.schedule_blocking_after_seconds(secs, tof, args)
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn task_wait(_: &Lua, secs: Option<f64>) -> LuaResult<f64> {
|
|
|
|
let start = Instant::now();
|
|
|
|
sleep(Duration::from_secs_f64(secs.unwrap_or_default())).await;
|
|
|
|
Ok(start.elapsed().as_secs_f64())
|
|
|
|
}
|