The Task Scheduler
Lune has a built-in task scheduler, which can let you run things at fixed intervals, ensure some work happens after everything else is already done, and more.
The task scheduler is the backbone of Lune, and lets you handle structured concurrency. It is implemented using lightweight Lua threads / coroutines, and has strong ordering guarantees.
Ordering
Section titled “Ordering”The main purpose of the task scheduler is to ensure consistent ordering, and to
let you prioritize work on three different levels by using the task
standard library:
- Immediate: Tasks that should run immediately can be spawned using
task.spawn
. - Deferred: Tasks that should run after all immediate tasks have finished can be spawned using
task.defer
. - Delayed: Tasks that should run after a certain amount of time has passed can be spawned using
task.delay
.
Advanced: Runtime-Controlled Threads & Prioritization
These are user-facing concepts, but perhaps more interesting, is that Lune prioritizes Lua threads
over runtime-spawned tasks, such as those for incoming requests in net.serve
.
This means that, in real world scenarios such as handling incoming requests in an HTTP server, the scheduler will ensure that your existing tasks are not starved of resources, and are always prioritized over handling new requests, for maximum throughput & lowest possible latency.
Example Usage
Section titled “Example Usage”Spawning Tasks & Waiting
Section titled “Spawning Tasks & Waiting”This example script will run several tasks concurrently, in lightweight Lua threads, also known as coroutines:
local task = require("@lune/task")
print("Hello, scheduler!")
task.spawn(function() print("Spawned a task that will run instantly but not block") task.wait(2) print("The instant task resumed again after 2 seconds")end)
print("Spawning a delayed task that will run after 5 seconds")
task.delay(5, function() print("Waking up from my deep slumber...") task.wait(1) print("Hello again!") task.wait(1) print("Goodbye again! 🌙")end)
Deferring Work
Section titled “Deferring Work”This example script runs a bit of work after all other threads have finished their work or are yielding waiting for some other result:
local task = require("@lune/task")
task.defer(function() print("All the scheduled work has finished, let's do some more!") local a = 0 for _ = 1, 100000 do local b = a + 1 end print("Phew, that was tough.")end)
print("Working...")local s = ""for _ = 1, 5000 do s ..= ""endprint("Done!")
Advanced Usage & Async
Section titled “Advanced Usage & Async”Spawning tasks like this can be very useful together with asynchronous APIs from other standard
libraries, such as net.request
:
local net = require("@lune/net")local task = require("@lune/task")
local completed = falsetask.spawn(function() while not completed do print("Waiting for response...") task.wait() -- Wait the minimum amount possible end print("No longer waiting!")end)
print("Sending request")net.request("https://google.com")print("Got response")
completed = true
Bonus
Barebones Signal Implementation
Section titled “Barebones Signal Implementation”Using the task library, it becomes trivial to implement signal objects that take callbacks to run when a signal is fired, and that can handle both synchronous and yielding (async) callbacks without additional complexity:
local task = require("@lune/task")
local function newSignal() local callbacks = {}
local function connect(callback: (...any) -> ()) table.insert(callbacks, callback) end
local function fire(...: any) for _, callback in callbacks do task.spawn(callback, ...) end end
return connect, fireend
local connectToThing, fireThing = newSignal()
connectToThing(function(value) print("Callback #1 got value:", value) task.wait(1) print("Callback #1 still has value:", value)end)
connectToThing(function(value) print("Callback #2 got value:", value) task.wait(0.5) print("Callback #2 still has value:", value)end)
print("Before firing")fireThing(123)print("After firing")
--> Before firing--> Callback #1 got value: 123--> Callback #2 got value: 123--> After firing--> ...--> Callback #2 still has value: 123--> ...--> Callback #1 still has value: 123