2023-01-23 02:31:55 +00:00
|
|
|
use mlua::prelude::*;
|
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 14:28:18 +00:00
|
|
|
const TASK_WAIT_IMPL_LUA: &str = r#"
|
2023-02-15 11:18:25 +00:00
|
|
|
local seconds = ...
|
|
|
|
local current = thread()
|
|
|
|
resumeAfter(seconds, current)
|
2023-02-13 14:28:18 +00:00
|
|
|
return yield()
|
|
|
|
"#;
|
2023-01-23 07:38:32 +00:00
|
|
|
|
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-15 12:22:13 +00:00
|
|
|
// Create a user-accessible function that cancels a task
|
2023-02-13 20:29:05 +00:00
|
|
|
let task_cancel = lua.create_function(|lua, task: TaskReference| {
|
2023-02-15 12:22:13 +00:00
|
|
|
let sched = lua
|
|
|
|
.app_data_ref::<&TaskScheduler>()
|
|
|
|
.expect(ERR_MISSING_SCHEDULER);
|
2023-02-13 22:36:30 +00:00
|
|
|
sched.remove_task(task)?;
|
2023-02-13 20:29:05 +00:00
|
|
|
Ok(())
|
2023-02-13 14:28:18 +00:00
|
|
|
})?;
|
2023-02-14 22:17:03 +00:00
|
|
|
// Create functions that manipulate non-blocking tasks in the scheduler
|
2023-02-13 14:28:18 +00:00
|
|
|
let task_defer = lua.create_function(|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_deferred(tof, args)
|
2023-02-13 14:28:18 +00:00
|
|
|
})?;
|
|
|
|
let task_delay =
|
|
|
|
lua.create_function(|lua, (secs, tof, args): (f64, LuaValue, LuaMultiValue)| {
|
2023-02-15 12:22:13 +00:00
|
|
|
let sched = lua
|
|
|
|
.app_data_ref::<&TaskScheduler>()
|
|
|
|
.expect(ERR_MISSING_SCHEDULER);
|
2023-02-13 14:28:18 +00:00
|
|
|
sched.schedule_delayed(secs, tof, args)
|
|
|
|
})?;
|
|
|
|
// Create our task wait function, this is a bit different since
|
|
|
|
// we have no way to yield from c / rust, we need to load a
|
|
|
|
// lua chunk that schedules and yields for us instead
|
|
|
|
let task_wait_env_thread: LuaFunction = lua.named_registry_value("co.thread")?;
|
|
|
|
let task_wait_env_yield: LuaFunction = lua.named_registry_value("co.yield")?;
|
|
|
|
let task_wait = lua
|
|
|
|
.load(TASK_WAIT_IMPL_LUA)
|
|
|
|
.set_environment(
|
|
|
|
TableBuilder::new(lua)?
|
|
|
|
.with_value("thread", task_wait_env_thread)?
|
|
|
|
.with_value("yield", task_wait_env_yield)?
|
|
|
|
.with_function(
|
2023-02-15 11:18:25 +00:00
|
|
|
"resumeAfter",
|
|
|
|
|lua, (secs, thread): (Option<f64>, LuaThread)| {
|
2023-02-15 12:22:13 +00:00
|
|
|
let sched = lua
|
|
|
|
.app_data_ref::<&TaskScheduler>()
|
|
|
|
.expect(ERR_MISSING_SCHEDULER);
|
2023-02-13 20:29:05 +00:00
|
|
|
sched.schedule_wait(secs.unwrap_or(0f64), LuaValue::Thread(thread))
|
|
|
|
},
|
|
|
|
)?
|
|
|
|
.build_readonly()?,
|
|
|
|
)?
|
|
|
|
.into_function()?;
|
|
|
|
// The spawn function also needs special treatment,
|
|
|
|
// 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)
|
|
|
|
.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)?
|
2023-02-13 20:29:05 +00:00
|
|
|
.with_value("cancel", task_cancel)?
|
2023-02-13 14:28:18 +00:00
|
|
|
.with_value("spawn", task_spawn)?
|
|
|
|
.with_value("defer", task_defer)?
|
|
|
|
.with_value("delay", task_delay)?
|
|
|
|
.with_value("wait", task_wait)?
|
|
|
|
.build_readonly()
|
2023-01-23 07:38:32 +00:00
|
|
|
}
|