Stock Components

#include <safi/ecs/components.h>

Every world created by safi_ecs_create (or safi_app_init) has these registered. Attach them to entities with ecs_set / ecs_add.

SafiTransform

typedef struct SafiTransform {
    vec3   position;   // local translation
    versor rotation;   // quaternion (x, y, z, w)
    vec3   scale;
} SafiTransform;

static inline SafiTransform safi_transform_identity(void);
static inline void safi_transform_to_mat4(const SafiTransform *xf, mat4 out);

Use safi_transform_identity() for a sensible default (zero position, unit quaternion, unit scale). safi_transform_to_mat4() composes a TRS model matrix.

SafiGlobalTransform

typedef struct SafiGlobalTransform {
    mat4 matrix;
} SafiGlobalTransform;

World-space model matrix. Written automatically each frame by the engine's transform propagation system. Add it alongside SafiTransform on any entity that needs a world-space pose (renderable meshes, physics bodies, etc.).

SafiCamera

typedef struct SafiCamera {
    float fov_y_radians;
    float z_near;
    float z_far;
    vec3  target;
    mat4  view;   // recomputed per frame by the render system
    mat4  proj;
} SafiCamera;
WIP — Camera system

SafiCamera exists as a component but projection is computed in the demo, not by the engine. Orthographic projection is not supported. There is no camera priority, viewport, or multi-camera system. See Camera System.

SafiActiveCamera

typedef struct SafiActiveCamera { char _unused; } SafiActiveCamera;

Tag component — marks which camera the engine renders from. Attach to exactly one entity that also has SafiCamera. The engine's render system finds the active camera via (SafiCamera, SafiActiveCamera) query.

Singleton by convention: the engine centralises the "at most one holder" policy via safi_ecs_make_tag_unique(world, ecs_id(SafiActiveCamera), holder) (declared in safi/ecs/singleton_tag.h). deser_active_camera calls it automatically, so snapshot restore and scene load can never end up with two active cameras at the same time.

SafiEngineOwned

typedef struct SafiEngineOwned { char _unused; } SafiEngineOwned;

Tag for engine infrastructure entities that must survive safi_scene_clear, stay out of safi_scene_save / snapshot, and be ignored by the editor's scene hierarchy panel. The editor fly-cam carries this tag; future additions (culling proxies, shadow-RT holders, debug billboards) should follow the same convention.

SafiStableId

typedef struct SafiStableId {
    uint64_t hi;
    uint64_t lo;
} SafiStableId;

128-bit random GUID that uniquely identifies an entity across snapshots, scene save/load, undo steps, and future prefab instantiation. Every named entity gets one automatically (via an EcsOnAdd observer on SafiName), so user code rarely touches the component directly.

Serialised as 32 lowercase hex characters alongside name in scene JSON; safi_scene_restore_snapshot matches on the id first and falls back to name for entities that predate the format. See safi/ecs/stable_id.h for generation helpers (safi_stable_id_new, safi_stable_id_to_string, safi_stable_id_from_string) and the safi_scene_find_entity_by_stable_id lookup.

SafiMeshRenderer

typedef struct SafiMeshRenderer {
    SafiModelHandle model;   // asset-registry handle; 0 = nothing to draw
    bool            visible;
} SafiMeshRenderer;

The engine's render system queries (SafiGlobalTransform, SafiMeshRenderer) and resolves the handle via safi_assets_resolve_model() each frame, then issues one safi_model_draw_lit call per visible entity. Set visible = false to skip rendering without removing the component.

Models are loaded through the Asset System with path-based deduplication and refcounting. The inspector shows the resolved path (or <code> for procedurally-registered models).

SafiPrimitive

typedef struct SafiPrimitive {
    SafiPrimitiveShape shape;   // PLANE / BOX / SPHERE / CAPSULE
    union {
        struct { float size; }                                 plane;
        struct { float half_extents[3]; }                      box;
        struct { float radius; int   segments; int rings; }    sphere;
        struct { float radius; float height;
                 int   segments; int rings; }                  capsule;
    } dims;
    float             color[4];  // RGBA 0..1; used when texture is invalid
    SafiTextureHandle texture;   // id=0 => solid color from color[]
    /* private: _model_handle, _hash — managed by primitive_system */
} SafiPrimitive;

A procedural mesh with editable shape, dimensions, color, and texture. The engine's primitive_system builds the GPU resources on EcsPreStore and auto-attaches a SafiMeshRenderer. Flecs .copy / .move / .dtor hooks on the component keep the refcounts of _model_handle and texture balanced — including on scene-restore overwrites — so no code path leaks a registry entry.

texture is a SafiTextureHandle, which means primitive textures dedup across entities, participate in hot-reload, and can be assigned by dropping a file onto the Inspector (the inspector edits a relative path string and resolves it through safi_assets_load_texture). Changing any field — shape, dims, color, texture — triggers a rebuild on the next frame. See the full reference on Primitive Shapes.

SafiName

typedef struct SafiName { const char *value; } SafiName;

Optional debug-display name; the MicroUI inspector shows it.

SafiSpin

typedef struct SafiSpin {
    vec3  axis;
    float speed;   // radians per second
} SafiSpin;

Shipped with the engine so the first demo can spin a cube with three lines of code.

Example

ecs_entity_t e = ecs_new(world);
ecs_set(world, e, SafiTransform, {
    .position = {0, 0, 0},
    .rotation = {0, 0, 0, 1},
    .scale    = {1, 1, 1},
});
ecs_set(world, e, SafiMeshRenderer, { .mesh = &mesh, .material = &mat });
ecs_set(world, e, SafiName, { .value = "Hero" });
ecs_set(world, e, SafiSpin, { .axis = {0, 1, 0}, .speed = 1.5f });

See also