Cinder architecture
Module map #
Dependencies point downward only. A module may import from the layers below it, never from the same layer except where noted, and never from above.
sample/ third-person sample game (Mojo), a `Game` run by cinder/app
└── cinder/app App runtime: owns every subsystem, fixed-step loop, headless mode, CLI
└── cinder/tools ImGui debug overlay (inspector, asset browser, render graph, log)
└── cinder/{render,physics,anim,audio,input,assets,scene}
└── cinder/ecs archetype ECS, queries, command buffers, hierarchy, scheduler
└── cinder/gfx safe typed wgpu layer, shader manager, hot reload, debug draw
└── cinder/platform SDL3 window/input/gamepad, surfaces, ImGui event forwarding
└── cinder/core log, assert, arena, handles, ring, math, jobs, time, fs, profile, boxed
└── cinder/ffi generated bindings + layout tests (never hand-edited)
└── native/ libcinder_native (C11 shim) + vendored C/C++ libs| Module | Language | Responsibility |
|---|---|---|
native/ |
C11 (shim), C/C++ (deps) | Single shared library libcinder_native. Wraps wgpu-native, SDL3, joltc, miniaudio, cgltf, stb_image, KTX, meshoptimizer, MikkTSpace, dcimgui + backends, Tracy, flecs (benchmark only). Owns every callback and the lock-free event queues. libcinder_alloccount is a separate LD_PRELOAD library for the allocation test. |
cinder/ffi |
Mojo (generated) | Raw bindings for each native header. Generated by tools/bindgen (ADR-0005), committed, verified by the layout test. |
cinder/core |
Mojo | Logging, assertions, arenas, generational handles, rings, SIMD math (core/math), work-stealing job system, time, filesystem, Tracy zones (profile), heap cells with stable addresses (boxed). No GPU or window I/O. |
cinder/platform |
Mojo | SDL3: window, events, input, gamepad, surface creation per OS, forwarding events to ImGui. The only module with per-OS branches. |
cinder/gfx |
Mojo | Typed handles over wgpu (Resources), ownership and leak accounting, WGSL shader manager with hot reload, validation surfacing, swapchain, debug draw. |
cinder/ecs |
Mojo | Archetype storage, typed queries and cached QuerySpecs, change ticks, command buffers, hierarchy and transform propagation, access-declared system scheduling on the job system. |
cinder/render |
Mojo + WGSL | Frame graph, clustered forward+, cascaded and atlas shadows, IBL, GTAO, bloom, tone mapping, FXAA, MSAA, culling, LODs, skinning, morph targets, debug views. Allocation-free in steady state. |
cinder/assets |
Mojo | glTF import, cooked formats, incremental cooker, KTX2 textures, AssetServer (async loads, refcounts, hot reload). |
cinder/scene |
Mojo | Text and binary scene formats with migrations, component registry, ECS to render-scene bridge (RenderBridge), glTF spawning into the ECS. |
cinder/physics |
Mojo | Safe layer over joltc: world, shapes, bodies, events, casts, CharacterController, ECS sync with interpolation (PhysicsSync). |
cinder/anim |
Mojo | Skeletons, clip sampling, blend trees, crossfades, root motion, events; one job per animator. |
cinder/audio |
Mojo | Buses, one-shots, streams, spatialisation over miniaudio; ECS sources and listener. |
cinder/input |
Mojo | Action/axis mapping over keyboard, mouse and gamepad; runtime rebinding; text save/load. |
cinder/app |
Mojo | App runtime and the Game trait: subsystem ownership, fixed-step loop, windowed or --headless, allocation accounting, CLI parsing. |
cinder/tools |
Mojo | ImGui debug overlay: frame graph, entity inspector, asset browser, render graph view, log console. |
sample/ |
Mojo | The sample game: components, levels, settings, gameplay (ADR-0014). |
tools/ |
Mojo, Python, shell | Cooker CLI, bindings generator, lint, test drivers, asset fetchers, benchmark checker. |
bench/ |
Mojo | Benchmarks writing JSON (stress, sample_bench, ecs_bench) and their baselines; see docs/BENCHMARKS.md. |
tests/ |
Mojo, C (CTest) | Unit, golden-image, replay, fuzz, playthrough and allocation tests. |
The C boundary #
- One shim, one shared library, flat C ABI (
cinder_native.h, C11). - Pull model. C callbacks (wgpu async requests, device lost, uncaptured errors, Jolt contacts, miniaudio data, SDL log) write fixed-size events into fixed-capacity SPSC/MPSC lock-free rings inside the shim. Mojo drains them once per frame on the main thread. Mojo code never executes on a C thread unless spike 4 proves it safe and an ADR records that (ADR-0003).
- Generated raw bindings live in
cinder/ffiand are never edited by hand. A C program emitssizeof/alignof/offsetoffor every bound struct and every enum value; a generated Mojo test asserts identical values. Mismatch fails CI. - Safe wrappers: one distinct handle struct per resource type, ownership through
__deinit__, explicit lifetimes for borrowed C strings and views, each shared library loaded exactly once, everyunsafe_call annotated with# SAFETY:.
Threading model #
- Main thread: SDL event pump, window, presentation (
wgpuSurfacePresent), ImGui. - Job system (
cinder/core/jobs): one worker per physical core minus one, work-stealing deques, a per-frame dependency graph. Everything else (culling, animation, ECS systems, asset decode, physics step through Jolt's own job system adapter) runs there. - Shared state crosses threads only through jobs, atomics or documented locks named in
docs/THREADING.md. - Audio: miniaudio's device thread runs in C only; it reads a lock-free command ring and never calls Mojo.
Frame loop #
Fixed 60 Hz simulation with accumulator (App.run_frame: poll events, fixed steps with physics and
Game.fixed_update, Game.update, animation jobs, transform propagation, audio sync, render-scene
bridge, render, ImGui, present or offscreen readback). Per-frame memory lives in persistent scratch
fields that are cleared, never freed, between frames; steady state performs zero heap allocations from
Mojo code (asserted by tests/perf/test_zero_alloc.mojo, ADR-0014).
Conventions (fixed) #
Right-handed, Y up, −Z forward, metres, column-major matrices, reverse-Z with infinite far plane, linear lighting, sRGB render targets, RGBA16F intermediates. Recoverable failures raise typed errors; broken invariants abort with a message.