@vworlds/vecs 1.0.9 → 1.0.11

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.
Files changed (46) hide show
  1. package/.husky/pre-commit +1 -0
  2. package/README.md +218 -229
  3. package/dist/command.d.ts +1 -0
  4. package/dist/command.js +2 -0
  5. package/dist/command.js.map +1 -0
  6. package/dist/component.d.ts +51 -59
  7. package/dist/component.js +31 -25
  8. package/dist/component.js.map +1 -1
  9. package/dist/dsl.d.ts +34 -26
  10. package/dist/dsl.js +46 -20
  11. package/dist/dsl.js.map +1 -1
  12. package/dist/entity.d.ts +110 -127
  13. package/dist/entity.js +323 -164
  14. package/dist/entity.js.map +1 -1
  15. package/dist/filter.d.ts +31 -23
  16. package/dist/filter.js +41 -32
  17. package/dist/filter.js.map +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/package.json +3 -1
  20. package/dist/phase.d.ts +5 -28
  21. package/dist/phase.js +11 -10
  22. package/dist/phase.js.map +1 -1
  23. package/dist/query.d.ts +128 -94
  24. package/dist/query.js +254 -145
  25. package/dist/query.js.map +1 -1
  26. package/dist/system.d.ts +64 -128
  27. package/dist/system.js +156 -149
  28. package/dist/system.js.map +1 -1
  29. package/dist/util/array_map.d.ts +4 -55
  30. package/dist/util/array_map.js +35 -37
  31. package/dist/util/array_map.js.map +1 -1
  32. package/dist/util/bitset.d.ts +40 -50
  33. package/dist/util/bitset.js +76 -62
  34. package/dist/util/bitset.js.map +1 -1
  35. package/dist/util/events.d.ts +14 -18
  36. package/dist/util/events.js +24 -3
  37. package/dist/util/events.js.map +1 -1
  38. package/dist/util/ordered_set.d.ts +1 -17
  39. package/dist/util/ordered_set.js +74 -25
  40. package/dist/util/ordered_set.js.map +1 -1
  41. package/dist/world.d.ts +222 -201
  42. package/dist/world.js +394 -323
  43. package/dist/world.js.map +1 -1
  44. package/eslint-rules/internal-underscore.js +60 -0
  45. package/eslint.config.js +5 -0
  46. package/package.json +3 -1
package/dist/system.d.ts CHANGED
@@ -1,18 +1,15 @@
1
- import { ArrayMap } from "./util/array_map.js";
2
- import { Bitset } from "./util/bitset.js";
3
1
  import { Component } from "./component.js";
4
2
  import { Query } from "./query.js";
5
3
  import { type QueryDSL, type MaybeRequired } from "./dsl.js";
6
4
  import type { Entity } from "./entity.js";
7
- import { Phase, type IPhase } from "./phase.js";
5
+ import { type IPhase } from "./phase.js";
8
6
  import { type World } from "./world.js";
9
7
  export type { QueryDSL as SystemQuery, EntityTestFunc } from "./dsl.js";
10
- type ComponentCallback = (c: Component) => void;
11
8
  type RunCallback = (now: number, delta: number) => void;
