Skip to main content
The Coroutine module (CoreModules.Coroutine) provides functions for creating and managing Lua coroutines, which enable cooperative multitasking.

What are Coroutines?

Coroutines are independent execution threads that can be paused and resumed. Unlike preemptive threads, coroutines use cooperative multitasking - they explicitly yield control back to the caller. Key characteristics:
  • Lightweight (much cheaper than OS threads)
  • Deterministic execution order
  • No race conditions or locks needed
  • Perfect for game logic, animations, and sequential workflows

Creating Coroutines

coroutine.create(func)

Creates a new coroutine.
function task()
    print("Task started")
    coroutine.yield()
    print("Task resumed")
    coroutine.yield()
    print("Task finished")
end

local co = coroutine.create(task)
print(type(co))  -- "thread"
Parameters:
  • func - Function to run in the coroutine
Returns: New coroutine object

coroutine.wrap(func)

Creates a coroutine and returns a function that resumes it.
function counter()
    local i = 0
    while true do
        i = i + 1
        coroutine.yield(i)
    end
end

local next = coroutine.wrap(counter)

print(next())  -- 1
print(next())  -- 2
print(next())  -- 3
Parameters:
  • func - Function to run in the coroutine
Returns: Function that resumes the coroutine when called Note: Unlike coroutine.resume(), the wrapper function throws errors instead of returning them.

Running Coroutines

coroutine.resume(co [, …])

Resumes execution of a coroutine.
function task(name)
    print("Hello, " .. name)
    local response = coroutine.yield("waiting")
    print("Response: " .. response)
    return "done"
end

local co = coroutine.create(task)

-- First resume
local ok, result = coroutine.resume(co, "Alice")
print(ok, result)  -- true  "waiting"
-- Prints: "Hello, Alice"

-- Second resume
local ok, result = coroutine.resume(co, "success")
print(ok, result)  -- true  "done"
-- Prints: "Response: success"
Parameters:
  • co - Coroutine to resume
  • ... - Arguments to pass to the coroutine (or return from yield)
Returns:
  • true plus return values if successful
  • false plus error message if error occurred
States:
  • First call: Starts the coroutine, arguments go to the function
  • Subsequent calls: Resume from yield, arguments become yield’s return values
  • After return: Cannot be resumed again

coroutine.yield(…)

Suspends execution and returns to the caller.
function numbers()
    print("Starting")
    coroutine.yield(1)
    coroutine.yield(2)
    coroutine.yield(3)
    print("Finished")
end

local co = coroutine.create(numbers)

coroutine.resume(co)  -- Prints "Starting", returns true, 1
coroutine.resume(co)  -- Returns true, 2
coroutine.resume(co)  -- Returns true, 3
coroutine.resume(co)  -- Prints "Finished", returns true
Parameters:
  • ... - Values to return from resume()
Returns: Arguments passed to the next resume() call Note: Can only be called from within a coroutine.

Coroutine Status

coroutine.status(co)

Returns the current state of a coroutine.
function task()
    coroutine.yield()
end

local co = coroutine.create(task)

print(coroutine.status(co))  -- "suspended" (not started yet)

coroutine.resume(co)
print(coroutine.status(co))  -- "suspended" (yielded)

coroutine.resume(co)
print(coroutine.status(co))  -- "dead" (finished)
Returns: One of:
  • "suspended" - Created but not started, or yielded
  • "running" - Currently executing
  • "normal" - Active but not running (has resumed another coroutine)
  • "dead" - Finished or encountered an error

coroutine.running()

Returns the currently running coroutine.
function task()
    local co, ismain = coroutine.running()
    print("Coroutine:", co)
    print("Is main:", ismain)
end

local co = coroutine.create(task)
coroutine.resume(co)
-- Prints:
-- Coroutine: thread: 0x...
-- Is main: false

-- From main thread
local co, ismain = coroutine.running()
print("Coroutine:", co)
print("Is main:", ismain)
-- Prints:
-- Coroutine: thread: 0x...
-- Is main: true
Returns:
  • Current coroutine object
  • true if running in the main thread, false otherwise

Patterns and Examples

Generator Pattern

function range(from, to, step)
    step = step or 1
    return coroutine.wrap(function()
        for i = from, to, step do
            coroutine.yield(i)
        end
    end)
end

for n in range(1, 10, 2) do
    print(n)
end
-- Prints: 1, 3, 5, 7, 9

Producer-Consumer

function producer()
    return coroutine.create(function()
        for i = 1, 10 do
            print("Producing", i)
            coroutine.yield(i)
        end
    end)
