@vworlds/vecs 1.0.25 → 1.0.27

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 (87) hide show
  1. package/README.md +29 -778
  2. package/dist/component.d.ts +1 -1
  3. package/dist/component.js +1 -1
  4. package/dist/component.js.map +1 -1
  5. package/dist/component_meta.d.ts +5 -1
  6. package/dist/component_meta.js +10 -0
  7. package/dist/component_meta.js.map +1 -1
  8. package/dist/dsl.d.ts +23 -2
  9. package/dist/dsl.js +21 -18
  10. package/dist/dsl.js.map +1 -1
  11. package/dist/entity/entity.base.d.ts +2 -18
  12. package/dist/entity/entity.base.js +2 -20
  13. package/dist/entity/entity.base.js.map +1 -1
  14. package/dist/entity/entity.components.d.ts +1 -0
  15. package/dist/entity/entity.components.js +50 -0
  16. package/dist/entity/entity.components.js.map +1 -1
  17. package/dist/entity/entity.d.ts +8 -0
  18. package/dist/entity/entity.js +21 -0
  19. package/dist/entity/entity.js.map +1 -1
  20. package/dist/entity/entity.lifecycle.d.ts +3 -3
  21. package/dist/entity/entity.lifecycle.js +6 -9
  22. package/dist/entity/entity.lifecycle.js.map +1 -1
  23. package/dist/entity/entity.relationships.d.ts +1 -1
  24. package/dist/entity/entity.relationships.js +3 -3
  25. package/dist/entity/entity.relationships.js.map +1 -1
  26. package/dist/filter.d.ts +1 -0
  27. package/dist/filter.js +3 -2
  28. package/dist/filter.js.map +1 -1
  29. package/dist/index.d.ts +4 -2
  30. package/dist/index.js +4 -2
  31. package/dist/index.js.map +1 -1
  32. package/dist/inject.d.ts +3 -2
  33. package/dist/inject.js.map +1 -1
  34. package/dist/modules/implements.d.ts +14 -0
  35. package/dist/modules/implements.js +98 -0
  36. package/dist/modules/implements.js.map +1 -0
  37. package/dist/modules/relationships.d.ts +1 -1
  38. package/dist/modules/relationships.js +2 -2
  39. package/dist/package.json +1 -4
  40. package/dist/query/callbacks.d.ts +6 -2
  41. package/dist/query/callbacks.js +5 -2
  42. package/dist/query/callbacks.js.map +1 -1
  43. package/dist/query/grouped_query.js +1 -1
  44. package/dist/query/query.d.ts +14 -1
  45. package/dist/query/query.js +26 -15
  46. package/dist/query/query.js.map +1 -1
  47. package/dist/system.d.ts +5 -4
  48. package/dist/system.js +17 -6
  49. package/dist/system.js.map +1 -1
  50. package/dist/terms/build.js +7 -5
  51. package/dist/terms/build.js.map +1 -1
  52. package/dist/util/array_map.d.ts +70 -12
  53. package/dist/util/array_map.js +113 -26
  54. package/dist/util/array_map.js.map +1 -1
  55. package/dist/util/bitset.js +0 -17
  56. package/dist/util/bitset.js.map +1 -1
  57. package/dist/util/events.d.ts +42 -12
  58. package/dist/util/events.js +94 -43
  59. package/dist/util/events.js.map +1 -1
  60. package/dist/util/ordered_set.js +43 -19
  61. package/dist/util/ordered_set.js.map +1 -1
  62. package/dist/world/world.deferred.js +2 -0
  63. package/dist/world/world.deferred.js.map +1 -1
  64. package/dist/world/world.entities.d.ts +8 -1
  65. package/dist/world/world.entities.js +25 -6
  66. package/dist/world/world.entities.js.map +1 -1
  67. package/dist/world/world.js +8 -1
  68. package/dist/world/world.js.map +1 -1
  69. package/dist/world/world.queries.js +6 -1
  70. package/dist/world/world.queries.js.map +1 -1
  71. package/dist/world/world.storage.d.ts +2 -2
  72. package/dist/world/world.storage.js +6 -3
  73. package/dist/world/world.storage.js.map +1 -1
  74. package/docs/README.md +50 -0
  75. package/docs/components.md +267 -0
  76. package/docs/concepts.md +86 -0
  77. package/docs/design-guide.md +506 -0
  78. package/docs/entities.md +177 -0
  79. package/docs/execution-model.md +173 -0
  80. package/docs/getting-started.md +215 -0
  81. package/docs/glossary.md +113 -0
  82. package/docs/modules.md +108 -0
  83. package/docs/queries-and-filters.md +187 -0
  84. package/docs/relationships.md +149 -0
  85. package/docs/systems.md +311 -0
  86. package/docs/utilities.md +139 -0
  87. package/package.json +1 -4