12
9
  /**
13
- * A reactive processor that operates on a filtered subset of world entities.
10
+ * A reactive processor running over a filtered subset of world entities.
14
11
  *
15
- * Systems are created and registered through {@link World.system}:
12
+ * Systems are created and configured through {@link World.system}:
16
13
  *
17
14
  * ```ts
18
15
  * world.system("Move")
@@ -23,127 +20,77 @@ type RunCallback = (now: number, delta: number) => void;
23
20
  * .exit((e) => { console.log("entity left", e.eid); });
24
21
  * ```
25
22
  *
26
- * All builder methods return `this` for chaining. Call {@link World.start}
27
- * once all systems are registered; after that, drive the loop with
28
- * {@link World.runPhase}.
23
+ * Every builder method returns `this` for chaining. After registering systems
24
+ * call {@link World.start} once, then drive the loop with
25
+ * {@link World.runPhase} or {@link World.progress}.
26
+ *
27
+ * Internally each system holds a single ordered **inbox** of routed events
28
+ * (`enter`, `exit`, `update`). The world appends to it during command-queue
29
+ * routing; the system replays the inbox at the top of every `_run` so
30
+ * callbacks observe events in arrival order.
29
31
  *
30
32
  * ### Component injection and type inference
31
33
  *
32
- * `enter`, `exit`, `update`, `each`, and `sort` all accept an array of
33
- * component classes that are resolved from the entity and passed as a typed
34
- * tuple to the callback. Use `{ parent: SomeComponent }` to resolve from the
35
- * entity's parent instead of the entity itself.
34
+ * `enter`, `exit`, `update`, `each`, and `sort` accept an array of component
35
+ * classes resolved from the entity and passed as a typed tuple to the
36
+ * callback. Use `{ parent: SomeComponent }` to resolve from the entity's
37
+ * parent instead of the entity itself.
38
+ *
39
+ * Components declared via {@link requires} (or the `_guaranteed` argument of
40
+ * {@link query}) are tracked as the type parameter `R`. Inside `sort`,
41
+ * `each`, and `update` injection callbacks they are non-nullable; any other
42
+ * component remains `Type | undefined`.
36
43
  *
37
- * Components declared via {@link requires} (or the second argument of
38
- * {@link query}) are tracked as a type parameter `R` on the system. In
39
- * `sort`, `each`, and `update` inject callbacks, those components appear as
40
- * non-nullable; any component not in `R` remains `Type | undefined`.
44
+ * @typeParam R - Component classes guaranteed present on every matched entity.
41
45
  */
