aiecsjs 0.5.2 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -28
- package/README_ZHTW.md +65 -28
- package/api.json +31 -2
- package/dist/{chunk-WZMRKSK5.js → chunk-ENULPSKV.js} +2 -2
- package/dist/{chunk-WZMRKSK5.js.map → chunk-ENULPSKV.js.map} +1 -1
- package/dist/chunk-EVPXWN4C.js +2 -0
- package/dist/chunk-EVPXWN4C.js.map +1 -0
- package/dist/{chunk-FEICW2CS.cjs → chunk-F6VQ6V3Y.cjs} +2 -2
- package/dist/{chunk-FEICW2CS.cjs.map → chunk-F6VQ6V3Y.cjs.map} +1 -1
- package/dist/{chunk-RNWXP3GU.cjs → chunk-M6L4SSE4.cjs} +2 -2
- package/dist/{chunk-RNWXP3GU.cjs.map → chunk-M6L4SSE4.cjs.map} +1 -1
- package/dist/chunk-O5OK5TLX.cjs +2 -0
- package/dist/chunk-O5OK5TLX.cjs.map +1 -0
- package/dist/{chunk-2E3GMRF5.js → chunk-ZWOAXX4R.js} +2 -2
- package/dist/{chunk-2E3GMRF5.js.map → chunk-ZWOAXX4R.js.map} +1 -1
- package/dist/commands.cjs +1 -1
- package/dist/commands.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -2
- package/dist/index.d.ts +18 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/observers.cjs +1 -1
- package/dist/observers.js +1 -1
- package/dist/relations.cjs +1 -1
- package/dist/relations.js +1 -1
- package/dist/serialize.cjs +1 -1
- package/dist/serialize.js +1 -1
- package/dist/worker.cjs +1 -1
- package/dist/worker.js +1 -1
- package/llms-full.txt +84 -31
- package/package.json +2 -2
- package/dist/chunk-P2KTJ2NQ.cjs +0 -2
- package/dist/chunk-P2KTJ2NQ.cjs.map +0 -1
- package/dist/chunk-Q55M3YEQ.js +0 -2
- package/dist/chunk-Q55M3YEQ.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,
|
|
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,11 +24,13 @@ 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) => {
|
|
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
|
+
> **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
|
+
|
|
32
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
|
|
@@ -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,
|
|
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
|
-
|
|
157
|
-
pos.x[
|
|
158
|
-
pos.y[
|
|
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
|
-
|
|
165
|
-
life.remaining[
|
|
166
|
-
if (life.remaining[
|
|
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
|
-
|
|
235
|
-
pos.x[
|
|
236
|
-
pos.y[
|
|
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
|
-
|
|
315
|
-
if (health.hp[
|
|
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,
|
|
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
|
-
|
|
542
|
-
pos.x[
|
|
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,
|
|
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 = (
|
|
589
|
-
const world = adoptSnapshot(
|
|
623
|
+
self.onmessage = (msg) => {
|
|
624
|
+
const world = adoptSnapshot(msg.data)
|
|
590
625
|
const movers = defineQuery([Position, Velocity])
|
|
591
626
|
setInterval(() => {
|
|
592
|
-
|
|
593
|
-
pos.x[
|
|
594
|
-
pos.y[
|
|
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,
|
|
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
|
-
|
|
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
|
|
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,7 +906,9 @@ 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
|
-
-
|
|
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
|
|
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,
|
|
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,11 +24,13 @@ 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) => {
|
|
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
|
+
> **欄位迭代請用 `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
|
+
|
|
32
34
|
> **狀態:實驗版(v0.5.x)。** `STABILITY.md` 中載明的 API 表面在 0.x 系列內承諾穩定,但仍可能微調。1.0 穩定凍結會在收集社群回饋後執行。
|
|
33
35
|
|
|
34
36
|
## 目錄
|
|
@@ -139,7 +141,7 @@ CDN (ESM):
|
|
|
139
141
|
import {
|
|
140
142
|
createWorld, createEntity, destroyEntity,
|
|
141
143
|
defineComponent, addComponent, removeComponent,
|
|
142
|
-
defineQuery,
|
|
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
|
-
|
|
165
|
-
pos.x[
|
|
166
|
-
pos.y[
|
|
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
|
-
|
|
173
|
-
life.remaining[
|
|
174
|
-
if (life.remaining[
|
|
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
|
-
|
|
243
|
-
pos.x[
|
|
244
|
-
pos.y[
|
|
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
|
-
|
|
322
|
-
if (health.hp[
|
|
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,
|
|
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
|
-
|
|
549
|
-
pos.x[
|
|
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,
|
|
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 = (
|
|
596
|
-
const world = adoptSnapshot(
|
|
630
|
+
self.onmessage = (msg) => {
|
|
631
|
+
const world = adoptSnapshot(msg.data)
|
|
597
632
|
const movers = defineQuery([Position, Velocity])
|
|
598
633
|
setInterval(() => {
|
|
599
|
-
|
|
600
|
-
pos.x[
|
|
601
|
-
pos.y[
|
|
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,
|
|
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
|
-
|
|
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
|
|
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,7 +915,9 @@ aiecsjs **不送遙測**、**不發出網路請求**、**不附帶 postinstall s
|
|
|
880
915
|
### 已知的 LLM 易混淆點
|
|
881
916
|
|
|
882
917
|
- **aiecsjs 不是 bitECS。** `addComponent` 參數順序:aiecsjs 用 `(world, eid, Component, init?)`;bitECS 用 `(world, Component, eid)`。
|
|
883
|
-
- **`
|
|
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
|
|
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[
|
|
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",
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var j="0.5.2";function T(t){return new Uint32Array(t)}function F(t,n){let e=n>>>5;t[e]=(t[e]??0)|1<<(n&31);}function I(t,n){let e=n>>>5;t[e]=(t[e]??0)&~(1<<(n&31));}function b(t,n){let e=n>>>5;return ((t[e]??0)&1<<(n&31))!==0}function h(t){let n=new Uint32Array(t.length);for(let e=0;e<t.length;e++)n[e]=t[e]??0;return n}function S(t){let n=[];for(let e=0;e<t.length;e++)n.push((t[e]??0).toString(16));return n.join(",")}function wn(t){for(let n=0;n<t.length;n++)if((t[n]??0)!==0)return false;return true}function xn(t,n,e,o,r,i){let s=!r;for(let a=0;a<i;a++){let c=t[a]??0,f=n[a]??0,d=e[a]??0,u=o[a]??0;if((c&f)!==f||(c&u)!==0)return false;!s&&(c&d)!==0&&(s=true);}return s}function gn(t){let n=[];for(let e=0;e<t.length;e++){let o=t[e]??0;for(;o!==0;){let r=(e<<5)+z(o);n.push(r),o&=o-1;}}return n}function W(t,n,e,o){for(let r=0;r<e;r++){let i=t[n+r]??0;for(;i!==0;){let s=(r<<5)+z(i);o(s),i&=i-1;}}}function kn(t,n,e,o,r,i,s){let a=!s;for(let c=0;c<e;c++){let f=t[n+c]??0,d=o[c]??0,u=r[c]??0,y=i[c]??0;if((f&d)!==d||(f&y)!==0)return false;!a&&(f&u)!==0&&(a=true);}return a}function z(t){return 31-Math.clz32(t&-t)}var E={initialCapacity:1024,maxEntities:1e6,indexBits:24,generationBits:8},J=256,nn=1,A=new Map;function l(t){let n=A.get(t.id);if(!n)throw new Error(`aiecsjs: world ${t.id} is destroyed or unknown`);if(n.destroyed)throw new Error(`aiecsjs: world ${t.id} is destroyed`);return n}function tn(t){A.set(t.id,t);}function en(t){A.delete(t);}function Sn(t){return A.has(t)}function on(t){let n=t??{},e=n.indexBits??E.indexBits,o=n.generationBits??E.generationBits,r=J;if(e<1||e>24)throw new Error("aiecsjs: indexBits must be in [1, 24]");if(o<0||o>16)throw new Error("aiecsjs: generationBits must be in [0, 16]");if(e+o>32)throw new Error(`aiecsjs: indexBits (${e}) + generationBits (${o}) must be <= 32`);let i=1<<e,s=Math.max(1,Math.min(n.initialCapacity??E.initialCapacity,i)),a=Math.max(s,Math.min(n.maxEntities??E.maxEntities,i));return {initialCapacity:s,maxEntities:a,indexBits:e,generationBits:o,indexMask:(1<<e)-1,generationMask:o===0?0:(1<<o)-1,maxComponents:r,maskWordCount:Math.ceil(r/32),buffer:n.buffer??null,bufferByteOffset:n.bufferByteOffset??0}}function rn(t,n){return {id:0,mask:T(t),size:0,capacity:16,entities:new Uint32Array(16),entityRow:new Map,componentBits:[],edgeAdd:new Int32Array(n).fill(-1),edgeRemove:new Int32Array(n).fill(-1)}}function Wn(t){let n=on(t),e=nn++,o=n.generationBits>8?Uint16Array:Uint8Array,r=new o(n.initialCapacity),i={id:e,capacity:n.initialCapacity,version:j,options:n,size:0,nextFreshIndex:1,freeList:[],generations:r,destroyed:false,componentBitFor:new Map,componentInfoByBit:new Array(n.maxComponents).fill(null),componentStorageByBit:new Array(n.maxComponents).fill(null),nextComponentBit:0,entityArchetype:new Uint32Array(n.initialCapacity),entityMask:new Uint32Array(n.initialCapacity*n.maskWordCount),archetypes:[],archetypeByMaskHash:new Map,queryVersion:0,queries:[],queryMasks:new Map,queryArchetypeCache:[],queryArchetypeStamp:[],bitToQueries:new Map,reactiveBuffers:new Map,observers:[],relationStorage:new Map,sab:n.buffer,readOnly:false},s=rn(n.maskWordCount,n.maxComponents);return s.id=0,i.archetypes.push(s),i.archetypeByMaskHash.set(S(s.mask),0),tn(i),sn(i)}function sn(t){return {id:t.id,get capacity(){return t.capacity},version:t.version}}function Bn(t){let n=A.get(t.id);!n||n.destroyed||(n.destroyed=true,n.archetypes=[],n.archetypeByMaskHash.clear(),n.componentInfoByBit=[],n.componentStorageByBit=[],n.queries=[],n.queryMasks.clear(),n.queryArchetypeCache=[],n.observers=[],n.relationStorage.clear(),n.reactiveBuffers.clear(),n.entityMask=new Uint32Array(0),n.entityArchetype=new Uint32Array(0),n.generations=new Uint8Array(0),n.freeList=[],n.componentBitFor.clear(),n.bitToQueries.clear(),n.queryArchetypeStamp=[],n.sab=null,en(t.id));}function Mn(t){let n=l(t);n.size=0,n.nextFreshIndex=1,n.freeList=[],n.generations.fill(0),n.entityArchetype.fill(0),n.entityMask.fill(0);for(let e of n.archetypes)e.size=0,e.entityRow.clear();for(let e of n.componentStorageByBit)if(e)if(e.kind==="soa"&&e.soa)for(let o of Object.keys(e.soa))e.soa[o]?.fill(0);else e.kind==="aos"&&e.aos&&e.aos.fill(void 0);n.queryVersion++;for(let e of n.reactiveBuffers.values())e.entered.length=0,e.exited.length=0;}function Un(t){return l(t).size}function On(t){return l(t).capacity}function B(t,n){if(n<=t.capacity)return;if(n>t.options.maxEntities)throw new Error(`aiecsjs: requested capacity ${n} exceeds maxEntities ${t.options.maxEntities}`);let e=t.capacity;for(;e<n;)e=Math.min(e*2,t.options.maxEntities);an(t,e);}function an(t,n){let e=t.generations.constructor,o=new e(n);o.set(t.generations),t.generations=o;let r=new Uint32Array(n);r.set(t.entityArchetype),t.entityArchetype=r;let i=t.options.maskWordCount,s=new Uint32Array(n*i);s.set(t.entityMask),t.entityMask=s;for(let a of t.componentStorageByBit)if(a)if(a.kind==="soa"&&a.soa){let c=t.componentInfoByBit[a.bit];c&&cn(a.soa,c.fields,n);}else a.kind==="aos"&&a.aos&&(a.aos.length=n);t.capacity=n;}function cn(t,n,e){for(let o of n){let r=t[o.name];if(!r)continue;let i=e*o.vectorLen;if(r.length>=i)continue;let s=new o.ctor(i);s.set(r),t[o.name]=s;}}function D(t,n){let e=S(n),o=t.archetypeByMaskHash.get(e);if(o!==void 0)return {archId:o,created:false};let r=t.archetypes.length,i={id:r,mask:h(n),size:0,capacity:16,entities:new Uint32Array(16),entityRow:new Map,componentBits:fn(n),edgeAdd:new Int32Array(t.options.maxComponents).fill(-1),edgeRemove:new Int32Array(t.options.maxComponents).fill(-1)};return t.archetypes.push(i),t.archetypeByMaskHash.set(e,r),t.queryVersion++,{archId:r,created:true}}function g(t,n){if(n<=t.capacity)return;let e=t.capacity;for(;e<n;)e*=2;let o=new Uint32Array(e);o.set(t.entities),t.entities=o,t.capacity=e;}function fn(t){let n=[];for(let e=0;e<t.length;e++){let o=t[e]??0;for(;o!==0;){let r=o&-o,i=(e<<5)+(31-Math.clz32(r));n.push(i),o&=o-1;}}return n}function N(t,n){let e=t.componentBitFor.get(n.id);if(e!==void 0)return e;if(t.nextComponentBit>=t.options.maxComponents)throw new Error(`aiecsjs: world reached maxComponents=${t.options.maxComponents}`);let o=t.nextComponentBit++;t.componentBitFor.set(n.id,o),t.componentInfoByBit[o]=n;let r;if(n.kind==="soa"){let i={};for(let s of n.fields)i[s.name]=new s.ctor(t.capacity*s.vectorLen);r={kind:"soa",componentId:n.id,bit:o,soa:i};}else n.kind==="aos"?r={kind:"aos",componentId:n.id,bit:o,aos:new Array(t.capacity)}:r={kind:"tag",componentId:n.id,bit:o};return t.componentStorageByBit[o]=r,o}function m(t,n){let e=t.options.maskWordCount,o=new Uint32Array(e),i=(n&t.options.indexMask)*e;for(let s=0;s<e;s++)o[s]=t.entityMask[i+s]??0;return o}function P(t,n,e){let o=t.options.maskWordCount,i=(n&t.options.indexMask)*o;for(let s=0;s<o;s++)t.entityMask[i+s]=e[s]??0;}function k(t,n){return t.componentBitFor.get(n.id)}function Rn(t){if(!t||typeof t!="object")return false;let n=t;return typeof n.id!="number"||typeof n.version!="string"?false:A.has(n.id)}var U=1,dn={i8:Int8Array,u8:Uint8Array,i16:Int16Array,u16:Uint16Array,i32:Int32Array,u32:Uint32Array,f32:Float32Array,f64:Float64Array,eid:Uint32Array,bool:Uint8Array},w=new Map;function Fn(t){let n=[];for(let[i,s]of Object.entries(t)){let a,c=1;if(typeof s=="string")a=s;else if(Array.isArray(s)){let[d,u]=s;a=d,c=u;}else throw new TypeError(`aiecsjs: invalid field declaration for "${i}"`);let f=dn[a];if(!f)throw new TypeError(`aiecsjs: unknown field type "${a}" for field "${i}"`);n.push({name:i,type:a,vectorLen:c,ctor:f,bytesPerElement:f.BYTES_PER_ELEMENT});}let e=U++,o={id:e,kind:"soa",schema:t,fields:n,factory:null};return w.set(e,o),{__kind:"soa",__id:e,__schema:t}}function zn(){let t=U++,n={id:t,kind:"tag",schema:null,fields:[],factory:null};return w.set(t,n),{__kind:"tag",__id:t}}function Dn(t){let n=U++,e=t??(()=>({})),o={id:n,kind:"aos",schema:null,fields:[],factory:e};return w.set(n,o),{__kind:"aos",__id:n,__factory:e}}function x(t){let n=w.get(t.__id);if(!n)throw new Error("aiecsjs: component is not registered (call defineComponent/defineTag/defineObjectComponent)");return n}function q(t,n,e,o){let r=l(t);if(r.readOnly)throw new Error("aiecsjs: cannot mutate a read-only world");if(!p(r,n))throw new Error(`aiecsjs: addComponent on dead entity ${n}`);let i=x(e),s=N(r,i),a=m(r,n);if(b(a,s)){o!==void 0&&M(r,n,e,o);return}let c=h(a);F(c,s),H(r,n,c),M(r,n,e,o),un(r,n,s),O(r,n,s,a,c);}function Nn(t,n,e){let o=l(t);if(o.readOnly)throw new Error("aiecsjs: cannot mutate a read-only world");if(!p(o,n))return;let r=x(e),i=k(o,r);if(i===void 0)return;let s=m(o,n);if(!b(s,i))return;let a=h(s);I(a,i),H(o,n,a),yn(o,n,i);let c=n&o.options.indexMask,f=o.componentStorageByBit[i];f?.kind==="soa"&&f.soa?$(f.soa,r.fields,c):f?.kind==="aos"&&f.aos&&(f.aos[c]=void 0),O(o,n,i,s,a);}function Pn(t,n,e){let o=l(t);if(!p(o,n))return false;let r=x(e),i=k(o,r);if(i===void 0)return false;let s=m(o,n);return b(s,i)}function qn(t,n,e){let o=l(t);if(!p(o,n))return;let r=x(e),i=k(o,r);if(i===void 0)return;let s=m(o,n);if(!b(s,i))return;let a=o.componentStorageByBit[i];if(a){if(a.kind==="soa")return a.soa;if(a.kind==="aos"){let c=n&o.options.indexMask;return a.aos?.[c]}return true}}function $n(t,n,e,o){let r=l(t);if(r.readOnly)throw new Error("aiecsjs: cannot mutate a read-only world");if(!p(r,n))throw new Error(`aiecsjs: setComponent on dead entity ${n}`);let i=x(e),s=k(r,i);if(s===void 0){q(t,n,e,o);return}let a=m(r,n);if(!b(a,s)){q(t,n,e,o);return}M(r,n,e,o),ln(r,n,s,o);}function M(t,n,e,o){let r=x(e),i=t.componentBitFor.get(r.id);if(i===void 0)return;let s=t.componentStorageByBit[i];if(!s)return;let a=n&t.options.indexMask;if(r.kind==="soa"&&s.soa){if(o==null)return;let c=o;for(let f of r.fields){if(!(f.name in c))continue;let d=s.soa[f.name];if(!d)continue;let u=c[f.name];if(f.vectorLen===1)d[a]=typeof u=="boolean"?u?1:0:Number(u);else if(Array.isArray(u)||ArrayBuffer.isView(u)){let y=a*f.vectorLen,Z=u;for(let C=0;C<f.vectorLen;C++)d[y+C]=Number(Z[C]??0);}}}else if(r.kind==="aos"&&s.aos){let c=r.factory??(()=>({})),f=s.aos[a];if(f===void 0&&(f=c(),s.aos[a]=f),o&&typeof o=="object"){let d=o,u=f;for(let y of Object.keys(d))y==="__proto__"||y==="constructor"||y==="prototype"||(u[y]=d[y]);}}}function $(t,n,e){for(let o of n){let r=t[o.name];if(r)if(o.vectorLen===1)r[e]=0;else {let i=e*o.vectorLen;for(let s=0;s<o.vectorLen;s++)r[i+s]=0;}}}function G(t,n){let e=t.options.maskWordCount,o=n&t.options.indexMask,r=o*e;W(t.entityMask,r,e,i=>{let s=t.componentStorageByBit[i],a=t.componentInfoByBit[i];s?.kind==="soa"&&s.soa&&a?$(s.soa,a.fields,o):s?.kind==="aos"&&s.aos&&(s.aos[o]=void 0);});}function H(t,n,e){let o=n&t.options.indexMask,r=t.entityArchetype[o]??0,i=t.archetypes[r];if(!i)return;let a=D(t,e).archId,c=t.archetypes[a];if(c){if(a!==r){let f=i.entityRow.get(n);if(f!==void 0){let u=i.size-1;if(f!==u){let y=i.entities[u]??0;i.entities[f]=y,i.entityRow.set(y,f);}i.entities[u]=0,i.entityRow.delete(n),i.size--;}g(c,c.size+1);let d=c.size;c.entities[d]=n,c.entityRow.set(n,d),c.size++,t.entityArchetype[o]=a;}P(t,n,e);}}var v={fireAdd:()=>{},fireRemove:()=>{},fireSet:()=>{}},V=()=>{};function Gn(t){V=t;}function O(t,n,e,o,r){V(t,n,e,o,r);}function Y(t,n,e){let o=t.options.maskWordCount,r=h(e),i=h(e);W(e,0,o,s=>{I(i,s),O(t,n,s,r,i),I(r,s);});}function Hn(t){v=t;}function un(t,n,e){v.fireAdd(t,n,e);}function yn(t,n,e){v.fireRemove(t,n,e);}function ln(t,n,e,o){v.fireSet(t,n,e,o);}function Vn(t){return w.get(t)}function Yn(){return Array.from(w.values())}var L=24,pn=8,X=(1<<L)-1,K=(1<<pn)-1;function Q(t,n,e){return ((n&e.generationMask)<<e.indexBits|t&e.indexMask)>>>0}function Zn(t,n){return t&n.indexMask}function Jn(t,n){return t>>>n.indexBits&n.generationMask}function nt(t){let n=l(t);if(n.readOnly)throw new Error("aiecsjs: cannot createEntity on a read-only world (worker-attached)");let e;if(n.freeList.length>0)e=n.freeList.pop();else {if(n.nextFreshIndex>=n.options.maxEntities)throw new Error(`aiecsjs: reached maxEntities ${n.options.maxEntities}`);n.nextFreshIndex>=n.capacity&&B(n,n.nextFreshIndex+1),e=n.nextFreshIndex++;}let o=n.generations[e]??0,r=Q(e,o,n.options),i=n.archetypes[0];if(!i)throw new Error("aiecsjs: missing empty archetype");g(i,i.size+1);let s=i.size;i.entities[s]=r,i.entityRow.set(r,s),i.size++,n.entityArchetype[e]=0;let a=n.options.maskWordCount,c=e*a;for(let f=0;f<a;f++)n.entityMask[c+f]=0;return n.size++,r}function tt(t,n){if(t.readOnly)throw new Error("aiecsjs: cannot create entities on a read-only world (worker-attached)");if(n<=0||n>=t.options.maxEntities)throw new Error(`aiecsjs: slot index ${n} out of range`);n>=t.capacity&&B(t,n+1);let e=t.generations[n]??0,o=Q(n,e,t.options);if(p(t,o))return o;if(n>=t.nextFreshIndex){for(let c=t.nextFreshIndex;c<n;c++)t.freeList.push(c);t.nextFreshIndex=n+1;}else {let c=t.freeList.lastIndexOf(n);if(c!==-1){let f=t.freeList.length-1;t.freeList[c]=t.freeList[f],t.freeList.pop();}}let r=t.archetypes[0];if(!r)throw new Error("aiecsjs: missing empty archetype");g(r,r.size+1);let i=r.size;r.entities[i]=o,r.entityRow.set(o,i),r.size++,t.entityArchetype[n]=0;let s=t.options.maskWordCount,a=n*s;for(let c=0;c<s;c++)t.entityMask[a+c]=0;return t.size++,o}function et(t,n){let e=l(t);if(e.readOnly)throw new Error("aiecsjs: cannot destroyEntity on a read-only world");if(!p(e,n))return;let o=n&e.options.indexMask,r=m(e,n),{dispatchDestroyObservers:i}=hn();i(e,n),bn(e,n),G(e,n);let s=e.entityArchetype[o]??0,a=e.archetypes[s];if(a){let d=a.entityRow.get(n);if(d!==void 0){let u=a.size-1;if(d!==u){let y=a.entities[u]??0;a.entities[d]=y,a.entityRow.set(y,d);}a.entities[u]=0,a.entityRow.delete(n),a.size--;}}Y(e,n,r),e.entityArchetype[o]=0;let c=e.options.maskWordCount,f=o*c;for(let d=0;d<c;d++)e.entityMask[f+d]=0;e.generations[o]=(e.generations[o]??0)+1&e.options.generationMask,e.freeList.push(o),e.size--;}function mn(t,n){let e=l(t);return p(e,n)}function p(t,n){let e=n&t.options.indexMask;if(e<=0||e>=t.capacity)return false;let o=t.entityArchetype[e]??0,r=t.archetypes[o];if(!r||!r.entityRow.has(n))return false;let i=t.generations[e]??0,s=n>>>t.options.indexBits&t.options.generationMask;return i===s}function ot(t){return t&X}function rt(t){return t>>>L&K}function it(t,n){return ((n&K)<<L|t&X)>>>0}function st(t,n){return typeof n!="number"?false:mn(t,n)}var R=null;function at(t){R=t;}function hn(){return R||{dispatchDestroyObservers:()=>{}}}var _=null;function ct(t){_=t;}function bn(t,n){_&&_(t,n);}export{rt as A,it as B,st as C,at as D,ct as E,Fn as F,zn as G,Dn as H,x as I,q as J,Nn as K,Pn as L,qn as M,$n as N,Gn as O,Hn as P,Vn as Q,Yn as R,j as a,T as b,F as c,wn as d,xn as e,gn as f,W as g,kn as h,l as i,Sn as j,Wn as k,Bn as l,Mn as m,Un as n,On as o,N as p,Rn as q,Q as r,Zn as s,Jn as t,nt as u,tt as v,et as w,mn as x,p as y,ot as z};//# sourceMappingURL=chunk-WZMRKSK5.js.map
|
|
2
|
-
//# sourceMappingURL=chunk-
|
|
1
|
+
var j="0.5.5";function T(t){return new Uint32Array(t)}function F(t,n){let e=n>>>5;t[e]=(t[e]??0)|1<<(n&31);}function I(t,n){let e=n>>>5;t[e]=(t[e]??0)&~(1<<(n&31));}function b(t,n){let e=n>>>5;return ((t[e]??0)&1<<(n&31))!==0}function h(t){let n=new Uint32Array(t.length);for(let e=0;e<t.length;e++)n[e]=t[e]??0;return n}function S(t){let n=[];for(let e=0;e<t.length;e++)n.push((t[e]??0).toString(16));return n.join(",")}function wn(t){for(let n=0;n<t.length;n++)if((t[n]??0)!==0)return false;return true}function xn(t,n,e,o,r,i){let s=!r;for(let a=0;a<i;a++){let c=t[a]??0,f=n[a]??0,d=e[a]??0,u=o[a]??0;if((c&f)!==f||(c&u)!==0)return false;!s&&(c&d)!==0&&(s=true);}return s}function gn(t){let n=[];for(let e=0;e<t.length;e++){let o=t[e]??0;for(;o!==0;){let r=(e<<5)+z(o);n.push(r),o&=o-1;}}return n}function W(t,n,e,o){for(let r=0;r<e;r++){let i=t[n+r]??0;for(;i!==0;){let s=(r<<5)+z(i);o(s),i&=i-1;}}}function kn(t,n,e,o,r,i,s){let a=!s;for(let c=0;c<e;c++){let f=t[n+c]??0,d=o[c]??0,u=r[c]??0,y=i[c]??0;if((f&d)!==d||(f&y)!==0)return false;!a&&(f&u)!==0&&(a=true);}return a}function z(t){return 31-Math.clz32(t&-t)}var E={initialCapacity:1024,maxEntities:1e6,indexBits:24,generationBits:8},J=256,nn=1,A=new Map;function l(t){let n=A.get(t.id);if(!n)throw new Error(`aiecsjs: world ${t.id} is destroyed or unknown`);if(n.destroyed)throw new Error(`aiecsjs: world ${t.id} is destroyed`);return n}function tn(t){A.set(t.id,t);}function en(t){A.delete(t);}function Sn(t){return A.has(t)}function on(t){let n=t??{},e=n.indexBits??E.indexBits,o=n.generationBits??E.generationBits,r=J;if(e<1||e>24)throw new Error("aiecsjs: indexBits must be in [1, 24]");if(o<0||o>16)throw new Error("aiecsjs: generationBits must be in [0, 16]");if(e+o>32)throw new Error(`aiecsjs: indexBits (${e}) + generationBits (${o}) must be <= 32`);let i=1<<e,s=Math.max(1,Math.min(n.initialCapacity??E.initialCapacity,i)),a=Math.max(s,Math.min(n.maxEntities??E.maxEntities,i));return {initialCapacity:s,maxEntities:a,indexBits:e,generationBits:o,indexMask:(1<<e)-1,generationMask:o===0?0:(1<<o)-1,maxComponents:r,maskWordCount:Math.ceil(r/32),buffer:n.buffer??null,bufferByteOffset:n.bufferByteOffset??0}}function rn(t,n){return {id:0,mask:T(t),size:0,capacity:16,entities:new Uint32Array(16),entityRow:new Map,componentBits:[],edgeAdd:new Int32Array(n).fill(-1),edgeRemove:new Int32Array(n).fill(-1)}}function Wn(t){let n=on(t),e=nn++,o=n.generationBits>8?Uint16Array:Uint8Array,r=new o(n.initialCapacity),i={id:e,capacity:n.initialCapacity,version:j,options:n,size:0,nextFreshIndex:1,freeList:[],generations:r,destroyed:false,componentBitFor:new Map,componentInfoByBit:new Array(n.maxComponents).fill(null),componentStorageByBit:new Array(n.maxComponents).fill(null),nextComponentBit:0,entityArchetype:new Uint32Array(n.initialCapacity),entityMask:new Uint32Array(n.initialCapacity*n.maskWordCount),archetypes:[],archetypeByMaskHash:new Map,queryVersion:0,queries:[],queryMasks:new Map,queryArchetypeCache:[],queryArchetypeStamp:[],bitToQueries:new Map,reactiveBuffers:new Map,observers:[],relationStorage:new Map,sab:n.buffer,readOnly:false},s=rn(n.maskWordCount,n.maxComponents);return s.id=0,i.archetypes.push(s),i.archetypeByMaskHash.set(S(s.mask),0),tn(i),sn(i)}function sn(t){return {id:t.id,get capacity(){return t.capacity},version:t.version}}function Bn(t){let n=A.get(t.id);!n||n.destroyed||(n.destroyed=true,n.archetypes=[],n.archetypeByMaskHash.clear(),n.componentInfoByBit=[],n.componentStorageByBit=[],n.queries=[],n.queryMasks.clear(),n.queryArchetypeCache=[],n.observers=[],n.relationStorage.clear(),n.reactiveBuffers.clear(),n.entityMask=new Uint32Array(0),n.entityArchetype=new Uint32Array(0),n.generations=new Uint8Array(0),n.freeList=[],n.componentBitFor.clear(),n.bitToQueries.clear(),n.queryArchetypeStamp=[],n.sab=null,en(t.id));}function Mn(t){let n=l(t);n.size=0,n.nextFreshIndex=1,n.freeList=[],n.generations.fill(0),n.entityArchetype.fill(0),n.entityMask.fill(0);for(let e of n.archetypes)e.size=0,e.entityRow.clear();for(let e of n.componentStorageByBit)if(e)if(e.kind==="soa"&&e.soa)for(let o of Object.keys(e.soa))e.soa[o]?.fill(0);else e.kind==="aos"&&e.aos&&e.aos.fill(void 0);n.queryVersion++;for(let e of n.reactiveBuffers.values())e.entered.length=0,e.exited.length=0;}function Un(t){return l(t).size}function On(t){return l(t).capacity}function B(t,n){if(n<=t.capacity)return;if(n>t.options.maxEntities)throw new Error(`aiecsjs: requested capacity ${n} exceeds maxEntities ${t.options.maxEntities}`);let e=t.capacity;for(;e<n;)e=Math.min(e*2,t.options.maxEntities);an(t,e);}function an(t,n){let e=t.generations.constructor,o=new e(n);o.set(t.generations),t.generations=o;let r=new Uint32Array(n);r.set(t.entityArchetype),t.entityArchetype=r;let i=t.options.maskWordCount,s=new Uint32Array(n*i);s.set(t.entityMask),t.entityMask=s;for(let a of t.componentStorageByBit)if(a)if(a.kind==="soa"&&a.soa){let c=t.componentInfoByBit[a.bit];c&&cn(a.soa,c.fields,n);}else a.kind==="aos"&&a.aos&&(a.aos.length=n);t.capacity=n;}function cn(t,n,e){for(let o of n){let r=t[o.name];if(!r)continue;let i=e*o.vectorLen;if(r.length>=i)continue;let s=new o.ctor(i);s.set(r),t[o.name]=s;}}function D(t,n){let e=S(n),o=t.archetypeByMaskHash.get(e);if(o!==void 0)return {archId:o,created:false};let r=t.archetypes.length,i={id:r,mask:h(n),size:0,capacity:16,entities:new Uint32Array(16),entityRow:new Map,componentBits:fn(n),edgeAdd:new Int32Array(t.options.maxComponents).fill(-1),edgeRemove:new Int32Array(t.options.maxComponents).fill(-1)};return t.archetypes.push(i),t.archetypeByMaskHash.set(e,r),t.queryVersion++,{archId:r,created:true}}function g(t,n){if(n<=t.capacity)return;let e=t.capacity;for(;e<n;)e*=2;let o=new Uint32Array(e);o.set(t.entities),t.entities=o,t.capacity=e;}function fn(t){let n=[];for(let e=0;e<t.length;e++){let o=t[e]??0;for(;o!==0;){let r=o&-o,i=(e<<5)+(31-Math.clz32(r));n.push(i),o&=o-1;}}return n}function N(t,n){let e=t.componentBitFor.get(n.id);if(e!==void 0)return e;if(t.nextComponentBit>=t.options.maxComponents)throw new Error(`aiecsjs: world reached maxComponents=${t.options.maxComponents}`);let o=t.nextComponentBit++;t.componentBitFor.set(n.id,o),t.componentInfoByBit[o]=n;let r;if(n.kind==="soa"){let i={};for(let s of n.fields)i[s.name]=new s.ctor(t.capacity*s.vectorLen);r={kind:"soa",componentId:n.id,bit:o,soa:i};}else n.kind==="aos"?r={kind:"aos",componentId:n.id,bit:o,aos:new Array(t.capacity)}:r={kind:"tag",componentId:n.id,bit:o};return t.componentStorageByBit[o]=r,o}function m(t,n){let e=t.options.maskWordCount,o=new Uint32Array(e),i=(n&t.options.indexMask)*e;for(let s=0;s<e;s++)o[s]=t.entityMask[i+s]??0;return o}function P(t,n,e){let o=t.options.maskWordCount,i=(n&t.options.indexMask)*o;for(let s=0;s<o;s++)t.entityMask[i+s]=e[s]??0;}function k(t,n){return t.componentBitFor.get(n.id)}function Rn(t){if(!t||typeof t!="object")return false;let n=t;return typeof n.id!="number"||typeof n.version!="string"?false:A.has(n.id)}var U=1,dn={i8:Int8Array,u8:Uint8Array,i16:Int16Array,u16:Uint16Array,i32:Int32Array,u32:Uint32Array,f32:Float32Array,f64:Float64Array,eid:Uint32Array,bool:Uint8Array},w=new Map;function Fn(t){let n=[];for(let[i,s]of Object.entries(t)){let a,c=1;if(typeof s=="string")a=s;else if(Array.isArray(s)){let[d,u]=s;a=d,c=u;}else throw new TypeError(`aiecsjs: invalid field declaration for "${i}"`);let f=dn[a];if(!f)throw new TypeError(`aiecsjs: unknown field type "${a}" for field "${i}"`);n.push({name:i,type:a,vectorLen:c,ctor:f,bytesPerElement:f.BYTES_PER_ELEMENT});}let e=U++,o={id:e,kind:"soa",schema:t,fields:n,factory:null};return w.set(e,o),{__kind:"soa",__id:e,__schema:t}}function zn(){let t=U++,n={id:t,kind:"tag",schema:null,fields:[],factory:null};return w.set(t,n),{__kind:"tag",__id:t}}function Dn(t){let n=U++,e=t??(()=>({})),o={id:n,kind:"aos",schema:null,fields:[],factory:e};return w.set(n,o),{__kind:"aos",__id:n,__factory:e}}function x(t){let n=w.get(t.__id);if(!n)throw new Error("aiecsjs: component is not registered (call defineComponent/defineTag/defineObjectComponent)");return n}function q(t,n,e,o){let r=l(t);if(r.readOnly)throw new Error("aiecsjs: cannot mutate a read-only world");if(!p(r,n))throw new Error(`aiecsjs: addComponent on dead entity ${n}`);let i=x(e),s=N(r,i),a=m(r,n);if(b(a,s)){o!==void 0&&M(r,n,e,o);return}let c=h(a);F(c,s),H(r,n,c),M(r,n,e,o),un(r,n,s),O(r,n,s,a,c);}function Nn(t,n,e){let o=l(t);if(o.readOnly)throw new Error("aiecsjs: cannot mutate a read-only world");if(!p(o,n))return;let r=x(e),i=k(o,r);if(i===void 0)return;let s=m(o,n);if(!b(s,i))return;let a=h(s);I(a,i),H(o,n,a),yn(o,n,i);let c=n&o.options.indexMask,f=o.componentStorageByBit[i];f?.kind==="soa"&&f.soa?$(f.soa,r.fields,c):f?.kind==="aos"&&f.aos&&(f.aos[c]=void 0),O(o,n,i,s,a);}function Pn(t,n,e){let o=l(t);if(!p(o,n))return false;let r=x(e),i=k(o,r);if(i===void 0)return false;let s=m(o,n);return b(s,i)}function qn(t,n,e){let o=l(t);if(!p(o,n))return;let r=x(e),i=k(o,r);if(i===void 0)return;let s=m(o,n);if(!b(s,i))return;let a=o.componentStorageByBit[i];if(a){if(a.kind==="soa")return a.soa;if(a.kind==="aos"){let c=n&o.options.indexMask;return a.aos?.[c]}return true}}function $n(t,n,e,o){let r=l(t);if(r.readOnly)throw new Error("aiecsjs: cannot mutate a read-only world");if(!p(r,n))throw new Error(`aiecsjs: setComponent on dead entity ${n}`);let i=x(e),s=k(r,i);if(s===void 0){q(t,n,e,o);return}let a=m(r,n);if(!b(a,s)){q(t,n,e,o);return}M(r,n,e,o),ln(r,n,s,o);}function M(t,n,e,o){let r=x(e),i=t.componentBitFor.get(r.id);if(i===void 0)return;let s=t.componentStorageByBit[i];if(!s)return;let a=n&t.options.indexMask;if(r.kind==="soa"&&s.soa){if(o==null)return;let c=o;for(let f of r.fields){if(!(f.name in c))continue;let d=s.soa[f.name];if(!d)continue;let u=c[f.name];if(f.vectorLen===1)d[a]=typeof u=="boolean"?u?1:0:Number(u);else if(Array.isArray(u)||ArrayBuffer.isView(u)){let y=a*f.vectorLen,Z=u;for(let C=0;C<f.vectorLen;C++)d[y+C]=Number(Z[C]??0);}}}else if(r.kind==="aos"&&s.aos){let c=r.factory??(()=>({})),f=s.aos[a];if(f===void 0&&(f=c(),s.aos[a]=f),o&&typeof o=="object"){let d=o,u=f;for(let y of Object.keys(d))y==="__proto__"||y==="constructor"||y==="prototype"||(u[y]=d[y]);}}}function $(t,n,e){for(let o of n){let r=t[o.name];if(r)if(o.vectorLen===1)r[e]=0;else {let i=e*o.vectorLen;for(let s=0;s<o.vectorLen;s++)r[i+s]=0;}}}function G(t,n){let e=t.options.maskWordCount,o=n&t.options.indexMask,r=o*e;W(t.entityMask,r,e,i=>{let s=t.componentStorageByBit[i],a=t.componentInfoByBit[i];s?.kind==="soa"&&s.soa&&a?$(s.soa,a.fields,o):s?.kind==="aos"&&s.aos&&(s.aos[o]=void 0);});}function H(t,n,e){let o=n&t.options.indexMask,r=t.entityArchetype[o]??0,i=t.archetypes[r];if(!i)return;let a=D(t,e).archId,c=t.archetypes[a];if(c){if(a!==r){let f=i.entityRow.get(n);if(f!==void 0){let u=i.size-1;if(f!==u){let y=i.entities[u]??0;i.entities[f]=y,i.entityRow.set(y,f);}i.entities[u]=0,i.entityRow.delete(n),i.size--;}g(c,c.size+1);let d=c.size;c.entities[d]=n,c.entityRow.set(n,d),c.size++,t.entityArchetype[o]=a;}P(t,n,e);}}var v={fireAdd:()=>{},fireRemove:()=>{},fireSet:()=>{}},V=()=>{};function Gn(t){V=t;}function O(t,n,e,o,r){V(t,n,e,o,r);}function Y(t,n,e){let o=t.options.maskWordCount,r=h(e),i=h(e);W(e,0,o,s=>{I(i,s),O(t,n,s,r,i),I(r,s);});}function Hn(t){v=t;}function un(t,n,e){v.fireAdd(t,n,e);}function yn(t,n,e){v.fireRemove(t,n,e);}function ln(t,n,e,o){v.fireSet(t,n,e,o);}function Vn(t){return w.get(t)}function Yn(){return Array.from(w.values())}var L=24,pn=8,X=(1<<L)-1,K=(1<<pn)-1;function Q(t,n,e){return ((n&e.generationMask)<<e.indexBits|t&e.indexMask)>>>0}function Zn(t,n){return t&n.indexMask}function Jn(t,n){return t>>>n.indexBits&n.generationMask}function nt(t){let n=l(t);if(n.readOnly)throw new Error("aiecsjs: cannot createEntity on a read-only world (worker-attached)");let e;if(n.freeList.length>0)e=n.freeList.pop();else {if(n.nextFreshIndex>=n.options.maxEntities)throw new Error(`aiecsjs: reached maxEntities ${n.options.maxEntities}`);n.nextFreshIndex>=n.capacity&&B(n,n.nextFreshIndex+1),e=n.nextFreshIndex++;}let o=n.generations[e]??0,r=Q(e,o,n.options),i=n.archetypes[0];if(!i)throw new Error("aiecsjs: missing empty archetype");g(i,i.size+1);let s=i.size;i.entities[s]=r,i.entityRow.set(r,s),i.size++,n.entityArchetype[e]=0;let a=n.options.maskWordCount,c=e*a;for(let f=0;f<a;f++)n.entityMask[c+f]=0;return n.size++,r}function tt(t,n){if(t.readOnly)throw new Error("aiecsjs: cannot create entities on a read-only world (worker-attached)");if(n<=0||n>=t.options.maxEntities)throw new Error(`aiecsjs: slot index ${n} out of range`);n>=t.capacity&&B(t,n+1);let e=t.generations[n]??0,o=Q(n,e,t.options);if(p(t,o))return o;if(n>=t.nextFreshIndex){for(let c=t.nextFreshIndex;c<n;c++)t.freeList.push(c);t.nextFreshIndex=n+1;}else {let c=t.freeList.lastIndexOf(n);if(c!==-1){let f=t.freeList.length-1;t.freeList[c]=t.freeList[f],t.freeList.pop();}}let r=t.archetypes[0];if(!r)throw new Error("aiecsjs: missing empty archetype");g(r,r.size+1);let i=r.size;r.entities[i]=o,r.entityRow.set(o,i),r.size++,t.entityArchetype[n]=0;let s=t.options.maskWordCount,a=n*s;for(let c=0;c<s;c++)t.entityMask[a+c]=0;return t.size++,o}function et(t,n){let e=l(t);if(e.readOnly)throw new Error("aiecsjs: cannot destroyEntity on a read-only world");if(!p(e,n))return;let o=n&e.options.indexMask,r=m(e,n),{dispatchDestroyObservers:i}=hn();i(e,n),bn(e,n),G(e,n);let s=e.entityArchetype[o]??0,a=e.archetypes[s];if(a){let d=a.entityRow.get(n);if(d!==void 0){let u=a.size-1;if(d!==u){let y=a.entities[u]??0;a.entities[d]=y,a.entityRow.set(y,d);}a.entities[u]=0,a.entityRow.delete(n),a.size--;}}Y(e,n,r),e.entityArchetype[o]=0;let c=e.options.maskWordCount,f=o*c;for(let d=0;d<c;d++)e.entityMask[f+d]=0;e.generations[o]=(e.generations[o]??0)+1&e.options.generationMask,e.freeList.push(o),e.size--;}function mn(t,n){let e=l(t);return p(e,n)}function p(t,n){let e=n&t.options.indexMask;if(e<=0||e>=t.capacity)return false;let o=t.entityArchetype[e]??0,r=t.archetypes[o];if(!r||!r.entityRow.has(n))return false;let i=t.generations[e]??0,s=n>>>t.options.indexBits&t.options.generationMask;return i===s}function ot(t){return t&X}function rt(t){return t>>>L&K}function it(t,n){return ((n&K)<<L|t&X)>>>0}function st(t,n){return typeof n!="number"?false:mn(t,n)}var R=null;function at(t){R=t;}function hn(){return R||{dispatchDestroyObservers:()=>{}}}var _=null;function ct(t){_=t;}function bn(t,n){_&&_(t,n);}export{rt as A,it as B,st as C,at as D,ct as E,Fn as F,zn as G,Dn as H,x as I,q as J,Nn as K,Pn as L,qn as M,$n as N,Gn as O,Hn as P,Vn as Q,Yn as R,j as a,T as b,F as c,wn as d,xn as e,gn as f,W as g,kn as h,l as i,Sn as j,Wn as k,Bn as l,Mn as m,Un as n,On as o,N as p,Rn as q,Q as r,Zn as s,Jn as t,nt as u,tt as v,et as w,mn as x,p as y,ot as z};//# sourceMappingURL=chunk-ENULPSKV.js.map
|
|
2
|
+
//# sourceMappingURL=chunk-ENULPSKV.js.map
|