@@ -0,0 +1,311 @@
1
+ # Systems
2
+
3
+ Build systems: declare membership, react to entities entering, changing, and leaving, run per-tick
4
+ logic, control cadence, and inject components into callbacks.
5
+
6
+ ## What a system is
7
+
8
+ A `System` (`lib/vecs/src/system.ts`) is a reactive processor over a filtered subset of world
9
+ entities. It extends [`Query`](./queries-and-filters.md), so membership, `enter`/`exit`/`update`
10
+ callbacks, sorting, and tracking are shared — and adds pipeline integration: a phase slot, an
11
+ event **inbox** replayed once per tick, plus `run` and `each` callbacks.
12
+
13
+ Create systems through the world and configure them with a fluent builder (every method returns
14
+ `this`):
15
+
16
+ ```ts
17
+ world
18
+ .system("Render")
19
+ .with(Sprite, Position)
20
+ .enter([Sprite, Position], (e, [sprite, pos]) => sprite.show(pos))
21
+ .update(Position, [Sprite], (e, pos, [sprite]) => sprite.moveTo(pos.x, pos.y))
22
+ .exit([Sprite], (e, [sprite]) => sprite.hide());
23
+ ```
24
+
25
+ Newly created systems enter the pipeline the next time `world.progress` runs. Configuration must
26
+ happen before the system is built (the first `progress`/`flush` after creation); builder calls
27
+ after that throw.
28
+
29
+ ## Membership: `with` / `without`
30
+
31
+ `.with(...)` declares which entities the system tracks. It accepts component classes, arrays, and
32
+ the full query DSL (operator semantics in
33
+ [Queries and filters](./queries-and-filters.md#the-query-dsl)):
34
+
35
+ ```ts
36
+ .with(Position, Velocity) // all listed components
37
+ .with([Position, Velocity]) // array shorthand
38
+ .with({ all: [Position, { any: [Sprite, Container] }] }) // compound DSL
39
+ .with({ not: Invisible })
40
+ .without(Invisible) // shorthand for .with({ not: Invisible })
41
+ .with({ target: [FriendOf, Position] }) // relationship target has Position
42
+ .with({ parent: Body }) // ChildOf target has Body
43
+ ```
44
+
45
+ Multiple `with` calls merge with `all`. The `source` / `children` operators are non-reactive and
46
+ rejected by systems — they are filter-only (see
47
+ [Queries and filters](./queries-and-filters.md#filters-one-shot-scans)).
48
+
49
+ **Type inference.** Components that `with()` can see statically (plain classes, arrays, `all`,
50
+ `only`) become non-nullable in `orderBy`, `each`, `forEach`, and `update` injection tuples. For
51
+ DSL shapes the type system can't introspect (`any`, `not`, `test`, …), supply a `hint`:
52
+
53
+ ```ts
54
+ .with({ with: { any: [Position, Velocity] }, hint: [Position] })
55
+ .each([Position], (e, [pos]) => pos.x);
56
+ ```
57
+
58
+ `hint` is type-level only — it is not validated at runtime.
59
+
60
+ ## Phase placement
61
+
62
+ Systems default to the `vecs::OnUpdate` phase. `.phase(anchor)` moves the system after another
63
+ pipeline anchor, by name, constant, or entity:
64
+
65
+ ```ts
66
+ .phase(ON_LOAD) // a built-in phase constant
67
+ .phase("physics-step") // a custom anchor, created and tagged Phase if missing
68
+ .phase(world.entity("anchor")) // an existing entity
69
+ ```
70
+
71
+ A system lives in exactly one phase; calling `.phase` again replaces the previous placement. Phase
72
+ taxonomy, ordering rules, and custom phases are covered in
73
+ [Execution model](./execution-model.md#phases-the-pipeline-spine).
74
+
75
+ ## Reactive callbacks: `enter`, `update`, `exit`
76
+
77
+ Mutation events are routed into the system's inbox as the world applies commands; the inbox is
78
+ replayed in arrival order at the top of the system's next run, inside a deferred scope.
79
+
80
+ ### `.enter(callback)` / `.enter(inject, callback)`
81
+
82
+ Fires once when an entity starts matching the system:
83
+
84
+ ```ts
85
+ .enter((e) => { ... })
86
+ .enter([Position, Sprite], (e, [pos, sprite]) => sprite.setPosition(pos.x, pos.y))
87
+ ```
88
+
89
+ ### `.update(ComponentClass, callback)` / `.update(ComponentClass, inject, callback)`
90
+
91
+ Fires when the watched component is set or marked modified on a tracked entity — and **on entry**
92
+ for each watched component the entity already has. `update(ComponentClass, ...)` also adds
93
+ `ComponentClass` to the membership predicate, so the system only matches entities that carry the
94
+ watched component. The callback receives the entity first because component instances carry no owner
95
+ reference:
96
+
97
+ ```ts
98
+ .update(Position, (entity, pos) => renderer.setPosition(entity.eid, pos.x, pos.y))
99
+ .update(Position, [Sprite], (entity, pos, [sprite]) => sprite.setPosition(pos.x, pos.y))
100
+ ```
101
+
102
+ Because `update` fires on entry, a separate `enter` that applies the same value is redundant —
103
+ lean on `update` alone for "apply current value when the entity appears, and again on change".
104
+
105
+ Use `.update(C, ...)` by itself when `C` is the membership shape and watched value. Use
106
+ `.with(A).update(C, ...)` when the callback should run only for entities carrying both `A` and `C`.
107
+
108
+ A query or system can register only one `update(C, ...)` callback for each component. A duplicate
109
+ registration throws instead of replacing the existing callback.
110
+
111
+ ### `.exit(callback)` / `.exit(inject, callback)`
112
+
113
+ Fires when an entity stops matching (component removed, or entity destroyed). Directly injected
114
+ components are read from a **snapshot captured at exit time**, so they are still resolvable even
115
+ though they are being removed; `target`-injected components resolve live at callback time and may
116
+ be `undefined`:
117
+
118
+ ```ts
119
+ .exit([Sprite], (e, [sprite]) => sprite.destroy());
120
+ ```
121
+
122
+ ## Per-tick callbacks: `each` and `run`
123
+
124
+ ### `.each(components, callback)`
125
+
126
+ Fires every tick the system runs, once per tracked entity, regardless of change. Use it only for
127
+ genuinely per-frame sweeps — when reacting to change suffices, prefer `update` (see
128
+ [Design guide](./design-guide.md#each-vs-reactive--dont-scan-when-you-can-react)):
129
+
130
+ ```ts
131
+ .with(Position, Velocity)
132
+ .each([Position, Velocity], (e, [pos, vel]) => {
133
+ pos.x += vel.vx;
134
+ });
135
+ ```
136
+
137
+ `each` implies `.track()`. Only one `each` per system — a second call throws. The resolved tuple
138
+ array is reused across entities; read it inside the callback, don't retain it.
139
+
140
+ ### `.run(callback)`
141
+
142
+ Fires once per tick when the system's phase runs, regardless of entity membership. Use it for
143
+ polling, network I/O, stepping an embedded simulation:
144
+
145
+ ```ts
146
+ .run((now, delta) => {
147
+ sendNetworkPacket(now);
148
+ });
149
+ ```
150
+
151
+ `now` is the absolute timestamp passed to `progress`; `delta` is milliseconds since the previous
152
+ tick — or, when the system has a cadence source, the milliseconds accumulated since this system
153
+ last fired.
154
+
155
+ Within one system run the order is: inbox replay (`enter`/`exit`/`update` events), then `run`,
156
+ then `each`.
157
+
158
+ ## Component injection
159
+
160
+ `enter`, `exit`, `update`, `each`, `orderBy`, and `forEach` accept an injection list of component
161
+ classes, resolved per entity and passed as a typed tuple:
162
+
163
+ ```ts
164
+ .each([Position, Sprite], (e, [pos, sprite]) => { ... })
165
+ ```
166
+
167
+ Nullability follows membership: components guaranteed by `with` (or `hint`) are non-nullable;
168
+ anything else resolves as `T | undefined` — guard it. (`enter`/`exit` injection requires the
169
+ direct components to be present and throws otherwise; list only components your predicate
170
+ guarantees.)
171
+
172
+ ### Relationship injection: `target` and `down`
173
+
174
+ `{ target: [Rel, [C1, C2]] }` follows the entity's relationship to its target and flattens the
175
+ target's components into the tuple. Target-side slots are `undefined` when the relationship or the
176
+ component is absent:
177
+
178
+ ```ts
179
+ .forEach([Body, { target: [FriendOf, [Position, Velocity]] }], (e, [body, pos, vel]) => {
180
+ // body is from e; pos and vel are from e.target(FriendOf).
181
+ });
182
+ ```
183
+
184
+ With two or more components in the inner tuple, TypeScript widens the slot types to a union;
185
+ mark the inner tuple `as const` (`{ target: [FriendOf, [Position, Velocity] as const] }`) to get
186
+ precise per-slot types. Single-component tuples infer precisely as-is.
187
+
188
+ `{ down: [Rel, [C1]] }` fans out: the callback fires once per child targeting the visited entity
189
+ through `Rel`, with that child's components filling the slots (nullable). `down` is allowed only
190
+ in `each` and `forEach`, at most once per tuple, and entities with zero children produce no calls.
191
+ `down` iterates **all** children through the relationship, independent of any membership filter:
192
+
193
+ ```ts
194
+ .with(Body)
195
+ .each([Body, { down: [ChildOf, [Position]] }], (parent, [body, childPos]) => {
196
+ // One call per ChildOf child; childPos is undefined when that child lacks Position.
197
+ });
198
+ ```
199
+
200
+ ### The `Iter` cursor
201
+
202
+ Pass `Iter` (from `@vworlds/vecs`) as the first argument to `each`, `forEach`, `enter`, `exit`,
203
+ `update`, or `orderBy` to receive a reusable cursor instead of the bare entity:
204
+
205
+ ```ts
206
+ import { Iter } from "@vworlds/vecs";
207
+
208
+ system.each(Iter, [Body, { target: [ChildOf, [Position]] }], (it, [body, pos]) => {
209
+ it.entity; // the visited entity
210
+ it.src[0]; // entity body was read from (the visited entity)
211
+ it.src[1]; // entity pos was read from (the ChildOf target), or undefined
212
+ });
213
+ ```
214
+
215
+ `it.src` holds the source entity per tuple slot: the visited entity for direct components, the
216
+ relationship target for `target` slots, the specific child for `down` slots. When `Iter` is not
217
+ requested, the non-cursor path runs with zero per-entity overhead — dispatch happens once at setup
218
+ time. `each`/`forEach`/`update`/`orderBy` reuse cursor and tuple instances across the pass, so
219
+ snapshot what you need before triggering further mutations from inside a callback. Passing
220
+ `Entity` as the first argument is also accepted and means the default entity-first signature.
221
+
222
+ ## Ordering and tracking
223
+
224
+ ### `.orderBy(components, compare)`
225
+
226
+ Keep matched entities in a custom order. Iteration, `forEach`, and `each` then walk entities in
227
+ sorted order. Implies tracking:
228
+
229
+ ```ts
230
+ world
231
+ .system("Render")
232
+ .with(Position, Sprite)
233
+ .orderBy([Position], (_ea, [a], _eb, [b]) => a.y - b.y)
234
+ .each([Position, Sprite], (e, [pos, sprite]) => sprite.draw(pos.x, pos.y));
235
+ ```
236
+
237
+ Ties break by entity id, so the order is deterministic.
238
+
239
+ The order is maintained reactively. When data for a component in the injection tuple changes via
240
+ `set` or `modified`, the system refreshes the sort before the next ordered read. Keep comparators
241
+ dependent only on those injected components; unrelated component data is not watched for ordering.
242
+
243
+ ### `.track()`
244
+
245
+ Enable membership tracking without an `each`: exposes `count`, `has(e)`, iteration, and `forEach`.
246
+ `each` and `orderBy` imply it. Tracked systems backfill already-matching entities when built.
247
+
248
+ ## Cadence: `interval`, `rate`, and tick sources
249
+
250
+ By default a system fires on every frame of its phase. Three builders change that
251
+ (`lib/vecs/src/timer.ts`):
252
+
253
+ ```ts
254
+ world.system("AI").interval(0.5).run(tickAI); // at most every 0.5 s (seconds!)
255
+ world.system("Send").rate(2).run(flush); // every 2nd tick of the current source
256
+ world.system("Log").tickSource(clock).run(log); // driven by a shared ITickSource
257
+ ```
258
+
259
+ - `.interval(seconds)` replaces the cadence with an `IntervalTickSource`. Note the unit: seconds,
260
+ unlike the millisecond deltas of `progress`.
261
+ - `.rate(n)` divides the current cadence source by `n` (for an unconfigured system: every `n`
262
+ frames). `.rate(n, source)` divides an explicit upstream source.
263
+ - `.tickSource(source)` mirrors another source directly — an `IntervalTickSource`,
264
+ `RateTickSource`, or **another system** (systems implement `ITickSource`).
265
+
266
+ When a cadence source is active, the `delta` passed to `run` is the accumulated milliseconds since
267
+ this system last fired. Standalone sources support `stop()`/`start()`. Chains compose:
268
+
269
+ ```ts
270
+ const second = new IntervalTickSource(1);
271
+ const minute = new RateTickSource(60, second);
272
+
273
+ world.system("Hourly").tickSource(minute).rate(60).run((now, delta) => { ... });
274
+ ```
275
+
276
+ Disabling a system suppresses its callbacks, but a disabled system used as a tick source still
277
+ drives downstream consumers — stop the clock itself if you need silence.
278
+
279
+ ## `disable` / `enable` / `destroy`
280
+
281
+ ```ts
282
+ const ai = world.system("AI").with(Enemy).run(tickAI);
283
+ ai.disable(); // pause: inbox cleared, new events dropped, run/each silent
284
+ ai.enable(); // resume; events from the disabled period are NOT replayed
285
+ ai.destroy(); // permanent: disables, then unregisters the backing entity
286
+ ```
287
+
288
+ While disabled, query membership is still maintained, so the tracked set is correct on resume.
289
+ `destroy` fires no `exit` callbacks. Both `disable` and `enable` are idempotent and chainable.
290
+
291
+ ## Reference
292
+
293
+ | Method | Description |
294
+ | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
295
+ | `with(...specs)` / `without(dsl)` | Declare membership; see [the DSL](./queries-and-filters.md#the-query-dsl). |
296
+ | `phase(anchor)` | Place after a phase anchor (name or entity). Default `ON_UPDATE`. |
297
+ | `enter(cb)` / `enter(inject, cb)` | Fires once when an entity starts matching. |
298
+ | `update(C, cb)` / `update(C, inject, cb)` | Fires on `set`/`modified` of `C` on a member, and on entry for present watched components. |
299
+ | `exit(cb)` / `exit(inject, cb)` | Fires when an entity stops matching; direct injection reads an exit-time snapshot. |
300
+ | `each([C...], cb)` | Fires per tracked entity every tick. Implies `track`. One per system. |
301
+ | `run(cb)` | Fires once per tick, `(now, delta)`. |
302
+ | `interval(seconds)` / `rate(n[, source])` / `tickSource(source)` | Cadence control. |
303
+ | `orderBy([C...], compare)` | Sorted membership refreshed when injected comparator data changes. Implies `track`. |
304
+ | `track()` | Expose `count`, `has`, iteration, `forEach` without `each`. |
305
+ | `ignoreSource(query \| dsl)` | Drop `update` events originating from matching systems; see [Queries](./queries-and-filters.md#silencing-feedback-ignoresource). |
306
+ | `disable()` / `enable()` | Pause / resume processing. Idempotent. |
307
+ | `destroy()` | Permanently remove the system (no `exit` callbacks). |
308
+ | `didTick` / `lastFireDelta` | This frame's cadence result; ms accumulated into the last fire. |
309
+ | `count` / `has(e)` / `belongs(e)` / iteration / `forEach` | Shared query surface; see [Queries](./queries-and-filters.md#reading-a-query). |
310
+
311
+ `groupBy` is not available on systems — use `world.query(...).groupBy(...)`.
@@ -0,0 +1,139 @@
1
+ # Utilities
2
+
3
+ Public helper classes exported by `@vworlds/vecs` for code that lives around the ECS core.
4
+
5
+ ## `Events`
6
+
7
+ Use `Events` when a vecs package or integration needs typed package-level events without adding an
8
+ external runtime dependency. It is not an entity lifecycle API; entity teardown is modeled through
9
+ component removal, hooks, query exits, and relationship cleanup.
10
+
11
+ ```ts
12
+ import { Events } from "@vworlds/vecs";
13
+
14
+ type ClientEvents = {
15
+ reset(event: { initial: boolean }): void;
16
+ };
17
+
18
+ const events = new Events<ClientEvents>();
19
+ events.on("reset", (event) => console.log(event.initial));
20
+ events.emit("reset", { initial: true });
21
+ ```
22
+
23
+ ### Reference
24
+
25
+ | Method | Description |
26
+ | ---------------------------------- | -------------------------------------------------------------------------- |
27
+ | `on(event, fn, context?)` | Register a listener. |
28
+ | `addListener(event, fn, ctx?)` | Alias of `on`. |
29
+ | `once(event, fn, context?)` | Register a listener that removes itself before its first call. |
30
+ | `emit(event, ...args)` | Invoke listeners and return `true` when at least one listener ran. |
31
+ | `off(event, fn?, context?)` | Remove one listener, or every listener for the event when `fn` is omitted. |
32
+ | `removeListener(event, fn?, ctx?)` | Alias of `off`. |
33
+ | `removeAllListeners(event?)` | Remove listeners for one event, or all listeners when no event is given. |
34
+ | `eventNames()` | Return event names with at least one listener. |
35
+ | `listeners(event)` | Return registered listener functions for an event. |
36
+ | `listenerCount(event)` | Return the number of listeners registered for an event. |
37
+
38
+ ## `ArrayMap`
39
+
40
+ `ArrayMap` (`lib/vecs/src/util/array_map.ts`) is a cheap `Map<number, T>` substitute backed by a
41
+ sparse JavaScript array. Lookups are a single array read and memory usage is one sparse backing
42
+ array. Use it when you only need random access (`get`, `set`, `has`, `delete`) by non-negative
43
+ integer keys and don't need iteration.
44
+
45
+ The world and entity APIs return read-only `ArrayMap` views (e.g. `entity.components`).
46
+
47
+ ```ts
48
+ import { ArrayMap } from "@vworlds/vecs";
49
+
50
+ const map = new ArrayMap<string>();
51
+ map.set(42, "hello");
52
+ map.get(42); // "hello"
53
+ map.has(42); // true
54
+ map.delete(42);
55
+ map.size; // 0
56
+ ```
57
+
58
+ ### Reference
59
+
60
+ | Method | Description |
61
+ | --------------- | ----------------------------------------------------------------- |
62
+ | `set(key, val)` | Insert or replace the value at `key`. Increments `size` when new. |
63
+ | `get(key)` | Retrieve the value at `key`, or `undefined` if no entry exists. |
64
+ | `has(key)` | `true` when an entry exists at `key`. |
65
+ | `delete(key)` | Remove the entry at `key`. Does nothing if no entry exists there. |
66
+ | `clear()` | Remove all entries and reset `size` to zero. |
67
+ | `size` | The number of entries currently in the map. |
68
+
69
+ ## `IterableArrayMap`
70
+
71
+ `IterableArrayMap` (`lib/vecs/src/util/array_map.ts`) is an iterable `Map<number, T>` substitute
72
+ backed by dense entries plus sparse indexes. Use it when callers need fast `forEach` or `values`
73
+ over present entries. Random access resolves `key → dense index` through an internal `ArrayMap`,
74
+ then indexes into a dense items array; iteration walks the dense items array directly.
75
+
76
+ Deletes keep the items array dense by moving the last value into the removed slot, so iteration
77
+ is always a tight loop without sparse holes. Compared with `ArrayMap`, this costs extra memory
78
+ for tuple storage and an index map.
79
+
80
+ ```ts
81
+ import { IterableArrayMap } from "@vworlds/vecs";
82
+
83
+ const map = new IterableArrayMap<{ x: number }>();
84
+ map.set(1, { x: 10 });
85
+ map.set(3, { x: 30 });
86
+
87
+ for (const [key, value] of map) {
88
+ console.log(key, value.x); // 1 10, 3 30 (or swapped after a delete)
89
+ }
90
+ ```
91
+
92
+ ### Reference
93
+
94
+ | Method | Description |
95
+ | ------------------- | ---------------------------------------------------------------------- |
96
+ | `set(key, val)` | Insert or replace the value at `key`. |
97
+ | `get(key)` | Retrieve the value at `key`, or `undefined` if no entry exists. |
98
+ | `has(key)` | `true` when an entry exists at `key`. |
99
+ | `delete(key)` | Remove the entry at `key`. Swaps last item into vacated slot (O(1)). |
100
+ | `forEach(cb)` | Visit every entry in dense item order, invoking `cb(value, key, map)`. |
101
+ | `entries()` | Iterator over `[key, value]` entries in dense item order. |
102
+ | `values()` | Every present value in dense item order as `T[]`. |
103
+ | `[Symbol.iterator]` | Same as `entries()`; supports `for..of`. |
104
+ | `clear()` | Remove all entries and reset `size` to zero. |
105
+ | `size` | The number of entries currently in the map. |
106
+
107
+ ## `Bitset`
108
+
109
+ `Bitset` (`lib/vecs/src/util/bitset.ts`) is the compact integer-set type the ECS uses for entity
110
+ archetypes and watchlists. It is exported so component data can use it for bit-flag fields:
111
+
112
+ ```ts
113
+ import { Bitset } from "@vworlds/vecs";
114
+
115
+ class Tags {
116
+ tags = new Bitset();
117
+ }
118
+
119
+ tags.tags.add(TAG_VISIBLE);
120
+ if (tags.tags.has(TAG_VISIBLE)) { ... }
121
+ ```
122
+
123
+ ### Reference
124
+
125
+ | Method | Description |
126
+ | --------------------------------------------------- | ------------------------------------------------------------------------------------ |
127
+ | `add(n)` | Set bit `n`. |
128
+ | `delete(n)` | Clear bit `n`. Storage is not compacted automatically; call `compact()` when needed. |
129
+ | `addBit(bptr)` / `deleteBit(bptr)` / `hasBit(bptr)` | Fast paths using a pre-computed `BitPtr`. |
130
+ | `has(n)` | `true` when bit `n` is set. |
131
+ | `equal(other)` | `true` when both bitsets have exactly the same bits set. |
132
+ | `hasBitset(other)` | `true` when every bit in `other` is also set here (subset check). |
133
+ | `forEach(cb)` | Visit each set bit index in ascending order. |
134
+ | `indices()` | All set bit indices as `number[]`. |
135
+ | `clear()` | Remove every set bit. |
136
+ | `compact()` | Trim trailing zero words from backing storage. |
137
+
138
+ Related: `getDSLKey(dsl, world)` returns a deterministic 32-bit hash of a DSL expression
139
+ (equivalent expressions hash identically), useful for caching keyed on predicates.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vworlds/vecs",
3
- "version": "1.0.25",
3
+ "version": "1.0.27",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -22,9 +22,6 @@
22
22
  "typecheck": "tsc --noEmit",
23
23
  "format:check": "prettier --check \"**/*.{ts,md}\" --ignore-path ../../.prettierignore"
24
24
  },
25
- "dependencies": {
26
- "eventemitter3": "^4.0.7"
27
- },
28
25
  "license": "UNLICENSED",
29
26
  "repository": {
30
27
  "type": "git",