@vworlds/vecs 1.0.10 → 1.0.12

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 (49) hide show
  1. package/.husky/pre-commit +1 -0
  2. package/README.md +299 -228
  3. package/dist/command.d.ts +1 -46
  4. package/dist/component.d.ts +51 -59
  5. package/dist/component.js +31 -25
  6. package/dist/component.js.map +1 -1
  7. package/dist/dsl.d.ts +34 -26
  8. package/dist/dsl.js +46 -20
  9. package/dist/dsl.js.map +1 -1
  10. package/dist/entity.d.ts +96 -106
  11. package/dist/entity.js +261 -190
  12. package/dist/entity.js.map +1 -1
  13. package/dist/filter.d.ts +31 -23
  14. package/dist/filter.js +24 -17
  15. package/dist/filter.js.map +1 -1
  16. package/dist/index.d.ts +2 -1
  17. package/dist/index.js +1 -0
  18. package/dist/index.js.map +1 -1
  19. package/dist/package.json +3 -1
  20. package/dist/phase.d.ts +12 -30
  21. package/dist/phase.js +11 -10
  22. package/dist/phase.js.map +1 -1
  23. package/dist/query.d.ts +107 -144
  24. package/dist/query.js +200 -169
  25. package/dist/query.js.map +1 -1
  26. package/dist/system.d.ts +170 -86
  27. package/dist/system.js +253 -114
  28. package/dist/system.js.map +1 -1
  29. package/dist/timer.d.ts +50 -0
  30. package/dist/timer.js +154 -0
  31. package/dist/timer.js.map +1 -0
  32. package/dist/util/array_map.d.ts +4 -55
  33. package/dist/util/array_map.js +35 -37
  34. package/dist/util/array_map.js.map +1 -1
  35. package/dist/util/bitset.d.ts +40 -50
  36. package/dist/util/bitset.js +76 -62
  37. package/dist/util/bitset.js.map +1 -1
  38. package/dist/util/events.d.ts +14 -18
  39. package/dist/util/events.js +24 -3
  40. package/dist/util/events.js.map +1 -1
  41. package/dist/util/ordered_set.d.ts +1 -17
  42. package/dist/util/ordered_set.js +74 -25
  43. package/dist/util/ordered_set.js.map +1 -1
  44. package/dist/world.d.ts +230 -218
  45. package/dist/world.js +422 -327
  46. package/dist/world.js.map +1 -1
  47. package/eslint-rules/internal-underscore.js +60 -0
  48. package/eslint.config.js +5 -0
  49. package/package.json +3 -1
package/dist/system.d.ts CHANGED
@@ -2,14 +2,15 @@ import { Component } from "./component.js";
2
2
  import { Query } from "./query.js";
3
3
  import { type QueryDSL, type MaybeRequired } from "./dsl.js";
4
4
  import type { Entity } from "./entity.js";
5
- import { Phase, type IPhase } from "./phase.js";
5
+ import { type IPhase } from "./phase.js";
6
6
  import { type World } from "./world.js";
7
+ import { type ITickSource } from "./timer.js";
7
8
  export type { QueryDSL as SystemQuery, EntityTestFunc } from "./dsl.js";
8
9
  type RunCallback = (now: number, delta: number) => void;
9
10
  /**
10
- * A reactive processor that operates on a filtered subset of world entities.
11
+ * A reactive processor running over a filtered subset of world entities.
11
12
  *
12
- * Systems are created and registered through {@link World.system}:
13
+ * Systems are created and configured through {@link World.system}:
13
14
  *
14
15
  * ```ts
15
16
  * world.system("Move")
@@ -20,9 +21,9 @@ type RunCallback = (now: number, delta: number) => void;
20
21
  * .exit((e) => { console.log("entity left", e.eid); });
21
22
  * ```
22
23
  *
23
- * All builder methods return `this` for chaining. Call {@link World.start}
24
- * once all systems are registered; after that, drive the loop with
25
- * {@link World.runPhase}.
24
+ * Every builder method returns `this` for chaining. After registering systems
25
+ * call {@link World.start} once, then drive the loop with
26
+ * {@link World.runPhase} or {@link World.progress}.
26
27
  *
27
28
  * Internally each system holds a single ordered **inbox** of routed events
28
29
  * (`enter`, `exit`, `update`). The world appends to it during command-queue
@@ -31,83 +32,135 @@ type RunCallback = (now: number, delta: number) => void;
31
32
  *
32
33
  * ### Component injection and type inference
33
34
  *
34
- * `enter`, `exit`, `update`, `each`, and `sort` all accept an array of
35
- * component classes that are resolved from the entity and passed as a typed
36
- * tuple to the callback. Use `{ parent: SomeComponent }` to resolve from the
37
- * entity's parent instead of the entity itself.
35
+ * `enter`, `exit`, `update`, `each`, and `sort` accept an array of component
36
+ * classes resolved from the entity and passed as a typed tuple to the
37
+ * callback. Use `{ parent: SomeComponent }` to resolve from the entity's
38
+ * parent instead of the entity itself.
38
39
  *
39
- * Components declared via {@link requires} (or the second argument of
40
- * {@link query}) are tracked as a type parameter `R` on the system. In
41
- * `sort`, `each`, and `update` inject callbacks, those components appear as
42
- * non-nullable; any component not in `R` remains `Type | undefined`.
40
+ * Components declared via {@link requires} (or the `_guaranteed` argument of
41
+ * {@link query}) are tracked as the type parameter `R`. Inside `sort`,
42
+ * `each`, and `update` injection callbacks they are non-nullable; any other
43
+ * component remains `Type | undefined`.
44
+ *
45
+ * @typeParam R - Component classes guaranteed present on every matched entity.
43
46
  */
