coroutine

Cooperative threads of Lua 5.1 / LuaJIT 2.1, and how they work with xiao.timer.

Cooperative threads of Lua 5.1 / LuaJIT 2.1: a coroutine has its own stack and runs until it yields or finishes. Nothing is removed by the client; to pause over several frames, use xiao.timer.spawn and wait.

At a glance

Function Description
coroutine.create(f) create a suspended coroutine
coroutine.resume(co, ...) start or continue it
coroutine.yield(...) suspend the running coroutine
coroutine.status(co) running, suspended, normal or dead
coroutine.wrap(f) a coroutine as a plain function
coroutine.running() the running coroutine
coroutine.isyieldable() whether yield is possible here

Functions

coroutine.create(f): thread

coroutine.create(f: function): thread

Creates a coroutine that will run f. It starts suspended.

Name Type Description
f function body of the coroutine

Returns: the new coroutine. Errors: bad argument #1 to 'create' (function expected, got number).

coroutine.resume(co, ...): boolean, any

coroutine.resume(co: thread, ...: any): boolean, ...any

Starts or continues co. The arguments are passed to the body on the first resume, then become the results of coroutine.yield.

Name Type Description
co thread coroutine to run
... any values sent to the coroutine

Returns: true and the values passed to yield (or returned by the body), or false and the error value; after an error the coroutine is dead. A dead coroutine gives false, "cannot resume dead coroutine", a running or normal one false, "cannot resume running coroutine". Errors: bad argument #1 to 'resume' (coroutine expected).

local co = coroutine.create(function(a, b)
    local c = coroutine.yield(a + b)
    return c * 2
end)
print(coroutine.resume(co, 1, 2))
print(coroutine.resume(co, 10))

coroutine.yield(...): any

coroutine.yield(...: any): ...any

Suspends the running coroutine.

Name Type Description
... any extra results of the resume that started this run

Returns: the arguments of the next resume. Errors: attempt to yield across C-call boundary outside a coroutine, or inside a function called from C, such as a table.sort comparator or a string.gsub replacement function. LuaJIT can yield across pcall, xpcall and metamethods.

coroutine.status(co): string

coroutine.status(co: thread): string
Name Type Description
co thread coroutine to inspect

Returns:

Value Meaning
"running" co is the running coroutine
"suspended" it yielded or has not started
"normal" it is active and has resumed another coroutine
"dead" it finished or failed

Errors: bad argument #1 to 'status' (coroutine expected).

coroutine.wrap(f): function

coroutine.wrap(f: function): function(...: any): ...any

Creates a coroutine and returns a function that resumes it at each call.

Name Type Description
f function body of the coroutine

Returns: a function. Its arguments go to resume, its results are the values yielded or returned. Errors, including cannot resume dead coroutine, are raised in the caller instead of being returned.

local gen = coroutine.wrap(function()
    for i = 1, 3 do coroutine.yield(i) end
end)
print(gen(), gen(), gen())

coroutine.running(): thread | nil

coroutine.running(): thread | nil

Returns: the running coroutine, or nil in the main thread (Lua 5.1 form, no second value).

coroutine.isyieldable(): boolean

coroutine.isyieldable(): boolean

Returns: true when coroutine.yield can be called here: inside a coroutine and not inside a function called from C. false in the main chunk, in a frame callback or in a hook.

print(coroutine.isyieldable())
coroutine.wrap(function() print(coroutine.isyieldable()) end)()

Coroutines and xiao.timer New

xiao.timer.spawn runs a function as a coroutine driven by the client clock, and xiao.timer.wait pauses it.

  • spawn(fn, ...) resumes the coroutine at once, in the caller, until its first wait, its first coroutine.yield() or its end. Later steps run with the timers, step 6 of the order of a frame, in the 100 ms frame budget.
  • A plain coroutine.yield() inside a spawned coroutine waits one frame. The values given to yield are ignored, and yield returns the seconds elapsed since it yielded, like wait.
  • wait works only in the coroutine created by spawn itself. In the main chunk, in a callback, or in a coroutine made with coroutine.create or coroutine.wrap, even inside a spawned one, it raises xiao.timer.wait must be called inside xiao.timer.spawn.
  • A coroutine of your own inside a spawned one behaves as usual: its yield returns to its own resume, not to the timer.
  • An error in a spawned coroutine removes it and prints timer <id> coroutine removed: and a traceback. A loop that never yields hits the frame time limit and the VM is cleared.
  • Do not resume a spawned coroutine yourself (through coroutine.running()): the timer still resumes it at its due time.
  • xiao.timer.cancel(id) stops a spawned coroutine, it is never resumed again.
xiao.timer.spawn(function()
    for step = 1, 3 do
        print("step", step)
        coroutine.yield()
    end
end)

Complete example

Spread a long job over several frames, first with a hand-driven coroutine in a paint listener (see xiao.paint), then with xiao.timer.spawn.

local worker = coroutine.wrap(function()
    for step = 1, 3 do
        print("paint step", step)
        coroutine.yield()
    end
end)

xiao.paint.add("worker", function()
    if not pcall(worker) then
        xiao.paint.remove("worker")
    end
end)

local job = xiao.timer.spawn(function(label)
    local total = 0
    for chunk = 1, 5 do
        for i = 1, 10000 do total = total + i * chunk end
        local waited = xiao.timer.wait(0.5)
        print(label, chunk, ("%.2f s"):format(waited))
    end
    print(label, "done", total)
end, "job")

print("spawned", job)