Coroutines

Introduction to Coroutines in Lua

Coroutines in Lua are a powerful feature for cooperative multitasking. Unlike threads, which run in parallel, coroutines allow for pausing and resuming functions at specific points, making them ideal for implementing tasks like animations, game logic, or asynchronous workflows without blocking the main thread.

A coroutine is created using coroutine.create, started with coroutine.resume, and paused with coroutine.yield. These functions form the foundation of Lua’s coroutine system.

Example of Native Lua Coroutine Usage

Here’s a simple example using vanilla Lua coroutines:

function simpleCoroutine()
    print("Step 1")
    coroutine.yield()
    print("Step 2")
end

local co = coroutine.create(simpleCoroutine)
coroutine.resume(co) -- prints "Step 1"
coroutine.resume(co) -- prints "Step 2"

This pattern, while flexible, can become cumbersome as projects grow. You must manage the coroutine object manually, handle errors explicitly, and use verbose function calls.

Introducing Co() — a Coroutine Wrapper

To improve usability, our game engine provides a global coroutine wrapper function called Co(). It simplifies coroutine creation and management while preserving the native behavior.

Key Features of Co()

  • Simplified interface: Use the wrapper as a callable object.

  • Error handling: Optional automatic error printing or rethrowing.

  • Encapsulation: Clean, object-like coroutine structure.

  • Utility methods: - resume(…) — resumes the coroutine. - yield(…) — yields control back. - status() — returns current coroutine status. - raw() — returns the original coroutine object.

  • Optional autostart: Run the coroutine immediately with { start = true }.

Example Usage

Here’s how you might use Co() in practice:

local myTask = Co(function(self)
    print("Task started")
    self:yield()
    print("Task resumed")
end)

myTask() -- prints "Task started"
myTask() -- prints "Task resumed"

Error Handling Example

local safeTask = Co(function(self)
    error("Something went wrong")
end, { nothrow = true })

safeTask() -- prints: [Coroutine Error]: Something went wrong

Autostart Example

Co(function(self)
    print("Auto-running coroutine")
end, { start = true })

Benefits Over Native Coroutines

The Co() wrapper offers several improvements over raw Lua coroutine usage:

  • Cleaner syntax: You can call the wrapper directly instead of separately creating and resuming.

  • Built-in helpers: Common coroutine operations are encapsulated.

  • Error control: Easier debugging with built-in error printing or propagation.