lune_std_task/
lib.rs

1#![allow(clippy::cargo_common_metadata)]
2
3use std::time::{Duration, Instant};
4
5use async_io::Timer;
6use futures_lite::future::yield_now;
7
8use mlua::prelude::*;
9use mlua_luau_scheduler::Functions;
10
11use lune_utils::TableBuilder;
12
13const TYPEDEFS: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/types.d.luau"));
14
15/**
16    Returns a string containing type definitions for the `task` standard library.
17*/
18#[must_use]
19pub fn typedefs() -> String {
20    TYPEDEFS.to_string()
21}
22
23/**
24    Creates the `task` standard library module.
25
26    # Errors
27
28    Errors when out of memory, or if default Lua globals are missing.
29*/
30pub fn module(lua: Lua) -> LuaResult<LuaTable> {
31    let fns = Functions::new(lua.clone())?;
32
33    // Create wait & delay functions
34    let task_wait = lua.create_async_function(wait)?;
35    let task_delay_env = TableBuilder::new(lua.clone())?
36        .with_value("select", lua.globals().get::<LuaFunction>("select")?)?
37        .with_value("spawn", fns.spawn.clone())?
38        .with_value("defer", fns.defer.clone())?
39        .with_value("wait", task_wait.clone())?
40        .build_readonly()?;
41    let task_delay = lua
42        .load(DELAY_IMPL_LUA)
43        .set_name("task.delay")
44        .set_environment(task_delay_env)
45        .into_function()?;
46
47    TableBuilder::new(lua)?
48        .with_value("cancel", fns.cancel)?
49        .with_value("defer", fns.defer)?
50        .with_value("delay", task_delay)?
51        .with_value("spawn", fns.spawn)?
52        .with_value("wait", task_wait)?
53        .build_readonly()
54}
55
56const DELAY_IMPL_LUA: &str = r"
57return defer(function(...)
58    wait(select(1, ...))
59    spawn(select(2, ...))
60end, ...)
61";
62
63async fn wait(lua: Lua, secs: Option<f64>) -> LuaResult<f64> {
64    // NOTE: We must guarantee that the task.wait API always yields
65    // from a lua perspective, even if sleep/timer completes instantly
66    yield_now().await;
67    wait_inner(lua, secs).await
68}
69
70async fn wait_inner(_: Lua, secs: Option<f64>) -> LuaResult<f64> {
71    // One millisecond is a reasonable minimum sleep duration,
72    // anything lower than this runs the risk of completing the
73    // the below timer instantly, without giving control to the OS ...
74    let duration = Duration::from_secs_f64(secs.unwrap_or_default());
75    let duration = duration.max(Duration::from_millis(1));
76    // ... however, we should still _guarantee_ that whatever
77    // coroutine that calls this sleep function always yields,
78    // even if the timer is able to complete without doing so
79    yield_now().await;
80    // We may then sleep as normal
81    let before = Instant::now();
82    let after = Timer::after(duration).await;
83    Ok((after - before).as_secs_f64())
84}