archetype-ecs-lib 0.6.0 → 1.0.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/README.md CHANGED
@@ -14,7 +14,8 @@ A tiny **archetype-based ECS** (Entity Component System) for TypeScript.
14
14
  Exports are defined in `index.ts`:
15
15
  - `Types`, `TypeRegistry`, `Commands`, `World`, `Schedule`
16
16
 
17
- > :exclamation: The full documentation is at [https://piratejl.github.io/archetype-ecs-lib/](https://piratejl.github.io/archetype-ecs-lib/)
17
+ > :exclamation: The full documentation is at [https://piratejl.github.io/archetype-ecs-lib/](https://piratejl.github.io/archetype-ecs-lib/)
18
+ > Can view the [Changelog here](https://piratejl.github.io/archetype-ecs-lib/changelog/)
18
19
 
19
20
  ---
20
21
 
@@ -55,7 +56,7 @@ world.addSystem((w) => {
55
56
  world.update(1 / 60);
56
57
  ```
57
58
 
58
- > Note: `SystemFn` is typed as `(world: WorldApi, dt) => void`..
59
+ > Note: `SystemFn` is typed as `(world: WorldApi, dt) => void`.
59
60
 
60
61
  ---
61
62
 
@@ -4,6 +4,13 @@ export declare class Archetype {
4
4
  readonly sig: Signature;
5
5
  private readonly cols;
6
6
  readonly entities: Entity[];
7
+ /**
8
+ * Archetype graph edges: cache destination archetype for single-component add/remove transitions.
9
+ * Key: TypeId being added/removed. Value: destination Archetype after the transition.
10
+ * Populated lazily in World.add() / World.remove().
11
+ */
12
+ readonly addEdges: Map<number, Archetype>;
13
+ readonly removeEdges: Map<number, Archetype>;
7
14
  constructor(id: number, sig: Signature);
8
15
  /**
9
16
  * Adds a new row. The caller must push per-column values in the same order.
@@ -35,6 +35,13 @@ var Archetype = /** @class */ (function () {
35
35
  this.cols = new Map();
36
36
  // Entity handles stored per row
37
37
  this.entities = [];
38
+ /**
39
+ * Archetype graph edges: cache destination archetype for single-component add/remove transitions.
40
+ * Key: TypeId being added/removed. Value: destination Archetype after the transition.
41
+ * Populated lazily in World.add() / World.remove().
42
+ */
43
+ this.addEdges = new Map();
44
+ this.removeEdges = new Map();
38
45
  this.id = id;
39
46
  this.sig = sig;
40
47
  try {
@@ -10,21 +10,29 @@ export type Command = {
10
10
  e: Entity;
11
11
  ctor: ComponentCtor<any>;
12
12
  value: any;
13
+ } | {
14
+ k: "addMany";
15
+ e: Entity;
16
+ items: ComponentCtorBundleItem[];
13
17
  } | {
14
18
  k: "remove";
15
19
  e: Entity;
16
20
  ctor: ComponentCtor<any>;
21
+ } | {
22
+ k: "removeMany";
23
+ e: Entity;
24
+ ctors: ComponentCtor<any>[];
17
25
  };
18
26
  export declare class Commands implements CommandsApi {
19
27
  private q;
20
28
  spawn(init?: (e: Entity) => void): void;
21
- spawnBundle(...items: ComponentCtorBundleItem[]): void;
29
+ spawnWith(...items: ComponentCtorBundleItem[]): void;
22
30
  despawn(e: Entity): void;
23
- despawnBundle(entities: Entity[]): void;
31
+ despawnMany(entities: Entity[]): void;
24
32
  add<T>(e: Entity, ctor: ComponentCtor<T>, value: T): void;
25
- addBundle(e: Entity, ...items: ComponentCtorBundleItem[]): void;
33
+ addMany(e: Entity, ...items: ComponentCtorBundleItem[]): void;
26
34
  remove<T>(e: Entity, ctor: ComponentCtor<T>): void;
27
- removeBundle(e: Entity, ...ctors: ComponentCtor<any>[]): void;
35
+ removeMany(e: Entity, ...ctors: ComponentCtor<any>[]): void;
28
36
  hasPending(): boolean;
29
37
  drain(): Command[];
30
38
  }
@@ -35,7 +35,7 @@ var Commands = /** @class */ (function () {
35
35
  Commands.prototype.spawn = function (init) {
36
36
  this.q.push({ k: "spawn", init: init });
37
37
  };
38
- Commands.prototype.spawnBundle = function () {
38
+ Commands.prototype.spawnWith = function () {
39
39
  var _this = this;
40
40
  var items = [];
41
41
  for (var _i = 0; _i < arguments.length; _i++) {
@@ -62,7 +62,7 @@ var Commands = /** @class */ (function () {
62
62
  Commands.prototype.despawn = function (e) {
63
63
  this.q.push({ k: "despawn", e: e });
64
64
  };
65
- Commands.prototype.despawnBundle = function (entities) {
65
+ Commands.prototype.despawnMany = function (entities) {
66
66
  var e_2, _a;
67
67
  try {
68
68
  for (var entities_1 = __values(entities), entities_1_1 = entities_1.next(); !entities_1_1.done; entities_1_1 = entities_1.next()) {
@@ -81,48 +81,26 @@ var Commands = /** @class */ (function () {
81
81
  Commands.prototype.add = function (e, ctor, value) {
82
82
  this.q.push({ k: "add", e: e, ctor: ctor, value: value });
83
83
  };
84
- Commands.prototype.addBundle = function (e) {
85
- var e_3, _a;
84
+ Commands.prototype.addMany = function (e) {
86
85
  var items = [];
87
86
  for (var _i = 1; _i < arguments.length; _i++) {
88
87
  items[_i - 1] = arguments[_i];
89
88
  }
90
- try {
91
- for (var items_2 = __values(items), items_2_1 = items_2.next(); !items_2_1.done; items_2_1 = items_2.next()) {
92
- var _b = __read(items_2_1.value, 2), ctor = _b[0], value = _b[1];
93
- this.add(e, ctor, value);
94
- }
95
- }
96
- catch (e_3_1) { e_3 = { error: e_3_1 }; }
97
- finally {
98
- try {
99
- if (items_2_1 && !items_2_1.done && (_a = items_2.return)) _a.call(items_2);
100
- }
101
- finally { if (e_3) throw e_3.error; }
102
- }
89
+ if (items.length === 0)
90
+ return;
91
+ this.q.push({ k: "addMany", e: e, items: items });
103
92
  };
104
93
  Commands.prototype.remove = function (e, ctor) {
105
94
  this.q.push({ k: "remove", e: e, ctor: ctor });
106
95
  };
107
- Commands.prototype.removeBundle = function (e) {
108
- var e_4, _a;
96
+ Commands.prototype.removeMany = function (e) {
109
97
  var ctors = [];
110
98
  for (var _i = 1; _i < arguments.length; _i++) {
111
99
  ctors[_i - 1] = arguments[_i];
112
100
  }
113
- try {
114
- for (var ctors_1 = __values(ctors), ctors_1_1 = ctors_1.next(); !ctors_1_1.done; ctors_1_1 = ctors_1.next()) {
115
- var ctor = ctors_1_1.value;
116
- this.remove(e, ctor);
117
- }
118
- }
119
- catch (e_4_1) { e_4 = { error: e_4_1 }; }
120
- finally {
121
- try {
122
- if (ctors_1_1 && !ctors_1_1.done && (_a = ctors_1.return)) _a.call(ctors_1);
123
- }
124
- finally { if (e_4) throw e_4.error; }
125
- }
101
+ if (ctors.length === 0)
102
+ return;
103
+ this.q.push({ k: "removeMany", e: e, ctors: ctors });
126
104
  };
127
105
  Commands.prototype.hasPending = function () {
128
106
  return this.q.length > 0;
@@ -124,7 +124,7 @@ export declare class Schedule {
124
124
  * - phases explicitly registered via `add()` keep insertion order when unconstrained
125
125
  * - remaining ties fall back to lexicographic order
126
126
  *
127
- * @param world World instance to get phases from.
127
+ * @param phases Phase -> systems mapping from the World.
128
128
  * @returns A valid phase order that satisfies all constraints.
129
129
  * @throws If constraints contain a cycle.
130
130
  * @internal
@@ -177,67 +177,72 @@ var Schedule = /** @class */ (function () {
177
177
  if (worldInstance._getSystemCount() > 0) {
178
178
  worldInstance._warnAboutLifecycleConflict("Schedule.run");
179
179
  }
180
- var phases = (_c = phaseOrder !== null && phaseOrder !== void 0 ? phaseOrder : this.phaseOrder) !== null && _c !== void 0 ? _c : this._computePhaseOrder(world);
180
+ var scheduleSystems = worldInstance._scheduleSystems;
181
+ var phases = (_c = phaseOrder !== null && phaseOrder !== void 0 ? phaseOrder : this.phaseOrder) !== null && _c !== void 0 ? _c : this._computePhaseOrder(scheduleSystems);
181
182
  if (!phases || phases.length === 0) {
182
183
  throw new Error('Schedule.run requires a phase order (pass it as an argument or call schedule.setOrder([...]))');
183
184
  }
184
185
  var frameStart = worldInstance._profBeginFrame(dt);
185
186
  try {
186
- for (var phases_1 = __values(phases), phases_1_1 = phases_1.next(); !phases_1_1.done; phases_1_1 = phases_1.next()) {
187
- var phase = phases_1_1.value;
188
- var phaseStart = performance.now();
189
- var list = world._scheduleSystems.get(phase);
190
- // Run systems only if they exist for this phase
191
- if (list) {
192
- try {
193
- 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()) {
194
- var fn = list_1_1.value;
195
- var sysStart = performance.now();
196
- try {
197
- fn(world, dt);
198
- }
199
- catch (error) {
200
- var sysName = fn.name && fn.name.length > 0 ? fn.name : "<anonymous>";
201
- var msg = error.message !== undefined && typeof error.message === 'string' ?
202
- error.message : JSON.stringify(error);
203
- var e = new Error("[phase=".concat(phase, " system=").concat(sysName, "] ").concat(msg));
204
- e.cause = error;
205
- throw e;
187
+ try {
188
+ for (var phases_1 = __values(phases), phases_1_1 = phases_1.next(); !phases_1_1.done; phases_1_1 = phases_1.next()) {
189
+ var phase = phases_1_1.value;
190
+ var phaseStart = performance.now();
191
+ var list = scheduleSystems.get(phase);
192
+ // Run systems only if they exist for this phase
193
+ if (list) {
194
+ try {
195
+ 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()) {
196
+ var fn = list_1_1.value;
197
+ var sysStart = performance.now();
198
+ try {
199
+ fn(world, dt);
200
+ }
201
+ catch (error) {
202
+ var sysName = fn.name && fn.name.length > 0 ? fn.name : "<anonymous>";
203
+ var msg = error.message !== undefined && typeof error.message === 'string' ?
204
+ error.message : JSON.stringify(error);
205
+ var e = new Error("[phase=".concat(phase, " system=").concat(sysName, "] ").concat(msg));
206
+ e.cause = error;
207
+ throw e;
208
+ }
209
+ finally {
210
+ var sysName = fn.name && fn.name.length > 0 ? fn.name : "<anonymous>";
211
+ worldInstance._profAddSystem("".concat(phase, ":").concat(sysName), performance.now() - sysStart);
212
+ }
206
213
  }
207
- finally {
208
- var sysName = fn.name && fn.name.length > 0 ? fn.name : "<anonymous>";
209
- worldInstance._profAddSystem("".concat(phase, ":").concat(sysName), performance.now() - sysStart);
214
+ }
215
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
216
+ finally {
217
+ try {
218
+ if (list_1_1 && !list_1_1.done && (_b = list_1.return)) _b.call(list_1);
210
219
  }
220
+ finally { if (e_2) throw e_2.error; }
211
221
  }
212
222
  }
213
- catch (e_2_1) { e_2 = { error: e_2_1 }; }
214
- finally {
215
- try {
216
- if (list_1_1 && !list_1_1.done && (_b = list_1.return)) _b.call(list_1);
223
+ if (this.boundaryMode !== "manual") {
224
+ // apply deferred commands between phases
225
+ if (world.cmd().hasPending()) {
226
+ world.flush();
217
227
  }
218
- finally { if (e_2) throw e_2.error; }
228
+ // deliver events emitted in this phase to the next phase
229
+ world.swapEvents();
219
230
  }
231
+ worldInstance._profAddPhase(phase, performance.now() - phaseStart);
220
232
  }
221
- if (this.boundaryMode !== "manual") {
222
- // apply deferred commands between phases
223
- if (world.cmd().hasPending()) {
224
- world.flush();
225
- }
226
- // deliver events emitted in this phase to the next phase
227
- world.swapEvents();
233
+ }
234
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
235
+ finally {
236
+ try {
237
+ if (phases_1_1 && !phases_1_1.done && (_a = phases_1.return)) _a.call(phases_1);
228
238
  }
229
- worldInstance._profAddPhase(phase, performance.now() - phaseStart);
239
+ finally { if (e_1) throw e_1.error; }
230
240
  }
231
241
  }
232
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
233
242
  finally {
234
- try {
235
- if (phases_1_1 && !phases_1_1.done && (_a = phases_1.return)) _a.call(phases_1);
236
- }
237
- finally { if (e_1) throw e_1.error; }
243
+ worldInstance._profEndFrame(frameStart);
244
+ worldInstance.updateOverlay(worldInstance.stats(), worldInstance.statsHistory());
238
245
  }
239
- worldInstance._profEndFrame(frameStart);
240
- worldInstance.updateOverlay(worldInstance.stats(), worldInstance.statsHistory());
241
246
  };
242
247
  /**
243
248
  * Record a phase ordering constraint `before -> after`.
@@ -266,15 +271,14 @@ var Schedule = /** @class */ (function () {
266
271
  * - phases explicitly registered via `add()` keep insertion order when unconstrained
267
272
  * - remaining ties fall back to lexicographic order
268
273
  *
269
- * @param world World instance to get phases from.
274
+ * @param phases Phase -> systems mapping from the World.
270
275
  * @returns A valid phase order that satisfies all constraints.
271
276
  * @throws If constraints contain a cycle.
272
277
  * @internal
273
278
  */
274
- Schedule.prototype._computePhaseOrder = function (world) {
279
+ Schedule.prototype._computePhaseOrder = function (phases) {
275
280
  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;
276
281
  var _k, _l;
277
- var phases = world._scheduleSystems;
278
282
  // Collect all known phases (defined or referenced by constraints)
279
283
  var all = new Set();
280
284
  try {
@@ -6,3 +6,7 @@ export declare function subtractSignature(sig: Signature, remove: TypeId): TypeI
6
6
  * True if `need` is a subset of `have`. Both must be sorted ascending.
7
7
  */
8
8
  export declare function signatureHasAll(have: Signature, need: Signature): boolean;
9
+ /**
10
+ * True if `have` contains at least one element from `any`. Both must be sorted ascending.
11
+ */
12
+ export declare function signatureHasAny(have: Signature, any: Signature): boolean;
@@ -4,6 +4,7 @@ exports.signatureKey = signatureKey;
4
4
  exports.mergeSignature = mergeSignature;
5
5
  exports.subtractSignature = subtractSignature;
6
6
  exports.signatureHasAll = signatureHasAll;
7
+ exports.signatureHasAny = signatureHasAny;
7
8
  function signatureKey(sig) {
8
9
  // canonical: sorted ascending, joined
9
10
  return sig.join(",");
@@ -48,3 +49,21 @@ function signatureHasAll(have, need) {
48
49
  }
49
50
  return j === need.length;
50
51
  }
52
+ /**
53
+ * True if `have` contains at least one element from `any`. Both must be sorted ascending.
54
+ */
55
+ function signatureHasAny(have, any) {
56
+ var i = 0;
57
+ var j = 0;
58
+ while (i < have.length && j < any.length) {
59
+ var a = have[i];
60
+ var b = any[j];
61
+ if (a === b)
62
+ return true;
63
+ if (a < b)
64
+ i++;
65
+ else
66
+ j++;
67
+ }
68
+ return false;
69
+ }
@@ -18,12 +18,47 @@ export type Signature = ReadonlyArray<TypeId>;
18
18
  */
19
19
  export type ComponentCtor<T> = (new (...args: any[]) => T) | ((...args: any[]) => T);
20
20
  export type ComponentCtorBundleItem<T = any> = readonly [ComponentCtor<T>, T];
21
+ /**
22
+ * A reusable, named group of component/value pairs.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const PhysicsBundle = bundle([Position, { x: 0, y: 0 }], [Velocity, { x: 0, y: 0 }]);
27
+ * world.spawnWith(...PhysicsBundle);
28
+ * world.cmd().spawnWith(...PhysicsBundle);
29
+ * ```
30
+ */
31
+ export type Bundle = readonly ComponentCtorBundleItem[];
32
+ /**
33
+ * Create a typed, reusable bundle (a list of component/value pairs).
34
+ * Bundles can be spread into `spawnWith`, `addMany`, or `cmd().spawnWith`.
35
+ */
36
+ export declare function bundle(...items: ComponentCtorBundleItem[]): Bundle;
21
37
  /**
22
38
  * Internal numeric id for a component "type".
23
39
  * (We keep it numeric so signatures can be sorted quickly.)
24
40
  */
25
41
  export type TypeId = number;
26
42
  export type SystemFn = (world: WorldApi, dt: number) => void;
43
+ /**
44
+ * Optional filter applied to any query.
45
+ *
46
+ * - `without`: exclude archetypes that have ANY of these component types.
47
+ * - `with`: require these component types to be present without returning their values.
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * // Entities with Position but NOT Dead
52
+ * world.query(Position, { without: [Dead] })
53
+ *
54
+ * // Entities with Position AND Active marker, but NOT Frozen
55
+ * world.query(Position, { with: [Active], without: [Frozen] })
56
+ * ```
57
+ */
58
+ export type QueryFilter = {
59
+ readonly without?: ReadonlyArray<ComponentCtor<any>>;
60
+ readonly with?: ReadonlyArray<ComponentCtor<any>>;
61
+ };
27
62
  export type SnapshotCodec<T, D = unknown> = Readonly<{
28
63
  /**
29
64
  * Stable key written into the snapshot payload.
@@ -63,13 +98,13 @@ export type WorldSnapshot = Readonly<{
63
98
  */
64
99
  export interface CommandsApi {
65
100
  spawn(init?: (e: Entity) => void): void;
66
- spawnBundle(...items: ComponentCtorBundleItem[]): void;
101
+ spawnWith(...items: ComponentCtorBundleItem[]): void;
67
102
  despawn(e: Entity): void;
68
- despawnBundle(entities: Entity[]): void;
103
+ despawnMany(entities: Entity[]): void;
69
104
  add<T>(e: Entity, ctor: ComponentCtor<T>, value: T): void;
70
- addBundle(e: Entity, ...items: ComponentCtorBundleItem[]): void;
105
+ addMany(e: Entity, ...items: ComponentCtorBundleItem[]): void;
71
106
  remove<T>(e: Entity, ctor: ComponentCtor<T>): void;
72
- removeBundle(e: Entity, ...ctors: ComponentCtor<any>[]): void;
107
+ removeMany(e: Entity, ...ctors: ComponentCtor<any>[]): void;
73
108
  hasPending(): boolean;
74
109
  }
75
110
  export type QueryRow1<A> = {
@@ -186,14 +221,15 @@ export type WorldStatsHistory = Readonly<{
186
221
  export interface WorldApi {
187
222
  stats(): WorldStats;
188
223
  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
224
  /** Enables/disables profiling (system/phase timing). */
194
225
  setProfilingEnabled(enabled: boolean): void;
195
226
  /** Set how many frames of the profiling history to keep (default: 120). */
196
227
  setProfilingHistorySize(frames: number): void;
228
+ /**
229
+ * Destroy this world, releasing all archetypes, entities, resources, and event channels.
230
+ * Any further use of the world after calling this will throw.
231
+ */
232
+ destroy(): void;
197
233
  cmd(): CommandsApi;
198
234
  addSystem(fn: SystemFn): this;
199
235
  update(dt: number): void;
@@ -254,7 +290,7 @@ export interface WorldApi {
254
290
  */
255
291
  swapEvents(): void;
256
292
  spawn(): Entity;
257
- spawnMany(...items: ComponentCtorBundleItem[]): Entity;
293
+ spawnWith(...items: ComponentCtorBundleItem[]): Entity;
258
294
  despawn(e: Entity): void;
259
295
  despawnMany(entities: Entity[]): void;
260
296
  isAlive(e: Entity): boolean;
@@ -265,25 +301,31 @@ export interface WorldApi {
265
301
  has<T>(e: Entity, ctor: ComponentCtor<T>): boolean;
266
302
  get<T>(e: Entity, ctor: ComponentCtor<T>): T | undefined;
267
303
  set<T>(e: Entity, ctor: ComponentCtor<T>, value: T): void;
268
- query<A>(c1: ComponentCtor<A>): Iterable<QueryRow1<A>>;
269
- query<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B>): Iterable<QueryRow2<A, B>>;
270
- query<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>): Iterable<QueryRow3<A, B, C>>;
271
- query<A, B, C, D>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>): Iterable<QueryRow4<A, B, C, D>>;
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>>;
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>>;
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>;
304
+ query<A>(c1: ComponentCtor<A>, filter?: QueryFilter): Iterable<QueryRow1<A>>;
305
+ query<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, filter?: QueryFilter): Iterable<QueryRow2<A, B>>;
306
+ query<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, filter?: QueryFilter): Iterable<QueryRow3<A, B, C>>;
307
+ query<A, B, C, D>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, filter?: QueryFilter): Iterable<QueryRow4<A, B, C, D>>;
308
+ query<A, B, C, D, E>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, filter?: QueryFilter): Iterable<QueryRow5<A, B, C, D, E>>;
309
+ 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>, filter?: QueryFilter): Iterable<QueryRow6<A, B, C, D, E, F>>;
310
+ query(...args: any[]): Iterable<any>;
311
+ queryTables<A>(c1: ComponentCtor<A>, filter?: QueryFilter): Iterable<QueryTable1<A>>;
312
+ queryTables<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, filter?: QueryFilter): Iterable<QueryTable2<A, B>>;
313
+ queryTables<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, filter?: QueryFilter): Iterable<QueryTable3<A, B, C>>;
314
+ queryTables<A, B, C, D>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, filter?: QueryFilter): Iterable<QueryTable4<A, B, C, D>>;
315
+ queryTables<A, B, C, D, E>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, filter?: QueryFilter): Iterable<QueryTable5<A, B, C, D, E>>;
316
+ 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>, filter?: QueryFilter): Iterable<QueryTable6<A, B, C, D, E, F>>;
317
+ queryTables(...args: any[]): Iterable<any>;
282
318
  queryEach<A>(c1: ComponentCtor<A>, fn: (e: Entity, c1: A) => void): void;
