Project I/O

#include <safi/project/project.h>
#include <safi/project/recents.h>

A SafiEngine project is a directory containing project.safi.json, an assets/ tree, and a scenes/ folder. These helpers read and write that descriptor and scaffold new projects from a template. They are pure file I/O — no ECS world, no renderer — so the Hub can call them before any project is open.

Robustness contract: every loader treats a missing file, a parse error, or a missing field as a soft failure (returns false, leaves out zeroed). Nothing here asserts on bad input — a corrupt project must never take down the launcher.

SafiProjectInfo

#define SAFI_PROJECT_SCHEMA_VERSION 1

typedef struct SafiProjectInfo {
    char name[128];
    char path[1024];          /* absolute project root directory          */
    char default_scene[256];  /* project-root-relative, e.g. scenes/main.scene.json */
    char engine_version[32];
    int  schema_version;
} SafiProjectInfo;

The on-disk project.safi.json (schema v1):

{
  "version": 1,
  "name": "MyGame",
  "engine_version": "0.3.0",
  "default_scene": "scenes/main.scene.json",
  "asset_root": "assets",
  "shader_root": null
}

Loaders best-effort an older schema and hard-fail only on a newer one.

API

/* Where safi_project_create finds template directories. The editor binary
 * sets this once at startup from its SAFI_TEMPLATES_DIR compile definition. */
void        safi_project_set_templates_dir(const char *abs_dir);
const char *safi_project_templates_dir(void);

bool safi_project_load  (const char *project_dir, SafiProjectInfo *out);
bool safi_project_save  (const SafiProjectInfo *info);
bool safi_project_create(const char *parent_dir, const char *name,
                         const char *template_id, SafiProjectInfo *out);

bool safi_project_is_project(const char *dir);                 /* has project.safi.json? */
void safi_project_scene_path(const SafiProjectInfo *info,
                             char *out, size_t cap);            /* absolute default-scene path */

safi_project_create makes <parent_dir>/<name>/, copies the template template_id (e.g. "empty", "3d_starter") verbatim, then patches the copied descriptor's name and engine version. It fails if the target already exists, the template is missing, or the copy fails.

Recent projects

The Hub's recents list lives at SDL_GetPrefPath("SafiEngine", "Hub")/recents.json, capped at SAFI_RECENTS_MAX (32) and LRU-evicted. A corrupt store is treated as empty.

#define SAFI_RECENTS_MAX 32

typedef struct SafiRecentProject {
    char    name[128];
    char    path[1024];
    char    engine_version[32];
    int64_t last_opened_unix;
} SafiRecentProject;

int  safi_recents_load  (SafiRecentProject *out, int cap);  /* newest-first; returns count */
void safi_recents_touch (const SafiProjectInfo *info);      /* upsert + stamp time + persist */
void safi_recents_remove(const char *path);

safi_project_session_open calls safi_recents_touch automatically, so opening any project bumps it to the top of the list.

Example

SafiProjectInfo info;
if (safi_project_create("/Users/me/Games", "MyGame", "3d_starter", &info)) {
    safi_project_session_open(world, &info);  /* re-roots assets, loads the scene */
}

See also