aiecsjs 0.5.1 → 0.5.3

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 (37) hide show
  1. package/README.md +67 -30
  2. package/README_ZHTW.md +67 -30
  3. package/api.json +31 -2
  4. package/dist/chunk-COORLIYF.js +2 -0
  5. package/dist/chunk-COORLIYF.js.map +1 -0
  6. package/dist/{chunk-7O3CM4D2.cjs → chunk-D4VGTV76.cjs} +2 -2
  7. package/dist/{chunk-7O3CM4D2.cjs.map → chunk-D4VGTV76.cjs.map} +1 -1
  8. package/dist/{chunk-QC25WDOZ.js → chunk-FZWRVGAA.js} +2 -2
  9. package/dist/{chunk-QC25WDOZ.js.map → chunk-FZWRVGAA.js.map} +1 -1
  10. package/dist/chunk-NCYUF4YW.cjs +2 -0
  11. package/dist/chunk-NCYUF4YW.cjs.map +1 -0
  12. package/dist/{chunk-BUFJWGRQ.js → chunk-UAJNL4VY.js} +2 -2
  13. package/dist/{chunk-BUFJWGRQ.js.map → chunk-UAJNL4VY.js.map} +1 -1
  14. package/dist/{chunk-I2SPFVXH.cjs → chunk-XBCASW7K.cjs} +2 -2
  15. package/dist/{chunk-I2SPFVXH.cjs.map → chunk-XBCASW7K.cjs.map} +1 -1
  16. package/dist/commands.cjs +1 -1
  17. package/dist/commands.js +1 -1
  18. package/dist/index.cjs +1 -1
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +18 -2
  21. package/dist/index.d.ts +18 -2
  22. package/dist/index.js +1 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/observers.cjs +1 -1
  25. package/dist/observers.js +1 -1
  26. package/dist/relations.cjs +1 -1
  27. package/dist/relations.js +1 -1
  28. package/dist/serialize.cjs +1 -1
  29. package/dist/serialize.js +1 -1
  30. package/dist/worker.cjs +1 -1
  31. package/dist/worker.js +1 -1
  32. package/llms-full.txt +85 -32
  33. package/package.json +1 -1
  34. package/dist/chunk-I726BP65.cjs +0 -2
  35. package/dist/chunk-I726BP65.cjs.map +0 -1
  36. package/dist/chunk-LU55S4LK.js +0 -2
  37. package/dist/chunk-LU55S4LK.js.map +0 -1