319
+ queryEach<A>(c1: ComponentCtor<A>, filter: QueryFilter, fn: (e: Entity, c1: A) => void): void;
283
320
  queryEach<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, fn: (e: Entity, c1: A, c2: B) => void): void;
321
+ queryEach<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, filter: QueryFilter, fn: (e: Entity, c1: A, c2: B) => void): void;
284
322
  queryEach<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, fn: (e: Entity, c1: A, c2: B, c3: C) => void): void;
323
+ queryEach<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, filter: QueryFilter, fn: (e: Entity, c1: A, c2: B, c3: C) => void): void;
285
324
  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;
325
+ queryEach<A, B, C, D>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, filter: QueryFilter, fn: (e: Entity, c1: A, c2: B, c3: C, c4: D) => void): void;
286
326
  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;
327
+ queryEach<A, B, C, D, E>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, filter: QueryFilter, fn: (e: Entity, c1: A, c2: B, c3: C, c4: D, c5: E) => void): void;
287
328
  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;
329
+ 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>, filter: QueryFilter, fn: (e: Entity, c1: A, c2: B, c3: C, c4: D, c5: E, c6: F) => void): void;
288
330
  queryEach(...args: any[]): void;
289
331
  }
package/lib/ecs/Types.js CHANGED
@@ -1,2 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.bundle = bundle;
4
+ /**
5
+ * Create a typed, reusable bundle (a list of component/value pairs).
6
+ * Bundles can be spread into `spawnWith`, `addMany`, or `cmd().spawnWith`.
7
+ */
8
+ function bundle() {
9
+ var items = [];
10
+ for (var _i = 0; _i < arguments.length; _i++) {
11
+ items[_i] = arguments[_i];
12
+ }
13
+ return items;
14
+ }
@@ -1,6 +1,6 @@
1
1
  import { Commands } from "./Commands";
