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