Opt-in scene/state stack — push/pop game scenes without rolling your own state machine. More...
#include <snes/types.h>Go to the source code of this file.
Classes | |
| struct | Scene |
| A single scene's callbacks. More... | |
Macros | |
| #define | SCENE_STACK_MAX 8 |
| Maximum scene stack depth. | |
Functions | |
| void | scenePop (void) |
| Pop the top scene. Resumes the scene below. | |
| void | scenePush (const Scene *next) |
| Push a scene onto the stack. Suspends the current top. | |
| void | sceneRun (const Scene *initial) |
Run the scene stack with initial at the bottom. Never returns. | |
Opt-in scene/state stack — push/pop game scenes without rolling your own state machine.
Third "framework opt-in" from PHILOSOPHY.md (alongside gameloop D.1 and the asset bundle convention D.2). This module gives you a tiny stack of Scene callbacks: the topmost scene's update runs every VBlank; pushing a new scene suspends the current one and runs the new scene's init + update; popping restores the suspended scene without re-running its init.
init runs once on the first push of a scene. It does NOT re-run on resume after scenePop. If a scene needs "did I just resume?" logic, it owns that bookkeeping in its own state.update runs every VBlank while the scene is on top of the stack. Suspended scenes (below the top) get no callbacks.SCENE_STACK_MAX = 8). Push beyond that is a silent no-op — the new scene is dropped, the active scene continues. Pop on a stack of size 1 is also a silent no-op: the bottom scene stays active (the stack is never empty after sceneRun is called).scenePush does not run the new scene's update synchronously. The currently-executing update finishes its frame; the next VBlank dispatches to the new top.sceneRun has its init called eagerly, before the first WaitForVBlank. This matches gameLoopRun's pattern and is required because the canonical init body (consoleInit → setMode → palettes → setScreenOn) must finish before the first NMI runs its DMAs.scenePush have their init deferred to the next VBlank dispatch — scenePush only records the new top, the dispatcher calls init right after WaitForVBlank() returns. This guarantees a fresh ~33 K-cycle VBlank budget for init's DMAs (palette load, tile upload, tilemap copy) instead of sharing whatever the caller's update already consumed.scenePop from inside init pops the scene before its update ever runs; the scene below resumes on the same frame. The popped scene effectively "ran init then bailed".scenePush from inside init cascades: the newly pushed scene's init also runs in the same VBlank window, chained right after the current init returns. If both inits do heavy DMAs and the combined cost exceeds the VBlank budget, move part of the work into update (which has a full new VBlank window of its own).resume/cleanup callbacks. Most scenes don't need them; if yours does, the first 1-2 frames of update post-pop can do the work.examples/basics/scene_stack does this with static u16 counter_value, which survives the counter→pause→ counter cycle because counter's init runs once on first push.sceneSwap() primitive. To replace the top scene without keeping it on the stack, call scenePop(); scenePush(&next);. The combo is intentional — the framework keeps the API surface minimal and lets the user spell the intent explicitly.update only.sceneRun adds one indirect call per frame (the top scene's update) and one direct call (WaitForVBlank) — same shape as gameLoopRun. scenePush does an array bounds check, an index update, and an optional indirect call to init. scenePop is a single index decrement. RAM footprint: 8 pointers × 8 bytes per cproc ABI = 64 bytes BSS plus a single byte for the stack depth.scene (and gameloop's transitive WaitForVBlank from the runtime). No other lib module is pulled in by sceneRun itself.gameloop (D.1)GameLoopConfig from <snes/gameloop.h> is an alias for Scene. The two types are interchangeable; gameLoopRun(&cfg) is semantically equivalent to running a single-scene stack with sceneRun(&cfg). Pick whichever name fits the use site: GameLoopConfig for "this is just a main loop", Scene for "this
is one of several swappable screens".| #define SCENE_STACK_MAX 8 |
Maximum scene stack depth.
scenePush beyond this depth is a silent no-op. 8 is enough for realistic SNES game shapes (title → menu → play → pause → over leaves 3 spare slots).
| void scenePop | ( | void | ) |
Pop the top scene. Resumes the scene below.
The popped scene gets no callbacks; the resumed scene's update runs on the next VBlank. The resumed scene's init is NOT re-invoked — it ran once at first push.
Silently ignored when the stack contains only the bottom scene (i.e. depth 1) — the stack is never empty after sceneRun is called.
| void scenePush | ( | const Scene * | next | ) |
Push a scene onto the stack. Suspends the current top.
Calls next->init if non-NULL. The caller's currently-executing update finishes its frame; the next VBlank dispatches to next.
Silently ignored when the stack is already at SCENE_STACK_MAX.
| next | Scene to push. Same lifetime contract as sceneRun's initial. |
| void sceneRun | ( | const Scene * | initial | ) |
Run the scene stack with initial at the bottom. Never returns.
Pushes initial, calls its init (if non-NULL), and enters the dispatch loop:
| initial | First scene. Must be non-NULL and have a non-NULL update. The pointer is held for the lifetime of the scene's stack residency, so the caller MUST keep the Scene struct alive for at least that long (typically: place it in static const). |