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.
- package/README.md +1 -1
- package/lib/ecs/Archetype.js +1 -1
- package/lib/ecs/EntityManager.d.ts +3 -1
- package/lib/ecs/EntityManager.js +89 -0
- package/lib/ecs/Schedule.d.ts +128 -5
- package/lib/ecs/Schedule.js +390 -32
- package/lib/ecs/Types.d.ts +140 -3
- package/lib/ecs/World.d.ts +71 -6
- package/lib/ecs/World.js +562 -82
- package/lib/ecs/WorldSnapshotStore.d.ts +30 -0
- package/lib/ecs/WorldSnapshotStore.js +339 -0
- package/lib/ecs/stats/StatsOverlay.d.ts +72 -0
- package/lib/ecs/stats/StatsOverlay.js +548 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/package.json +8 -4
package/lib/ecs/Schedule.js
CHANGED
|
@@ -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
|
|
17
|
-
*
|
|
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.
|
|
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
|
-
|
|
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
|
|
123
|
+
var phases = world._scheduleSystems;
|
|
124
|
+
var list = (_a = phases.get(phase)) !== null && _a !== void 0 ? _a : [];
|
|
26
125
|
list.push(fn);
|
|
27
|
-
|
|
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
|
-
|
|
34
|
-
var
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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 (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
throw
|
|
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 (
|
|
307
|
+
catch (e_5_1) { e_5 = { error: e_5_1 }; }
|
|
54
308
|
finally {
|
|
55
309
|
try {
|
|
56
|
-
if (
|
|
310
|
+
if (bs_1_1 && !bs_1_1.done && (_c = bs_1.return)) _c.call(bs_1);
|
|
57
311
|
}
|
|
58
|
-
finally { if (
|
|
312
|
+
finally { if (e_5) throw e_5.error; }
|
|
59
313
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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 (
|
|
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 (
|
|
398
|
+
if (indeg_1_1 && !indeg_1_1.done && (_h = indeg_1.return)) _h.call(indeg_1);
|
|
72
399
|
}
|
|
73
|
-
finally { if (
|
|
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
|
}());
|
package/lib/ecs/Types.d.ts
CHANGED
|
@@ -10,9 +10,13 @@ export type EntityMeta = {
|
|
|
10
10
|
arch: number;
|
|
11
11
|
row: number;
|
|
12
12
|
};
|
|
13
|
-
export type Column<T =
|
|
13
|
+
export type Column<T = any> = T[];
|
|
14
14
|
export type Signature = ReadonlyArray<TypeId>;
|
|
15
|
-
|
|
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[]):
|
|
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
|
}
|