lune/packages/lib/src/globals/task.rs

176 lines
6 KiB
Rust
Raw Normal View History

use mlua::prelude::*;
2023-01-23 07:38:32 +00:00
use crate::{
2023-02-17 18:20:17 +00:00
lua::{
async_ext::LuaAsyncExt,
task::{
LuaThreadOrFunction, LuaThreadOrTaskReference, TaskKind, TaskReference, TaskScheduler,
TaskSchedulerScheduleExt,
},
},
utils::table::TableBuilder,
};
2023-01-23 07:38:32 +00:00
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(
"
scheduleNext(thread())
local task = scheduleNext(...)
yield()
return task
",
)
2023-02-17 18:20:17 +00:00
.set_name("task.spawn")?
.set_environment(
TableBuilder::new(lua)?
2023-02-17 18:20:17 +00:00
.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()?;
// 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
let globals = lua.globals();
globals.set("type", lua.create_function(proxy_type)?)?;
globals.set("typeof", lua.create_function(proxy_typeof)?)?;
// 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(coroutine_resume)?)?;
coroutine.set("wrap", lua.create_function(coroutine_wrap)?)?;
// All good, return the task scheduler lib
TableBuilder::new(lua)?
2023-02-17 18:20:17 +00:00
.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()
2023-01-23 07:38:32 +00:00
}
/*
Basic task functions
*/
fn task_cancel(lua: &Lua, task: 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)
}
/*
Type getter overrides for compat with task scheduler
*/
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)
}
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");
}
}
lua.named_registry_value::<_, LuaFunction>("typeof")?
.call(value)
}
/*
Coroutine library overrides for compat with task scheduler
*/
fn coroutine_resume<'lua>(
lua: &'lua Lua,
value: LuaThreadOrTaskReference,
) -> LuaResult<LuaMultiValue<'lua>> {
// FIXME: Resume should return true, return vals OR false, error message
let sched = lua.app_data_ref::<&TaskScheduler>().unwrap();
match value {
LuaThreadOrTaskReference::Thread(t) => {
if sched.current_task().is_none() {
return Err(LuaError::RuntimeError(
"No current task to inherit".to_string(),
));
}
2023-02-17 18:20:17 +00:00
let task = sched.create_task(TaskKind::Instant, t, None, true)?;
let current = sched.current_task().unwrap();
let result = sched.resume_task(task, None);
sched.force_set_current_task(Some(current));
result
}
LuaThreadOrTaskReference::TaskReference(t) => sched.resume_task(t, None),
}
}
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,
2023-02-17 18:20:17 +00:00
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));
result
})
}