archetype-ecs-lib 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,68 +10,426 @@ var __values = (this && this.__values) || function(o) {
10
10
  };
11
11
  throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
12
12
  };
13
+ var __read = (this && this.__read) || function (o, n) {
14
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
15
+ if (!m) return o;
16
+ var i = m.call(o), r, ar = [], e;
17
+ try {
18
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
19
+ }
20
+ catch (error) { e = { error: error }; }
21
+ finally {
22
+ try {
23
+ if (r && !r.done && (m = i["return"])) m.call(i);
24
+ }
25
+ finally { if (e) throw e.error; }
26
+ }
27
+ return ar;
28
+ };
29
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
30
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
31
+ if (ar || !(i in from)) {
32
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
33
+ ar[i] = from[i];
34
+ }
35
+ }
36
+ return to.concat(ar || Array.prototype.slice.call(from));
37
+ };
13
38
  Object.defineProperty(exports, "__esModule", { value: true });
14
39
  exports.Schedule = void 0;
15
40
  /**
16
- * Minimal scheduler that supports phases, without borrow-checking.
17
- * (Add conflict detection later if you want parallelism.)
41
+ * Minimal multiphase scheduler for running ECS systems in named "phases".
42
+ *
43
+ * ## What it does
44
+ * - Group systems by phase name (e.g. `"input"`, `"update"`, `"render"`).
45
+ * - Executes phases in a chosen order.
46
+ * - Optionally performs **phase-boundary work** (flush commands and swap events).
47
+ * - Supports **ordering constraints** between phases via `.after()` / `.before()`.
48
+ *
49
+ * ## Phase boundaries
50
+ * By default (`boundaryMode = "auto"`), after each phase:
51
+ * - if `world.cmd().hasPending()` → `world.flush()`
52
+ * - `world.swapEvents()` so events emitted in this phase become visible to the next phase
53
+ *
54
+ * Use `"manual"` boundary mode if you want to control flush/event delivery yourself.
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * const schedule = new Schedule();
59
+ *
60
+ * schedule
61
+ * .add(world, "input", inputSystem)
62
+ * .add(world, "sim", simSystem).after("input")
63
+ * .add(world, "render", renderSystem).after("sim");
64
+ *
65
+ * // Either pass an explicit phase list...
66
+ * schedule.run(world, dt, ["input", "sim", "render"]);
67
+ *
68
+ * // ...or omit it and let constraints drive the order:
69
+ * // schedule.run(world, dt);
70
+ * ```
18
71
  */
