Editor State

#include <safi/editor/editor_state.h>

SafiEditorState is the source of truth for whether the app is authoring (Edit), simulating (Play), or temporarily halted (Paused). It is the foundation that the upcoming editor toolbar builds on — M1 of the Editor plan.

The engine installs the singleton automatically in safi_app_init with mode = Edit as the default. Systems, panels, and user code read or mutate it via the ECS singleton API or the convenience accessors below.

Struct

typedef enum SafiEditorMode {
    SAFI_EDITOR_MODE_EDIT   = 0,  /* default; gameplay paused, world editable */
    SAFI_EDITOR_MODE_PLAY   = 1,  /* fixed-update + game pipelines run        */
    SAFI_EDITOR_MODE_PAUSED = 2,  /* entered Play, temporarily halted         */
} SafiEditorMode;

typedef enum SafiEditorTool {
    SAFI_EDITOR_TOOL_SELECT    = 0,  /* default: click to pick entities */
    SAFI_EDITOR_TOOL_TRANSLATE = 1,  /* three axis arrows               */
    SAFI_EDITOR_TOOL_ROTATE    = 2,  /* three orthogonal rings          */
    SAFI_EDITOR_TOOL_SCALE     = 3,  /* three axes ending in cubes      */
} SafiEditorTool;

typedef struct SafiEditorState {
    SafiEditorMode mode;
    SafiEditorTool selected_tool;
    ecs_entity_t   selected_entity;  /* 0 = none */
} SafiEditorState;

API

SafiEditorMode safi_editor_get_mode    (const ecs_world_t *world);
void           safi_editor_set_mode    (ecs_world_t       *world, SafiEditorMode mode);
SafiEditorTool safi_editor_get_tool    (const ecs_world_t *world);
void           safi_editor_set_tool    (ecs_world_t       *world, SafiEditorTool tool);
ecs_entity_t   safi_editor_get_selected(const ecs_world_t *world);
void           safi_editor_set_selected(ecs_world_t       *world, ecs_entity_t e);

Thin wrappers over ecs_singleton_get / ecs_singleton_set. The getters return a sensible default (MODE_EDIT, TOOL_SELECT, 0) if the singleton is missing; the setters no-op in the same case.

selected_entity is the single source of truth — the debug UI Scene panel writes it on click and the Inspector, toolbar, and gizmo system all read it from here.

What the mode controls

The mode field gates two pipelines in safi_app_tick:

PipelineRuns when
Variable (EcsOnLoad..EcsPostUpdate)Always
Fixed (SafiFixedUpdate)mode == PLAY
Game (SafiGamePhase)mode == PLAY
Render (EcsPreStore..EcsOnStore)Always

Edit and Paused keep input pumping, the Inspector live, and the viewport rendering — only gameplay freezes. When re-entering Play, the fixed-step accumulator is drained so the simulation doesn't try to catch up on minutes of paused time. See Scheduler for the full pipeline breakdown and the SafiGamePhase usage pattern.

Typical uses

Toolbar buttons (the future M1 UI writes the mode):

if (play_button_clicked) safi_editor_set_mode(world, SAFI_EDITOR_MODE_PLAY);
if (stop_button_clicked) safi_editor_set_mode(world, SAFI_EDITOR_MODE_EDIT);

Inspector panels (read the selection):

ecs_entity_t sel = safi_editor_get_selected(world);
if (sel && ecs_is_alive(world, sel)) {
    /* draw component widgets for sel */
}

Opt into Play at startup for demos that don't have a toolbar yet:

safi_app_init(&app, &desc);
/* ... scene_setup ... */
safi_editor_set_mode(app.world, SAFI_EDITOR_MODE_PLAY);
safi_app_run(&app);

Keybind

Pressing F1 toggles Edit ↔ Play; the shortcut is engine-owned (see Editor Shortcuts). In Edit mode any gameplay system registered on SafiGamePhase freezes, so e.g. the gltf_viewer demo's falling cube stops mid-air; flip back to Play and it resumes. This exercises the pipeline gate described in Scheduler.

See also

  • Editor Toolbar — the Play / Pause / Stop + tool-picker buttons that write this singleton
  • Editor Gizmo — reads selected_tool + selected_entity to drive the manipulator
  • SchedulerSafiGamePhase and how gating works under the hood
  • Scene Serializationsafi_scene_snapshot_all / safi_scene_restore_snapshot, the primitives Play → Stop uses
  • Editor plan — milestone-by-milestone breakdown