> Cinder 0.1.0 documentation: cinder.app API
> Canonical page: https://cinder.ash10.dev/docs/api/app/ · Source: `docs/api/app.md` in the engine repository (engine commit 47c9352, imported 2026-11-09)
> Index of every page for agents: https://cinder.ash10.dev/llms.txt

# `cinder.app`


## `cinder.app`

Source: `cinder/app/__init__.mojo`

Application runtime: subsystems, the fixed-step game loop, windowed and headless modes.


## `cinder.app.app`

Source: `cinder/app/app.mojo`

The application runtime: owns every subsystem, runs the fixed-step game loop and renders. `App` works in two modes. Windowed: an SDL window, a swapchain and the ImGui debug overlay. Headless (`AppConfig.headless`, the `--headless` flag of the sample): no window, no audio device, frames render into an offscreen texture that `read_frame` reads back, so tests and benchmarks drive exactly the same loop. A `Game` supplies the per-step and per-frame logic. Subsystems that hand out pointers to themselves live in `Boxed` heap cells so the `App` value can move. Fields are declared in destruction order: the overlay and gameplay systems first, GPU objects next, the native library last.

- `comptime READBACK_ALIGN = 256` — Row alignment in bytes of the headless readback buffer; each row `read_frame` copies is padded to a multiple of it.
### def `ui_consumes_event`

```mojo
def ui_consumes_event(ev: Event, ui_wants_mouse: Bool, ui_wants_keyboard: Bool) -> Bool
```

Whether the debug overlay swallows `ev` instead of the game's `InputMap`. While ImGui wants the mouse, mouse presses, motion and wheel go only to the overlay; while it wants the keyboard, key presses do. Key and mouse button *releases* always reach the input map: a key or button held when the overlay takes focus would otherwise stay held there.


### struct `AppConfig`

```mojo
struct AppConfig(Copyable, Movable)
```

Settings read once by `App.__init__`; `parse_app_args` fills them from the command line. Defaults: 1280x720 windowed, 60 Hz fixed step, audio and debug UI on, profiler off, 3 worker threads.

Fields:

- `title`: String
- `width`: Int
- `height`: Int
- `headless`: Bool
- `fixed_hz`: Float64
- `render`: RenderSettings
- `layers`: LayerConfig
- `audio`: Bool — Create the audio engine (headless apps use miniaudio's device-less engine).
- `debug_ui`: Bool — Create the ImGui overlay (windowed only).
- `profile`: Bool — Start the Tracy client at startup.
- `worker_threads`: Int

- **`__init__`** `__init__( out self, title: String = String("Cinder"), width: Int = 1280, height: Int = 720, headless: Bool = False, )`

### trait `Game`

```mojo
trait Game
```

Gameplay hooks called by `App.run_frame`.

- **`start`** `start(mut self, mut app: App) raises`  
  Called once, at the start of the first `run_frame` and before its events are polled.
- **`fixed_update`** `fixed_update(mut self, mut app: App, dt: Float32) raises`  
  Called once per fixed step with the step length in seconds, after physics has stepped and body transforms were pulled into the world.
- **`update`** `update(mut self, mut app: App, dt: Float32) raises`  
  Called once per frame with the frame time in seconds, after the fixed steps and before animation, transform propagation, audio and rendering.
- **`draw_ui`** `draw_ui(mut self, mut app: App) raises`  
  Called while the debug overlay is built (windowed with `debug_ui` only, on frames that acquired a swapchain image), before the engine's own windows; make ImGui calls here.

### struct `App`

```mojo
struct App(Movable)
```

The engine runtime: owns the native library, the platform and window (windowed), the GPU device, renderer, ECS world, physics, audio, animation, input map, scene registry and assets, and drives them from `run_frame`. Construction raises when a subsystem fails to start. Fields are destroyed in declaration order, the overlay first and `lib` last. Use it from the main thread, where `Platform` must live.

Fields:

- `debug_ui`: Optional[DebugUi]
- `assets`: Optional[Boxed[AssetServer]]
- `anim`: AnimationSystem
- `audio`: Optional[AudioEngine]
- `physics_sync`: PhysicsSync
- `bridge`: RenderBridge
- `physics`: Boxed[PhysicsWorld]
- `input`: InputMap
- `world`: World
- `scene`: RenderScene
- `registry`: SceneRegistry
- `logs`: MemorySink
- `events`: List[Event]
- `swapchain`: Optional[Swapchain]
- `target_view`: TextureViewHandle
- `target_tex`: TextureHandle
- `readback`: BufferHandle
- `renderer`: Boxed[Renderer]
- `res`: Boxed[Resources]
- `gfx`: Boxed[Gfx]
- `window`: Optional[Boxed[Window]]
- `platform`: Optional[Boxed[Platform]]
- `jobs`: Boxed[JobSystem]
- `profiler`: Profiler
- `clock`: Clock
- `fixed`: FixedStep
- `config`: AppConfig
- `width`: Int
- `height`: Int
- `frame_index`: Int
- `fixed_steps`: Int
- `quit_requested`: Bool
- `started`: Bool
- `count_allocations`: Bool — When set, `alloc_*` record Mojo heap allocations per phase of the last frame (needs the allocation counter library preloaded; see tests/perf).
- `alloc_fixed`: Int
- `alloc_update`: Int
- `alloc_anim`: Int
- `alloc_propagate`: Int
- `alloc_audio`: Int
- `alloc_render`: Int
- `alloc_other`: Int
- `frame_ms`: Float64 — CPU time of the last `run_frame` (ms).
- `physics_ms`: Float64 — Time spent in physics steps during the last frame (ms).
- `render_ms`: Float64 — Time spent building and submitting the frame (ms), without presentation waits.
- `lib`: NativeLibrary

- **`__init__`** `__init__(out self, var config: AppConfig) raises`
- **`mount_assets`** `mount_assets(mut self, root: String) raises`  
  Attaches a cooked-asset directory (its `manifest.txt`).
- **`log`** `log(mut self, level: LogLevel, category: String, message: String)`  
  Records a message in `logs`, the in-memory sink shown in the debug UI's Log tab.
- **`run_frame`** `run_frame[ G: Game ](mut self, mut game: G, frame_seconds: Float64 = -1.0) raises -> Bool`  
  One frame: events, fixed steps, per-frame update, render, present. `frame_seconds` overrides the measured frame time (tests and benchmarks run deterministically). Returns False once a quit was requested.
- **`read_frame`** `read_frame(mut self) raises -> Image`  
  Headless only: the last rendered frame as an image.
- **`request_quit`** `request_quit(mut self)`  
  Makes `run_frame` return False. Requested from `fixed_update`, `update` or `draw_ui`, the current frame still completes; later calls return right after polling events.
- **`gpu_error_count`** `gpu_error_count(self) -> Int`  
  Uncaptured wgpu validation errors seen since the device was created.
- **`live_objects`** `live_objects(self) -> Int`  
  Physics objects plus audio sounds and buses alive: the game-owned objects that must be zero before shutdown (the leak report). GPU objects are not counted: the engine's own render targets, pipelines and renderer meshes stay alive for the App's whole life, so a total including them is never zero. Use `res[].live_counts()` for GPU resources; `Resources` releases and counts leftovers when it is destroyed.


## `cinder.app.cli`

Source: `cinder/app/cli.mojo`

Command-line options shared by every Cinder executable (`--headless` for tests and benchmarks).

### def `parse_app_args`

```mojo
def parse_app_args( args: List[String], mut config: AppConfig ) raises -> List[String]
```

Applies engine options to `config` and returns the arguments it did not consume. Options: `--headless`, `--profile`, `--no-audio`, `--no-debug-ui`, `--width N`, `--height N`, `--title TEXT`. Everything else (positional arguments and the program's own options) is returned for the caller.