2
2
  import { EventChannel } from "./Events";
3
- import type { ComponentCtor, ComponentCtorBundleItem, Entity, QueryRow1, QueryRow2, QueryRow3, QueryRow4, QueryRow5, QueryRow6, QueryTable1, QueryTable2, QueryTable3, QueryTable4, QueryTable5, QueryTable6, SnapshotCodec, SystemFn, WorldApi, WorldSnapshot, WorldStats, WorldStatsHistory } from "./Types";
3
+ import type { ComponentCtor, ComponentCtorBundleItem, Entity, QueryFilter, QueryRow1, QueryRow2, QueryRow3, QueryRow4, QueryRow5, QueryRow6, QueryTable1, QueryTable2, QueryTable3, QueryTable4, QueryTable5, QueryTable6, SnapshotCodec, SystemFn, WorldApi, WorldSnapshot, WorldStats, WorldStatsHistory } from "./Types";
4
4
  import { StatsOverlay, type StatsOverlayOptions } from "./stats/StatsOverlay";
5
5
  export declare class World extends StatsOverlay implements WorldApi {
6
6
  private readonly entities;
@@ -12,8 +12,15 @@ export declare class World extends StatsOverlay implements WorldApi {
12
12
  private readonly resources;
13
13
  private readonly eventChannels;
14
14
  private readonly snapshotStore;
15
+ /**
16
+ * Query result cache: maps a sorted query signature key → matching archetypes.
17
+ * `checked` tracks how many archetypes were scanned when the entry was last updated.
18
+ * Because archetypes are append-only, new ones are scanned incrementally on next use.
19
+ */
20
+ private readonly _queryCache;
15
21
  /** @internal Phase -> systems mapping for Schedule */
16
22
  readonly _scheduleSystems: Map<string, SystemFn[]>;
23
+ private _destroyed;
17
24
  constructor(options?: {
18
25
  statsOverlayOptions: StatsOverlayOptions;
19
26
  });
@@ -50,6 +57,7 @@ export declare class World extends StatsOverlay implements WorldApi {
50
57
  */
51
58
  update(dt: number): void;
52
59
  flush(): void;
60
+ destroy(): void;
53
61
  registerComponentSnapshot<T, D = unknown>(key: ComponentCtor<T>, codec: SnapshotCodec<T, D>): this;
54
62
  unregisterComponentSnapshot<T>(key: ComponentCtor<T>): boolean;
55
63
  registerResourceSnapshot<T, D = unknown>(key: ComponentCtor<T>, codec: SnapshotCodec<T, D>): this;
@@ -69,7 +77,7 @@ export declare class World extends StatsOverlay implements WorldApi {
69
77
  /** @internal Called by Schedule at phase boundaries */
70
78
  swapEvents(): void;
71
79
  spawn(): Entity;
72
- spawnMany(...items: ComponentCtorBundleItem[]): Entity;
80
+ spawnWith(...items: ComponentCtorBundleItem[]): Entity;
73
81
  isAlive(e: Entity): boolean;
74
82
  despawn(e: Entity): void;
75
83
  despawnMany(entities: Entity[]): void;
@@ -84,34 +92,49 @@ export declare class World extends StatsOverlay implements WorldApi {
84
92
  * Query all entities having all required component types.
85
93
  * Iterates archetypes (tables) and yields SoA columns for cache-friendly loops.
86
94
  */
87
- query<A>(c1: ComponentCtor<A>): Iterable<QueryRow1<A>>;
88
- query<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B>): Iterable<QueryRow2<A, B>>;
89
- query<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>): Iterable<QueryRow3<A, B, C>>;
90
- query<A, B, C, D>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>): Iterable<QueryRow4<A, B, C, D>>;
91
- 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>>;
92
- 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>>;
95
+ query<A>(c1: ComponentCtor<A>, filter?: QueryFilter): Iterable<QueryRow1<A>>;
96
+ query<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, filter?: QueryFilter): Iterable<QueryRow2<A, B>>;
97
+ query<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, filter?: QueryFilter): Iterable<QueryRow3<A, B, C>>;
98
+ query<A, B, C, D>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, filter?: QueryFilter): Iterable<QueryRow4<A, B, C, D>>;
99
+ query<A, B, C, D, E>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, filter?: QueryFilter): Iterable<QueryRow5<A, B, C, D, E>>;
100
+ 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>, filter?: QueryFilter): Iterable<QueryRow6<A, B, C, D, E, F>>;
93
101
  /**
94
102
  * Table query: yields one item per matching archetype (SoA columns + entity array).
95
103
  * This avoids allocating one object per entity row.
96
104
  */