44
- export declare class System<R extends (typeof Component)[] = []> extends Query<R> {
45
- protected eachCallback: ((e: Entity) => void) | undefined;
46
- private _runCallback;
47
- private readonly inbox;
48
- /** @internal */
49
- _phase: string | Phase | undefined;
47
+ export declare class System<R extends (typeof Component)[] = []> extends Query<R> implements ITickSource {
50
48
  constructor(name: string, world: World);
49
+ /** True when this system's cadence source fired during the current frame. */
50
+ get didTick(): boolean;
51
+ /** Milliseconds accumulated into this system's most recent fire. */
52
+ get lastFireDelta(): number;
53
+ /**
54
+ * Run this system at a fixed interval, expressed in seconds.
55
+ *
56
+ * This is seconds, unlike `World.beginFrame`, `World.progress`, and
57
+ * `runPhase`, which receive millisecond deltas. Calling `interval` replaces
58
+ * the current cadence source with an {@link IntervalTickSource}. When this
59
+ * system has a cadence source, the `delta` passed to {@link run} is the
60
+ * accumulated milliseconds since the previous fire, not the per-frame delta.
61
+ *
62
+ * @param seconds - Positive interval duration in seconds.
63
+ * @returns This system, for chaining.
64
+ * @throws When `seconds` is less than or equal to zero.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * world.system("AI")
69
+ * .interval(0.5)
70
+ * .run((now, delta) => tickAI(delta));
71
+ * ```
72
+ */
73
+ interval(seconds: number): this;
74
+ /**
75
+ * Run this system every `n` ticks from the world or an upstream source.
76
+ *
77
+ * Without a `source`, this composes with the current cadence source. For an
78
+ * unconfigured system, that means every `n` frames. With a `source`, it
79
+ * replaces the current cadence with a {@link RateTickSource} over that source.
80
+ * The `delta` passed to {@link run} is the accumulated milliseconds since
81
+ * this system last fired.
82
+ *
83
+ * @param n - Positive integer tick divisor.
84
+ * @param source - Optional upstream source to divide.
85
+ * @returns This system, for chaining.
86
+ * @throws When `n` is not a positive integer.
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * world.system("SendSnapshots")
91
+ * .rate(2)
92
+ * .run(flushNetwork);
93
+ * ```
94
+ */
95
+ rate(n: number): this;
96
+ rate(n: number, source: ITickSource): this;
97
+ /**
98
+ * Run this system only when another timer or system ticks.
99
+ *
100
+ * Calling `tickSource` mirrors `source` directly; no wrapper source is
101
+ * created. Use `.tickSource(source).rate(n)` or `.rate(n, source)` when this
102
+ * system should divide an upstream source. The `delta` passed to {@link run}
103
+ * is the accumulated milliseconds since this system last fired.
104
+ *
105
+ * @param source - Tick source or system source to mirror.
106
+ * @returns This system, for chaining.
107
+ *
108
+ * @example
109
+ * ```ts
110
+ * const second = new IntervalTickSource(1);
111
+ * world.system("Logger").tickSource(second).run(logStats);
112
+ * ```
113
+ */
114
+ tickSource(source: ITickSource): this;
115
+ private _setTickSource;
51
116
  /**
52
117
  * Assign this system to a pipeline phase.
53
118
  *
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.
119
+ * Pass either a phase name (resolved at {@link World.start}) or an
120
+ * {@link IPhase} reference returned from {@link World.addPhase}. Systems
121
+ * with no explicit phase fall into the built-in `"update"` phase.
58
122
  *
59
123
  * @param p - Phase name or `IPhase` reference.
60
- * @returns `this` for chaining.
124
+ * @returns This system, for chaining.
125
+ * @throws When the phase reference is not a `Phase`, or belongs to a
126
+ * different world.
61
127
  */
62
128
  phase(p: string | IPhase): this;
63
- /** @internal Routing entry: register membership and enqueue an enter event. */
64
- _enter(e: Entity): void;
65
- /** @internal Routing entry: deregister membership and enqueue an exit event. */
66
- _exit(e: Entity): void;
67
- /** @internal Routing entry: enqueue an update event if the watchlist matches. */
68
- notifyModified(c: Component): void;
69
129
  /**
70
- * @internal Execute one tick: drain the inbox in arrival order, then run
71
- * `runCallback` and `eachCallback`. The whole body runs in a deferred
72
- * scope; any mutations made by callbacks land in the world queue and are
73
- * processed by the world after `_run` returns.
74
- */
75
- _run(now: number, delta: number): void;
76
- /**
77
- * Register a per-tick callback that runs every time this system's phase
78
- * executes, regardless of entity membership.
130
+ * Register a per-tick callback fired every time this system's phase runs,
131
+ * regardless of entity membership.
79
132
  *
80
- * Use this for logic that is not driven by component updates — polling,
133
+ * Use it for logic that is not driven by component changes — polling,
81
134
  * network flushing, global timers, etc.
82
135
  *
83
136
  * @param callback - Receives `now` (absolute timestamp in ms) and `delta`
84
- * (ms since the last tick).
85
- * @returns `this` for chaining.
137
+ * (ms since the previous tick). If the system has an active interval,
138
+ * rate, or tick source, `delta` is the accumulated milliseconds since this
139
+ * system last fired.
140
+ * @returns This system, for chaining.
86
141
  */
87
142
  run(callback: RunCallback): this;
88
143
  /**
89
- * Register a callback that fires **every tick** for every entity currently
90
- * tracked by this system, with the listed components resolved from each
91
- * entity.
144
+ * Register a callback fired **every tick** for **every tracked entity**,
145
+ * unconditionally, with the listed components resolved from each entity.
92
146
  *
93
147
  * Unlike {@link update} (which only fires when `component.modified()` is
94
- * called), `each` fires unconditionally on every tick the system runs,
95
- * once per tracked entity. Components declared via {@link requires} are
96
- * guaranteed non-null in the resolved tuple; any other component class
97
- * may be `undefined` if the entity lacks it.
148
+ * called), `each` fires every tick the system runs, once per tracked entity.
149
+ * Components declared via {@link requires} are non-nullable in the resolved
150
+ * tuple; any other component class may be `undefined` if the entity lacks it.
98
151
  *
99
152
  * `each` does **not** modify the system's query — define membership with
100
153
  * {@link requires} or {@link query} as usual. It does, however, implicitly
101
154
  * enable {@link track}, so matched entities are exposed via {@link entities}.
102
155
  *
103
- * Only a single `each` callback may be registered per system; calling
104
- * `each` a second time throws.
156
+ * Only one `each` callback may be registered per system; calling `each` a
157
+ * second time throws.
105
158
  *
106
159
  * @param components - Component classes to resolve from each entity.
107
160
  * @param callback - Receives the entity and a tuple of resolved component
108
- * instances (`undefined` for components not covered by {@link requires}).
109
- * @returns `this` for chaining.
110
- * @throws If `each` has already been registered on this system.
161
+ * instances (`undefined` for any not covered by {@link requires}).
162
+ * @returns This system, for chaining.
163
+ * @throws When `each` has already been registered on this system.
111
164
  *
112
165
  * @example
113
166
  * ```ts
@@ -123,50 +176,81 @@ export declare class System<R extends (typeof Component)[] = []> extends Query<R
123
176
  [K in keyof J]: MaybeRequired<J[K], R>;
124
177
  }) => void): this;
125
178
  /**
126
- * Not supported on `System`. Throws unconditionally.
127
- *
128
- * Systems are owned by the world for the duration of the session. If you
129
- * need a temporary reactive set, use a standalone {@link Query} instead.
130
- */
131
- destroy(): never;
132
- /**
133
- * Set the entity membership predicate using the {@link QueryDSL} DSL.
179
+ * Set the entity-membership predicate using a {@link QueryDSL} expression.
134
180
  *
135
181
  * Replaces any implicit query derived from `update` watchlists and any
136
- * previous `requires` call. After calling `query`, auto-expanding of
137
- * `update` watchlists is disabled.
182
+ * previous `requires` call. After calling `query`, watchlist auto-expansion
183
+ * via `update` is disabled.
138
184
  *
139
- * The optional `guaranteed` tuple is a pure type-level hint: it tells
140
- * `sort`, `each`, and `update` callbacks which components are guaranteed
141
- * to be present on every matched entity, eliminating `| undefined` from
142
- * those positions. It has no effect at runtime.
185
+ * The optional `_guaranteed` tuple is a pure type-level hint see
186
+ * {@link Query.query} for details.
143
187
  *
144
- * @param q - A {@link QueryDSL} expression.
188
+ * @param q - Query expression.
145
189
  * @param _guaranteed - Component classes guaranteed present on every matched
146
190
  * entity (type hint only — not validated at runtime).
147
- * @returns `this` for chaining.
191
+ * @returns This system, retyped with the guaranteed tuple as its `R`.
192
+ */
193
+ query<T extends (typeof Component)[] = []>(q: QueryDSL, _guaranteed?: readonly [...T]): System<T>;
194
+ /**
195
+ * Shorthand for `query([...components])` — tracks entities that have **all**
196
+ * of the listed component types.
197
+ *
198
+ * Equivalent to `query({ HAS: components })`. The listed components are also
199
+ * recorded in the type parameter `R`, so {@link sort}, {@link each}, and
200
+ * {@link update} callbacks treat them as non-nullable.
201
+ *
202
+ * @param components - Component classes to require.
203
+ * @returns This system, retyped with the required tuple as its `R`.
204
+ */
205
+ requires<T extends (typeof Component)[]>(...components: [...T]): System<T>;
206
+ /**
207
+ * Disable this system.
208
+ *
209
+ * While disabled the system is effectively invisible: the inbox is cleared
210
+ * immediately, any new `enter`, `exit`, or `update` events are silently
211
+ * dropped, and {@link _run} returns without executing any callbacks. Entity
212
+ * membership in the underlying query is still maintained so the tracked set
213
+ * remains consistent and the system resumes correctly when
214
+ * {@link enable} is called.
215
+ *
216
+ * Disabling is independent from tick-source cadence: `disable` suppresses
217
+ * callbacks, but a disabled system used as a tick source still drives
218
+ * downstream consumers. Use `stop()` on an external `IntervalTickSource` or
219
+ * `RateTickSource` reference to halt that clock itself.
220
+ *
221
+ * Calling `disable` on an already-disabled system is a no-op.
222
+ *
223
+ * @returns This system, for chaining.
148
224
  *
149
225
  * @example
150
226
  * ```ts
151
- * world.system("Move")
152
- * .query({ AND: [{ HAS: Position }, { HAS: Velocity }] }, [Position, Velocity])
153
- * .each([Position, Velocity], (e, [pos, vel]) => {
154
- * pos.x += vel.vx; // no ! needed
155
- * });
227
+ * const sys = world.system("AI").requires(Enemy).run(runAI);
228
+ * // Pause AI processing during a cutscene:
229
+ * sys.disable();
230
+ * // Resume:
231
+ * sys.enable();
156
232
  * ```
157
233
  */
158
- query<T extends (typeof Component)[] = []>(q: QueryDSL, _guaranteed?: readonly [...T]): System<T>;
234
+ disable(): this;
159
235
  /**
160
- * Shorthand for `query([...components])` the system tracks entities that
161
- * have **all** of the listed component types.
236
+ * Enable this system after a previous {@link disable} call.
237
+ *
238
+ * Once re-enabled the system resumes its normal tick behaviour: enter, exit,
239
+ * and update events are queued, and {@link _run} processes the inbox and fires
240
+ * all registered callbacks. Events that occurred while the system was disabled
241
+ * are not replayed.
162
242
  *
163
- * Equivalent to `query({ HAS: components })`. Unlike `query`, passing
164
- * component classes here also informs the types of {@link sort} and
165
- * {@link each} callbacks: listed components will be non-nullable in those
166
- * tuples.
243
+ * Calling `enable` on an already-enabled system is a no-op.
167
244
  *
168
- * @param components - One or more component classes.
169
- * @returns `this` for chaining.
245
+ * @returns This system, for chaining.
170
246
  */
171
- requires<T extends (typeof Component)[]>(...components: [...T]): System<T>;
247
+ enable(): this;
248
+ /**
249
+ * Not supported on `System`. Throws unconditionally.
250
+ *
251
+ * Systems are owned by the world for the duration of the session; if you
252
+ * need a temporary reactive set use a standalone {@link Query} via
253
+ * {@link World.query}.
254
+ */
255
+ destroy(): never;
172
256
  }