42
46
  export declare class System<R extends (typeof Component)[] = []> extends Query<R> {
43
- protected componentUpdateCallbacks: ArrayMap<ComponentCallback>;
44
- protected eachCallback: ((e: Entity) => void) | undefined;
45
- private _runCallback;
46
- private readonly updateQueue;
47
- /** @internal */
48
- _phase: string | Phase | undefined;
49
- protected watchlistBitmask: Bitset;
50
47
  constructor(name: string, world: World);
51
48
  /**
52
49
  * Assign this system to a pipeline phase.
53
50
  *
54
- * The phase can be specified by name (the world will resolve it at
55
- * {@link World.start | start} time) or by an {@link IPhase} reference
56
- * returned from {@link World.addPhase}. Systems without an explicit phase
57
- * are placed in the built-in `"update"` phase.
51
+ * Pass either a phase name (resolved at {@link World.start}) or an
52
+ * {@link IPhase} reference returned from {@link World.addPhase}. Systems
53
+ * with no explicit phase fall into the built-in `"update"` phase.
58
54
  *
59
55
  * @param p - Phase name or `IPhase` reference.
60
- * @returns `this` for chaining.
56
+ * @returns This system, for chaining.
57
+ * @throws When the phase reference is not a `Phase`, or belongs to a
58
+ * different world.
61
59
  */
62
60
  phase(p: string | IPhase): this;
63
- /** @internal Delivers a component-modified notification to this system. */
64
- notifyModified(c: Component): void;
65
- /** @internal Fires enter callbacks, adds entity to tracked set, queues component updates. */
66
- _enter(e: Entity): void;
67
- /** @internal Fires exit callbacks, removes entity from tracked set, drains update queue. */
68
- _exit(e: Entity): void;
69
- /** @internal Execute one tick: run `run`, fire `each`, then drain the update queue. */
70
- _run(now: number, delta: number): void;
71
61
  /**
72
- * Register a per-tick callback that runs every time this system's phase
73
- * executes, regardless of entity membership.
62
+ * Register a per-tick callback fired every time this system's phase runs,
63
+ * regardless of entity membership.
74
64
  *
75
- * Use this for logic that is not driven by component updates — polling,
65
+ * Use it for logic that is not driven by component changes — polling,
76
66
  * network flushing, global timers, etc.
77
67
  *
78
68
  * @param callback - Receives `now` (absolute timestamp in ms) and `delta`
79
- * (ms since the last tick).
80
- * @returns `this` for chaining.
69
+ * (ms since the previous tick).
70
+ * @returns This system, for chaining.
81
71
  */
82
72
  run(callback: RunCallback): this;
83
73
  /**
84
- * Register a callback that fires when a component of type `ComponentClass`
85
- * is modified on any entity in this system.
86
- *
87
- * The system will automatically begin tracking entities that have this
88
- * component type (equivalent to adding it to a `requires` / `HAS` query)
89
- * unless a custom {@link query} was already set.
90
- *
91
- * @param ComponentClass - The component class to watch.
92
- * @param callback - Receives the modified component instance.
93
- * @returns `this` for chaining.
94
- *
95
- * @example
96
- * ```ts
97
- * world.system("RenderPosition")
98
- * .update(Position, (pos) => {
99
- * sprite.setPosition(pos.x, pos.y);
100
- * });
101
- * ```
102
- */
103
- update<C extends typeof Component>(ComponentClass: C, callback: (c: InstanceType<C>) => void): this;
104
- /**
105
- * Register a callback that fires when `ComponentClass` is modified, with
106
- * additional components injected from the same entity.
107
- *
108
- * @param ComponentClass - The component class to watch.
109
- * @param inject - Additional component classes to resolve from the entity.
110
- * @param callback - Receives the modified component and the injected tuple.
111
- * @returns `this` for chaining.
112
- *
113
- * @example
114
- * ```ts
115
- * world.system("SyncSprite")
116
- * .update(Position, [Sprite], (pos, [sprite]) => {
117
- * sprite.sprite.setPosition(pos.x, pos.y);
118
- * });
119
- * ```
120
- */
121
- update<C extends typeof Component, J extends (typeof Component)[]>(ComponentClass: C, inject: readonly [...J], callback: (c: InstanceType<C>, injected: {
122
- [K in keyof J]: MaybeRequired<J[K], R>;
123
- }) => void): this;
124
- /**
125
- * Register a callback that fires **every tick** for every entity currently
126
- * tracked by this system, with the listed components resolved from each
127
- * entity.
74
+ * Register a callback fired **every tick** for **every tracked entity**,
75
+ * unconditionally, with the listed components resolved from each entity.
128
76
  *
129
77
  * Unlike {@link update} (which only fires when `component.modified()` is
130
- * called), `each` fires unconditionally on every tick the system runs,
131
- * once per tracked entity. Components declared via {@link requires} are
132
- * guaranteed non-null in the resolved tuple; any other component class
133
- * may be `undefined` if the entity lacks it.
78
+ * called), `each` fires every tick the system runs, once per tracked entity.
79
+ * Components declared via {@link requires} are non-nullable in the resolved
80
+ * tuple; any other component class may be `undefined` if the entity lacks it.
134
81
  *
135
82
  * `each` does **not** modify the system's query — define membership with
136
83
  * {@link requires} or {@link query} as usual. It does, however, implicitly
137
84
  * enable {@link track}, so matched entities are exposed via {@link entities}.
138
85
  *
139
- * Only a single `each` callback may be registered per system; calling
140
- * `each` a second time throws.
86
+ * Only one `each` callback may be registered per system; calling `each` a
87
+ * second time throws.
141
88
  *
142
89
  * @param components - Component classes to resolve from each entity.
143
90
  * @param callback - Receives the entity and a tuple of resolved component
144
- * instances (`undefined` for components not covered by {@link requires}).
145
- * @returns `this` for chaining.
146
- * @throws If `each` has already been registered on this system.
91
+ * instances (`undefined` for any not covered by {@link requires}).
92
+ * @returns This system, for chaining.
93
+ * @throws When `each` has already been registered on this system.
147
94
  *
148
95
  * @example
149
96
  * ```ts
@@ -159,50 +106,39 @@ export declare class System<R extends (typeof Component)[] = []> extends Query<R
159
106
  [K in keyof J]: MaybeRequired<J[K], R>;
160
107
  }) => void): this;
161
108
  /**
162
- * Not supported on `System`. Throws unconditionally.
163
- *
164
- * Systems are owned by the world for the duration of the session. If you
165
- * need a temporary reactive set, use a standalone {@link Query} instead.
166
- */
167
- destroy(): never;
168
- /**
169
- * Set the entity membership predicate using the {@link QueryDSL} DSL.
109
+ * Set the entity-membership predicate using a {@link QueryDSL} expression.
170
110
  *
171
111
  * Replaces any implicit query derived from `update` watchlists and any
172
- * previous `requires` call. After calling `query`, auto-expanding of
173
- * `update` watchlists is disabled.
112
+ * previous `requires` call. After calling `query`, watchlist auto-expansion
113
+ * via `update` is disabled.
174
114
  *
175
- * The optional `guaranteed` tuple is a pure type-level hint: it tells
176
- * `sort`, `each`, and `update` callbacks which components are guaranteed
177
- * to be present on every matched entity, eliminating `| undefined` from
178
- * those positions. It has no effect at runtime.
115
+ * The optional `_guaranteed` tuple is a pure type-level hint see
116
+ * {@link Query.query} for details.
179
117
  *
180
- * @param q - A {@link QueryDSL} expression.
118
+ * @param q - Query expression.
181
119
  * @param _guaranteed - Component classes guaranteed present on every matched
182
120
  * entity (type hint only — not validated at runtime).
183
- * @returns `this` for chaining.
184
- *
185
- * @example
186
- * ```ts
187
- * world.system("Move")
188
- * .query({ AND: [{ HAS: Position }, { HAS: Velocity }] }, [Position, Velocity])
189
- * .each([Position, Velocity], (e, [pos, vel]) => {
190
- * pos.x += vel.vx; // no ! needed
191
- * });
192
- * ```
121
+ * @returns This system, retyped with the guaranteed tuple as its `R`.
193
122
  */
194
123
  query<T extends (typeof Component)[] = []>(q: QueryDSL, _guaranteed?: readonly [...T]): System<T>;
195
124
  /**
196
- * Shorthand for `query([...components])` — the system tracks entities that
197
- * have **all** of the listed component types.
125
+ * Shorthand for `query([...components])` — tracks entities that have **all**
126
+ * of the listed component types.
198
127
  *
199
- * Equivalent to `query({ HAS: components })`. Unlike `query`, passing
200
- * component classes here also informs the types of {@link sort} and
201
- * {@link each} callbacks: listed components will be non-nullable in those
202
- * tuples.
128
+ * Equivalent to `query({ HAS: components })`. The listed components are also
129
+ * recorded in the type parameter `R`, so {@link sort}, {@link each}, and
130
+ * {@link update} callbacks treat them as non-nullable.
203
131
  *
204
- * @param components - One or more component classes.
205
- * @returns `this` for chaining.
132
+ * @param components - Component classes to require.
133
+ * @returns This system, retyped with the required tuple as its `R`.
206
134
  */
207
135
  requires<T extends (typeof Component)[]>(...components: [...T]): System<T>;
136
+ /**
137
+ * Not supported on `System`. Throws unconditionally.
138
+ *
139
+ * Systems are owned by the world for the duration of the session; if you
140
+ * need a temporary reactive set use a standalone {@link Query} via
141
+ * {@link World.query}.
142
+ */
143
+ destroy(): never;
208
144
  }