97
- queryTables<A>(c1: ComponentCtor<A>): Iterable<QueryTable1<A>>;
98
- queryTables<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B>): Iterable<QueryTable2<A, B>>;
99
- queryTables<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>): Iterable<QueryTable3<A, B, C>>;
100
- queryTables<A, B, C, D>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>): Iterable<QueryTable4<A, B, C, D>>;
101
- 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>>;
102
- 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>>;
105
+ queryTables<A>(c1: ComponentCtor<A>, filter?: QueryFilter): Iterable<QueryTable1<A>>;
106
+ queryTables<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, filter?: QueryFilter): Iterable<QueryTable2<A, B>>;
107
+ queryTables<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, filter?: QueryFilter): Iterable<QueryTable3<A, B, C>>;
108
+ queryTables<A, B, C, D>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, filter?: QueryFilter): Iterable<QueryTable4<A, B, C, D>>;
109
+ queryTables<A, B, C, D, E>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, filter?: QueryFilter): Iterable<QueryTable5<A, B, C, D, E>>;
110
+ 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>, filter?: QueryFilter): Iterable<QueryTable6<A, B, C, D, E, F>>;
103
111
  /**
104
112
  * Callback query: calls `fn` for each matching entity row (no yield object allocations).
105
113
  */
106
114
  queryEach<A>(c1: ComponentCtor<A>, fn: (e: Entity, c1: A) => void): void;
