archetype-ecs-lib 0.4.1 → 0.5.0

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/lib/ecs/World.js CHANGED
@@ -37,11 +37,28 @@ var __values = (this && this.__values) || function(o) {
37
37
  };
38
38
  throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
39
39
  };
40
+ var __read = (this && this.__read) || function (o, n) {
41
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
42
+ if (!m) return o;
43
+ var i = m.call(o), r, ar = [], e;
44
+ try {
45
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
46
+ }
47
+ catch (error) { e = { error: error }; }
48
+ finally {
49
+ try {
50
+ if (r && !r.done && (m = i["return"])) m.call(i);
51
+ }
52
+ finally { if (e) throw e.error; }
53
+ }
54
+ return ar;
55
+ };
40
56
  Object.defineProperty(exports, "__esModule", { value: true });
41
57
  exports.World = void 0;
42
58
  var Archetype_1 = require("./Archetype");
43
59
  var Commands_1 = require("./Commands");
44
60
  var EntityManager_1 = require("./EntityManager");
61
+ var Events_1 = require("./Events");
45
62
  var Signature_1 = require("./Signature");
46
63
  var TypeRegistry_1 = require("./TypeRegistry");
47
64
  var World = /** @class */ (function () {
@@ -51,14 +68,18 @@ var World = /** @class */ (function () {
51
68
  this.archByKey = new Map();
52
69
  this.systems = [];
53
70
  this.commands = new Commands_1.Commands();
54
- this._isIterating = false;
71
+ this._iterateDepth = 0;
72
+ this.resources = new Map();
73
+ this.eventChannels = new Map();
55
74
  // Archetype 0: empty signature
56
75
  var archetype0 = new Archetype_1.Archetype(0, []);
57
76
  this.archetypes[0] = archetype0;
58
77
  this.archByKey.set("", archetype0);
59
78
  }
60
79
  /** Queue structural changes to apply safely after systems run. */
61
- World.prototype.cmd = function () { return this.commands; };
80
+ World.prototype.cmd = function () {
81
+ return this.commands;
82
+ };
62
83
  World.prototype.addSystem = function (fn) {
63
84
  this.systems.push(fn);
64
85
  return this;
@@ -70,7 +91,7 @@ var World = /** @class */ (function () {
70
91
  */
71
92
  World.prototype.update = function (dt) {
72
93
  var e_1, _a;
73
- this._isIterating = true;
94
+ this._iterateDepth++;
74
95
  try {
75
96
  try {
76
97
  for (var _b = __values(this.systems), _c = _b.next(); !_c.done; _c = _b.next()) {
@@ -87,27 +108,120 @@ var World = /** @class */ (function () {
87
108
  }
88
109
  }
89
110
  finally {
90
- this._isIterating = false;
111
+ this._iterateDepth--;
91
112
  this.flush();
92
113
  }
93
114
  };
94
115
  World.prototype.flush = function () {
95
116
  var e_2, _a;
96
- var ops = this.commands.drain();
117
+ this._ensureNotIterating("flush");
118
+ // Apply commands until queue is empty. This allows spawn(init) to enqueue add/remove
119
+ // operations that will be applied during the same flush.
120
+ while (true) {
121
+ var ops = this.commands.drain();
122
+ if (ops.length === 0)
123
+ break;
124
+ try {
125
+ for (var ops_1 = (e_2 = void 0, __values(ops)), ops_1_1 = ops_1.next(); !ops_1_1.done; ops_1_1 = ops_1.next()) {
126
+ var op = ops_1_1.value;
127
+ this._apply(op);
128
+ }
129
+ }
130
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
131
+ finally {
132
+ try {
133
+ if (ops_1_1 && !ops_1_1.done && (_a = ops_1.return)) _a.call(ops_1);
134
+ }
135
+ finally { if (e_2) throw e_2.error; }
136
+ }
137
+ }
138
+ };
139
+ //#region ---------- Resources (singletons) ----------
140
+ World.prototype.setResource = function (key, value) {
141
+ this.resources.set(key, value);
142
+ };
143
+ World.prototype.getResource = function (key) {
144
+ if (!this.resources.has(key))
145
+ return undefined;
146
+ return this.resources.get(key);
147
+ };
148
+ World.prototype.requireResource = function (key) {
149
+ if (!this.resources.has(key)) {
150
+ var name = this._formatCtor(key);
151
+ throw new Error("Missing resource ".concat(name, ". ") +
152
+ "Insert it via world.setResource(".concat(name, ", value) or world.initResource(").concat(name, ", () => value)."));
153
+ }
154
+ return this.resources.get(key);
155
+ };
156
+ World.prototype.hasResource = function (key) {
157
+ return this.resources.has(key);
158
+ };
159
+ World.prototype.removeResource = function (key) {
160
+ return this.resources.delete(key);
161
+ };
162
+ World.prototype.initResource = function (key, factory) {
163
+ if (this.resources.has(key))
164
+ return this.resources.get(key);
165
+ var value = factory();
166
+ this.resources.set(key, value);
167
+ return value;
168
+ };
169
+ //#endregion
170
+ //#region ---------- Events (phase-scoped) ----------
171
+ World.prototype.emit = function (key, ev) {
172
+ this._events(key).emit(ev);
173
+ };
174
+ World.prototype.events = function (key) {
175
+ return this._events(key);
176
+ };
177
+ World.prototype.drainEvents = function (key, fn) {
178
+ var ch = this.eventChannels.get(key);
179
+ if (!ch)
180
+ return;
181
+ ch.drain(fn);
182
+ };
183
+ World.prototype.clearEvents = function (key) {
184
+ var e_3, _a;
185
+ if (key) {
186
+ var ch = this.eventChannels.get(key);
187
+ if (!ch)
188
+ return;
189
+ ch.clear();
190
+ return;
191
+ }
97
192
  try {
98
- for (var ops_1 = __values(ops), ops_1_1 = ops_1.next(); !ops_1_1.done; ops_1_1 = ops_1.next()) {
99
- var op = ops_1_1.value;
100
- this._apply(op);
193
+ // clear all readable buffers
194
+ for (var _b = __values(this.eventChannels.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
195
+ var ch = _c.value;
196
+ ch.clear();
101
197
  }
102
198
  }
103
- catch (e_2_1) { e_2 = { error: e_2_1 }; }
199
+ catch (e_3_1) { e_3 = { error: e_3_1 }; }
104
200
  finally {
105
201
  try {
106
- if (ops_1_1 && !ops_1_1.done && (_a = ops_1.return)) _a.call(ops_1);
202
+ if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
107
203
  }
108
- finally { if (e_2) throw e_2.error; }
204
+ finally { if (e_3) throw e_3.error; }
109
205
  }
110
206
  };
207
+ /** @internal Called by Schedule at phase boundaries */
208
+ World.prototype.swapEvents = function () {
209
+ var e_4, _a;
210
+ try {
211
+ for (var _b = __values(this.eventChannels.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
212
+ var ch = _c.value;
213
+ ch.swapBuffers();
214
+ }
215
+ }
216
+ catch (e_4_1) { e_4 = { error: e_4_1 }; }
217
+ finally {
218
+ try {
219
+ if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
220
+ }
221
+ finally { if (e_4) throw e_4.error; }
222
+ }
223
+ };
224
+ //#endregion
111
225
  //#region ---------- Entity lifecycle ----------
112
226
  World.prototype.spawn = function () {
113
227
  var entity = this.entities.create();
@@ -119,16 +233,53 @@ var World = /** @class */ (function () {
119
233
  entityMeta.row = row;
120
234
  return entity;
121
235
  };
236
+ World.prototype.spawnMany = function () {
237
+ var e_5, _a;
238
+ var items = [];
239
+ for (var _i = 0; _i < arguments.length; _i++) {
240
+ items[_i] = arguments[_i];
241
+ }
242
+ var e = this.spawn();
243
+ try {
244
+ for (var items_1 = __values(items), items_1_1 = items_1.next(); !items_1_1.done; items_1_1 = items_1.next()) {
245
+ var _b = __read(items_1_1.value, 2), ctor = _b[0], value = _b[1];
246
+ this.add(e, ctor, value);
247
+ }
248
+ }
249
+ catch (e_5_1) { e_5 = { error: e_5_1 }; }
250
+ finally {
251
+ try {
252
+ if (items_1_1 && !items_1_1.done && (_a = items_1.return)) _a.call(items_1);
253
+ }
254
+ finally { if (e_5) throw e_5.error; }
255
+ }
256
+ return e;
257
+ };
122
258
  World.prototype.isAlive = function (e) {
123
259
  return this.entities.isAlive(e);
124
260
  };
125
261
  World.prototype.despawn = function (e) {
126
- if (!this.entities.isAlive(e))
127
- return;
262
+ this._assertAlive(e, 'despawn');
128
263
  this._ensureNotIterating("despawn");
129
264
  this._removeFromArchetype(e);
130
265
  this.entities.kill(e);
131
266
  };
267
+ World.prototype.despawnMany = function (entities) {
268
+ var e_6, _a;
269
+ try {
270
+ for (var entities_1 = __values(entities), entities_1_1 = entities_1.next(); !entities_1_1.done; entities_1_1 = entities_1.next()) {
271
+ var e = entities_1_1.value;
272
+ this.despawn(e);
273
+ }
274
+ }
275
+ catch (e_6_1) { e_6 = { error: e_6_1 }; }
276
+ finally {
277
+ try {
278
+ if (entities_1_1 && !entities_1_1.done && (_a = entities_1.return)) _a.call(entities_1);
279
+ }
280
+ finally { if (e_6) throw e_6.error; }
281
+ }
282
+ };
132
283
  //#endregion
133
284
  //#region ---------- Components ----------
134
285
  World.prototype.has = function (e, ctor) {
@@ -149,18 +300,15 @@ var World = /** @class */ (function () {
149
300
  return a.column(tid)[meta.row];
150
301
  };
151
302
  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;
303
+ var meta = this._assertAlive(e, "set(".concat(this._formatCtor(ctor), ")"));
155
304
  var tid = (0, TypeRegistry_1.typeId)(ctor);
156
305
  var a = this.archetypes[meta.arch];
157
306
  if (!a.has(tid))
158
- throw new Error("set() requires component to exist; use add()");
307
+ throw new Error("set(".concat(this._formatCtor(ctor), ") requires component to exist on ").concat(this._formatEntity(e), "; use add()"));
159
308
  a.column(tid)[meta.row] = value;
160
309
  };
161
310
  World.prototype.add = function (e, ctor, value) {
162
- if (!this.entities.isAlive(e))
163
- return;
311
+ this._assertAlive(e, "add(".concat(this._formatCtor(ctor), ")"));
164
312
  this._ensureNotIterating("add");
165
313
  var tid = (0, TypeRegistry_1.typeId)(ctor);
166
314
  var srcMeta = this.entities.meta[e.id];
@@ -178,9 +326,28 @@ var World = /** @class */ (function () {
178
326
  return src.column(t)[srcMeta.row];
179
327
  });
180
328
  };
329
+ World.prototype.addMany = function (e) {
330
+ var e_7, _a;
331
+ var items = [];
332
+ for (var _i = 1; _i < arguments.length; _i++) {
333
+ items[_i - 1] = arguments[_i];
334
+ }
335
+ try {
336
+ for (var items_2 = __values(items), items_2_1 = items_2.next(); !items_2_1.done; items_2_1 = items_2.next()) {
337
+ var _b = __read(items_2_1.value, 2), ctor = _b[0], value = _b[1];
338
+ this.add(e, ctor, value);
339
+ }
340
+ }
341
+ catch (e_7_1) { e_7 = { error: e_7_1 }; }
342
+ finally {
343
+ try {
344
+ if (items_2_1 && !items_2_1.done && (_a = items_2.return)) _a.call(items_2);
345
+ }
346
+ finally { if (e_7) throw e_7.error; }
347
+ }
348
+ };
181
349
  World.prototype.remove = function (e, ctor) {
182
- if (!this.entities.isAlive(e))
183
- return;
350
+ this._assertAlive(e, "remove(".concat(this._formatCtor(ctor), ")"));
184
351
  this._ensureNotIterating("remove");
185
352
  var tid = (0, TypeRegistry_1.typeId)(ctor);
186
353
  var srcMeta = this.entities.meta[e.id];
@@ -194,90 +361,104 @@ var World = /** @class */ (function () {
194
361
  return src.column(t)[srcMeta.row];
195
362
  });
196
363
  };
197
- //#endregion
198
- //region ---------- Queries ----------
199
- /**
200
- * Query all entities having all required component types.
201
- * Iterates archetypes (tables) and yields SoA columns for cache-friendly loops.
202
- */
364
+ World.prototype.removeMany = function (e) {
365
+ var e_8, _a;
366
+ var ctors = [];
367
+ for (var _i = 1; _i < arguments.length; _i++) {
368
+ ctors[_i - 1] = arguments[_i];
369
+ }
370
+ try {
371
+ for (var ctors_1 = __values(ctors), ctors_1_1 = ctors_1.next(); !ctors_1_1.done; ctors_1_1 = ctors_1.next()) {
372
+ var ctor = ctors_1_1.value;
373
+ this.remove(e, ctor);
374
+ }
375
+ }
376
+ catch (e_8_1) { e_8 = { error: e_8_1 }; }
377
+ finally {
378
+ try {
379
+ if (ctors_1_1 && !ctors_1_1.done && (_a = ctors_1.return)) _a.call(ctors_1);
380
+ }
381
+ finally { if (e_8) throw e_8.error; }
382
+ }
383
+ };
203
384
  World.prototype.query = function () {
204
385
  var ctors = [];
205
386
  for (var _i = 0; _i < arguments.length; _i++) {
206
387
  ctors[_i] = arguments[_i];
207
388
  }
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
389
+ // Preserve caller order for (c1,c2,c3,...) mapping.
390
+ var requested = new Array(ctors.length);
391
+ for (var i = 0; i < ctors.length; i++)
392
+ requested[i] = (0, TypeRegistry_1.typeId)(ctors[i]);
393
+ // Same ids, but sorted + deduped for signatureHasAll().
394
+ var needSorted = requested.slice();
395
+ needSorted.sort(function (a, b) { return a - b; });
396
+ var w = 0;
397
+ for (var i = 0; i < needSorted.length; i++) {
398
+ var v = needSorted[i];
399
+ if (i === 0 || v !== needSorted[w - 1])
400
+ needSorted[w++] = v;
401
+ }
402
+ needSorted.length = w;
210
403
  function gen(world) {
211
- var _loop_1, _a, _b, a, e_3_1;
212
- var e_3, _c;
404
+ var _a, _b, a, cols, i, row, e, out, i, e_9_1;
405
+ var e_9, _c;
213
406
  return __generator(this, function (_d) {
214
407
  switch (_d.label) {
215
408
  case 0:
216
- world._isIterating = true;
409
+ world._iterateDepth++;
217
410
  _d.label = 1;
218
411
  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
- };
412
+ _d.trys.push([1, , 12, 13]);
250
413
  _d.label = 2;
251
414
  case 2:
252
- _d.trys.push([2, 7, 8, 9]);
415
+ _d.trys.push([2, 9, 10, 11]);
253
416
  _a = __values(world.archetypes), _b = _a.next();
254
417
  _d.label = 3;
255
418
  case 3:
256
- if (!!_b.done) return [3 /*break*/, 6];
419
+ if (!!_b.done) return [3 /*break*/, 8];
257
420
  a = _b.value;
258
- return [5 /*yield**/, _loop_1(a)];
421
+ if (!a)
422
+ return [3 /*break*/, 7];
423
+ if (!(0, Signature_1.signatureHasAll)(a.sig, needSorted))
424
+ return [3 /*break*/, 7];
425
+ cols = new Array(requested.length);
426
+ for (i = 0; i < requested.length; i++)
427
+ cols[i] = a.column(requested[i]);
428
+ row = 0;
429
+ _d.label = 4;
259
430
  case 4:
260
- _d.sent();
261
- _d.label = 5;
431
+ if (!(row < a.entities.length)) return [3 /*break*/, 7];
432
+ e = a.entities[row];
433
+ out = { e: e };
434
+ for (i = 0; i < cols.length; i++)
435
+ out["c".concat(i + 1)] = cols[i][row];
436
+ return [4 /*yield*/, out];
262
437
  case 5:
438
+ _d.sent();
439
+ _d.label = 6;
440
+ case 6:
441
+ row++;
442
+ return [3 /*break*/, 4];
443
+ case 7:
263
444
  _b = _a.next();
264
445
  return [3 /*break*/, 3];
265
- case 6: return [3 /*break*/, 9];
266
- case 7:
267
- e_3_1 = _d.sent();
268
- e_3 = { error: e_3_1 };
269
- return [3 /*break*/, 9];
270
- case 8:
446
+ case 8: return [3 /*break*/, 11];
447
+ case 9:
448
+ e_9_1 = _d.sent();
449
+ e_9 = { error: e_9_1 };
450
+ return [3 /*break*/, 11];
451
+ case 10:
271
452
  try {
272
453
  if (_b && !_b.done && (_c = _a.return)) _c.call(_a);
273
454
  }
274
- finally { if (e_3) throw e_3.error; }
455
+ finally { if (e_9) throw e_9.error; }
275
456
  return [7 /*endfinally*/];
276
- case 9: return [3 /*break*/, 11];
277
- case 10:
278
- world._isIterating = false;
457
+ case 11: return [3 /*break*/, 13];
458
+ case 12:
459
+ world._iterateDepth--;
279
460
  return [7 /*endfinally*/];
280
- case 11: return [2 /*return*/];
461
+ case 13: return [2 /*return*/];
281
462
  }
282
463
  });
283
464
  }
@@ -286,7 +467,7 @@ var World = /** @class */ (function () {
286
467
  //#endregion
287
468
  //#region ---------- Internals ----------
288
469
  World.prototype._ensureNotIterating = function (op) {
289
- if (this._isIterating) {
470
+ if (this._iterateDepth > 0) {
290
471
  throw new Error("Cannot do structural change (".concat(op, ") while iterating. Use world.cmd() and flush at end of frame."));
291
472
  }
292
473
  };
@@ -316,7 +497,7 @@ var World = /** @class */ (function () {
316
497
  * Then swap-remove from src.
317
498
  */
318
499
  World.prototype._moveEntity = function (e, src, srcRow, dst, pick) {
319
- var e_4, _a;
500
+ var e_10, _a;
320
501
  // add row in dst
321
502
  var dstRow = dst.addRow(e);
322
503
  try {
@@ -325,12 +506,12 @@ var World = /** @class */ (function () {
325
506
  dst.column(t).push(pick(t));
326
507
  }
327
508
  }
328
- catch (e_4_1) { e_4 = { error: e_4_1 }; }
509
+ catch (e_10_1) { e_10 = { error: e_10_1 }; }
329
510
  finally {
330
511
  try {
331
512
  if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
332
513
  }
333
- finally { if (e_4) throw e_4.error; }
514
+ finally { if (e_10) throw e_10.error; }
334
515
  }
335
516
  // update meta to dst before removing from src (in case src==dst should never happen here)
336
517
  var m = this.entities.meta[e.id];
@@ -360,6 +541,32 @@ var World = /** @class */ (function () {
360
541
  return this.remove(op.e, op.ctor);
361
542
  }
362
543
  };
544
+ World.prototype._formatEntity = function (e) {
545
+ return "e#".concat(e.id, "@").concat(e.gen);
546
+ };
547
+ World.prototype._formatCtor = function (ctor) {
548
+ var n = ctor === null || ctor === void 0 ? void 0 : ctor.name;
549
+ return n && n.length > 0 ? n : "<token>";
550
+ };
551
+ /**
552
+ * Throws an error if the entity is not alive
553
+ */
554
+ World.prototype._assertAlive = function (e, op) {
555
+ var _a, _b;
556
+ var meta = this.entities.meta[e.id];
557
+ if (!this.entities.isAlive(e)) {
558
+ 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", ")"));
559
+ }
560
+ return meta;
561
+ };
562
+ World.prototype._events = function (key) {
563
+ var ch = this.eventChannels.get(key);
564
+ if (!ch) {
565
+ ch = new Events_1.EventChannel();
566
+ this.eventChannels.set(key, ch);
567
+ }
568
+ return ch;
569
+ };
363
570
  return World;
364
571
  }());
365
572
  exports.World = World;
package/lib/index.d.ts CHANGED
@@ -3,3 +3,4 @@ export * from "./ecs/TypeRegistry";
3
3
  export * from "./ecs/Commands";
4
4
  export * from "./ecs/World";
5
5
  export * from "./ecs/Schedule";
6
+ export * from "./ecs/Events";
package/lib/index.js CHANGED
@@ -19,3 +19,4 @@ __exportStar(require("./ecs/TypeRegistry"), exports);
19
19
  __exportStar(require("./ecs/Commands"), exports);
20
20
  __exportStar(require("./ecs/World"), exports);
21
21
  __exportStar(require("./ecs/Schedule"), exports);
22
+ __exportStar(require("./ecs/Events"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archetype-ecs-lib",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "An Archetype Entity Component System (AECS)",
5
5
  "main": "./lib/index.js",
6
6
  "scripts": {
@@ -9,10 +9,22 @@
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
+ },
16
+ "homepage": "https://piratejl.github.io/archetype-ecs-lib",
12
17
  "keywords": [
13
- "archetype",
18
+ "game",
19
+ "gamedev",
20
+ "threejs",
21
+ "game-development",
14
22
  "ecs",
15
- "lib"
23
+ "entity-component-system",
24
+ "gameloop",
25
+ "ecs-framework",
26
+ "ecs-archetype",
27
+ "typescript"
16
28
  ],
17
29
  "author": {
18
30
  "name": "PirateJL"