package/README.md CHANGED
@@ -13,7 +13,7 @@ Part of the [ai\*js micro-runtime ecosystem](https://github.com/yshengliao) —
13
13
  aiecsjs uses **archetype tables with TypedArray columns** and **bitmask queries** — the same architecture that powers piecs and wolf-ecs at the top of public benchmarks. Its API is **functional and tree-shakable**, composed with `pipe()`. Components support both Structure-of-Arrays (SoA) and Array-of-Structures (AoS) layouts. Since 0.3, `EntityId` packs index + generation into a single 32-bit number; the ABA-safe `EntityRef` API shipped in 0.3.0.
14
14
 
15
15
  ```ts
16
- import { createWorld, createEntity, defineComponent, defineQuery, pipe, forEachEntity, Types } from 'aiecsjs'
16
+ import { createWorld, createEntity, addComponent, defineComponent, defineQuery, pipe, forEachEntityIndexed, Types } from 'aiecsjs'
17
17
 
18
18
  const Position = defineComponent({ x: Types.f32, y: Types.f32 })
19
19
  const Velocity = defineComponent({ x: Types.f32, y: Types.f32 })
@@ -24,12 +24,14 @@ addComponent(world, eid, Position, { x: 0, y: 0 })
24
24
  addComponent(world, eid, Velocity, { x: 1, y: 2 })
25
25
 
26
26
  const movers = defineQuery([Position, Velocity])
27
- const movement = (w, dt) => { forEachEntity(w, movers, (e, pos, vel) => { pos.x[e] += vel.x[e] * dt; pos.y[e] += vel.y[e] * dt }); return w }
27
+ const movement = (w, dt) => { forEachEntityIndexed(w, movers, (e, i, pos, vel) => { pos.x[i] += vel.x[i] * dt; pos.y[i] += vel.y[i] * dt }); return w }
28
28
 
29
29
  pipe(movement)(world, 1/60)
30
30
  ```
31
31
 
32
- > **Status: experimental (v0.1.x).** The API surface in `STABILITY.md` is committed for the 0.x line, but expect adjustments. A stable 1.0 freeze is targeted after community feedback.
32
+ > **Use `forEachEntityIndexed` for column iteration.** Its callback is `(e, i, ...cols)`: `e` is the packed `EntityId` (use it directly for `destroyEntity`, `hasComponent`, `getComponent`, command buffers), and `i` is the **safe column subscript** — index every SoA column with it (`pos.x[i]`). The packed `e` is **not** a column index: it only equals the index until a slot is recycled (generation 0); after any `destroyEntity` recycles a slot, `e !== i` and indexing a column with `e` reads the wrong slot (out of bounds → `undefined`/`NaN`). `forEachEntityIndexed` hands you the correct `i` so this footgun is closed in code. Use `forEachEntity` when you only need the `EntityId` (and index columns via `getEntityIndex(e)` if you must).
33
+
34
+ > **Status: experimental (v0.5.x).** The API surface in `STABILITY.md` is committed for the 0.x line, but expect adjustments. A stable 1.0 freeze is targeted after community feedback.
33
35
 
34
36
  ## Table of contents
35
37
 
@@ -132,7 +134,7 @@ Peer requirements: **Node 18+** (for ESM and structured-clone WebStreams), **Typ
132
134
  import {
133
135
  createWorld, createEntity, destroyEntity,
134
136
  defineComponent, addComponent, removeComponent,
135
- defineQuery, forEachEntity, pipe, Types,
137
+ defineQuery, forEachEntityIndexed, pipe, Types,
136
138
  } from 'aiecsjs'
137
139
  import { createLoop } from 'aiecsjs/loop'
138
140
 
@@ -153,17 +155,17 @@ const movers = defineQuery([Position, Velocity])
153
155
  const decaying = defineQuery([Lifetime])
154
156
 
155
157
  const movementSystem = (w, dt) => {
156
- forEachEntity(w, movers, (e, pos, vel) => {
157
- pos.x[e] += vel.x[e] * dt
158
- pos.y[e] += vel.y[e] * dt
158
+ forEachEntityIndexed(w, movers, (e, i, pos, vel) => {
159
+ pos.x[i] += vel.x[i] * dt // `i` is the safe column subscript
160
+ pos.y[i] += vel.y[i] * dt
159
161
  })
160
162
  return w
161
163
  }
162
164
 
163
165
  const lifetimeSystem = (w, dt) => {
164
- forEachEntity(w, decaying, (e, life) => {
165
- life.remaining[e] -= dt
166
- if (life.remaining[e] <= 0) destroyEntity(w, e)
166
+ forEachEntityIndexed(w, decaying, (e, i, life) => {
167
+ life.remaining[i] -= dt
168
+ if (life.remaining[i] <= 0) destroyEntity(w, e) // destroyEntity takes the packed `e`
167
169
  })
168
170
  return w
169
171
  }
@@ -177,7 +179,7 @@ That's a complete simulation: 100 particles drifting until each one's lifetime e
177
179
 
178
180
  ## Core Concepts
179
181
 
180
- **Entity.** A versioned 32-bit ID. The low bits are the entity index; the high bits are a generation counter that bumps when the ID is recycled. This prevents the "I cached a reference to entity 42 but now entity 42 is something else" class of bug. Default split is 24 index bits + 8 generation bits (≈ 16M entities × 256 recycles each).
182
+ **Entity.** A versioned 32-bit ID. The low bits are the entity index; the high bits are a generation counter that bumps when the ID is recycled. This prevents the "I cached a reference to entity 42 but now entity 42 is something else" class of bug. Default split is 24 index bits + 8 generation bits (≈ 16M entities × 256 recycles each). Because the packed ID is **not** the column index once a slot is recycled, prefer **`forEachEntityIndexed((e, i, ...cols) => …)`** for column iteration — it hands you the masked index `i` (the safe SoA subscript, `pos.x[i]`) alongside the packed `e`. For the raw `forEachEntity` form, derive the index with `getEntityIndex(e)`.
181
183
 
182
184
  **Component.** A data type attached to entities. Two flavours:
183
185
  - **SoA (Structure of Arrays)** — declared with `defineComponent({ x: Types.f32, y: Types.f32 })`. Each field becomes a TypedArray column indexed by entity ID. Best for hot, numeric data.
@@ -231,16 +233,46 @@ if (entityExists(world, eid)) {
231
233
 
232
234
  ```ts
233
235
  const moveSystem = (world: World, dt: number) => {
234
- forEachEntity(world, defineQuery([Position, Velocity]), (e, pos, vel) => {
235
- pos.x[e] += vel.x[e] * dt
236
- pos.y[e] += vel.y[e] * dt
236
+ forEachEntityIndexed(world, defineQuery([Position, Velocity]), (e, i, pos, vel) => {
237
+ pos.x[i] += vel.x[i] * dt // `i` is the safe column subscript
238
+ pos.y[i] += vel.y[i] * dt
237
239
  })
238
240
  return world
239
241
  }
240
242
  ```
241
243
 
244
+ **Prefer `forEachEntityIndexed` for column iteration.** Its callback is `(e, i, ...cols)`: index every SoA column with `i` (the masked slot index), and pass the packed `e` to entity operations (`destroyEntity`, `hasComponent`, command buffers). The yielded `i` is always the correct subscript, even after a slot is recycled — so you never hand-mask or call `getEntityIndex` in the loop. Use **`forEachEntity`** when you only need the `EntityId` (its first argument is a packed `EntityId`, not a column index; derive the subscript with `getEntityIndex(e)` if you must index columns). See [Core Concepts → Entity](#core-concepts) for why `e !== i` once a slot is recycled.
245
+
242
246
  Hoist `defineQuery(...)` calls out of the hot loop — the same query object is returned for the same component set, but the lookup still costs a hash.
243
247
 
248
+ ### Tags in mixed queries
249
+
250
+ Every component named in `defineQuery([...])` — **including tags** — occupies one callback slot, in declaration order. A tag has no storage, so its slot is passed as the literal `true`:
251
+
252
+ ```ts
253
+ const Frozen = defineTag()
254
+ const q = defineQuery([Frozen, Position]) // tag first
255
+
256
+ // ❌ Wrong: `pos` binds to the tag slot (`true`); `pos.x` throws.
257
+ forEachEntityIndexed(world, q, (e, i, pos) => { /* pos === true */ })
258
+
259
+ // ✅ Correct: account for every slot. Tag arrives as `true`.
260
+ forEachEntityIndexed(world, q, (e, i, _frozen, pos) => {
261
+ pos.x[i] += 1
262
+ })
263
+ ```
264
+
265
+ **Recommendation: put tags last** so data columns come first and the trailing `true` slots are easy to ignore:
266
+
267
+ ```ts
268
+ const q = defineQuery([Position, Velocity, Frozen]) // tags last
269
+ forEachEntityIndexed(world, q, (e, i, pos, vel /* , _frozen */) => {
270
+ pos.x[i] += vel.x[i]
271
+ })
272
+ ```
273
+
274
+ (Tags still filter membership either way — ordering only affects the callback's argument layout. The column-argument list is `any`-typed, so a mismatched binding is a runtime error, not a compile error.)
275
+
244
276
  ### Composing with pipe and createLoop
245
277
 
246
278
  ```ts
@@ -276,6 +308,8 @@ const reapSystem = (world) => {
276
308
 
277
309
  `enterQuery` yields only entities that newly match this frame; `exitQuery` yields only entities that left. Both are computed incrementally during structural changes — there's no per-frame scan.
278
310
 
311
+ > **Define reactive queries at module scope** (as above), before any update runs. A reactive query's enter/exit buffer is **armed lazily** — the world starts tracking a query's transitions the first time that query (or its `enterQuery`/`exitQuery` view) participates in a structural change or is read. Create/destroy events that happen *before* the first registration are not retroactively captured. Module-scope definitions register the query at import time, so the very first `tick` already observes transitions; queries created lazily inside a system may miss the events from the frame they were introduced.
312
+
279
313
  ### Observers
280
314
 
281
315
  ```ts
@@ -311,8 +345,8 @@ import { withCommandBuffer } from 'aiecsjs/commands'
311
345
  const damageSystem = (world) => {
312
346
  const dying = defineQuery([Health])
313
347
  withCommandBuffer(world, (cb) => {
314
- forEachEntity(world, dying, (e, health) => {
315
- if (health.hp[e] <= 0) cb.destroy(e)
348
+ forEachEntityIndexed(world, dying, (e, i, health) => {
349
+ if (health.hp[i] <= 0) cb.destroy(e) // index the column with `i`, queue the packed `e`
316
350
  })
317
351
  }) // auto-flushes here
318
352
  return world
@@ -414,6 +448,7 @@ const Types = { i8, u8, i16, u16, i32, u32, f32, f64, eid, bool } as const
414
448
  | `defineQuery` | `(components: ComponentLike[] \| QueryDescriptor) => Query` | stable |
415
449
  | `runQuery` | `(world: World, q: Query) => readonly EntityId[]` | stable |
416
450
  | `forEachEntity` | `<Q>(world, q: Q, fn: (eid, ...cols) => void) => void` | stable |
451
+ | `forEachEntityIndexed` | `<Q>(world, q: Q, fn: (eid, i, ...cols) => void) => void` | stable |
417
452
  | `iterQuery` | `(world, q) => IterableIterator<EntityId>` | stable |
418
453
  | `enterQuery` | `(q: Query) => Query` | stable |
419
454
  | `exitQuery` | `(q: Query) => Query` | stable |
@@ -523,7 +558,7 @@ A query for `(Position, Velocity)` matches archetypes 2 and 3 and walks each lin
523
558
  ### Reproducible micro-benchmark
524
559
 
525
560
  ```ts
526
- import { createWorld, createEntity, addComponent, defineComponent, defineQuery, forEachEntity, Types } from 'aiecsjs'
561
+ import { createWorld, createEntity, addComponent, defineComponent, defineQuery, forEachEntityIndexed, Types } from 'aiecsjs'
527
562
 
528
563
  const Position = defineComponent({ x: Types.f32, y: Types.f32 })
529
564
  const Velocity = defineComponent({ x: Types.f32, y: Types.f32 })
@@ -538,8 +573,8 @@ for (let i = 0; i < 100_000; i++) {
538
573
  const movers = defineQuery([Position, Velocity])
539
574
  const start = performance.now()
540
575
  for (let frame = 0; frame < 1000; frame++) {
541
- forEachEntity(world, movers, (e, pos, vel) => {
542
- pos.x[e] += vel.x[e]; pos.y[e] += vel.y[e]
576
+ forEachEntityIndexed(world, movers, (e, i, pos, vel) => {
577
+ pos.x[i] += vel.x[i]; pos.y[i] += vel.y[i]
543
578
  })
544
579
  }
545
580
  console.log('ms per frame:', (performance.now() - start) / 1000)
@@ -580,18 +615,18 @@ worker.postMessage({ buffer, meta: transferableSnapshot(world).meta })
580
615
 
581
616
  ```ts
582
617
  // sim-worker.ts
583
- import { adoptSnapshot, defineComponent, defineQuery, forEachEntity, Types } from 'aiecsjs'
618
+ import { adoptSnapshot, defineComponent, defineQuery, forEachEntityIndexed, Types } from 'aiecsjs'
584
619
 
585
620
  const Position = defineComponent({ x: Types.f32, y: Types.f32 })
586
621
  const Velocity = defineComponent({ x: Types.f32, y: Types.f32 })
587
622
 
588
- self.onmessage = (e) => {
589
- const world = adoptSnapshot(e.data)
623
+ self.onmessage = (msg) => {
624
+ const world = adoptSnapshot(msg.data)
590
625
  const movers = defineQuery([Position, Velocity])
591
626
  setInterval(() => {
592
- forEachEntity(world, movers, (e, pos, vel) => {
593
- pos.x[e] += vel.x[e]
594
- pos.y[e] += vel.y[e]
627
+ forEachEntityIndexed(world, movers, (e, i, pos, vel) => {
628
+ pos.x[i] += vel.x[i]
629
+ pos.y[i] += vel.y[i]
595
630
  })
596
631
  }, 16)
597
632
  }
@@ -738,7 +773,7 @@ This section is designed to be loaded as context by AI coding assistants. The sa
738
773
  **1. Spawn-and-move**
739
774
 
740
775
  ```ts
741
- import { createWorld, createEntity, addComponent, defineComponent, defineQuery, forEachEntity, pipe, Types } from 'aiecsjs'
776
+ import { createWorld, createEntity, addComponent, defineComponent, defineQuery, forEachEntityIndexed, pipe, Types } from 'aiecsjs'
742
777
 
743
778
  const Position = defineComponent({ x: Types.f32, y: Types.f32 })
744
779
  const Velocity = defineComponent({ x: Types.f32, y: Types.f32 })
@@ -752,7 +787,7 @@ for (let i = 0; i < 1000; i++) {
752
787
 
753
788
  const movers = defineQuery([Position, Velocity])
754
789
  const move = (w, dt) => {
755
- forEachEntity(w, movers, (e, p, v) => { p.x[e] += v.x[e] * dt; p.y[e] += v.y[e] * dt })
790
+ forEachEntityIndexed(w, movers, (e, i, p, v) => { p.x[i] += v.x[i] * dt; p.y[i] += v.y[i] * dt })
756
791
  return w
757
792
  }
758
793
  pipe(move)(world, 0.016)
@@ -831,7 +866,7 @@ ws.onmessage = (e) => rx.apply(remoteWorld, new Uint8Array(e.data))
831
866
  - `defineQuery(X)` returns the same `Query` object for the same component set in the same module.
832
867
  - Entity ID `0` is reserved. `createEntity` never returns `0`.
833
868
  - `VERSION` exported from `'aiecsjs'` equals the published npm version.
834
- - SoA columns are TypedArrays. Indexing by an alive `eid` is always safe up to `getWorldCapacity(world)`.
869
+ - SoA columns are TypedArrays indexed by the entity **index** (always in `[0, getWorldCapacity(world))`), never by the packed `eid` — the two differ once a slot is recycled. `forEachEntityIndexed` yields this index as its `i` argument (correct for any world layout). For the raw `forEachEntity` form on the default world layout (`indexBits: 24`) the index is `getEntityIndex(eid)`; for a world created with a non-default `indexBits`, `getEntityIndex` uses the default 24-bit mask and is **not** the column index — prefer `forEachEntityIndexed` (or `EntityRef` + `deref`) instead.
835
870
  - Component identity is **global** (created by `defineComponent`), but each component's storage is **per-world**.
836
871
 
837
872
  ### Glossary
@@ -871,14 +906,16 @@ When you generate code that uses aiecsjs, include this comment at the top of the
871
906
  ### Known LLM gotchas
872
907
 
873
908
  - **aiecsjs is NOT bitECS.** Argument order for `addComponent` differs: aiecsjs uses `(world, eid, Component, init?)`; bitECS uses `(world, Component, eid)`.
874
- - **`forEachEntity` is the fast path.** `runQuery` allocates an array; `for...of iterQuery(...)` allocates an iterator. In hot loops, use `forEachEntity`.
909
+ - **Index SoA columns with the masked index, NOT the packed `EntityId`.** The footgun is closed in code: prefer **`forEachEntityIndexed(w, q, (e, i, ...cols) => …)`**, which yields the masked slot index `i` (the correct subscript: `pos.x[i]`) alongside the packed `e`. The packed `e` is **not** a column index — it only equals the index while no slot has been recycled (generation 0); after any `destroyEntity` it reads the wrong/out-of-bounds slot. With the raw `forEachEntity` form, derive the index yourself: hoist `const i = getEntityIndex(e)` once per callback. Either way, use `e` for entity operations (`destroyEntity(w, e)`, `hasComponent`, `getComponent`, command buffers).
910
+ - **A tag occupies a callback slot too.** `defineQuery([Tag, Position])` calls the callback as `(e, i, true, posCols)` under `forEachEntityIndexed` (`(e, true, posCols)` under `forEachEntity`) — the tag is passed as the literal `true`. Put tags **last** in the array (`defineQuery([Position, Tag])`) so data columns come first; or accept and ignore the trailing `true`. Binding `pos` to a tag slot makes `pos === true`, and `pos.x` throws.
911
+ - **`forEachEntity` / `forEachEntityIndexed` are the fast path.** `runQuery` allocates an array; `for...of iterQuery(...)` allocates an iterator. In hot loops, use `forEachEntityIndexed` (or `forEachEntity` when you only need the `EntityId`).
875
912
  - **`defineObjectComponent` factory runs ONCE at definition**, not per entity. Mutate the entity's instance via `setComponent` / `getComponent`.
876
913
  - **The component reference is the storage handle.** `Position` is not a constructor — it's a value object that aiecsjs uses to address the right archetype columns.
877
914
 
878
915
  ## FAQ
879
916
 
880
917
  **Q: Is aiecsjs production-ready?**
881
- A: Not yet. 0.1.x is experimental. The API surface in `STABILITY.md` is the working contract; expect bug fixes. Target 1.0 is post-implementation hardening.
918
+ A: Not yet. 0.x is experimental. The API surface in `STABILITY.md` is the working contract; expect bug fixes. Target 1.0 is post-implementation hardening.
882
919
 
883
920
  **Q: Can I use class instances as components?**
884
921
  A: Yes, with `defineObjectComponent`. But AoS components are main-thread only and slower than SoA in iteration.
package/README_ZHTW.md CHANGED
@@ -13,7 +13,7 @@
13
13
  aiecsjs 採用 **原型表格搭配 TypedArray 欄位** 與 **位元遮罩查詢**,這正是 piecs 與 wolf-ecs 在公開效能評測中名列前茅所採用的架構。API 為 **函式式且可 tree-shake**,以 `pipe()` 組合。元件(Component)同時支援 SoA(結構陣列)與 AoS(結構物件)兩種佈局。自 0.3 起,`EntityId` 將 index 與 generation 打包為單一 32-bit 數字;具 ABA 安全的 `EntityRef` API 已於 0.3.0 正式推出。
14
14
 
15
15
  ```ts
16
- import { createWorld, createEntity, defineComponent, defineQuery, pipe, forEachEntity, Types } from 'aiecsjs'
16
+ import { createWorld, createEntity, addComponent, defineComponent, defineQuery, pipe, forEachEntityIndexed, Types } from 'aiecsjs'
17
17
 
18
18
  const Position = defineComponent({ x: Types.f32, y: Types.f32 })
19
19
  const Velocity = defineComponent({ x: Types.f32, y: Types.f32 })
@@ -24,12 +24,14 @@ addComponent(world, eid, Position, { x: 0, y: 0 })
24
24
  addComponent(world, eid, Velocity, { x: 1, y: 2 })
25
25
 
26
26
  const movers = defineQuery([Position, Velocity])
27
- const movement = (w, dt) => { forEachEntity(w, movers, (e, pos, vel) => { pos.x[e] += vel.x[e] * dt; pos.y[e] += vel.y[e] * dt }); return w }
27
+ const movement = (w, dt) => { forEachEntityIndexed(w, movers, (e, i, pos, vel) => { pos.x[i] += vel.x[i] * dt; pos.y[i] += vel.y[i] * dt }); return w }
28
28
 
29
29
  pipe(movement)(world, 1/60)
30
30
  ```
31
31
 
32
- > **狀態:實驗版(v0.1.x)。** `STABILITY.md` 中載明的 API 表面在 0.x 系列內承諾穩定,但仍可能微調。1.0 穩定凍結會在收集社群回饋後執行。
32
+ > **欄位迭代請用 `forEachEntityIndexed`。** 其 callback 為 `(e, i, ...cols)`:`e` 是封裝後的 `EntityId`(直接用於 `destroyEntity`、`hasComponent`、`getComponent`、command buffer),`i` 則是**安全的欄位索引** —— 以它索引每個 SoA 欄位(`pos.x[i]`)。封裝後的 `e` **不是**欄位索引:它只有在 slot 尚未被回收(世代 0)時才等於索引;任何 `destroyEntity` 回收 slot 之後,`e !== i`,以 `e` 索引欄位會讀到錯誤的 slot(越界 → `undefined`/`NaN`)。`forEachEntityIndexed` 直接給你正確的 `i`,因此這個陷阱已在程式碼層面消除。若只需要 `EntityId`,仍可用 `forEachEntity`(必要時以 `getEntityIndex(e)` 索引欄位)。
33
+
34
+ > **狀態:實驗版(v0.5.x)。** `STABILITY.md` 中載明的 API 表面在 0.x 系列內承諾穩定,但仍可能微調。1.0 穩定凍結會在收集社群回饋後執行。
33
35
 
34
36
  ## 目錄
35
37
 
@@ -139,7 +141,7 @@ CDN (ESM):
139
141
  import {
140
142
  createWorld, createEntity, destroyEntity,
141
143
  defineComponent, addComponent, removeComponent,
142
- defineQuery, forEachEntity, pipe, Types,
144
+ defineQuery, forEachEntityIndexed, pipe, Types,
143
145
  } from 'aiecsjs'
144
146
  import { createLoop } from 'aiecsjs/loop'
145
147
 
@@ -161,17 +163,17 @@ const movers = defineQuery([Position, Velocity])
161
163
  const decaying = defineQuery([Lifetime])
162
164
 
163
165
  const movementSystem = (w, dt) => {
164
- forEachEntity(w, movers, (e, pos, vel) => {
165
- pos.x[e] += vel.x[e] * dt
166
- pos.y[e] += vel.y[e] * dt
166
+ forEachEntityIndexed(w, movers, (e, i, pos, vel) => {
167
+ pos.x[i] += vel.x[i] * dt // `i` 是安全的欄位索引
168
+ pos.y[i] += vel.y[i] * dt
167
169
  })
168
170
  return w
169
171
  }
170
172
 
171
173
  const lifetimeSystem = (w, dt) => {
172
- forEachEntity(w, decaying, (e, life) => {
173
- life.remaining[e] -= dt
174
- if (life.remaining[e] <= 0) destroyEntity(w, e)
174
+ forEachEntityIndexed(w, decaying, (e, i, life) => {
175
+ life.remaining[i] -= dt
176
+ if (life.remaining[i] <= 0) destroyEntity(w, e) // destroyEntity 接收封裝後的 `e`
175
177
  })
176
178
  return w
177
179
  }
@@ -185,7 +187,7 @@ loop.start()
185
187
 
186
188
  ## 核心概念
187
189
 
188
- **實體(Entity)** — 一個含世代計數的 32 位元整數 ID。低位為實體索引;高位為當 ID 被回收時遞增的世代計數器。這能防止「我快取了 entity 42,可是 entity 42 已經不是同一個東西了」這類錯誤。預設切分為 24 索引位元 + 8 世代位元(約 16M 實體 × 各 256 次回收)。
190
+ **實體(Entity)** — 一個含世代計數的 32 位元整數 ID。低位為實體索引;高位為當 ID 被回收時遞增的世代計數器。這能防止「我快取了 entity 42,可是 entity 42 已經不是同一個東西了」這類錯誤。預設切分為 24 索引位元 + 8 世代位元(約 16M 實體 × 各 256 次回收)。由於 slot 被回收後封裝後的 ID **不是**欄位索引,欄位迭代請優先使用 **`forEachEntityIndexed((e, i, ...cols) => …)`** —— 它在封裝後的 `e` 之外,直接給你遮罩過的索引 `i`(安全的 SoA 索引:`pos.x[i]`)。若使用原始的 `forEachEntity` 形式,請以 `getEntityIndex(e)` 取得索引。
189
191
 
190
192
  **元件(Component)** — 附加在實體上的資料型態,有兩種風味:
191
193
  - **SoA(結構陣列)** — 用 `defineComponent({ x: Types.f32, y: Types.f32 })` 宣告。每個欄位變成一個 TypedArray,以實體 ID 索引。適合熱資料、數值資料。
@@ -239,16 +241,46 @@ if (entityExists(world, eid)) {
239
241
 
240
242
  ```ts
241
243
  const moveSystem = (world: World, dt: number) => {
242
- forEachEntity(world, defineQuery([Position, Velocity]), (e, pos, vel) => {
243
- pos.x[e] += vel.x[e] * dt
244
- pos.y[e] += vel.y[e] * dt
244
+ forEachEntityIndexed(world, defineQuery([Position, Velocity]), (e, i, pos, vel) => {
245
+ pos.x[i] += vel.x[i] * dt // `i` 是安全的欄位索引
246
+ pos.y[i] += vel.y[i] * dt
245
247
  })
246
248
  return world
247
249
  }
248
250
  ```
249
251
 
252
+ **欄位迭代請優先使用 `forEachEntityIndexed`。** 其 callback 為 `(e, i, ...cols)`:以 `i`(遮罩過的 slot 索引)索引每個 SoA 欄位,並把封裝後的 `e` 傳給實體操作(`destroyEntity`、`hasComponent`、command buffer)。即使 slot 被回收,提供的 `i` 永遠是正確索引 —— 因此你不必在迴圈裡手動遮罩或呼叫 `getEntityIndex`。若**只需要 `EntityId`**,請用 **`forEachEntity`**(其第一個參數是封裝後的 `EntityId`,不是欄位索引;必要時以 `getEntityIndex(e)` 取得索引)。關於 slot 回收後為何 `e !== i`,見[核心概念 → 實體](#核心概念)。
253
+
250
254
  務必把 `defineQuery(...)` 拉出熱迴圈 — 同樣的元件集合會回傳同一個 query 物件,但查找仍要算一次 hash。
251
255
 
256
+ ### 混合查詢中的 tag
257
+
258
+ `defineQuery([...])` 中列出的每個元件 —— **包含 tag** —— 都會依宣告順序佔用一個 callback 參數位。tag 沒有儲存空間,因此其參數位會以字面值 `true` 傳入:
259
+
260
+ ```ts
261
+ const Frozen = defineTag()
262
+ const q = defineQuery([Frozen, Position]) // tag 在前
263
+
264
+ // ❌ 錯誤:`pos` 綁定到 tag 參數位(`true`);`pos.x` 會丟例外。
265
+ forEachEntityIndexed(world, q, (e, i, pos) => { /* pos === true */ })
266
+
267
+ // ✅ 正確:對齊每個參數位。tag 以 `true` 傳入。
268
+ forEachEntityIndexed(world, q, (e, i, _frozen, pos) => {
269
+ pos.x[i] += 1
270
+ })
271
+ ```
272
+
273
+ **建議:把 tag 放到最後**,讓資料欄位排在前面,尾端的 `true` 參數位易於忽略:
274
+
275
+ ```ts
276
+ const q = defineQuery([Position, Velocity, Frozen]) // tag 在後
277
+ forEachEntityIndexed(world, q, (e, i, pos, vel /* , _frozen */) => {
278
+ pos.x[i] += vel.x[i]
279
+ })
280
+ ```
281
+
282
+ (無論排序,tag 都會過濾成員資格 —— 排序只影響 callback 的參數佈局。欄位參數為 `any` 型別,因此綁定錯誤是執行期錯誤,不是編譯期錯誤。)
283
+
252
284
  ### 用 pipe 與 createLoop 組合
253
285
 
254
286
  ```ts
@@ -284,6 +316,8 @@ const reapSystem = (world) => {
284
316
 
285
317
  `enterQuery` 只丟出本幀新匹配的實體;`exitQuery` 只丟出本幀剛離開匹配的實體。兩者皆在結構變動時增量計算,沒有每幀掃描。
286
318
 
319
+ > **請在模組層級定義 reactive query**(如上),在任何 update 執行前。reactive query 的 enter/exit 緩衝是**惰性啟動**的 —— world 是在該 query(或其 `enterQuery`/`exitQuery` view)第一次參與結構變動或被讀取時,才開始追蹤其轉換。發生在第一次註冊**之前**的 create/destroy 事件不會被回溯捕捉。模組層級定義會在 import 時就註冊該 query,因此第一次 `tick` 就能觀察到轉換;在系統內惰性建立的 query 可能漏掉它被引入那一幀的事件。
320
+
287
321
  ### Observer
288
322
 
289
323
  ```ts
@@ -318,8 +352,8 @@ import { withCommandBuffer } from 'aiecsjs/commands'
318
352
  const damageSystem = (world) => {
319
353
  const dying = defineQuery([Health])
320
354
  withCommandBuffer(world, (cb) => {
321
- forEachEntity(world, dying, (e, health) => {
322
- if (health.hp[e] <= 0) cb.destroy(e)
355
+ forEachEntityIndexed(world, dying, (e, i, health) => {
356
+ if (health.hp[i] <= 0) cb.destroy(e) // 索引欄位用 `i`,排入佇列用封裝後的 `e`
323
357
  })
324
358
  }) // 區塊結束時自動 flush
325
359
  return world
@@ -421,6 +455,7 @@ const Types = { i8, u8, i16, u16, i32, u32, f32, f64, eid, bool } as const
421
455
  | `defineQuery` | `(components: ComponentLike[] \| QueryDescriptor) => Query` | stable |
422
456
  | `runQuery` | `(world: World, q: Query) => readonly EntityId[]` | stable |
423
457
  | `forEachEntity` | `<Q>(world, q: Q, fn: (eid, ...cols) => void) => void` | stable |
458
+ | `forEachEntityIndexed` | `<Q>(world, q: Q, fn: (eid, i, ...cols) => void) => void` | stable |
424
459
  | `iterQuery` | `(world, q) => IterableIterator<EntityId>` | stable |
425
460
  | `enterQuery` | `(q: Query) => Query` | stable |
426
461
  | `exitQuery` | `(q: Query) => Query` | stable |
@@ -530,7 +565,7 @@ World
530
565
  ### 可重現的微基準
531
566
 
532
567
  ```ts
533
- import { createWorld, createEntity, addComponent, defineComponent, defineQuery, forEachEntity, Types } from 'aiecsjs'
568
+ import { createWorld, createEntity, addComponent, defineComponent, defineQuery, forEachEntityIndexed, Types } from 'aiecsjs'
534
569
 
535
570
  const Position = defineComponent({ x: Types.f32, y: Types.f32 })
536
571
  const Velocity = defineComponent({ x: Types.f32, y: Types.f32 })
@@ -545,8 +580,8 @@ for (let i = 0; i < 100_000; i++) {
545
580
  const movers = defineQuery([Position, Velocity])
546
581
  const start = performance.now()
547
582
  for (let frame = 0; frame < 1000; frame++) {
548
- forEachEntity(world, movers, (e, pos, vel) => {
549
- pos.x[e] += vel.x[e]; pos.y[e] += vel.y[e]
583
+ forEachEntityIndexed(world, movers, (e, i, pos, vel) => {
584
+ pos.x[i] += vel.x[i]; pos.y[i] += vel.y[i]
550
585
  })
551
586
  }
552
587
  console.log('每幀毫秒:', (performance.now() - start) / 1000)
@@ -587,18 +622,18 @@ worker.postMessage({ buffer, meta: transferableSnapshot(world).meta })
587
622
 
588
623
  ```ts
589
624
  // sim-worker.ts
590
- import { adoptSnapshot, defineComponent, defineQuery, forEachEntity, Types } from 'aiecsjs'
625
+ import { adoptSnapshot, defineComponent, defineQuery, forEachEntityIndexed, Types } from 'aiecsjs'
591
626
 
592
627
  const Position = defineComponent({ x: Types.f32, y: Types.f32 })
593
628
  const Velocity = defineComponent({ x: Types.f32, y: Types.f32 })
594
629
 
595
- self.onmessage = (e) => {
596
- const world = adoptSnapshot(e.data)
630
+ self.onmessage = (msg) => {
631
+ const world = adoptSnapshot(msg.data)
597
632
  const movers = defineQuery([Position, Velocity])
598
633
  setInterval(() => {
599
- forEachEntity(world, movers, (e, pos, vel) => {
600
- pos.x[e] += vel.x[e]
601
- pos.y[e] += vel.y[e]
634
+ forEachEntityIndexed(world, movers, (e, i, pos, vel) => {
635
+ pos.x[i] += vel.x[i]
636
+ pos.y[i] += vel.y[i]
602
637
  })
603
638
  }, 16)
604
639
  }
@@ -747,7 +782,7 @@ ECSY 於 2025 年 4 月 [封存](https://github.com/ecsyjs/ecsy)。因兩者皆
747
782
  **1. 生成-移動**
748
783
 
749
784
  ```ts
750
- import { createWorld, createEntity, addComponent, defineComponent, defineQuery, forEachEntity, pipe, Types } from 'aiecsjs'
785
+ import { createWorld, createEntity, addComponent, defineComponent, defineQuery, forEachEntityIndexed, pipe, Types } from 'aiecsjs'
751
786
 
752
787
  const Position = defineComponent({ x: Types.f32, y: Types.f32 })
753
788
  const Velocity = defineComponent({ x: Types.f32, y: Types.f32 })
@@ -761,7 +796,7 @@ for (let i = 0; i < 1000; i++) {
761
796
 
762
797
  const movers = defineQuery([Position, Velocity])
763
798
  const move = (w, dt) => {
764
- forEachEntity(w, movers, (e, p, v) => { p.x[e] += v.x[e] * dt; p.y[e] += v.y[e] * dt })
799
+ forEachEntityIndexed(w, movers, (e, i, p, v) => { p.x[i] += v.x[i] * dt; p.y[i] += v.y[i] * dt })
765
800
  return w
766
801
  }
767
802
  pipe(move)(world, 0.016)
@@ -840,7 +875,7 @@ ws.onmessage = (e) => rx.apply(remoteWorld, new Uint8Array(e.data))
840
875
  - 同一模組內,相同元件集合的 `defineQuery(X)` 永遠回傳同一個 `Query` 物件。
841
876
  - 實體 ID `0` 為保留值。`createEntity` 絕不回傳 `0`。
842
877
  - 由 `'aiecsjs'` 匯出的 `VERSION` 等於發佈的 npm 版本。
843
- - SoA 欄位是 TypedArray。在 `getWorldCapacity(world)` 範圍內,用存活的 `eid` 索引永遠安全。
878
+ - SoA 欄位是以實體**索引**(永遠落在 `[0, getWorldCapacity(world))`)索引的 TypedArray,切勿直接用封裝後的 `eid` —— slot 被回收後兩者就不同。`forEachEntityIndexed` 會把此索引作為其 `i` 參數傳入(對任何 world 佈局都正確)。若使用原始的 `forEachEntity` 形式,在預設佈局(`indexBits: 24`)下索引即 `getEntityIndex(eid)`;若以非預設 `indexBits` 建立 world,`getEntityIndex` 使用預設 24-bit mask、並非欄位索引 —— 此時請優先改用 `forEachEntityIndexed`(或 `EntityRef` + `deref`)。
844
879
  - 元件身分為 **全域**(由 `defineComponent` 產生),但每個元件的儲存為 **per-world**。
845
880
 
846
881
  ### 詞彙表
@@ -880,14 +915,16 @@ aiecsjs **不送遙測**、**不發出網路請求**、**不附帶 postinstall s
880
915
  ### 已知的 LLM 易混淆點
881
916
 
882
917
  - **aiecsjs 不是 bitECS。** `addComponent` 參數順序:aiecsjs 用 `(world, eid, Component, init?)`;bitECS 用 `(world, Component, eid)`。
883
- - **`forEachEntity` 為高速路徑。** `runQuery` 會分配陣列;`for...of iterQuery(...)` 會分配 iterator。熱迴圈請用 `forEachEntity`。
918
+ - **索引 SoA 欄位請用遮罩過的索引,不是封裝後的 `EntityId`。** 此陷阱已在程式碼層面消除:欄位迭代請優先使用 **`forEachEntityIndexed(w, q, (e, i, ...cols) => …)`**,它在封裝後的 `e` 之外,提供遮罩過的 slot 索引 `i`(正確的索引:`pos.x[i]`)。封裝後的 `e` **不是**欄位索引 —— 它只有在尚無 slot 被回收(世代 0)時才等於索引;任何 `destroyEntity` 之後就會讀到錯誤/越界的 slot。若使用原始的 `forEachEntity` 形式,請自行取得索引:在 callback 開頭轉一次(`const i = getEntityIndex(e)`)。兩者都用 `e` 進行實體操作(`destroyEntity(w, e)`、`hasComponent`、`getComponent`、command buffer)。
919
+ - **tag 也會佔用一個 callback 參數位。** `defineQuery([Tag, Position])` 在 `forEachEntityIndexed` 下會以 `(e, i, true, posCols)` 呼叫 callback(在 `forEachEntity` 下為 `(e, true, posCols)`)—— tag 以字面值 `true` 傳入。請把 tag 放到陣列**最後**(`defineQuery([Position, Tag])`),讓資料欄位排在前面;或接受並忽略尾端的 `true`。把 `pos` 綁定到 tag 參數位會讓 `pos === true`,`pos.x` 會丟例外。
920
+ - **`forEachEntity` / `forEachEntityIndexed` 為高速路徑。** `runQuery` 會分配陣列;`for...of iterQuery(...)` 會分配 iterator。熱迴圈請用 `forEachEntityIndexed`(或只需要 `EntityId` 時用 `forEachEntity`)。
884
921
  - **`defineObjectComponent` 的 factory 在定義時只跑一次**,不是每個實體跑一次。請透過 `setComponent` / `getComponent` 變動該實體的實例。
885
922
  - **元件參考就是儲存控制代碼。** `Position` 不是 constructor — 它是 aiecsjs 用來定位正確 archetype 欄位的 value 物件。
886
923
 
887
924
  ## 常見問答
888
925
 
889
926
  **Q:aiecsjs 可以上線生產嗎?**
890
- A:尚未。0.1.x 為實驗版。`STABILITY.md` 中的 API 為工作中契約;預期會有 bug 修正。1.0 目標是實作硬化完成後。
927
+ A:尚未。0.x 為實驗版。`STABILITY.md` 中的 API 為工作中契約;預期會有 bug 修正。1.0 目標是實作硬化完成後。
891
928
 
892
929
  **Q:可以用 class 實例當元件嗎?**
893
930
  A:可以,用 `defineObjectComponent`。但 AoS 僅限主執行緒,且迭代上比 SoA 慢。
package/api.json CHANGED
@@ -36,6 +36,7 @@
36
36
  "defineQuery",
37
37
  "runQuery",
38
38
  "forEachEntity",
39
+ "forEachEntityIndexed",
39
40
  "iterQuery",
40
41
  "enterQuery",
41
42
  "exitQuery",
@@ -648,10 +649,38 @@
648
649
  "examples": [
649
650
  {
650
651
  "title": "Update positions",
651
- "code": "forEachEntity(world, movers, (e, pos, vel) => { pos.x[e] += vel.x[e]; pos.y[e] += vel.y[e] })"
652
+ "code": "forEachEntity(world, movers, (e, pos, vel) => { const i = getEntityIndex(e); pos.x[i] += vel.x[i]; pos.y[i] += vel.y[i] })"
652
653
  }
653
654
  ],
654
- "seeAlso": ["defineQuery", "runQuery", "iterQuery"]
655
+ "seeAlso": ["defineQuery", "runQuery", "forEachEntityIndexed", "iterQuery"]
656
+ },
657
+ {
658
+ "name": "forEachEntityIndexed",
659
+ "kind": "function",
660
+ "module": "aiecsjs",
661
+ "since": "0.5.3",
662
+ "stability": "stable",
663
+ "deprecated": false,
664
+ "signature": "function forEachEntityIndexed<Q extends Query>(world: World, query: Q, fn: (eid: EntityId, i: number, ...columns: any[]) => void): void",
665
+ "summary": "Hot iteration path that yields the masked column index. Recommended for column iteration: index SoA columns with the safe subscript `i` (pos.x[i]); the packed `eid` is for entity operations. Closes the A1 packed-EntityId footgun in code.",
666
+ "params": [
667
+ { "name": "world", "type": "World", "optional": false, "description": "Target world." },
668
+ { "name": "query", "type": "Query", "optional": false, "description": "Query handle." },
669
+ {
670
+ "name": "fn",
671
+ "type": "(eid, i, ...columns) => void",
672
+ "optional": false,
673
+ "description": "Per-entity callback. Receives the packed eid, the masked column index i (eid & indexMask), then component column views in declaration order."
674
+ }
675
+ ],
676
+ "returns": { "type": "void", "description": "" },
677
+ "examples": [
678
+ {
679
+ "title": "Update positions (safe column indexing)",
680
+ "code": "forEachEntityIndexed(world, movers, (e, i, pos, vel) => { pos.x[i] += vel.x[i]; pos.y[i] += vel.y[i] })"
681
+ }
682
+ ],
683
+ "seeAlso": ["forEachEntity", "defineQuery", "runQuery", "iterQuery"]
655
684
  },
656
685
  {
657
686
  "name": "iterQuery",
@@ -0,0 +1,2 @@
1
+ import {i,I,e,b,p,c,d,f as f$1}from'./chunk-UAJNL4VY.js';var E=1,f=new Map;function K(n){let e=(n.all??[]).map(o=>o.__id).sort((o,i)=>o-i).join("-"),t=(n.any??[]).map(o=>o.__id).sort((o,i)=>o-i).join("-"),r=(n.none??[]).map(o=>o.__id).sort((o,i)=>o-i).join("-");return `A${e}|Y${t}|N${r}`}function $(n){let e=Array.isArray(n)?{all:n}:n;for(let i of [...e.all??[],...e.any??[],...e.none??[]])if(!i||typeof i!="object"||typeof i.__id!="number")throw new TypeError("aiecsjs: defineQuery received a non-component value");let t=K(e),r=f.get(t);if(r)return r;let o={id:E++,mask:[],all:(e.all??[]).map(i=>i.__id),any:(e.any??[]).map(i=>i.__id),none:(e.none??[]).map(i=>i.__id),columnViewCache:[...e.all??[],...e.any??[]],reactiveKind:"normal",sourceQueryId:-1,sourceQuery:null};return f.set(t,o),o}function D(n){let e=n,t=`enter:${e.id}`,r=f.get(t);if(r)return r;let o={id:E++,mask:[],all:e.all,any:e.any,none:e.none,columnViewCache:e.columnViewCache,reactiveKind:"enter",sourceQueryId:e.id,sourceQuery:e};return f.set(t,o),o}function H(n){let e=n,t=`exit:${e.id}`,r=f.get(t);if(r)return r;let o={id:E++,mask:[],all:e.all,any:e.any,none:e.none,columnViewCache:e.columnViewCache,reactiveKind:"exit",sourceQueryId:e.id,sourceQuery:e};return f.set(t,o),o}function h(n,e){if(n.queries[e.id]===e&&n.queryMasks.has(e.id))return;let t=n.options.maskWordCount,r=b(t),o=b(t),i=b(t);for(let a of e.all){let u=C(a),d=p(n,u);c(r,d);}for(let a of e.any){let u=C(a),d=p(n,u);c(o,d);}for(let a of e.none){let u=C(a),d=p(n,u);c(i,d);}let c$1={withMask:r,anyMask:o,noneMask:i,anyHasBits:!d(o)};n.queryMasks.set(e.id,c$1),n.queries[e.id]=e,n.queryArchetypeCache[e.id]=null,n.queryArchetypeStamp[e.id]=-1;let s=[...f$1(r),...f$1(o),...f$1(i)];for(let a of s){let u=n.bitToQueries.get(a);u||(u=new Set,n.bitToQueries.set(a,u)),u.add(e.id);}e.reactiveKind!=="normal"&&(n.reactiveBuffers.has(e.id)||n.reactiveBuffers.set(e.id,{entered:[],exited:[]}),e.sourceQuery&&h(n,e.sourceQuery));}function m(n,e$1){if(h(n,e$1),n.queryArchetypeCache[e$1.id]&&n.queryArchetypeStamp[e$1.id]===n.queryVersion)return n.queryArchetypeCache[e$1.id];let t=n.queryMasks.get(e$1.id),r=n.options.maskWordCount,o=[];for(let i=0;i<n.archetypes.length;i++){let c=n.archetypes[i];e(c.mask,t.withMask,t.anyMask,t.noneMask,t.anyHasBits,r)&&o.push(i);}return n.queryArchetypeCache[e$1.id]=o,n.queryArchetypeStamp[e$1.id]=n.queryVersion,o}function L(n,e){let t=i(n),o=m(t,e),i$1=[];for(let c of o){let s=t.archetypes[c];i$1.push({id:s.id,mask:Array.from(s.mask),size:s.size});}return i$1}function F(n,e){let t=i(n),r=e,o=[];if(r.reactiveKind==="enter"){let c=t.reactiveBuffers.get(r.id);if(c){for(let s of c.entered)o.push(s);c.entered.length=0;}return o}if(r.reactiveKind==="exit"){let c=t.reactiveBuffers.get(r.id);if(c){for(let s of c.exited)o.push(s);c.exited.length=0;}return o}let i$1=m(t,r);for(let c of i$1){let s=t.archetypes[c];for(let a=0;a<s.size;a++)o.push(s.entities[a]);}return o}function*O(n,e){let t=i(n),r=e;if(r.reactiveKind!=="normal"){let i=t.reactiveBuffers.get(r.id);if(!i)return;let c=r.reactiveKind==="enter"?i.entered:i.exited;for(let s of c)yield s;c.length=0;return}let o=m(t,r);for(let i of o){let c=t.archetypes[i];for(let s=0;s<c.size;s++)yield c.entities[s];}}function G(n,e,t){let r=i(n),o=e;if(o.reactiveKind!=="normal"){let s=r.reactiveBuffers.get(o.id);if(!s)return;let a=o.reactiveKind==="enter"?s.entered:s.exited;if(a.length===0)return;let u=g(r,o);for(let d=0;d<a.length;d++){let y=a[d];W(t,y,u);}a.length=0;return}h(r,o);let i$1=m(r,o),c=g(r,o);for(let s of i$1){let a=r.archetypes[s],u=a.entities,d=a.size;for(let y=0;y<d;y++)W(t,u[y],c);}}function W(n,e,t){switch(t.length){case 0:n(e);break;case 1:n(e,t[0]);break;case 2:n(e,t[0],t[1]);break;case 3:n(e,t[0],t[1],t[2]);break;case 4:n(e,t[0],t[1],t[2],t[3]);break;case 5:n(e,t[0],t[1],t[2],t[3],t[4]);break;default:n(e,...t);}}function N(n,e,t){let r=i(n),o=e,i$1=r.options.indexMask;if(o.reactiveKind!=="normal"){let a=r.reactiveBuffers.get(o.id);if(!a)return;let u=o.reactiveKind==="enter"?a.entered:a.exited;if(u.length===0)return;let d=g(r,o);for(let y=0;y<u.length;y++){let l=u[y];_(t,l,l&i$1,d);}u.length=0;return}h(r,o);let c=m(r,o),s=g(r,o);for(let a of c){let u=r.archetypes[a],d=u.entities,y=u.size;for(let l=0;l<y;l++){let B=d[l];_(t,B,B&i$1,s);}}}function _(n,e,t,r){switch(r.length){case 0:n(e,t);break;case 1:n(e,t,r[0]);break;case 2:n(e,t,r[0],r[1]);break;case 3:n(e,t,r[0],r[1],r[2]);break;case 4:n(e,t,r[0],r[1],r[2],r[3]);break;case 5:n(e,t,r[0],r[1],r[2],r[3],r[4]);break;default:n(e,t,...r);}}function g(n,e){let t=[];for(let r of e.columnViewCache){let o=I(r),i=n.componentBitFor.get(o.id);if(i===void 0){t.push(void 0);continue}let c=n.componentStorageByBit[i];if(!c){t.push(void 0);continue}c.kind==="soa"?t.push(c.soa):c.kind==="aos"?t.push(c.aos):t.push(true);}return t}function U(n,e$1,t,r,o){for(let s of f.values())s.reactiveKind!=="normal"&&s.sourceQuery&&n.queries[s.sourceQuery.id]!==s.sourceQuery&&h(n,s.sourceQuery);let i=n.bitToQueries.get(t);if(!i)return;let c=n.options.maskWordCount;for(let s of i){let a=n.queries[s];if(!a||a.reactiveKind!=="normal")continue;let u=n.queryMasks.get(s);if(!u)continue;let d=e(r,u.withMask,u.anyMask,u.noneMask,u.anyHasBits,c),y=e(o,u.withMask,u.anyMask,u.noneMask,u.anyHasBits,c);!d&&y?A(n,s,"enter",e$1):d&&!y&&A(n,s,"exit",e$1);}}function A(n,e,t,r){for(let o of f.values()){if(o.sourceQueryId!==e||o.reactiveKind!==t)continue;h(n,o);let i=S(n,o.id);t==="enter"?i.entered.push(r):i.exited.push(r);}}function S(n,e){let t=n.reactiveBuffers.get(e);return t||(t={entered:[],exited:[]},n.reactiveBuffers.set(e,t)),t}function C(n){let e=V(n);if(!e)throw new Error(`aiecsjs: component id ${n} not registered`);return e}var x=null;function Y(n){x=n;}function V(n){return x?x(n):void 0}export{$ as a,D as b,H as c,L as d,F as e,O as f,G as g,N as h,U as i,Y as j};//# sourceMappingURL=chunk-COORLIYF.js.map
2
+ //# sourceMappingURL=chunk-COORLIYF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/query.ts"],"names":["nextQueryId","moduleQueryCache","descKey","d","all","c","a","b","any","none","defineQuery","arg","desc","key","cached","q","enterQuery","query","reactive","exitQuery","ensureQueryRegistered","state","ww","withMask","createMask","anyMask","noneMask","compId","info","getComponentInfoById","bit","getOrRegisterComponentBit","setBit","bundle","isMaskZero","involvedBits","listBits","s","getQueryArchetypes","words","list","arch","matches","queryArchetypes","world","getWorldState","ids","out","id","runQuery","buf","e","archIds","r","iterQuery","src","forEachEntity","fn","cols","buildColumnViews","i","callWithCols","ents","n","eid","forEachEntityIndexed","indexMask","callWithColsIndexed","comp","getComponentInfo","storage","recordEntityMaskChange","changedBit","prevMask","nextMask","involved","qid","wasMatch","isMatch","pushReactive","queryId","kind","ensureReactiveBuffer","componentId","lazyGetComponentInfo","_getComponentInfoFn","registerComponentLookup"],"mappings":"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","file":"chunk-COORLIYF.js","sourcesContent":["import { createMask, isMaskZero, listBits, matches, setBit } from './bitmask.js'\nimport { getComponentInfo } from './component.js'\nimport type {\n Archetype,\n ComponentInfo,\n ComponentLike,\n EntityId,\n Query,\n QueryDescriptor,\n QueryInternal,\n QueryMaskBundle,\n ReactiveBuffer,\n World,\n WorldState,\n} from './types.js'\nimport {\n getOrRegisterComponentBit,\n getWorldState,\n readEntityMask,\n tryGetComponentBit,\n} from './world.js'\n\nlet nextQueryId = 1\nconst moduleQueryCache = new Map<string, QueryInternal>()\n\nfunction descKey(d: QueryDescriptor): string {\n const all = (d.all ?? [])\n .map((c) => c.__id)\n .sort((a, b) => a - b)\n .join('-')\n const any = (d.any ?? [])\n .map((c) => c.__id)\n .sort((a, b) => a - b)\n .join('-')\n const none = (d.none ?? [])\n .map((c) => c.__id)\n .sort((a, b) => a - b)\n .join('-')\n return `A${all}|Y${any}|N${none}`\n}\n\nexport function defineQuery(arg: ComponentLike[] | QueryDescriptor): Query {\n const desc: QueryDescriptor = Array.isArray(arg) ? { all: arg } : arg\n\n // Validate all members are components\n for (const c of [...(desc.all ?? []), ...(desc.any ?? []), ...(desc.none ?? [])]) {\n if (!c || typeof c !== 'object' || typeof (c as any).__id !== 'number') {\n throw new TypeError('aiecsjs: defineQuery received a non-component value')\n }\n }\n\n const key = descKey(desc)\n const cached = moduleQueryCache.get(key)\n if (cached) return cached\n\n const q: QueryInternal = {\n id: nextQueryId++,\n mask: [],\n all: (desc.all ?? []).map((c) => c.__id),\n any: (desc.any ?? []).map((c) => c.__id),\n none: (desc.none ?? []).map((c) => c.__id),\n columnViewCache: [...(desc.all ?? []), ...(desc.any ?? [])],\n reactiveKind: 'normal',\n sourceQueryId: -1,\n sourceQuery: null,\n }\n moduleQueryCache.set(key, q)\n return q\n}\n\nexport function enterQuery(query: Query): Query {\n const q = query as QueryInternal\n const key = `enter:${q.id}`\n const cached = moduleQueryCache.get(key)\n if (cached) return cached\n const reactive: QueryInternal = {\n id: nextQueryId++,\n mask: [],\n all: q.all,\n any: q.any,\n none: q.none,\n columnViewCache: q.columnViewCache,\n reactiveKind: 'enter',\n sourceQueryId: q.id,\n sourceQuery: q,\n }\n moduleQueryCache.set(key, reactive)\n return reactive\n}\n\nexport function exitQuery(query: Query): Query {\n const q = query as QueryInternal\n const key = `exit:${q.id}`\n const cached = moduleQueryCache.get(key)\n if (cached) return cached\n const reactive: QueryInternal = {\n id: nextQueryId++,\n mask: [],\n all: q.all,\n any: q.any,\n none: q.none,\n columnViewCache: q.columnViewCache,\n reactiveKind: 'exit',\n sourceQueryId: q.id,\n sourceQuery: q,\n }\n moduleQueryCache.set(key, reactive)\n return reactive\n}\n\n// --- Per-world query setup ---\n\nfunction ensureQueryRegistered(state: WorldState, q: QueryInternal): void {\n if (state.queries[q.id] === q && state.queryMasks.has(q.id)) return\n\n // Build per-world bitmasks (this may register new bits)\n const ww = state.options.maskWordCount\n const withMask = createMask(ww)\n const anyMask = createMask(ww)\n const noneMask = createMask(ww)\n for (const compId of q.all) {\n const info = getComponentInfoById(compId)\n const bit = getOrRegisterComponentBit(state, info)\n setBit(withMask, bit)\n }\n for (const compId of q.any) {\n const info = getComponentInfoById(compId)\n const bit = getOrRegisterComponentBit(state, info)\n setBit(anyMask, bit)\n }\n for (const compId of q.none) {\n const info = getComponentInfoById(compId)\n const bit = getOrRegisterComponentBit(state, info)\n setBit(noneMask, bit)\n }\n\n const bundle: QueryMaskBundle = {\n withMask,\n anyMask,\n noneMask,\n anyHasBits: !isMaskZero(anyMask),\n }\n state.queryMasks.set(q.id, bundle)\n\n state.queries[q.id] = q\n state.queryArchetypeCache[q.id] = null\n state.queryArchetypeStamp[q.id] = -1\n\n // Build bit → queries index for fast reactive lookup\n const involvedBits: number[] = [\n ...listBits(withMask),\n ...listBits(anyMask),\n ...listBits(noneMask),\n ]\n for (const b of involvedBits) {\n let s = state.bitToQueries.get(b)\n if (!s) {\n s = new Set<number>()\n state.bitToQueries.set(b, s)\n }\n s.add(q.id)\n }\n\n // Initialize reactive buffer for enter/exit queries\n if (q.reactiveKind !== 'normal') {\n if (!state.reactiveBuffers.has(q.id)) {\n state.reactiveBuffers.set(q.id, { entered: [], exited: [] })\n }\n // Also register the source query so recordEntityMaskChange will see it\n if (q.sourceQuery) ensureQueryRegistered(state, q.sourceQuery)\n }\n}\n\nfunction getQueryArchetypes(state: WorldState, q: QueryInternal): number[] {\n ensureQueryRegistered(state, q)\n if (state.queryArchetypeCache[q.id] && state.queryArchetypeStamp[q.id] === state.queryVersion) {\n return state.queryArchetypeCache[q.id]!\n }\n const bundle = state.queryMasks.get(q.id)!\n const words = state.options.maskWordCount\n const list: number[] = []\n for (let i = 0; i < state.archetypes.length; i++) {\n const arch = state.archetypes[i]!\n if (\n matches(arch.mask, bundle.withMask, bundle.anyMask, bundle.noneMask, bundle.anyHasBits, words)\n ) {\n list.push(i)\n }\n }\n state.queryArchetypeCache[q.id] = list\n state.queryArchetypeStamp[q.id] = state.queryVersion\n return list\n}\n\nexport function queryArchetypes(world: World, query: Query): readonly Archetype[] {\n const state = getWorldState(world)\n const q = query as QueryInternal\n const ids = getQueryArchetypes(state, q)\n const out: Archetype[] = []\n for (const id of ids) {\n const arch = state.archetypes[id]!\n out.push({ id: arch.id, mask: Array.from(arch.mask), size: arch.size })\n }\n return out\n}\n\nexport function runQuery(world: World, query: Query): readonly EntityId[] {\n const state = getWorldState(world)\n const q = query as QueryInternal\n const out: EntityId[] = []\n if (q.reactiveKind === 'enter') {\n const buf = state.reactiveBuffers.get(q.id)\n if (buf) {\n for (const e of buf.entered) out.push(e as EntityId)\n buf.entered.length = 0\n }\n return out\n }\n if (q.reactiveKind === 'exit') {\n const buf = state.reactiveBuffers.get(q.id)\n if (buf) {\n for (const e of buf.exited) out.push(e as EntityId)\n buf.exited.length = 0\n }\n return out\n }\n const archIds = getQueryArchetypes(state, q)\n for (const id of archIds) {\n const arch = state.archetypes[id]!\n for (let r = 0; r < arch.size; r++) {\n out.push(arch.entities[r] as EntityId)\n }\n }\n return out\n}\n\nexport function* iterQuery(world: World, query: Query): IterableIterator<EntityId> {\n const state = getWorldState(world)\n const q = query as QueryInternal\n if (q.reactiveKind !== 'normal') {\n const buf = state.reactiveBuffers.get(q.id)\n if (!buf) return\n const src = q.reactiveKind === 'enter' ? buf.entered : buf.exited\n for (const e of src) yield e as EntityId\n src.length = 0\n return\n }\n const archIds = getQueryArchetypes(state, q)\n for (const id of archIds) {\n const arch = state.archetypes[id]!\n for (let r = 0; r < arch.size; r++) {\n yield arch.entities[r] as EntityId\n }\n }\n}\n\nexport function forEachEntity(\n world: World,\n query: Query,\n fn: (eid: EntityId, ...cols: any[]) => void,\n): void {\n const state = getWorldState(world)\n const q = query as QueryInternal\n\n if (q.reactiveKind !== 'normal') {\n const buf = state.reactiveBuffers.get(q.id)\n if (!buf) return\n const src = q.reactiveKind === 'enter' ? buf.entered : buf.exited\n if (src.length === 0) return\n const cols = buildColumnViews(state, q)\n for (let i = 0; i < src.length; i++) {\n const e = src[i] as EntityId\n callWithCols(fn, e, cols)\n }\n src.length = 0\n return\n }\n\n ensureQueryRegistered(state, q)\n const archIds = getQueryArchetypes(state, q)\n const cols = buildColumnViews(state, q)\n for (const id of archIds) {\n const arch = state.archetypes[id]!\n const ents = arch.entities\n const n = arch.size\n for (let r = 0; r < n; r++) {\n callWithCols(fn, ents[r] as EntityId, cols)\n }\n }\n}\n\nfunction callWithCols(\n fn: (eid: EntityId, ...cols: any[]) => void,\n eid: EntityId,\n cols: any[],\n): void {\n // Specialise for low arities to avoid spread allocation.\n switch (cols.length) {\n case 0:\n fn(eid)\n break\n case 1:\n fn(eid, cols[0])\n break\n case 2:\n fn(eid, cols[0], cols[1])\n break\n case 3:\n fn(eid, cols[0], cols[1], cols[2])\n break\n case 4:\n fn(eid, cols[0], cols[1], cols[2], cols[3])\n break\n case 5:\n fn(eid, cols[0], cols[1], cols[2], cols[3], cols[4])\n break\n default:\n fn(eid, ...cols)\n }\n}\n\n/**\n * Like {@link forEachEntity}, but yields the masked column index `i` alongside\n * the packed `EntityId`. The callback signature is `(e, i, ...cols)`:\n *\n * - `e` — the packed EntityId (carries the generation in its high bits). Use\n * it for in-loop `destroyEntity` / `hasComponent` / `refOf`, exactly as with\n * `forEachEntity`.\n * - `i` — `e & indexMask`, the raw slot index. This is the **correct subscript**\n * for SoA column views (`pos.x[i]`), and stays correct after a slot is\n * recycled — where indexing with the packed `e` would read out of bounds.\n * - `...cols` — the same column views `forEachEntity` passes.\n *\n * This closes the packed-EntityId footgun (A1): callers no longer need to call\n * `getEntityIndex(e)` (or hand-mask) themselves to index columns safely.\n */\nexport function forEachEntityIndexed(\n world: World,\n query: Query,\n fn: (e: EntityId, i: number, ...cols: any[]) => void,\n): void {\n const state = getWorldState(world)\n const q = query as QueryInternal\n const indexMask = state.options.indexMask\n\n if (q.reactiveKind !== 'normal') {\n const buf = state.reactiveBuffers.get(q.id)\n if (!buf) return\n const src = q.reactiveKind === 'enter' ? buf.entered : buf.exited\n if (src.length === 0) return\n const cols = buildColumnViews(state, q)\n for (let i = 0; i < src.length; i++) {\n const e = src[i] as EntityId\n callWithColsIndexed(fn, e, e & indexMask, cols)\n }\n src.length = 0\n return\n }\n\n ensureQueryRegistered(state, q)\n const archIds = getQueryArchetypes(state, q)\n const cols = buildColumnViews(state, q)\n for (const id of archIds) {\n const arch = state.archetypes[id]!\n const ents = arch.entities\n const n = arch.size\n for (let r = 0; r < n; r++) {\n const e = ents[r] as EntityId\n callWithColsIndexed(fn, e, e & indexMask, cols)\n }\n }\n}\n\nfunction callWithColsIndexed(\n fn: (e: EntityId, i: number, ...cols: any[]) => void,\n e: EntityId,\n i: number,\n cols: any[],\n): void {\n // Specialise for low arities to avoid spread allocation.\n switch (cols.length) {\n case 0:\n fn(e, i)\n break\n case 1:\n fn(e, i, cols[0])\n break\n case 2:\n fn(e, i, cols[0], cols[1])\n break\n case 3:\n fn(e, i, cols[0], cols[1], cols[2])\n break\n case 4:\n fn(e, i, cols[0], cols[1], cols[2], cols[3])\n break\n case 5:\n fn(e, i, cols[0], cols[1], cols[2], cols[3], cols[4])\n break\n default:\n fn(e, i, ...cols)\n }\n}\n\nfunction buildColumnViews(state: WorldState, q: QueryInternal): any[] {\n const out: any[] = []\n for (const comp of q.columnViewCache) {\n const info = getComponentInfo(comp)\n const bit = state.componentBitFor.get(info.id)\n if (bit === undefined) {\n out.push(undefined)\n continue\n }\n const storage = state.componentStorageByBit[bit]\n if (!storage) {\n out.push(undefined)\n continue\n }\n if (storage.kind === 'soa') out.push(storage.soa)\n else if (storage.kind === 'aos') out.push(storage.aos)\n else out.push(true) // tag\n }\n return out\n}\n\n// --- Reactive query updates (called from migration code) ---\n\nexport function recordEntityMaskChange(\n state: WorldState,\n eid: EntityId,\n changedBit: number,\n prevMask: Uint32Array,\n nextMask: Uint32Array,\n): void {\n // Lazy-register any reactive queries (and their sources) so we can correctly\n // determine match transitions even if the user only ever called enterQuery/exitQuery.\n for (const q of moduleQueryCache.values()) {\n if (q.reactiveKind === 'normal') continue\n if (q.sourceQuery && state.queries[q.sourceQuery.id] !== q.sourceQuery) {\n ensureQueryRegistered(state, q.sourceQuery)\n }\n }\n\n const involved = state.bitToQueries.get(changedBit)\n if (!involved) return\n const words = state.options.maskWordCount\n for (const qid of involved) {\n const q = state.queries[qid]\n if (!q) continue\n if (q.reactiveKind !== 'normal') continue\n const bundle = state.queryMasks.get(qid)\n if (!bundle) continue\n const wasMatch = matches(\n prevMask,\n bundle.withMask,\n bundle.anyMask,\n bundle.noneMask,\n bundle.anyHasBits,\n words,\n )\n const isMatch = matches(\n nextMask,\n bundle.withMask,\n bundle.anyMask,\n bundle.noneMask,\n bundle.anyHasBits,\n words,\n )\n if (!wasMatch && isMatch) {\n pushReactive(state, qid, 'enter', eid)\n } else if (wasMatch && !isMatch) {\n pushReactive(state, qid, 'exit', eid)\n }\n }\n}\n\nfunction pushReactive(\n state: WorldState,\n queryId: number,\n kind: 'enter' | 'exit',\n eid: EntityId,\n): void {\n // Walk the module cache (not just state.queries) so reactive variants that\n // haven't been registered with this world yet still receive events.\n for (const r of moduleQueryCache.values()) {\n if (r.sourceQueryId !== queryId) continue\n if (r.reactiveKind !== kind) continue\n // Lazily register the reactive query in this world so subsequent reads can find it\n ensureQueryRegistered(state, r)\n const buf = ensureReactiveBuffer(state, r.id)\n if (kind === 'enter') buf.entered.push(eid as number)\n else buf.exited.push(eid as number)\n }\n}\n\nfunction ensureReactiveBuffer(state: WorldState, qid: number): ReactiveBuffer {\n let buf = state.reactiveBuffers.get(qid)\n if (!buf) {\n buf = { entered: [], exited: [] }\n state.reactiveBuffers.set(qid, buf)\n }\n return buf\n}\n\n// --- Helpers ---\n\nfunction getComponentInfoById(componentId: number): ComponentInfo {\n // We need access to the component registry's lookup by id. Re-import dynamically to avoid cycles.\n const info = lazyGetComponentInfo(componentId)\n if (!info) throw new Error(`aiecsjs: component id ${componentId} not registered`)\n return info\n}\n\nlet _getComponentInfoFn: ((id: number) => ComponentInfo | undefined) | null = null\nexport function registerComponentLookup(fn: (id: number) => ComponentInfo | undefined): void {\n _getComponentInfoFn = fn\n}\nfunction lazyGetComponentInfo(id: number): ComponentInfo | undefined {\n return _getComponentInfoFn ? _getComponentInfoFn(id) : undefined\n}\n\nexport function _resetQueryRegistry_FOR_TESTS_ONLY(): void {\n moduleQueryCache.clear()\n nextQueryId = 1\n}\n"]}