115
+ queryEach<A>(c1: ComponentCtor<A>, filter: QueryFilter, fn: (e: Entity, c1: A) => void): void;
107
116
  queryEach<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, fn: (e: Entity, c1: A, c2: B) => void): void;
117
+ queryEach<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, filter: QueryFilter, fn: (e: Entity, c1: A, c2: B) => void): void;
108
118
  queryEach<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, fn: (e: Entity, c1: A, c2: B, c3: C) => void): void;
119
+ queryEach<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, filter: QueryFilter, fn: (e: Entity, c1: A, c2: B, c3: C) => void): void;
109
120
  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;
121
+ queryEach<A, B, C, D>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, filter: QueryFilter, fn: (e: Entity, c1: A, c2: B, c3: C, c4: D) => void): void;
110
122
  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;
123
+ queryEach<A, B, C, D, E>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, filter: QueryFilter, fn: (e: Entity, c1: A, c2: B, c3: C, c4: D, c5: E) => void): void;
111
124
  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;
125
+ 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>, filter: QueryFilter, fn: (e: Entity, c1: A, c2: B, c3: C, c4: D, c5: E, c6: F) => void): void;
112
126
  private _snapshotRuntime;
113
127
  private _resetArchetypes;
128
+ private static _isQueryFilter;
129
+ private static _splitArgs;
130
+ /**
131
+ * Returns the list of archetypes matching `needSorted` (a sorted, deduped TypeId array).
132
+ * Results are cached per query signature. Because archetypes are append-only, stale entries
133
+ * are extended incrementally — only newly-added archetypes are re-scanned.
134
+ */
135
+ private _matchingArchetypes;
114
136
  private static _buildQueryTypeIds;
137
+ private _ensureNotDestroyed;
115
138
  private _ensureNotIterating;
116
139
  private _getOrCreateArchetype;
117
140
  private _removeFromArchetype;