summaryrefslogtreecommitdiffhomepage
path: root/.agents/knowledge/flecs-observability.md
blob: c67efc75f1e81daca7fefcaf17e69cf183ae3a6f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# Tribal knowledge: flecs observability (REST / Explorer / stats) — R5

> Status: **desktop IMPLEMENTED + verified** (R5). Web (R5a) follows in the web
> phase. `world.enable_rest` / `world.enable_stats` in `flecs_bindings.c` + `flecs.rb`.

## At a glance
- **What:** surface flecs's built-in remote API so a human/agent gets a full ECS
  view — entities, components, queries, per-system timing — with **zero UI code on
  our side** (the hosted Flecs Explorer connects remotely).
- **Key files:** `mrbgems/flecs/src/flecs_bindings.c` (add `_enable_rest`/
  `_enable_stats`); sugar `mrbgems/flecs/mrblib/flecs.rb` (`enable_rest`,
  `enable_stats`). Amalgamation already built **with** `FLECS_REST`/HTTP/STATS.
- **Cross-refs:** `flecs-binding.md`, `agent-bridge.md` (web ships REST JSON over the
  bridge), roadmap R5/R5a; principle P5 (observe).

## Desktop mechanism (the whole thing)
```c
FlecsRestImport(world);                               // register REST module
ecs_set(world, EcsWorld, EcsRest, {.port = port});    // -> starts HTTP server
FlecsStatsImport(world);                              // per-system timing/world stats
```
The REST HTTP server runs on `:27750` (flecs manages its own thread); it's serviced
during `world.progress`, which the game already calls each frame. Then point the
**hosted Explorer** at it remotely — no local UI:
`https://www.flecs.dev/explorer/?host=localhost:27750`.
Quick check without a browser: `curl http://localhost:27750/world` (or
`/entity/<name>`) returns JSON.

## Ruby API (planned)
```ruby
world.enable_rest(27750)   # default ECS_REST_DEFAULT_PORT
world.enable_stats         # FLECS_STATS for per-system timing
```
Gate behind dev/JAMSTACK_BRIDGE in game code (it opens a local port — P7).

## Web (R5a) — flecs already did most of it
`ecs_http` can't bind a listening socket in the browser, BUT flecs ships, under
`ECS_TARGET_EM`, an `EMSCRIPTEN_KEEPALIVE char* flecs_explorer_request(method, req,
body)` that runs `ecs_http_server_request` against a socketless
`flecs_wasm_rest_server` (a non-static global). Plan for the web phase:
- init it once: `extern ecs_http_server_t *flecs_wasm_rest_server;
  flecs_wasm_rest_server = ecs_rest_server_init(world, NULL);`
- `flecs_explorer_request` is already exported — call it from JS / the bridge and
  ship the JSON over the agent channel; feed `.live/state.json`.
This is the R5a spike's happy path: the request handler is reachable **without** the
socket server thread.

## Acceptance
Desktop: `curl :27750/world` returns JSON and the hosted Explorer shows the live
world; per-system timing visible with `enable_stats`. (Web: covered in R5a.)

## Scar tissue (verified desktop)
- **Works as a 3-liner:** `FlecsRestImport(w)` + `ecs_set(w, EcsWorld, EcsRest,
  {.port})` + `FlecsStatsImport(w)`. No `ECS_IMPORT` macro needed — call the import
  functions directly (avoids a local-var declaration). `ecs_id(EcsRest)` is a fixed
  compile-time id, but you still must import the module so its system/observers run.
- **Verified endpoints** (game looping `progress`, `curl`/urllib on :27750):
  `/world` → 200; `/entity/player?values=true` →
  `{"components":{"Position":{"x":1,"y":2},"Velocity":{...}}}`;
  `/query?expr=Position&values=true` → all matches with values. These are exactly
  what the hosted Explorer drives (`flecs.dev/explorer?host=localhost:27750`).
- **Threading:** the REST HTTP server runs on flecs's own thread (accepts
  connections), but requests are *processed* during `world.progress` — so the game
  must keep ticking for responses (it does, each frame).
- **Gotcha (not REST):** the **base** `world.system(with: [...])` expects component
  **ids/objects** — it does `.to_i`, so string names silently become `0` and the
  system matches nothing. Use `Flecs::Hot.define_system` (resolves names via lookup)
  or pass `Component` objects.
- **In-process REST requests (`rest_request`):** `world.rest_request("GET", "/world")`
  calls `ecs_http_server_request` on the `fl_rest_server` handle (set by
  `enable_rest`). No socket needed — works on desktop AND web identically. This is
  what `bin/snapshot` and `bin/query` use to dump ECS state JSON through the `.live/`
  mount. On desktop, `enable_rest` creates TWO server objects: one with a port (HTTP
  listener for the hosted Explorer) and one socketless (for in-process requests).
  Both share the same world. On web, only the socketless one exists (shared with
  `flecs_wasm_rest_server` for the JS C export). Key endpoints: `/world` (full world
  state incl. entities + components), `/query?expr=<ComponentName>&values=true`
  (matching entities with component values), `/entity/::<name>?values=true` (single
  entity — note the `::` scope separator; may not find entities created without a
  scope path — use `/query` as the reliable fallback).