end

function consumer(prod)
    while true do
        local ok, value = coroutine.resume(prod)
        if not ok or value == nil then
            break
        end
        print("Consuming", value)
    end
end

local prod = producer()
consumer(prod)

Iterator with State

function fibonacci()
    return coroutine.wrap(function()
        local a, b = 1, 1
        while true do
            coroutine.yield(a)
            a, b = b, a + b
        end
    end)
end

local fib = fibonacci()
for i = 1, 10 do
    print(fib())
end
-- Prints: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55

Async-style Operations

function async(func)
    local co = coroutine.create(func)
    
    local function step(...)
        local ok, result = coroutine.resume(co, ...)
        if not ok then
            error(result)
        end
        return result
    end
    
    return step
end

function wait(seconds)
    -- Simulate async wait
    print("Waiting for", seconds, "seconds")
    return seconds
end

local task = async(function()
    print("Task started")
    wait(1)
    print("After 1 second")
    wait(2)
    print("After 3 seconds total")
end)

task()  -- Run the async task

Pipeline Processing

function pipeline(source, ...)
    local filters = {...}
    return coroutine.wrap(function()
        for value in source do
            for _, filter in ipairs(filters) do
                value = filter(value)
                if value == nil then
                    break
                end
            end
            if value ~= nil then
                coroutine.yield(value)
            end
        end
    end)
end

-- Example filters
local function double(x) return x * 2 end
local function isEven(x) return x % 2 == 0 and x or nil end

-- Create source
local source = coroutine.wrap(function()
    for i = 1, 10 do
        coroutine.yield(i)
    end
end)

-- Apply pipeline
for n in pipeline(source, double, isEven) do
    print(n)
end
-- Prints: 2, 4, 6, 8, 10, 12, 14, 16, 18, 20

State Machine

function stateMachine()
    local state = "init"
    
    return coroutine.wrap(function()
        while true do
            if state == "init" then
                print("Initializing")
                state = "running"
            elseif state == "running" then
                print("Running")
                local input = coroutine.yield()
                if input == "stop" then
                    state = "stopping"
                end
            elseif state == "stopping" then
                print("Stopping")
                break
            end
        end
    end)
end

local sm = stateMachine()
sm()              -- Initializing
sm()              -- Running
sm("continue")    -- Running
sm("stop")        -- Stopping

Tree Traversal

function traverse(node)
    return coroutine.wrap(function()
        local function visit(n)
            coroutine.yield(n.value)
            if n.left then visit(n.left) end
            if n.right then visit(n.right) end
        end
        visit(node)
    end)
end

local tree = {
    value = 1,
    left = { value = 2, left = { value = 4 }, right = { value = 5 } },
    right = { value = 3 }
}

for value in traverse(tree) do
    print(value)
end
-- Prints: 1, 2, 4, 5, 3 (pre-order)

Error Handling

function riskyTask()
    print("Starting")
    coroutine.yield()
    error("Something went wrong!")
end

local co = coroutine.create(riskyTask)

local ok, result = coroutine.resume(co)
print(ok, result)  -- true  nil

local ok, result = coroutine.resume(co)
print(ok, result)  -- false  [error message]

print(coroutine.status(co))  -- "dead"

Best Practices

  1. Use wrap for simple iterators
    -- Simpler:
    for value in coroutine.wrap(generator) do
        process(value)
    end
    
    -- More verbose:
    local co = coroutine.create(generator)
    while coroutine.status(co) ~= "dead" do
        local ok, value = coroutine.resume(co)
        if ok and value then
            process(value)
        end
    end
    
  2. Check coroutine status before resuming
    if coroutine.status(co) ~= "dead" then
        coroutine.resume(co)
    end
    
  3. Handle errors from resume
    local ok, result = coroutine.resume(co)
    if not ok then
        print("Error:", result)
    end
    
  4. Use coroutines for sequential async operations
    • Game animations
    • Dialogue systems
    • Tutorial sequences
    • Level loading

C# Integration

using SolarSharp.Interpreter;
using SolarSharp.Interpreter.Modules;

var script = new Script(CoreModules.Coroutine);

// Access coroutine from C#
script.DoString(@"
    function task()
        coroutine.yield('waiting')
        return 'done'
    end
    
    co = coroutine.create(task)
");

var coroutine = script.Globals.Get("co");
var result = coroutine.Coroutine.Resume();
Console.WriteLine(result.Tuple[0]);  // "waiting"

See Also

Build docs developers (and LLMs) love