SafiApp

SafiApp owns the window, the SDL_gpu renderer, the flecs world, and the main loop. Every SafiEngine program creates exactly one.

#include <safi/core/app.h>

Types

typedef struct SafiAppDesc {
    const char *title;
    int         width;
    int         height;
    bool        vsync;
    bool        enable_debug_ui;
    /* Seconds per fixed-update step. 0 → default (1/60). */
    float       fixed_dt;
    /* Max fixed steps per frame (spiral-of-death cap). 0 → default (4). */
    int         fixed_max_steps;
    /* Root directory against which relative asset paths resolve. Scene
     * files store paths in this form so they stay portable. NULL → CWD. */
    const char *project_root;
    /* Directory holding compiled shader artifacts. Separate from
     * project_root because CMake writes shaders into the build tree.
     * NULL → `<project_root>/shaders`. Exposed through
     * `safi_assets_shader_root()` for loaders that take `shader_dir = NULL`. */
    const char *shader_root;
    /* Poll-based mtime watcher that reloads changed models/textures.
     * Defaults on in debug-UI builds (editor) and off in shipping builds. */
    bool        enable_hot_reload;
} SafiAppDesc;

typedef struct SafiApp {
    SafiRenderer  renderer;
    ecs_world_t  *world;
    bool          running;
    // ...internal fields...
    float         fixed_dt;           // seconds per fixed step
    float         fixed_accumulator;  // unconsumed dt
    float         fixed_elapsed;      // accumulated fixed time
    int           fixed_max_steps;    // spiral-of-death cap
} SafiApp;

Functions

safi_app_init

bool safi_app_init(SafiApp *app, const SafiAppDesc *desc);

Creates the window, initialises SDL_gpu, builds a flecs world pre-populated with the stock components, the three engine pipelines, and the engine-owned render system on EcsOnStore. Installs the SafiInput and SafiTime singletons, and optionally boots the MicroUI overlay. Returns false on failure; check SDL_GetError() for details.

safi_app_run

void safi_app_run(SafiApp *app);

Blocks the calling thread in the main loop until the window closes or a system sets app->running = false. Each frame, safi_app_tick drives three pipelines in order:

  1. VariableEcsOnLoad → … → EcsPostUpdate at wall-clock dt
  2. FixedSafiFixedUpdate, looped N times at stable fixed_delta
  3. RenderEcsPreStore → EcsOnStore at wall-clock dt

See Scheduler & Stages for the details.

safi_app_tick

bool safi_app_tick(SafiApp *app);

Runs a single frame. Useful when embedding the engine in another loop or writing tests. Returns false when the app should exit.

safi_app_shutdown

void safi_app_shutdown(SafiApp *app);

Tears everything down in reverse order (ImGui → world → renderer → SDL).

safi_app_world

static inline ecs_world_t *safi_app_world(SafiApp *app);

Returns the underlying flecs world so you can register systems and spawn entities.

Example

examples/gltf_viewer/main.c
SafiApp app;
safi_app_init(&app, &(SafiAppDesc){
    .title = "SafiEngine — glTF Viewer",
    .width = 1280, .height = 720,
    .vsync = true,
    .enable_debug_ui = true,
    /* Scene files store asset paths relative to this directory. */
    .project_root = SAFI_DEMO_ASSET_DIR,
    /* Compiled shaders land in the CMake build dir. */
    .shader_root  = SAFI_DEMO_SHADER_DIR,
    /* Editor auto-enables hot-reload; set explicitly in release builds
     * if desired. */
});

ecs_world_t *world = safi_app_world(&app);
// ...register systems, spawn entities...

safi_app_run(&app);
safi_app_shutdown(&app);

See also