Patterns from Game Engines
Game engines push patterns to their limits — every frame counts at 60fps.
| Pattern | Project | Where | What It Does |
|---|---|---|---|
| Object Pool | Godot | core/templates/pooled_list.h | Freelist-based pool for entities, particles, physics bodies |
| Double Buffering | SDL | src/render/SDL_render.c | Front/back buffer swap for tear-free rendering |
| Free List | Godot | core/templates/pooled_list.h | Non-intrusive freelist allocator for O(1) entity alloc/free |
| Ring Buffer | Game audio | Various engines | Lock-free audio streaming buffers between main and audio threads |
| State Machine | Godot | scene/animation/animation_tree.h | Animation state machines for character animation blending |
| Arena Allocator | Frame allocators | Common pattern | Per-frame bump allocator — reset every frame, zero free cost |
| Flyweight | Godot | servers/rendering/ | Shared mesh/texture resources referenced by multiple instances |
| Batch Processing | Godot / Unity | Render batching | Batch draw calls to minimize GPU state changes |
| Tagged Union | Godot | variant.h | Variant::Type enum + union — every GDScript value is a Variant |
| Dirty Flag | Godot / Unity | Transform hierarchies | Dirty flag on parent transform invalidates child world matrices — recompute only when accessed |
| Event Loop | Godot | main_loop.h | Main game loop — process input, update, render in fixed-step cycle |
How They Compose: One Game Frame
At 60fps, each frame has ~16ms. Multiple patterns work together inside that budget:
Frame N starts▼The main loop ticks: process input, run physics, update game state, render. Fixed timestep ensures deterministic simulation.
Spawning a bullet grabs a slot from the pool (O(1)), not malloc. Destroyed entities return to the free list.
Moving a parent transform marks children dirty. Only recompute world matrices for nodes that actually changed this frame.
Character animation transitions (idle → run → jump) driven by state machine. Each state knows its blend tree and exit rules.
Draw calls are batched by material/texture to minimize GPU state changes. 100 sprites with same texture = 1 draw call.
The back buffer is swapped to front atomically. Players see a complete frame, never a half-drawn one.
Per-frame temp allocations (particles, debug draws) use a bump allocator. At frame end: reset pointer to zero. Done.
The key insight: game engines minimize per-object overhead. Pools avoid malloc, dirty flags avoid recomputation, batching avoids GPU calls, and arenas avoid per-object deallocation. All of these share the same design philosophy — pay O(1) per operation, defer or amortize the expensive work.
Further Reading
- Godot Engine (GitHub) · SDL (GitHub)
- Game Programming Patterns (book) by Robert Nystrom