PixelUI’s Coroutine is a cooperative state machine built from a fixed context and macros. It is designed for short flows such as ordered startup Animations and non-blocking delays.
1 | Coroutine intro_{[this](CoroutineContext& ctx) { |
Registration and cleanup
1 | void onEnter(ExitCallback cb) override { |
A Coroutine should normally be an App member. Object state, scheduler registration, deadlines, and resume points are independent concerns; the API sections below explain each rule.
API details
Include this header to use the APIs:
1 |
CoroutineContext
Each Coroutine contains a context that stores execution position and waiting state across calls:
1 | struct CoroutineContext { |
| Field | Meaning |
|---|---|
pc |
Execution position of the macro state machine. Maintained by CORO_*; application code should not modify it. |
waitUntil |
Absolute timestamp used by CORO_DELAY. Maintained by the macro. |
localData[8] |
Eight persistent uint32_t slots for simple application state across resume calls. |
state |
Current state: CREATED, RUNNING, SUSPENDED, or FINISHED. |
waitReason |
Suspension reason: NONE, DELAY, or ANIMATION. The scheduler uses it to calculate the next wakeup. |
reset() preserves localData. Clear the slots used by a flow before starting a run that requires fresh values:
1 | intro_.reset(); |
Application code uses localData for persistent values and treats pc, state, waitUntil, and waitReason as scheduler-managed fields. The const context overload supports diagnostics.
Coroutine
Constructor
1 | Coroutine(CoroutineFunction func); |
The actual CoroutineFunction type is:
1 | etl::inplace_function<void(CoroutineContext&), CALLBACK_STORAGE_SIZE> |
The callback uses inline storage inside the object. A lambda capture larger than the configured capacity fails at compile time; see Resource limits. Construction leaves execution in the CREATED state.
start()
1 | void start(); |
When state is CREATED, start() changes it to RUNNING and places execution at the beginning. The scheduler invokes the Coroutine function during a later update. Calls in RUNNING, SUSPENDED, or FINISHED preserve the current state. Restart a completed Coroutine with reset() followed by start().
reset()
1 | void reset(); |
Restores CREATED state and resets pc, waitUntil, and waitReason. It preserves localData and the scheduler’s registration list.
State queries
1 | bool isFinished() const; |
isFinished() only checks whether context state is FINISHED. getContext() provides state diagnostics and access to localData; prefer the const overload for diagnostics.
Coroutine macros
Use every macro inside the same Coroutine function between CORO_BEGIN(ctx) and CORO_END(ctx).
CORO_BEGIN(ctx) / CORO_END(ctx)
1 | CORO_BEGIN(ctx); |
CORO_BEGIN opens a state machine based on ctx.pc. CORO_END sets state to FINISHED and returns. Each normal execution path reaches CORO_END; a path that bypasses it remains RUNNING and is scheduled again.
CORO_YIELD(ctx, line)
1 | CORO_YIELD(ctx, 1); |
Saves the execution position and returns immediately while keeping state RUNNING. The scheduler continues at the next processing opportunity. Use it to split a longer flow into short steps; CORO_DELAY provides elapsed-time waits.
CORO_DELAY(ctx, ui, ms, line)
1 | CORO_DELAY(ctx, ui_, 250U, 2); |
Records ui.getCurrentTime() + ms as the deadline, sets state to SUSPENDED, and continues after the macro when due. Deadline comparison is safe across unsigned wraparound, but each wait must be shorter than 2^31 ms. The deadline participates in tickless earliest-wakeup calculation and does not require continuous drawing.
CORO_WAIT_ANIMATION(ctx, ui, line)
1 | CORO_WAIT_ANIMATION(ctx, ui_, 3); |
Suspends until ui.activeAnimationCount() reaches zero. The count covers every Animation belonging to that PixelUI, including Animations started by other Apps, modules, or Coroutines. Continued Animation creation extends the wait.
Rules for the line parameter
line becomes a C++ switch case label, so it must be a compile-time integer constant and unique inside one Coroutine function. CORO_BEGIN already uses 0, which cannot be reused. Use manual numbering or __LINE__:
1 | CORO_DELAY(ctx, ui_, 100U, __LINE__); |
Resume points are case labels, so persistent state belongs in App members or ctx.localData:
1 | Coroutine worker_{[this](CoroutineContext& ctx) { |
PixelUI registration interface
addCoroutine()
1 | void addCoroutine(Coroutine* coroutine); |
Adds a non-owning pointer to the scheduler; nullptr leaves the scheduler unchanged. Registration preserves the Coroutine state and accepts repeated pointers. Call reset() and start() first, register each pointer once, and keep the registration count within PIXELUI_MAX_COROUTINE_NUM. The void API places capacity enforcement with the application design.
The object must remain alive until it is removed or the scheduler is cleared. An App member is safer than a function-local Coroutine.
removeCoroutine()
1 | void removeCoroutine(Coroutine* coroutine); |
Removes all registrations matching the pointer while preserving the Coroutine state. The object can later be reset, started, and registered again. An unregistered pointer leaves the scheduler unchanged.
clearAllCoroutines()
1 | void clearAllCoroutines(); |
Clears every non-owning pointer from this PixelUI scheduler and preserves each Coroutine context. The operation covers all Apps and modules. Individual Apps normally remove their own Coroutines, while ViewManager performs global cleanup before App destruction.
getActiveCoroutineCount()
1 | size_t getActiveCoroutineCount(); |
Returns the current number of scheduler registrations, including waiting Coroutines and registered CREATED Coroutines awaiting start(). The value represents registrations; the scheduler removes completed Coroutines during update.
When PIXELUI_USE_COROUTINE is 0, these four PixelUI methods remain callable: add, remove, and clear are no-ops, and getActiveCoroutineCount() always returns zero. Code that declares a Coroutine directly must still keep its header and build configuration consistent.
CoroutineScheduler (framework level)
1 | explicit CoroutineScheduler(PixelUI& ui); |
PixelUI owns and drives the scheduler. update() removes finished entries, resumes each eligible Coroutine, and removes newly completed entries again at the end of the pass. It re-reads global Animation state before each Coroutine, so an Animation started by an earlier Coroutine can affect a later Coroutine’s CORO_WAIT_ANIMATION decision. nextWakeupMs() supplies the earliest deadline to PixelUI’s event-driven and tickless scheduler.



