archetype-ecs-lib 0.4.1 → 0.4.2

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 CHANGED
@@ -1,3 +1,5 @@
1
+ ![Coverage](https://raw.githubusercontent.com/PirateJL/archetype-ecs-lib/refs/heads/gh-pages/coverage.svg)
2
+
1
3
  # Archetype ECS Lib
2
4
 
3
5
  A tiny **archetype-based ECS** (Entity Component System) for TypeScript.
@@ -16,10 +18,6 @@ Exports are defined in `index.ts`:
16
18
 
17
19
  ```bash
18
20
  npm i archetype-ecs-lib
19
- # or
20
- pnpm add archetype-ecs-lib
21
- # or
22
- yarn add archetype-ecs-lib
23
21
  ````
24
22
 
25
23
  ---
@@ -16,7 +16,7 @@ export type Command = {
16
16
  ctor: ComponentCtor<any>;
17
17
  };
18
18
  export declare class Commands {
19
- private readonly q;
19
+ private q;
20
20
  spawn(init?: (e: Entity) => void): void;
21
21
  despawn(e: Entity): void;
22
22
  add<T>(e: Entity, ctor: ComponentCtor<T>, value: T): void;
@@ -18,8 +18,8 @@ var Commands = /** @class */ (function () {
18
18
  this.q.push({ k: "remove", e: e, ctor: ctor });
19
19
  };
20
20
  Commands.prototype.drain = function () {
21
- var out = this.q.slice();
22
- this.q.length = 0;
21
+ var out = this.q;
22
+ this.q = [];
23
23
  return out;
24
24
  };
25
25
  return Commands;
@@ -38,7 +38,16 @@ var Schedule = /** @class */ (function () {
38
38
  try {
39
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
40
  var fn = list_1_1.value;
41
- fn(world, dt);
41
+ try {
42
+ fn(world, dt);
43
+ }
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;
50
+ }
42
51
  }
43
52
  }
44
53
  catch (e_2_1) { e_2 = { error: e_2_1 }; }
@@ -6,7 +6,7 @@ export declare class World implements WorldI {
6
6
  private readonly archByKey;
7
7
  private readonly systems;
8
8
  private readonly commands;
9
- private _isIterating;
9
+ private _iterateDepth;
10
10
  constructor();
11
11
  /** Queue structural changes to apply safely after systems run. */
12
12
  cmd(): Commands;
@@ -40,4 +40,10 @@ export declare class World implements WorldI {
40
40
  */
41
41
  private _moveEntity;
42
42
  private _apply;
43
+ private _formatEntity;
44
+ private _formatCtor;
45
+ /**
46
+ * Throws an error if the entity is not alive
47
+ */
48
+ private _assertAlive;
43
49
  }
package/lib/ecs/World.js CHANGED
@@ -51,14 +51,16 @@ var World = /** @class */ (function () {
51
51
  this.archByKey = new Map();
52
52
  this.systems = [];
53
53
  this.commands = new Commands_1.Commands();
54
- this._isIterating = false;
54
+ this._iterateDepth = 0;
55
55
  // Archetype 0: empty signature
56
56
  var archetype0 = new Archetype_1.Archetype(0, []);
57
57
  this.archetypes[0] = archetype0;
58
58
  this.archByKey.set("", archetype0);
59
59
  }
60
60
  /** Queue structural changes to apply safely after systems run. */
61
- World.prototype.cmd = function () { return this.commands; };
61
+ World.prototype.cmd = function () {
62
+ return this.commands;
63
+ };
62
64
  World.prototype.addSystem = function (fn) {
63
65
  this.systems.push(fn);
64
66
  return this;
@@ -70,7 +72,7 @@ var World = /** @class */ (function () {
70
72
  */
71
73
  World.prototype.update = function (dt) {
72
74
  var e_1, _a;
73
- this._isIterating = true;
75
+ this._iterateDepth++;
74
76
  try {
75
77
  try {
76
78
  for (var _b = __values(this.systems), _c = _b.next(); !_c.done; _c = _b.next()) {
@@ -87,12 +89,13 @@ var World = /** @class */ (function () {
87
89
  }
88
90
  }
89
91
  finally {
90
- this._isIterating = false;
92
+ this._iterateDepth--;
91
93
  this.flush();
92
94
  }
93
95
  };
94
96
  World.prototype.flush = function () {
95
97
  var e_2, _a;
98
+ this._ensureNotIterating("flush");
96
99
  var ops = this.commands.drain();
97
100
  try {
98
101
  for (var ops_1 = __values(ops), ops_1_1 = ops_1.next(); !ops_1_1.done; ops_1_1 = ops_1.next()) {
@@ -123,8 +126,7 @@ var World = /** @class */ (function () {
123
126
  return this.entities.isAlive(e);
124
127
  };
125
128
  World.prototype.despawn = function (e) {
126
- if (!this.entities.isAlive(e))
127
- return;
129
+ this._assertAlive(e, 'despawn');
128
130
  this._ensureNotIterating("despawn");
129
131
  this._removeFromArchetype(e);
130
132
  this.entities.kill(e);
@@ -149,18 +151,15 @@ var World = /** @class */ (function () {
149
151
  return a.column(tid)[meta.row];
150
152
  };
151
153
  World.prototype.set = function (e, ctor, value) {
152
- var meta = this.entities.meta[e.id];
153
- if (!meta || !meta.alive || meta.gen !== e.gen)
154
- return;
154
+ var meta = this._assertAlive(e, "set(".concat(this._formatCtor(ctor), ")"));
155
155
  var tid = (0, TypeRegistry_1.typeId)(ctor);
156
156
  var a = this.archetypes[meta.arch];
157
157
  if (!a.has(tid))
158
- throw new Error("set() requires component to exist; use add()");
158
+ throw new Error("set(".concat(this._formatCtor(ctor), ") requires component to exist on ").concat(this._formatEntity(e), "; use add()"));
159
159
  a.column(tid)[meta.row] = value;
160
160
  };
161
161
  World.prototype.add = function (e, ctor, value) {
162
- if (!this.entities.isAlive(e))
163
- return;
162
+ this._assertAlive(e, "add(".concat(this._formatCtor(ctor), ")"));
164
163
  this._ensureNotIterating("add");
165
164
  var tid = (0, TypeRegistry_1.typeId)(ctor);
166
165
  var srcMeta = this.entities.meta[e.id];
@@ -179,8 +178,7 @@ var World = /** @class */ (function () {
179
178
  });
180
179
  };
181
180
  World.prototype.remove = function (e, ctor) {
182
- if (!this.entities.isAlive(e))
183
- return;
181
+ this._assertAlive(e, "remove(".concat(this._formatCtor(ctor), ")"));
184
182
  this._ensureNotIterating("remove");
185
183
  var tid = (0, TypeRegistry_1.typeId)(ctor);
186
184
  var srcMeta = this.entities.meta[e.id];
@@ -205,79 +203,79 @@ var World = /** @class */ (function () {
205
203
  for (var _i = 0; _i < arguments.length; _i++) {
206
204
  ctors[_i] = arguments[_i];
207
205
  }
208
- var requested = ctors.map(TypeRegistry_1.typeId); // preserve caller order
209
- var needSorted = Array.from(new Set(requested)).sort(function (a, b) { return a - b; }); // for signatureHasAll
206
+ // Preserve caller order for (c1,c2,c3,...) mapping.
207
+ var requested = new Array(ctors.length);
208
+ for (var i = 0; i < ctors.length; i++)
209
+ requested[i] = (0, TypeRegistry_1.typeId)(ctors[i]);
210
+ // Same ids, but sorted + deduped for signatureHasAll().
211
+ var needSorted = requested.slice();
212
+ needSorted.sort(function (a, b) { return a - b; });
213
+ var w = 0;
214
+ for (var i = 0; i < needSorted.length; i++) {
215
+ var v = needSorted[i];
216
+ if (i === 0 || v !== needSorted[w - 1])
217
+ needSorted[w++] = v;
218
+ }
219
+ needSorted.length = w;
210
220
  function gen(world) {
211
- var _loop_1, _a, _b, a, e_3_1;
221
+ var _a, _b, a, cols, i, row, e, out, i, e_3_1;
212
222
  var e_3, _c;
213
223
  return __generator(this, function (_d) {
214
224
  switch (_d.label) {
215
225
  case 0:
216
- world._isIterating = true;
226
+ world._iterateDepth++;
217
227
  _d.label = 1;
218
228
  case 1:
219
- _d.trys.push([1, , 10, 11]);
220
- _loop_1 = function (a) {
221
- var cols, row, e, out, i;
222
- return __generator(this, function (_e) {
223
- switch (_e.label) {
224
- case 0:
225
- if (!a)
226
- return [2 /*return*/, "continue"];
227
- // if (!signatureHasAll(a.sig, need)) continue;
228
- if (!(0, Signature_1.signatureHasAll)(a.sig, needSorted))
229
- return [2 /*return*/, "continue"];
230
- cols = requested.map(function (t) { return a.column(t); });
231
- row = 0;
232
- _e.label = 1;
233
- case 1:
234
- if (!(row < a.entities.length)) return [3 /*break*/, 4];
235
- e = a.entities[row];
236
- out = { e: e };
237
- for (i = 0; i < cols.length; i++)
238
- out["c".concat(i + 1)] = cols[i][row];
239
- return [4 /*yield*/, out];
240
- case 2:
241
- _e.sent();
242
- _e.label = 3;
243
- case 3:
244
- row++;
245
- return [3 /*break*/, 1];
246
- case 4: return [2 /*return*/];
247
- }
248
- });
249
- };
229
+ _d.trys.push([1, , 12, 13]);
250
230
  _d.label = 2;
251
231
  case 2:
252
- _d.trys.push([2, 7, 8, 9]);
232
+ _d.trys.push([2, 9, 10, 11]);
253
233
  _a = __values(world.archetypes), _b = _a.next();
254
234
  _d.label = 3;
255
235
  case 3:
256
- if (!!_b.done) return [3 /*break*/, 6];
236
+ if (!!_b.done) return [3 /*break*/, 8];
257
237
  a = _b.value;
258
- return [5 /*yield**/, _loop_1(a)];
238
+ if (!a)
239
+ return [3 /*break*/, 7];
240
+ if (!(0, Signature_1.signatureHasAll)(a.sig, needSorted))
241
+ return [3 /*break*/, 7];
242
+ cols = new Array(requested.length);
243
+ for (i = 0; i < requested.length; i++)
244
+ cols[i] = a.column(requested[i]);
245
+ row = 0;
246
+ _d.label = 4;
259
247
  case 4:
260
- _d.sent();
261
- _d.label = 5;
248
+ if (!(row < a.entities.length)) return [3 /*break*/, 7];
249
+ e = a.entities[row];
250
+ out = { e: e };
251
+ for (i = 0; i < cols.length; i++)
252
+ out["c".concat(i + 1)] = cols[i][row];
253
+ return [4 /*yield*/, out];
262
254
  case 5:
255
+ _d.sent();
256
+ _d.label = 6;
257
+ case 6:
258
+ row++;
259
+ return [3 /*break*/, 4];
260
+ case 7:
263
261
  _b = _a.next();
264
262
  return [3 /*break*/, 3];
265
- case 6: return [3 /*break*/, 9];
266
- case 7:
263
+ case 8: return [3 /*break*/, 11];
264
+ case 9:
267
265
  e_3_1 = _d.sent();
268
266
  e_3 = { error: e_3_1 };
269
- return [3 /*break*/, 9];
270
- case 8:
267
+ return [3 /*break*/, 11];
268
+ case 10:
271
269
  try {
272
270
  if (_b && !_b.done && (_c = _a.return)) _c.call(_a);
273
271
  }
274
272
  finally { if (e_3) throw e_3.error; }
275
273
  return [7 /*endfinally*/];
276
- case 9: return [3 /*break*/, 11];
277
- case 10:
278
- world._isIterating = false;
274
+ case 11: return [3 /*break*/, 13];
275
+ case 12:
276
+ world._iterateDepth--;
279
277
  return [7 /*endfinally*/];
280
- case 11: return [2 /*return*/];
278
+ case 13: return [2 /*return*/];
281
279
  }
282
280
  });
283
281
  }
@@ -286,7 +284,7 @@ var World = /** @class */ (function () {
286
284
  //#endregion
287
285
  //#region ---------- Internals ----------
288
286
  World.prototype._ensureNotIterating = function (op) {
289
- if (this._isIterating) {
287
+ if (this._iterateDepth > 0) {
290
288
  throw new Error("Cannot do structural change (".concat(op, ") while iterating. Use world.cmd() and flush at end of frame."));
291
289
  }
292
290
  };
@@ -360,6 +358,24 @@ var World = /** @class */ (function () {
360
358
  return this.remove(op.e, op.ctor);
361
359
  }
362
360
  };
361
+ World.prototype._formatEntity = function (e) {
362
+ return "e#".concat(e.id, "@").concat(e.gen);
363
+ };
364
+ World.prototype._formatCtor = function (ctor) {
365
+ var n = ctor === null || ctor === void 0 ? void 0 : ctor.name;
366
+ return n && n.length > 0 ? n : "<token>";
367
+ };
368
+ /**
369
+ * Throws an error if the entity is not alive
370
+ */
371
+ World.prototype._assertAlive = function (e, op) {
372
+ var _a, _b;
373
+ var meta = this.entities.meta[e.id];
374
+ if (!this.entities.isAlive(e)) {
375
+ throw new Error("".concat(op, " on stale entity ").concat(this._formatEntity(e), " (alive=").concat((_a = meta === null || meta === void 0 ? void 0 : meta.alive) !== null && _a !== void 0 ? _a : false, ", gen=").concat((_b = meta === null || meta === void 0 ? void 0 : meta.gen) !== null && _b !== void 0 ? _b : "n/a", ")"));
376
+ }
377
+ return meta;
378
+ };
363
379
  return World;
364
380
  }());
365
381
  exports.World = World;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archetype-ecs-lib",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "An Archetype Entity Component System (AECS)",
5
5
  "main": "./lib/index.js",
6
6
  "scripts": {
@@ -9,10 +9,21 @@
9
9
  "lint": "tslint -c tslint.json src/**/*.ts",
10
10
  "reload-packages": "rm -Rf node_modules && npm cache clean --force && npm ci"
11
11
  },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/PirateJL/archetype-ecs-lib.git"
15
+ },
12
16
  "keywords": [
13
- "archetype",
17
+ "game",
18
+ "gamedev",
19
+ "threejs",
20
+ "game-development",
14
21
  "ecs",
15
- "lib"
22
+ "entity-component-system",
23
+ "gameloop",
24
+ "ecs-framework",
25
+ "ecs-archetype",
26
+ "typescript"
16
27
  ],
17
28
  "author": {
18
29
  "name": "PirateJL"