package/dist/system.js CHANGED
@@ -1,12 +1,9 @@
1
- import { ArrayMap } from "./util/array_map.js";
2
- import { Bitset } from "./util/bitset.js";
3
1
  import { Query } from "./query.js";
4
- import { HAS } from "./dsl.js";
5
2
  import { Phase } from "./phase.js";
6
3
  /**
7
- * A reactive processor that operates on a filtered subset of world entities.
4
+ * A reactive processor running over a filtered subset of world entities.
8
5
  *
9
- * Systems are created and registered through {@link World.system}:
6
+ * Systems are created and configured through {@link World.system}:
10
7
  *
11
8
  * ```ts
12
9
  * world.system("Move")
@@ -17,39 +14,133 @@ import { Phase } from "./phase.js";
17
14
  * .exit((e) => { console.log("entity left", e.eid); });
18
15
  * ```
19
16
  *
20
- * All builder methods return `this` for chaining. Call {@link World.start}
21
- * once all systems are registered; after that, drive the loop with
22
- * {@link World.runPhase}.
17
+ * Every builder method returns `this` for chaining. After registering systems
18
+ * call {@link World.start} once, then drive the loop with
19
+ * {@link World.runPhase} or {@link World.progress}.
20
+ *
21
+ * Internally each system holds a single ordered **inbox** of routed events
22
+ * (`enter`, `exit`, `update`). The world appends to it during command-queue
23
+ * routing; the system replays the inbox at the top of every `_run` so
24
+ * callbacks observe events in arrival order.
23
25
  *
24
26
  * ### Component injection and type inference
25
27
  *
26
- * `enter`, `exit`, `update`, `each`, and `sort` all accept an array of
27
- * component classes that are resolved from the entity and passed as a typed
28
- * tuple to the callback. Use `{ parent: SomeComponent }` to resolve from the
29
- * entity's parent instead of the entity itself.
28
+ * `enter`, `exit`, `update`, `each`, and `sort` accept an array of component
29
+ * classes resolved from the entity and passed as a typed tuple to the
30
+ * callback. Use `{ parent: SomeComponent }` to resolve from the entity's
31
+ * parent instead of the entity itself.
32
+ *
33
+ * Components declared via {@link requires} (or the `_guaranteed` argument of
34
+ * {@link query}) are tracked as the type parameter `R`. Inside `sort`,
35
+ * `each`, and `update` injection callbacks they are non-nullable; any other
36
+ * component remains `Type | undefined`.
30
37
  *
31
- * Components declared via {@link requires} (or the second argument of
32
- * {@link query}) are tracked as a type parameter `R` on the system. In
33
- * `sort`, `each`, and `update` inject callbacks, those components appear as
34
- * non-nullable; any component not in `R` remains `Type | undefined`.
38
+ * @typeParam R - Component classes guaranteed present on every matched entity.
35
39
  */