19
72
  var Schedule = /** @class */ (function () {
20
73
  function Schedule() {
21
- this.phases = new Map();
74
+ this.phaseOrder = undefined;
75
+ this.boundaryMode = "auto";
76
+ /**
77
+ * Phase ordering constraints stored as edges `before -> after`.
78
+ * Example: calling `after("input")` for phase `"sim"` records `input -> sim`.
79
+ */
80
+ this.phaseEdges = new Map();
81
+ /**
82
+ * Tracks the most recently modified phase (via add()) so `.after()`/`.before()` can be chained.
83
+ */
84
+ this._lastPhase = undefined;
22
85
  }
23
- Schedule.prototype.add = function (phase, fn) {
86
+ /**
87
+ * Set a default phase order used when calling `run(world, dt)` without passing `phaseOrder`.
88
+ *
89
+ * @param phases Ordered list of phase names to execute.
90
+ */
91
+ Schedule.prototype.setOrder = function (phases) {
92
+ this.phaseOrder = phases.slice();
93
+ return this;
94
+ };
95
+ /**
96
+ * Control what happens at phase boundaries.
97
+ *
98
+ * - `"auto"` (default): flush deferred commands (if pending) and swap event buffers after each phase.
99
+ * - `"manual"`: do nothing automatically; the caller is responsible for `world.flush()` / `world.swapEvents()`.
100
+ *
101
+ * @param mode Boundary behavior.
102
+ */
103
+ Schedule.prototype.setBoundaryMode = function (mode) {
104
+ this.boundaryMode = mode;
105
+ return this;
106
+ };
107
+ /**
108
+ * Add a system function to a phase in the given world.
109
+ *
110
+ * The returned object allows you to attach **phase ordering constraints**:
111
+ * - `.after("input")` means this phase must run after `"input"`.
112
+ * - `.before("render")` means this phase must run before `"render"`.
113
+ *
114
+ * Constraints are **phase-level**, not system-level: they affect the relative order of phases, not
115
+ * the order of systems within the same phase.
116
+ *
117
+ * @param world World instance to add the system to.
118
+ * @param phase Phase name.
119
+ * @param fn System function `(world, dt) => void`.
120
+ */
121
+ Schedule.prototype.add = function (world, phase, fn) {
24
122
  var _a;
25
- var list = (_a = this.phases.get(phase)) !== null && _a !== void 0 ? _a : [];
123
+ var phases = world._scheduleSystems;
124
+ var list = (_a = phases.get(phase)) !== null && _a !== void 0 ? _a : [];
26
125
  list.push(fn);
27
- this.phases.set(phase, list);
126
+ phases.set(phase, list);
127
+ this._lastPhase = phase;
28
128
  return this;
29
129
  };
130
+ /**
131
+ * Constrain the last added phase to run after `otherPhase`.
132
+ *
133
+ * Must be called after `add(...)`.
134
+ */
135
+ Schedule.prototype.after = function (otherPhase) {
136
+ if (!this._lastPhase) {
137
+ throw new Error("Schedule.after(\"".concat(otherPhase, "\") must be called after schedule.add(world, phase, fn)."));
138
+ }
139
+ this._addPhaseConstraint(otherPhase, this._lastPhase);
140
+ return this;
141
+ };
142
+ /**
143
+ * Constrain the last added phase to run before `otherPhase`.
144
+ *
145
+ * Must be called after `add(...)`.
146
+ */
147
+ Schedule.prototype.before = function (otherPhase) {
148
+ if (!this._lastPhase) {
149
+ throw new Error("Schedule.before(\"".concat(otherPhase, "\") must be called after schedule.add(world, phase, fn)."));
150
+ }
151
+ this._addPhaseConstraint(this._lastPhase, otherPhase);
152
+ return this;
153
+ };
154
+ /**
155
+ * Execute all scheduled systems for the given frame.
156
+ *
157
+ * Phase order selection:
158
+ * 1) If `phaseOrder` is provided, it is used as-is.
159
+ * 2) Else if `setOrder()` was called, the stored order is used.
160
+ * 3) Else an order is computed from `.after()`/`.before()` constraints.
161
+ *
162
+ * @param world World instance (API) passed to each system.
163
+ * @param dt Delta time in seconds.
164
+ * @param phaseOrder Optional explicit phase order for this run.
165
+ *
166
+ * @throws If both `Schedule.run()` and `World.update()` are used on the same World instance.
167
+ * @throws If phase constraints contain a cycle and no explicit order is provided.
168
+ * @throws Re-throws system errors, wrapped with `[phase=... system=...]` context.
169
+ *
170
+ * @note When using `Schedule`, avoid calling `world.update()` on the same World instance.
171
+ */
30
172
  Schedule.prototype.run = function (world, dt, phaseOrder) {
31
173
  var e_1, _a, e_2, _b;
174
+ var _c;
175
+ // Runtime conflict detection (cast to access private fields)
176
+ var worldInstance = world;
177
+ if (worldInstance._getSystemCount() > 0) {
178
+ worldInstance._warnAboutLifecycleConflict("Schedule.run");
179
+ }
180
+ var phases = (_c = phaseOrder !== null && phaseOrder !== void 0 ? phaseOrder : this.phaseOrder) !== null && _c !== void 0 ? _c : this._computePhaseOrder(world);
181
+ if (!phases || phases.length === 0) {
182
+ throw new Error('Schedule.run requires a phase order (pass it as an argument or call schedule.setOrder([...]))');
183
+ }
184
+ var frameStart = worldInstance._profBeginFrame(dt);
32
185
  try {
33
- for (var phaseOrder_1 = __values(phaseOrder), phaseOrder_1_1 = phaseOrder_1.next(); !phaseOrder_1_1.done; phaseOrder_1_1 = phaseOrder_1.next()) {
34
- var phase = phaseOrder_1_1.value;
35
- var list = this.phases.get(phase);
36
- if (!list)
37
- continue;
38
- try {
39
- for (var list_1 = (e_2 = void 0, __values(list)), list_1_1 = list_1.next(); !list_1_1.done; list_1_1 = list_1.next()) {
40
- var fn = list_1_1.value;
186
+ try {
187
+ for (var phases_1 = __values(phases), phases_1_1 = phases_1.next(); !phases_1_1.done; phases_1_1 = phases_1.next()) {
188
+ var phase = phases_1_1.value;
189
+ var phaseStart = performance.now();
190
+ var list = world._scheduleSystems.get(phase);
191
+ // Run systems only if they exist for this phase
192
+ if (list) {
41
193
  try {
42
- fn(world, dt);
194
+ for (var list_1 = (e_2 = void 0, __values(list)), list_1_1 = list_1.next(); !list_1_1.done; list_1_1 = list_1.next()) {
195
+ var fn = list_1_1.value;
196
+ var sysStart = performance.now();
197
+ try {
198
+ fn(world, dt);
199
+ }
200
+ catch (error) {
201
+ var sysName = fn.name && fn.name.length > 0 ? fn.name : "<anonymous>";
202
+ var msg = error.message !== undefined && typeof error.message === 'string' ?
203
+ error.message : JSON.stringify(error);
204
+ var e = new Error("[phase=".concat(phase, " system=").concat(sysName, "] ").concat(msg));
205
+ e.cause = error;
206
+ throw e;
207
+ }
208
+ finally {
209
+ var sysName = fn.name && fn.name.length > 0 ? fn.name : "<anonymous>";
210
+ worldInstance._profAddSystem("".concat(phase, ":").concat(sysName), performance.now() - sysStart);
211
+ }
212
+ }
43
213
  }
44
- catch (error) {
45
- var sysName = fn.name && fn.name.length > 0 ? fn.name : "<anonymous>";
46
- var msg = error.message !== undefined && typeof 'string' ? error.message : JSON.stringify(error);
47
- var e = new Error("[phase=".concat(phase, " system=").concat(sysName, "] ").concat(msg));
48
- e.cause = error;
49
- throw e;
214
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
215
+ finally {
216
+ try {
217
+ if (list_1_1 && !list_1_1.done && (_b = list_1.return)) _b.call(list_1);
218
+ }
219
+ finally { if (e_2) throw e_2.error; }
50
220
  }
51
221
  }
222
+ if (this.boundaryMode !== "manual") {
223
+ // apply deferred commands between phases
224
+ if (world.cmd().hasPending()) {
225
+ world.flush();
226
+ }
227
+ // deliver events emitted in this phase to the next phase
228
+ world.swapEvents();
229
+ }
230
+ worldInstance._profAddPhase(phase, performance.now() - phaseStart);
231
+ }
232
+ }
233
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
234
+ finally {
235
+ try {
236
+ if (phases_1_1 && !phases_1_1.done && (_a = phases_1.return)) _a.call(phases_1);
237
+ }
238
+ finally { if (e_1) throw e_1.error; }
239
+ }
240
+ }
241
+ finally {
242
+ worldInstance._profEndFrame(frameStart);
243
+ worldInstance.updateOverlay(worldInstance.stats(), worldInstance.statsHistory());
244
+ }
245
+ };
246
+ /**
247
+ * Record a phase ordering constraint `before -> after`.
248
+ *
249
+ * @param before Phase that must execute first.
250
+ * @param after Phase that must execute later.
251
+ *
252
+ * @throws If `before === after`.
253
+ * @internal
254
+ */
255
+ Schedule.prototype._addPhaseConstraint = function (before, after) {
256
+ if (before === after) {
257
+ throw new Error("Invalid phase constraint: \"".concat(before, "\" cannot be before/after itself."));
258
+ }
259
+ var outs = this.phaseEdges.get(before);
260
+ if (!outs) {
261
+ outs = new Set();
262
+ this.phaseEdges.set(before, outs);
263
+ }
264
+ outs.add(after);
265
+ };
266
+ /**
267
+ * Compute a phase order from the currently registered constraints.
268
+ *
269
+ * Uses a stable topological sort:
270
+ * - phases explicitly registered via `add()` keep insertion order when unconstrained
271
+ * - remaining ties fall back to lexicographic order
272
+ *
273
+ * @param world World instance to get phases from.
274
+ * @returns A valid phase order that satisfies all constraints.
275
+ * @throws If constraints contain a cycle.
276
+ * @internal
277
+ */
278
+ Schedule.prototype._computePhaseOrder = function (world) {
279
+ var e_3, _a, e_4, _b, e_5, _c, e_6, _d, e_7, _e, e_8, _f, e_9, _g, e_10, _h, e_11, _j;
280
+ var _k, _l;
281
+ var phases = world._scheduleSystems;
282
+ // Collect all known phases (defined or referenced by constraints)
283
+ var all = new Set();
284
+ try {
285
+ for (var _m = __values(phases.keys()), _o = _m.next(); !_o.done; _o = _m.next()) {
286
+ var k = _o.value;
287
+ all.add(k);
288
+ }
289
+ }
290
+ catch (e_3_1) { e_3 = { error: e_3_1 }; }
291
+ finally {
292
+ try {
293
+ if (_o && !_o.done && (_a = _m.return)) _a.call(_m);
294
+ }
295
+ finally { if (e_3) throw e_3.error; }
296
+ }
297
+ try {
298
+ for (var _p = __values(this.phaseEdges), _q = _p.next(); !_q.done; _q = _p.next()) {
299
+ var _r = __read(_q.value, 2), a = _r[0], bs = _r[1];
300
+ all.add(a);
301
+ try {
302
+ for (var bs_1 = (e_5 = void 0, __values(bs)), bs_1_1 = bs_1.next(); !bs_1_1.done; bs_1_1 = bs_1.next()) {
303
+ var b = bs_1_1.value;
304
+ all.add(b);
305
+ }
52
306
  }
53
- catch (e_2_1) { e_2 = { error: e_2_1 }; }
307
+ catch (e_5_1) { e_5 = { error: e_5_1 }; }
54
308
  finally {
55
309
  try {
56
- if (list_1_1 && !list_1_1.done && (_b = list_1.return)) _b.call(list_1);
310
+ if (bs_1_1 && !bs_1_1.done && (_c = bs_1.return)) _c.call(bs_1);
57
311
  }
58
- finally { if (e_2) throw e_2.error; }
312
+ finally { if (e_5) throw e_5.error; }
59
313
  }
60
- // apply deferred commands between phases
61
- if (world.cmd().hasPending()) {
62
- world.flush();
314
+ }
315
+ }
316
+ catch (e_4_1) { e_4 = { error: e_4_1 }; }
317
+ finally {
318
+ try {
319
+ if (_q && !_q.done && (_b = _p.return)) _b.call(_p);
320
+ }
321
+ finally { if (e_4) throw e_4.error; }
322
+ }
323
+ // Stable tie-breaker: insertion order of phase registration (then lexicographic fallback)
324
+ var stableRank = new Map();
325
+ var r = 0;
326
+ try {
327
+ for (var _s = __values(phases.keys()), _t = _s.next(); !_t.done; _t = _s.next()) {
328
+ var k = _t.value;
329
+ stableRank.set(k, r++);
330
+ }
331
+ }
332
+ catch (e_6_1) { e_6 = { error: e_6_1 }; }
333
+ finally {
334
+ try {
335
+ if (_t && !_t.done && (_d = _s.return)) _d.call(_s);
336
+ }
337
+ finally { if (e_6) throw e_6.error; }
338
+ }
339
+ var rank = function (p) { var _a; return (_a = stableRank.get(p)) !== null && _a !== void 0 ? _a : Number.MAX_SAFE_INTEGER; };
340
+ // Build indegree + adjacency
341
+ var indeg = new Map();
342
+ var adj = new Map();
343
+ try {
344
+ for (var all_1 = __values(all), all_1_1 = all_1.next(); !all_1_1.done; all_1_1 = all_1.next()) {
345
+ var p = all_1_1.value;
346
+ indeg.set(p, 0);
347
+ adj.set(p, new Set());
348
+ }
349
+ }
350
+ catch (e_7_1) { e_7 = { error: e_7_1 }; }
351
+ finally {
352
+ try {
353
+ if (all_1_1 && !all_1_1.done && (_e = all_1.return)) _e.call(all_1);
354
+ }
355
+ finally { if (e_7) throw e_7.error; }
356
+ }
357
+ try {
358
+ for (var _u = __values(this.phaseEdges), _v = _u.next(); !_v.done; _v = _u.next()) {
359
+ var _w = __read(_v.value, 2), a = _w[0], bs = _w[1];
360
+ var _out = adj.get(a);
361
+ try {
362
+ for (var bs_2 = (e_9 = void 0, __values(bs)), bs_2_1 = bs_2.next(); !bs_2_1.done; bs_2_1 = bs_2.next()) {
363
+ var b = bs_2_1.value;
364
+ if (!_out.has(b)) {
365
+ _out.add(b);
366
+ indeg.set(b, ((_k = indeg.get(b)) !== null && _k !== void 0 ? _k : 0) + 1);
367
+ }
368
+ }
369
+ }
370
+ catch (e_9_1) { e_9 = { error: e_9_1 }; }
371
+ finally {
372
+ try {
373
+ if (bs_2_1 && !bs_2_1.done && (_g = bs_2.return)) _g.call(bs_2);
374
+ }
375
+ finally { if (e_9) throw e_9.error; }
63
376
  }
64
- // deliver events emitted in this phase to the next phase
65
- world.swapEvents();
66
377
  }
67
378
  }
68
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
379
+ catch (e_8_1) { e_8 = { error: e_8_1 }; }
380
+ finally {
381
+ try {
382
+ if (_v && !_v.done && (_f = _u.return)) _f.call(_u);
383
+ }
384
+ finally { if (e_8) throw e_8.error; }
385
+ }
386
+ // Kahn's algorithm with stable ordering
387
+ var ready = [];
388
+ try {
389
+ for (var indeg_1 = __values(indeg), indeg_1_1 = indeg_1.next(); !indeg_1_1.done; indeg_1_1 = indeg_1.next()) {
390
+ var _x = __read(indeg_1_1.value, 2), p = _x[0], d = _x[1];
391
+ if (d === 0)
392
+ ready.push(p);
393
+ }
394
+ }
395
+ catch (e_10_1) { e_10 = { error: e_10_1 }; }
69
396
  finally {
70
397
  try {
71
- if (phaseOrder_1_1 && !phaseOrder_1_1.done && (_a = phaseOrder_1.return)) _a.call(phaseOrder_1);
398
+ if (indeg_1_1 && !indeg_1_1.done && (_h = indeg_1.return)) _h.call(indeg_1);
72
399
  }
73
- finally { if (e_1) throw e_1.error; }
400
+ finally { if (e_10) throw e_10.error; }
401
+ }
402
+ ready.sort(function (x, y) { return rank(x) - rank(y) || x.localeCompare(y); });
403
+ var out = [];
404
+ while (ready.length > 0) {
405
+ var p = ready.shift();
406
+ out.push(p);
407
+ try {
408
+ for (var _y = (e_11 = void 0, __values(adj.get(p))), _z = _y.next(); !_z.done; _z = _y.next()) {
409
+ var q = _z.value;
410
+ var nd = ((_l = indeg.get(q)) !== null && _l !== void 0 ? _l : 0) - 1;
411
+ indeg.set(q, nd);
412
+ if (nd === 0) {
413
+ ready.push(q);
414
+ ready.sort(function (x, y) { return rank(x) - rank(y) || x.localeCompare(y); });
415
+ }
416
+ }
417
+ }
418
+ catch (e_11_1) { e_11 = { error: e_11_1 }; }
419
+ finally {
420
+ try {
421
+ if (_z && !_z.done && (_j = _y.return)) _j.call(_y);
422
+ }
423
+ finally { if (e_11) throw e_11.error; }
424
+ }
425
+ }
426
+ if (out.length !== all.size) {
427
+ // Find a cycle-friendly message
428
+ var stuck = __spreadArray([], __read(all), false).filter(function (p) { var _a; return ((_a = indeg.get(p)) !== null && _a !== void 0 ? _a : 0) > 0; }).sort();
429
+ throw new Error("Schedule phase constraints contain a cycle (cannot compute order). " +
430
+ "Phases involved: ".concat(stuck.join(", ")));
74
431
  }
432
+ return out;
75
433
  };
76
434
  return Schedule;
77
435
  }());
@@ -10,9 +10,13 @@ export type EntityMeta = {
10
10
  arch: number;
11
11
  row: number;
12
12
  };
13
- export type Column<T = unknown> = T[];
13
+ export type Column<T = any> = T[];
14
14
  export type Signature = ReadonlyArray<TypeId>;
15
- export type ComponentCtor<T> = new (...args: any[]) => T;
15
+ /**
16
+ * Component/resource token used as an identity key.
17
+ * Supports class constructors and plain function tokens.
18
+ */
19
+ export type ComponentCtor<T> = (new (...args: any[]) => T) | ((...args: any[]) => T);
16
20
  export type ComponentCtorBundleItem<T = any> = readonly [ComponentCtor<T>, T];
17
21
  /**
18
22
  * Internal numeric id for a component "type".
@@ -20,6 +24,39 @@ export type ComponentCtorBundleItem<T = any> = readonly [ComponentCtor<T>, T];
20
24
  */
21
25
  export type TypeId = number;
22
26
  export type SystemFn = (world: WorldApi, dt: number) => void;
27
+ export type SnapshotCodec<T, D = unknown> = Readonly<{
28
+ /**
29
+ * Stable key written into the snapshot payload.
30
+ * Keep this string stable across versions for backward compatibility.
31
+ */
32
+ key: string;
33
+ serialize(value: T): D;
34
+ deserialize(data: D): T;
35
+ }>;
36
+ export type WorldSnapshotComponent = Readonly<{
37
+ type: string;
38
+ data: unknown;
39
+ }>;
40
+ export type WorldSnapshotEntity = Readonly<{
41
+ id: EntityId;
42
+ gen: number;
43
+ components: ReadonlyArray<WorldSnapshotComponent>;
44
+ }>;
45
+ export type WorldSnapshotResource = Readonly<{
46
+ type: string;
47
+ data: unknown;
48
+ }>;
49
+ export type WorldSnapshotAllocator = Readonly<{
50
+ nextId: EntityId;
51
+ free: ReadonlyArray<EntityId>;
52
+ generations: ReadonlyArray<readonly [EntityId, number]>;
53
+ }>;
54
+ export type WorldSnapshot = Readonly<{
55
+ format: "archetype-ecs/world-snapshot@1";
56
+ allocator: WorldSnapshotAllocator;
57
+ entities: ReadonlyArray<WorldSnapshotEntity>;
58
+ resources: ReadonlyArray<WorldSnapshotResource>;
59
+ }>;
23
60
  /**
24
61
  * Commands API exposed to systems.
25
62
  * > Hides internal stuff like `drain()`
@@ -74,13 +111,99 @@ export type QueryRow6<A, B, C, D, E, F> = {
74
111
  c5: E;
75
112
  c6: F;
76
113
  };
114
+ export type QueryTable1<A> = {
115
+ entities: Entity[];
116
+ c1: Column<A>;
117
+ };
118
+ export type QueryTable2<A, B> = {
119
+ entities: Entity[];
120
+ c1: Column<A>;
121
+ c2: Column<B>;
122
+ };
123
+ export type QueryTable3<A, B, C> = {
124
+ entities: Entity[];
125
+ c1: Column<A>;
126
+ c2: Column<B>;
127
+ c3: Column<C>;
128
+ };
129
+ export type QueryTable4<A, B, C, D> = {
130
+ entities: Entity[];
131
+ c1: Column<A>;
132
+ c2: Column<B>;
133
+ c3: Column<C>;
134
+ c4: Column<D>;
135
+ };
136
+ export type QueryTable5<A, B, C, D, E> = {
137
+ entities: Entity[];
138
+ c1: Column<A>;
139
+ c2: Column<B>;
140
+ c3: Column<C>;
141
+ c4: Column<D>;
142
+ c5: Column<E>;
143
+ };
144
+ export type QueryTable6<A, B, C, D, E, F> = {
145
+ entities: Entity[];
146
+ c1: Column<A>;
147
+ c2: Column<B>;
148
+ c3: Column<C>;
149
+ c4: Column<D>;
150
+ c5: Column<E>;
151
+ c6: Column<F>;
152
+ };
153
+ export type WorldStats = Readonly<{
154
+ aliveEntities: number;
155
+ archetypes: number;
156
+ rows: number;
157
+ systems: number;
158
+ resources: number;
159
+ eventChannels: number;
160
+ pendingCommands: boolean;
161
+ frame: number;
162
+ dt: number;
163
+ frameMs: number;
164
+ phaseMs: Readonly<Record<string, number>>;
165
+ systemMs: Readonly<Record<string, number>>;
166
+ }>;
167
+ export type WorldStatsHistory = Readonly<{
168
+ /** Max frames kept in history (ring-buffer window). */
169
+ capacity: number;
170
+ /** How many samples are currently stored (<= capacity). */
171
+ size: number;
172
+ /** Rolling history for overall timing. */
173
+ dt: ReadonlyArray<number>;
174
+ frameMs: ReadonlyArray<number>;
175
+ /**
176
+ * Rolling history per phase/system.
177
+ * Arrays align with dt/frameMs indices (same `size`).
178
+ */
179
+ phaseMs: Readonly<Record<string, ReadonlyArray<number>>>;
180
+ systemMs: Readonly<Record<string, ReadonlyArray<number>>>;
181
+ }>;
77
182
  /**
78
183
  * Public World API visible from system functions.
79
184
  * Structural typing keeps typings fast and avoids generic plumbing across the whole library.
80
185
  */
81
186
  export interface WorldApi {
187
+ stats(): WorldStats;
188
+ statsHistory(): WorldStatsHistory;
189
+ /** @internal Phase -> systems mapping for Schedule */
190
+ readonly _scheduleSystems: Map<string, SystemFn[]>;
191
+ /** @internal Returns the number of systems registered via addSystem() */
192
+ _getSystemCount(): number;
193
+ /** Enables/disables profiling (system/phase timing). */
194
+ setProfilingEnabled(enabled: boolean): void;
195
+ /** Set how many frames of the profiling history to keep (default: 120). */
196
+ setProfilingHistorySize(frames: number): void;
82
197
  cmd(): CommandsApi;
198
+ addSystem(fn: SystemFn): this;
199
+ update(dt: number): void;
83
200
  flush(): void;
201
+ registerComponentSnapshot<T, D = unknown>(key: ComponentCtor<T>, codec: SnapshotCodec<T, D>): this;
202
+ unregisterComponentSnapshot<T>(key: ComponentCtor<T>): boolean;
203
+ registerResourceSnapshot<T, D = unknown>(key: ComponentCtor<T>, codec: SnapshotCodec<T, D>): this;
204
+ unregisterResourceSnapshot<T>(key: ComponentCtor<T>): boolean;
205
+ snapshot(): WorldSnapshot;
206
+ restore(snapshot: WorldSnapshot): void;
84
207
  /**
85
208
  * Insert or replace a resource (singleton) stored on the World, keyed by ctor.
86
209
  * This is NOT a structural change (safe during iteration).
@@ -131,7 +254,7 @@ export interface WorldApi {
131
254
  */
132
255
  swapEvents(): void;
133
256
  spawn(): Entity;
134
- spawnMany(...items: ComponentCtorBundleItem[]): void;
257
+ spawnMany(...items: ComponentCtorBundleItem[]): Entity;
135
258
  despawn(e: Entity): void;
136
259
  despawnMany(entities: Entity[]): void;
137
260
  isAlive(e: Entity): boolean;
@@ -149,4 +272,18 @@ export interface WorldApi {
149
272
  query<A, B, C, D, E>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>): Iterable<QueryRow5<A, B, C, D, E>>;
150
273
  query<A, B, C, D, E, F>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, c6: ComponentCtor<F>): Iterable<QueryRow6<A, B, C, D, E, F>>;
151
274
  query(...ctors: ComponentCtor<any>[]): Iterable<any>;
275
+ queryTables<A>(c1: ComponentCtor<A>): Iterable<QueryTable1<A>>;
276
+ queryTables<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B>): Iterable<QueryTable2<A, B>>;
277
+ queryTables<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>): Iterable<QueryTable3<A, B, C>>;
278
+ queryTables<A, B, C, D>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>): Iterable<QueryTable4<A, B, C, D>>;
279
+ queryTables<A, B, C, D, E>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>): Iterable<QueryTable5<A, B, C, D, E>>;
280
+ queryTables<A, B, C, D, E, F>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, c6: ComponentCtor<F>): Iterable<QueryTable6<A, B, C, D, E, F>>;
281
+ queryTables(...ctors: ComponentCtor<any>[]): Iterable<any>;
282
+ queryEach<A>(c1: ComponentCtor<A>, fn: (e: Entity, c1: A) => void): void;
283
+ queryEach<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, fn: (e: Entity, c1: A, c2: B) => void): void;
284
+ queryEach<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, fn: (e: Entity, c1: A, c2: B, c3: C) => void): void;
285
+ queryEach<A, B, C, D>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, fn: (e: Entity, c1: A, c2: B, c3: C, c4: D) => void): void;
286
+ queryEach<A, B, C, D, E>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, fn: (e: Entity, c1: A, c2: B, c3: C, c4: D, c5: E) => void): void;
287
+ queryEach<A, B, C, D, E, F>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, c6: ComponentCtor<F>, fn: (e: Entity, c1: A, c2: B, c3: C, c4: D, c5: E, c6: F) => void): void;
288
+ queryEach(...args: any[]): void;
152
289
  }