36
40
  export class System extends Query {
37
41
  constructor(name, world) {
38
42
  super(name, world, false);
39
- this.componentUpdateCallbacks = new ArrayMap();
40
- this.updateQueue = [];
41
- this.watchlistBitmask = new Bitset();
43
+ /** @internal Single inbox replayed in arrival order on every `_run`. */
44
+ this._inbox = [];
45
+ }
46
+ /**
47
+ * @internal Routing entry: register query membership for `e`, push an
48
+ * inbox `enter` event when an `enter` callback is registered, and bridge
49
+ * watched components through {@link _notifyModified} to surface them as
50
+ * inbox `update` events on entry.
51
+ */
52
+ _enter(e) {
53
+ this._entities?.add(e);
54
+ e._addQueryMembership(this);
55
+ if (this._enterCallback !== undefined) {
56
+ this._inbox.push({ kind: 0 /* InboxCommand.Enter */, entity: e });
57
+ }
58
+ e.components.forEach((c) => {
59
+ if (this._watchlistBitmask.hasBit(c.bitPtr)) {
60
+ this._notifyModified(c);
61
+ }
62
+ });
63
+ }
64
+ /**
65
+ * @internal Routing entry: deregister query membership for `e` and push an
66
+ * inbox `exit` event when an `exit` callback is registered, capturing a
67
+ * snapshot of the components the callback wants to inject so they remain
68
+ * resolvable after the underlying components are removed.
69
+ */
70
+ _exit(e) {
71
+ this._entities?.delete(e);
72
+ e._removeQueryMembership(this);
73
+ if (this._exitCallback !== undefined) {
74
+ let snapshot;
75
+ if (this._exitSnapshotTypes && this._exitSnapshotTypes.length > 0) {
76
+ snapshot = new Map();
77
+ for (const type of this._exitSnapshotTypes) {
78
+ const c = e._get(type);
79
+ if (c) {
80
+ snapshot.set(type, c);
81
+ }
82
+ }
83
+ }
84
+ this._inbox.push({ kind: 1 /* InboxCommand.Exit */, entity: e, snapshot });
85
+ }
86
+ }
87
+ /**
88
+ * @internal Routing entry: push an inbox `update` event when the modified
89
+ * component matches the watchlist.
90
+ */
91
+ _notifyModified(c) {
92
+ if (!this._watchlistBitmask.hasBit(c.bitPtr)) {
93
+ return;
94
+ }
95
+ this._inbox.push({ kind: 2 /* InboxCommand.Update */, component: c });
96
+ }
97
+ /**
98
+ * @internal Execute one tick: drain the inbox in arrival order, then run
99
+ * the `run` callback, then the `each` callback for every tracked entity.
100
+ *
101
+ * The whole body executes inside a `World.defer` scope; mutations made by
102
+ * callbacks land in the world queue and are processed when `_run` returns.
103
+ */
104
+ _run(now, delta) {
105
+ this.world.defer(() => {
106
+ for (let i = 0; i < this._inbox.length; i++) {
107
+ const event = this._inbox[i];
108
+ switch (event.kind) {
109
+ case 0 /* InboxCommand.Enter */:
110
+ this._enterCallback(event.entity);
111
+ break;
112
+ case 1 /* InboxCommand.Exit */:
113
+ this._exitCallback(event.entity, event.snapshot);
114
+ break;
115
+ case 2 /* InboxCommand.Update */:
116
+ const callback = this._componentUpdateCallbacks.get(event.component.type);
117
+ if (callback) {
118
+ callback(event.component);
119
+ }
120
+ break;
121
+ }
122
+ }
123
+ this._inbox.length = 0;
124
+ if (this._runCallback) {
125
+ this._runCallback(now, delta);
126
+ }
127
+ if (this._eachCallback) {
128
+ const cb = this._eachCallback;
129
+ this.forEach((e) => cb(e));
130
+ }
131
+ });
42
132
  }
43
133
  /**
44
134
  * Assign this system to a pipeline phase.
45
135
  *
46
- * The phase can be specified by name (the world will resolve it at
47
- * {@link World.start | start} time) or by an {@link IPhase} reference
48
- * returned from {@link World.addPhase}. Systems without an explicit phase
49
- * are placed in the built-in `"update"` phase.
136
+ * Pass either a phase name (resolved at {@link World.start}) or an
137
+ * {@link IPhase} reference returned from {@link World.addPhase}. Systems
138
+ * with no explicit phase fall into the built-in `"update"` phase.
50
139
  *
51
140
  * @param p - Phase name or `IPhase` reference.
52
- * @returns `this` for chaining.
141
+ * @returns This system, for chaining.
142
+ * @throws When the phase reference is not a `Phase`, or belongs to a
143
+ * different world.
53
144
  */
54
145
  phase(p) {
55
146
  if (typeof p !== "string") {
@@ -63,115 +154,42 @@ export class System extends Query {
63
154
  this._phase = p;
64
155
  return this;
65
156
  }
66
- /** @internal Delivers a component-modified notification to this system. */
67
- notifyModified(c) {
68
- if (!this.watchlistBitmask.hasBit(c.bitPtr)) {
69
- return;
70
- }
71
- this.updateQueue.push(c);
72
- }
73
- /** @internal Fires enter callbacks, adds entity to tracked set, queues component updates. */
74
- _enter(e) {
75
- super._enter(e);
76
- e.forEachComponent((c) => this.notifyModified(c));
77
- }
78
- /** @internal Fires exit callbacks, removes entity from tracked set, drains update queue. */
79
- _exit(e) {
80
- super._exit(e);
81
- this.updateQueue.forEach((c, i) => {
82
- if (!c) {
83
- return;
84
- }
85
- if (c.entity === e) {
86
- this.updateQueue[i] = undefined;
87
- }
88
- });
89
- }
90
- /** @internal Execute one tick: run `run`, fire `each`, then drain the update queue. */
91
- _run(now, delta) {
92
- if (this._runCallback) {
93
- this._runCallback(now, delta);
94
- }
95
- if (this.eachCallback) {
96
- const cb = this.eachCallback;
97
- this.forEach((e) => cb(e));
98
- }
99
- this.updateQueue.forEach((c) => {
100
- if (!c) {
101
- return;
102
- }
103
- const callback = this.componentUpdateCallbacks.get(c.type);
104
- if (callback) {
105
- callback(c);
106
- }
107
- });
108
- this.updateQueue.length = 0;
109
- }
110
157
  /**
111
- * Register a per-tick callback that runs every time this system's phase
112
- * executes, regardless of entity membership.
158
+ * Register a per-tick callback fired every time this system's phase runs,
159
+ * regardless of entity membership.
113
160
  *
114
- * Use this for logic that is not driven by component updates — polling,
161
+ * Use it for logic that is not driven by component changes — polling,
115
162
  * network flushing, global timers, etc.
116
163
  *
117
164
  * @param callback - Receives `now` (absolute timestamp in ms) and `delta`
118
- * (ms since the last tick).
119
- * @returns `this` for chaining.
165
+ * (ms since the previous tick).
166
+ * @returns This system, for chaining.
120
167
  */
121
168
  run(callback) {
122
169
  this._runCallback = callback;
123
170
  return this;
124
171
  }
125
- update(ComponentClass, injectOrCallback, callback) {
126
- const type = this.world.getComponentType(ComponentClass);
127
- if (typeof injectOrCallback === "function") {
128
- callback = injectOrCallback;
129
- this.componentUpdateCallbacks.set(type, callback);
130
- }
131
- else {
132
- const inject = injectOrCallback;
133
- const injectedComponentTypes = inject.map((C) => this.world.getComponentType(C));
134
- const cb = (c) => {
135
- const injected = [];
136
- injectedComponentTypes.forEach((InjectedComponentType) => {
137
- injected.push(c.entity.get(InjectedComponentType));
138
- });
139
- if (callback) {
140
- callback(c, injected);
141
- }
142
- };
143
- this.componentUpdateCallbacks.set(type, cb);
144
- }
145
- this.watchlistBitmask.add(type);
146
- if (!this.hasQuery) {
147
- const watchlist = this.watchlistBitmask.indices();
148
- this._belongs = HAS(this.world, ...watchlist);
149
- }
150
- return this;
151
- }
152
172
  /**
153
- * Register a callback that fires **every tick** for every entity currently
154
- * tracked by this system, with the listed components resolved from each
155
- * entity.
173
+ * Register a callback fired **every tick** for **every tracked entity**,
174
+ * unconditionally, with the listed components resolved from each entity.
156
175
  *
157
176
  * Unlike {@link update} (which only fires when `component.modified()` is
158
- * called), `each` fires unconditionally on every tick the system runs,
159
- * once per tracked entity. Components declared via {@link requires} are
160
- * guaranteed non-null in the resolved tuple; any other component class
161
- * may be `undefined` if the entity lacks it.
177
+ * called), `each` fires every tick the system runs, once per tracked entity.
178
+ * Components declared via {@link requires} are non-nullable in the resolved
179
+ * tuple; any other component class may be `undefined` if the entity lacks it.
162
180
  *
163
181
  * `each` does **not** modify the system's query — define membership with
164
182
  * {@link requires} or {@link query} as usual. It does, however, implicitly
165
183
  * enable {@link track}, so matched entities are exposed via {@link entities}.
166
184
  *
167
- * Only a single `each` callback may be registered per system; calling
168
- * `each` a second time throws.
185
+ * Only one `each` callback may be registered per system; calling `each` a
186
+ * second time throws.
169
187
  *
170
188
  * @param components - Component classes to resolve from each entity.
171
189
  * @param callback - Receives the entity and a tuple of resolved component
172
- * instances (`undefined` for components not covered by {@link requires}).
173
- * @returns `this` for chaining.
174
- * @throws If `each` has already been registered on this system.
190
+ * instances (`undefined` for any not covered by {@link requires}).
191
+ * @returns This system, for chaining.
192
+ * @throws When `each` has already been registered on this system.
175
193
  *
176
194
  * @example
177
195
  * ```ts
@@ -184,71 +202,60 @@ export class System extends Query {
184
202
  * ```
185
203
  */
186
204
  each(components, callback) {
187
- if (this.eachCallback) {
205
+ if (this._eachCallback) {
188
206
  throw `each already registered for system '${this.name}'`;
189
207
  }
190
208
  this.track();
191
209
  const types = components.map((C) => this.world.getComponentType(C));
192
- this.eachCallback = (e) => {
210
+ this._eachCallback = (e) => {
193
211
  const resolved = types.map((t) => e.get(t));
194
212
  callback(e, resolved);
195
213
  };
196
214
  return this;
197
215
  }
198
216
  /**
199
- * Not supported on `System`. Throws unconditionally.
200
- *
201
- * Systems are owned by the world for the duration of the session. If you
202
- * need a temporary reactive set, use a standalone {@link Query} instead.
203
- */
204
- destroy() {
205
- throw `destroy() is not supported on System '${this.name}'`;
206
- }
207
- /**
208
- * Set the entity membership predicate using the {@link QueryDSL} DSL.
217
+ * Set the entity-membership predicate using a {@link QueryDSL} expression.
209
218
  *
210
219
  * Replaces any implicit query derived from `update` watchlists and any
211
- * previous `requires` call. After calling `query`, auto-expanding of
212
- * `update` watchlists is disabled.
220
+ * previous `requires` call. After calling `query`, watchlist auto-expansion
221
+ * via `update` is disabled.
213
222
  *
214
- * The optional `guaranteed` tuple is a pure type-level hint: it tells
215
- * `sort`, `each`, and `update` callbacks which components are guaranteed
216
- * to be present on every matched entity, eliminating `| undefined` from
217
- * those positions. It has no effect at runtime.
223
+ * The optional `_guaranteed` tuple is a pure type-level hint see
224
+ * {@link Query.query} for details.
218
225
  *
219
- * @param q - A {@link QueryDSL} expression.
226
+ * @param q - Query expression.
220
227
  * @param _guaranteed - Component classes guaranteed present on every matched
221
228
  * entity (type hint only — not validated at runtime).
222
- * @returns `this` for chaining.
223
- *
224
- * @example
225
- * ```ts
226
- * world.system("Move")
227
- * .query({ AND: [{ HAS: Position }, { HAS: Velocity }] }, [Position, Velocity])
228
- * .each([Position, Velocity], (e, [pos, vel]) => {
229
- * pos.x += vel.vx; // no ! needed
230
- * });
231
- * ```
229
+ * @returns This system, retyped with the guaranteed tuple as its `R`.
232
230
  */
233
231
  query(q, _guaranteed) {
234
232
  super.query(q, _guaranteed);
235
233
  return this;
236
234
  }
237
235
  /**
238
- * Shorthand for `query([...components])` — the system tracks entities that
239
- * have **all** of the listed component types.
236
+ * Shorthand for `query([...components])` — tracks entities that have **all**
237
+ * of the listed component types.
240
238
  *
241
- * Equivalent to `query({ HAS: components })`. Unlike `query`, passing
242
- * component classes here also informs the types of {@link sort} and
243
- * {@link each} callbacks: listed components will be non-nullable in those
244
- * tuples.
239
+ * Equivalent to `query({ HAS: components })`. The listed components are also
240
+ * recorded in the type parameter `R`, so {@link sort}, {@link each}, and
241
+ * {@link update} callbacks treat them as non-nullable.
245
242
  *
246
- * @param components - One or more component classes.
247
- * @returns `this` for chaining.
243
+ * @param components - Component classes to require.
244
+ * @returns This system, retyped with the required tuple as its `R`.
248
245
  */
249
246
  requires(...components) {
250
247
  super.requires(...components);
251
248
  return this;
252
249
  }
250
+ /**
251
+ * Not supported on `System`. Throws unconditionally.
252
+ *
253
+ * Systems are owned by the world for the duration of the session; if you
254
+ * need a temporary reactive set use a standalone {@link Query} via
255
+ * {@link World.query}.
256
+ */
257
+ destroy() {
258
+ throw `destroy() is not supported on System '${this.name}'`;
259
+ }
253
260
  }
254
261
  //# sourceMappingURL=system.js.map