aiecsjs 0.3.0 → 0.4.0
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/CHANGELOG.md +45 -2
- package/README.md +24 -17
- package/README_ZHTW.md +24 -17
- package/STABILITY.md +11 -8
- package/STABILITY_ZHTW.md +12 -9
- package/api.json +42 -17
- package/dist/chunk-B777DQR7.cjs +2 -0
- package/dist/chunk-B777DQR7.cjs.map +1 -0
- package/dist/chunk-CTESP3XL.js +2 -0
- package/dist/chunk-CTESP3XL.js.map +1 -0
- package/dist/chunk-F7KNZ27O.js +2 -0
- package/dist/chunk-F7KNZ27O.js.map +1 -0
- package/dist/chunk-RHH5JA74.cjs +2 -0
- package/dist/chunk-RHH5JA74.cjs.map +1 -0
- package/dist/chunk-SJDWI3OZ.cjs +2 -0
- package/dist/chunk-SJDWI3OZ.cjs.map +1 -0
- package/dist/chunk-SQWZUC2Q.js +2 -0
- package/dist/chunk-SQWZUC2Q.js.map +1 -0
- package/dist/commands.cjs +1 -1
- package/dist/commands.cjs.map +1 -1
- package/dist/commands.js +1 -1
- package/dist/commands.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/observers.cjs +1 -1
- package/dist/observers.cjs.map +1 -1
- package/dist/observers.js +1 -1
- package/dist/observers.js.map +1 -1
- package/dist/relations.cjs +1 -1
- package/dist/relations.cjs.map +1 -1
- package/dist/relations.d.cts +20 -1
- package/dist/relations.d.ts +20 -1
- package/dist/relations.js +1 -1
- package/dist/relations.js.map +1 -1
- package/dist/serialize.cjs +1 -1
- package/dist/serialize.cjs.map +1 -1
- package/dist/serialize.js +1 -1
- package/dist/serialize.js.map +1 -1
- package/dist/worker.cjs +1 -1
- package/dist/worker.cjs.map +1 -1
- package/dist/worker.js +1 -1
- package/dist/worker.js.map +1 -1
- package/llms-full.txt +80 -27
- package/package.json +11 -5
package/CHANGELOG.md
CHANGED
|
@@ -6,18 +6,61 @@ All notable changes to `aiecsjs` are recorded in this file.
|
|
|
6
6
|
|
|
7
7
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
8
8
|
|
|
9
|
+
## [0.4.0] - 2026-05-29
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **`getRelationData(world, source, rel, target)`**: new stable export on `aiecsjs/relations`. Returns the `data` payload attached via `addRelation`, or `undefined` when no such edge exists or no data was stored. Closes the write-only-data asymmetry present since 0.1: `addRelation` accepted a data argument but there was no corresponding public read path.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- **`aiecsjs/relations` graduated from experimental to stable.** The graph API (`defineRelation`, `addRelation`, `removeRelation`, `getRelationTargets`, `getRelationData`) and the built-in `ChildOf` relation are now frozen for the 1.x track. See [`STABILITY.md`](./STABILITY.md) for the full stability contract, including the raw slot-keying ABA semantic.
|
|
18
|
+
- **`aiecsjs/worker` remains experimental.** True SAB shared-memory column aliasing is deferred; the worker sub-path continues on snapshot-copy semantics.
|
|
19
|
+
|
|
20
|
+
### Build & Tooling
|
|
21
|
+
|
|
22
|
+
- **size-limit → `scripts/check-size.mjs`**: replaced the `size-limit` + `@size-limit/file` dev dependencies with a zero-dependency script that measures transitive chunk-closure gzip size per ESM entry. Required because `tsup splitting: true` (introduced in 0.3.1) makes each entry a thin re-export shell; the vanilla single-file measurement reported ~899 B for index when the true closure is ~7295 B. The new script resolves chunk imports recursively via BFS, sums per-file gzip, and enforces per-entry budgets.
|
|
23
|
+
- **npm → pnpm**: migrated from `package-lock.json` to `pnpm-lock.yaml`. Added `"packageManager": "pnpm@9.12.3"` and `"publishConfig": { "access": "public" }`. CI and publish workflows updated to use `pnpm/action-setup@v6` + `pnpm install --frozen-lockfile`. `npm publish --provenance --access public` in the publish workflow is intentionally preserved (OIDC trusted publishing requires npm CLI, not pnpm publish).
|
|
24
|
+
- **Coverage tests added + unreachable gaps documented**: new tests cover previously-unreachable paths in `serialize.ts`, `component.ts`, `query.ts`, and `loop.ts`. Thresholds updated to the honestly-achieved floor (statements 95 / branches 81 / functions 98 / lines 99). Unreachable-by-design gaps are now documented in `vitest.config.ts` with Chesterton rationale.
|
|
25
|
+
|
|
26
|
+
## [0.3.1] - 2026-05-29
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- **Packed EntityId signed-overflow for generation ≥ 128**: `createEntity` returned a negative number diverging from the unsigned value stored in archetype row arrays (`Uint32Array`), so query iteration (`runQuery`/`iterQuery`/`forEachEntity`) yielded an eid that failed `entityRow` lookups; `refOf`/`entityExists`/`deref` on a query-iterated high-generation entity misbehaved (`refOf` threw on a live entity). `packEid`/`packEntity` now normalise with `>>> 0`. No public-bundle behaviour change beyond the corrected eid representation (EntityId is opaque + in-memory-only).
|
|
31
|
+
- **`toJSON` silently dropped high-generation entities** (gen ≥ 128 with default 8-bit generation): `toJSON` contained its own inline pack expression that produced a signed (negative) result, diverging from the unsigned key stored in `arch.entityRow`. The affected entity passed the archetype check but failed `entityRow.has()`, so it was omitted from every snapshot and `serializeWorld` call. Fixed by replacing the inline expression with the canonical `packEid` (which applies `>>> 0`). SPOT principle: one pack source of truth.
|
|
32
|
+
- **Cross-subpath registry isolation** (`tsup splitting: false` → `splitting: true`): each compiled entry point (`dist/index.js`, `dist/serialize.js`, etc.) previously bundled its own private copy of `internal/world.ts`, including the module-scope `worldRegistry`. A world created via the core subpath was invisible to `serializeWorld`/`getRelationTargets`/`transferableSnapshot` imported from their respective subpaths, causing `world N is destroyed or unknown` at runtime. With `splitting: true`, esbuild extracts a shared chunk used by all entries; ESM and CJS are both verified by the new `scripts/check-dist-subpaths.mjs` smoke script.
|
|
33
|
+
- **`getRelationTargets` returned raw index as `EntityId`** (gen always 0): `addRelation` stores the target as a raw slot index (`& indexMask`). The previous return path cast this raw index directly to `EntityId`, which is equivalent to a packed id with generation 0. For any target that had been recycled (gen > 0), callers received a stale id that failed `entityExists`, `entityRow` lookups, and component access. Fixed by re-packing each raw index against the current generation via `packEid` before returning.
|
|
34
|
+
- **`resolveOptions` did not validate `indexBits + generationBits ≤ 32`**: the individual range checks (`indexBits ∈ [1, 24]`, `generationBits ∈ [0, 16]`) allowed combinations such as `indexBits=24, generationBits=16` (40 bits), where `gen << 24` silently overflowed and high-generation bits were lost. A sum check is now enforced with a clear error message. The `[Unreleased]` example corrected accordingly (`indexBits: 16, generationBits: 16` = 32 bits).
|
|
35
|
+
|
|
36
|
+
### Known Limitations
|
|
37
|
+
|
|
38
|
+
- **`createDeltaSerializer.apply` with a recycled target world**: `apply` uses the raw entity index from the delta snapshot as the `EntityId` directly. When the target world has already recycled any of those slots (generation > 0), component operations silently act on the wrong packed id. This is a known limitation of the experimental delta API; the common usage (delta → a fresh gen-0 render-mirror world) is unaffected. A proper raw-index-to-packed-id mapping is planned for 0.4. Avoid `apply` against a world that has previously destroyed entities.
|
|
39
|
+
|
|
40
|
+
### Documentation
|
|
41
|
+
|
|
42
|
+
- README / README_ZHTW updated to reflect the shipped 0.3.0 `EntityRef` API: the previous README still described EntityRef as "targeted for 0.3+" and `getEntityGeneration`/`packEntity` as experimental. Both files now correctly state EntityId has been packed since 0.3, and `EntityRef` / `refOf` / `deref` / `aliveRef` / `EntityNotAliveError` are all stable since 0.3.0. API table entries for these symbols added.
|
|
43
|
+
|
|
44
|
+
### Build & Tooling
|
|
45
|
+
|
|
46
|
+
- Coverage gate: `@vitest/coverage-v8` installed and wired into `prepublishOnly` (replaces `npm run test`) and CI. Thresholds: statements 95 / branches 80 / functions 97 / lines 98 — the achievable bar on pristine source. The branch figure honours the `?? 0` / `noUncheckedIndexedAccess` idiom on TypedArray reads (nullish-fallback branches unreachable by design); thresholds are raised only by adding tests, never by stripping defensive guards or scattering `/* v8 ignore */`.
|
|
47
|
+
- `fast-check` property tests (`tests/properties.test.ts`): pack/unpack round-trip invariant (asserts `e >= 0` to guard the P0 regression) and ABA-deref always-null invariant.
|
|
48
|
+
- Dispose three-cycle tests, error-path tests, and observer handler-throw behaviour documented in `tests/world.test.ts` / `tests/observers.test.ts`.
|
|
49
|
+
- `scripts/check-dist-subpaths.mjs` (`npm run verify:dist`): post-build smoke test that imports `createWorld`+`createEntity` from the core subpath and calls `serializeWorld`, `addRelation`/`getRelationTargets`, and `transferableSnapshot` from their respective subpaths for both ESM (`dist/*.js`) and CJS (`dist/*.cjs`). Wired into `prepublishOnly` (after `build`) and CI.
|
|
50
|
+
|
|
9
51
|
## [Unreleased]
|
|
10
52
|
|
|
11
53
|
### Planned for 0.4+
|
|
12
54
|
|
|
13
55
|
- Add `pipeAsync` for async system composition.
|
|
14
56
|
- Doc-test harness so README code blocks are mechanically verified.
|
|
15
|
-
- Promote `aiecsjs/
|
|
57
|
+
- Promote `aiecsjs/worker` to `stable` once true SAB shared-memory column aliasing is implemented.
|
|
16
58
|
- Document the 8-bit generation wrap caveat in [STABILITY.md](./STABILITY.md): with the
|
|
17
59
|
default `generationBits=8`, a single slot recycled 256 times wraps back to its
|
|
18
60
|
starting generation, briefly re-opening the ABA window. Safe for v0.5 shmup
|
|
19
61
|
workloads (~5000 frame to wrap a single slot at 60 fps × ~1k destroys); high-churn
|
|
20
|
-
pools should set `createWorld({ generationBits: 16 })
|
|
62
|
+
pools should set `createWorld({ indexBits: 16, generationBits: 16 })` (16 + 16 = 32 bits;
|
|
63
|
+
65 536 entities × 65 536 generations). See test
|
|
21
64
|
[tests/ref.test.ts](./tests/ref.test.ts) `generation wrap` describe block.
|
|
22
65
|
|
|
23
66
|
## [0.3.0] - 2026-05-29
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
Part of the [ai\*js micro-runtime ecosystem](https://github.com/yshengliao) — see also [aifsmjs](https://github.com/yshengliao/aifsmjs) (FSM) and [aibridgejs](https://github.com/yshengliao/aibridgejs) (cross-context RPC).
|
|
12
12
|
|
|
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.
|
|
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
16
|
import { createWorld, createEntity, defineComponent, defineQuery, pipe, forEachEntity, Types } from 'aiecsjs'
|
|
@@ -329,21 +329,22 @@ forEachEntity(world, q, (e) => { cb.remove(e, SomeTag) })
|
|
|
329
329
|
flush(cb)
|
|
330
330
|
```
|
|
331
331
|
|
|
332
|
-
### Relations and hierarchies
|
|
332
|
+
### Relations and hierarchies
|
|
333
333
|
|
|
334
|
-
>
|
|
334
|
+
> The Relations API is **stable since 0.4.0**. The graph API (`defineRelation` / `addRelation` / `removeRelation` / `getRelationTargets` / `getRelationData`) and the built-in `ChildOf` relation are frozen for the 1.x track.
|
|
335
335
|
|
|
336
336
|
```ts
|
|
337
|
-
import { defineRelation, addRelation, ChildOf, getRelationTargets } from 'aiecsjs/relations'
|
|
337
|
+
import { defineRelation, addRelation, ChildOf, getRelationTargets, getRelationData } from 'aiecsjs/relations'
|
|
338
338
|
|
|
339
|
-
const Likes = defineRelation()
|
|
340
|
-
addRelation(world, alice, Likes, bob)
|
|
339
|
+
const Likes = defineRelation<{ since: number }>()
|
|
340
|
+
addRelation(world, alice, Likes, bob, { since: 2020 })
|
|
341
341
|
addRelation(world, alice, ChildOf, parent)
|
|
342
342
|
|
|
343
343
|
const parentOfAlice = getRelationTargets(world, alice, ChildOf)
|
|
344
|
+
const likedSince = getRelationData(world, alice, Likes, bob) // { since: 2020 }
|
|
344
345
|
```
|
|
345
346
|
|
|
346
|
-
|
|
347
|
+
Exclusive relations (one target only) and the `getRelationData` reader are stable as of 0.4.0. Wildcard relation queries and serialisation of relation graphs remain future work and are not part of the frozen surface.
|
|
347
348
|
|
|
348
349
|
## API Reference
|
|
349
350
|
|
|
@@ -380,8 +381,13 @@ type WorldOptions = {
|
|
|
380
381
|
| `destroyEntity` | `(world: World, eid: EntityId) => void` | stable |
|
|
381
382
|
| `entityExists` | `(world: World, eid: EntityId) => boolean` | stable |
|
|
382
383
|
| `getEntityIndex` | `(eid: EntityId) => number` | stable |
|
|
383
|
-
| `getEntityGeneration` | `(eid: EntityId) => number` |
|
|
384
|
-
| `packEntity` | `(index: number, generation: number) => EntityId` |
|
|
384
|
+
| `getEntityGeneration` | `(eid: EntityId) => number` | stable (since 0.3.0) — returns the 8-bit generation field; uses default 24/8 layout |
|
|
385
|
+
| `packEntity` | `(index: number, generation: number) => EntityId` | stable (since 0.3.0) — packs index + generation using default 24/8 layout |
|
|
386
|
+
| `refOf` | `<T>(world: World, eid: EntityId) => EntityRef<T>` | stable (since 0.3.0) — creates ABA-safe ref; throws `EntityNotAliveError` if entity is dead |
|
|
387
|
+
| `deref` | `<T>(world: World, ref: EntityRef<T>) => EntityId \| null` | stable (since 0.3.0) — returns live `EntityId` or `null` if stale/cross-world; never throws |
|
|
388
|
+
| `aliveRef` | `<T>(world: World, ref: EntityRef<T>) => boolean` | stable (since 0.3.0) — boolean guard form of `deref`; never throws |
|
|
389
|
+
| `EntityRef` | `interface EntityRef<T> { id: EntityId; worldId: number }` | stable (since 0.3.0) — opaque ABA-safe reference; in-memory only |
|
|
390
|
+
| `EntityNotAliveError` | `class EntityNotAliveError extends Error { eid: number }` | stable (since 0.3.0) — thrown by `refOf` when entity is not alive |
|
|
385
391
|
|
|
386
392
|
### Component — `aiecsjs`
|
|
387
393
|
|
|
@@ -462,15 +468,16 @@ const Types = { i8, u8, i16, u16, i32, u32, f32, f64, eid, bool } as const
|
|
|
462
468
|
| `attachWorld` | `(buffer, opts?) => World` | experimental |
|
|
463
469
|
| `detachWorld` | `(world) => void` | experimental |
|
|
464
470
|
|
|
465
|
-
### Relations — `aiecsjs/relations`
|
|
471
|
+
### Relations — `aiecsjs/relations`
|
|
466
472
|
|
|
467
473
|
| Function | Signature | Stability |
|
|
468
474
|
|---|---|---|
|
|
469
|
-
| `defineRelation` | `<T>(opts?) => Relation<T>` |
|
|
470
|
-
| `addRelation` | `(world, src, rel, tgt, data?) => void` |
|
|
471
|
-
| `removeRelation` | `(world, src, rel, tgt) => void` |
|
|
472
|
-
| `getRelationTargets` | `(world, src, rel) => readonly EntityId[]` |
|
|
473
|
-
| `
|
|
475
|
+
| `defineRelation` | `<T>(opts?) => Relation<T>` | stable |
|
|
476
|
+
| `addRelation` | `(world, src, rel, tgt, data?) => void` | stable |
|
|
477
|
+
| `removeRelation` | `(world, src, rel, tgt) => void` | stable |
|
|
478
|
+
| `getRelationTargets` | `(world, src, rel) => readonly EntityId[]` | stable |
|
|
479
|
+
| `getRelationData` | `<T>(world, src, rel, tgt) => T \| undefined` | stable (since 0.4.0) |
|
|
480
|
+
| `ChildOf` (constant) | `Relation` | stable |
|
|
474
481
|
|
|
475
482
|
### Utility — `aiecsjs`
|
|
476
483
|
|
|
@@ -847,7 +854,7 @@ if (VERSION.startsWith('0.')) {
|
|
|
847
854
|
|
|
848
855
|
### Stability contract
|
|
849
856
|
|
|
850
|
-
See [`STABILITY.md`](./STABILITY.md). In short: imports from `aiecsjs` (root) are **stable** within 0.x minors.
|
|
857
|
+
See [`STABILITY.md`](./STABILITY.md). In short: imports from `aiecsjs` (root) are **stable** within 0.x minors. `aiecsjs/relations` is **stable** since 0.4.0; `aiecsjs/worker` is **experimental**. Anything in `aiecsjs/internal` is **internal** — do not import.
|
|
851
858
|
|
|
852
859
|
### Telemetry / privacy
|
|
853
860
|
|
|
@@ -892,7 +899,7 @@ A: It will be on first stable publish. Until then, the docs are the contract.
|
|
|
892
899
|
|
|
893
900
|
- **Max entity count** is capped by `indexBits` × `generationBits`. Default 24 + 8 = 16M entities × 256 recycles.
|
|
894
901
|
- **No automatic system scheduler / parallel execution** in 0.1. Systems run in `pipe()` order on one thread (you can launch additional workers manually).
|
|
895
|
-
- **Relations API is
|
|
902
|
+
- **Relations API (`aiecsjs/relations`) is stable since 0.4.0.** Wildcard relation queries and relation-graph serialisation remain future work.
|
|
896
903
|
- **AoS components** not SAB-shareable across workers.
|
|
897
904
|
- **Network delta serializer** wire format is experimental in 0.1; may change.
|
|
898
905
|
- **WebGPU integration is one-way** (CPU → GPU). No compute-shader system generation.
|
package/README_ZHTW.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
隸屬 [ai\*js micro-runtime 生態系](https://github.com/yshengliao) ─ 另見 [aifsmjs](https://github.com/yshengliao/aifsmjs)(FSM)與 [aibridgejs](https://github.com/yshengliao/aibridgejs)(cross-context RPC)。
|
|
12
12
|
|
|
13
|
-
aiecsjs 採用 **原型表格搭配 TypedArray 欄位** 與 **位元遮罩查詢**,這正是 piecs 與 wolf-ecs 在公開效能評測中名列前茅所採用的架構。API 為 **函式式且可 tree-shake**,以 `pipe()` 組合。元件(Component)同時支援 SoA(結構陣列)與 AoS
|
|
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
16
|
import { createWorld, createEntity, defineComponent, defineQuery, pipe, forEachEntity, Types } from 'aiecsjs'
|
|
@@ -336,21 +336,22 @@ forEachEntity(world, q, (e) => { cb.remove(e, SomeTag) })
|
|
|
336
336
|
flush(cb)
|
|
337
337
|
```
|
|
338
338
|
|
|
339
|
-
### Relations
|
|
339
|
+
### Relations 與階層
|
|
340
340
|
|
|
341
|
-
>
|
|
341
|
+
> Relations API **自 0.4.0 起穩定(stable)**。graph API(`defineRelation` / `addRelation` / `removeRelation` / `getRelationTargets` / `getRelationData`)與內建的 `ChildOf` relation 已凍結至 1.x 軌道。
|
|
342
342
|
|
|
343
343
|
```ts
|
|
344
|
-
import { defineRelation, addRelation, ChildOf, getRelationTargets } from 'aiecsjs/relations'
|
|
344
|
+
import { defineRelation, addRelation, ChildOf, getRelationTargets, getRelationData } from 'aiecsjs/relations'
|
|
345
345
|
|
|
346
|
-
const Likes = defineRelation()
|
|
347
|
-
addRelation(world, alice, Likes, bob)
|
|
346
|
+
const Likes = defineRelation<{ since: number }>()
|
|
347
|
+
addRelation(world, alice, Likes, bob, { since: 2020 })
|
|
348
348
|
addRelation(world, alice, ChildOf, parent)
|
|
349
349
|
|
|
350
350
|
const parentOfAlice = getRelationTargets(world, alice, ChildOf)
|
|
351
|
+
const likedSince = getRelationData(world, alice, Likes, bob) // { since: 2020 }
|
|
351
352
|
```
|
|
352
353
|
|
|
353
|
-
|
|
354
|
+
獨佔關係(exclusive,只允許一個目標)與 `getRelationData` 讀取器自 0.4.0 起穩定。wildcard relation 查詢與關係圖序列化仍屬未來工作,不在凍結表面內。
|
|
354
355
|
|
|
355
356
|
## API 參考
|
|
356
357
|
|
|
@@ -387,8 +388,13 @@ type WorldOptions = {
|
|
|
387
388
|
| `destroyEntity` | `(world: World, eid: EntityId) => void` | stable |
|
|
388
389
|
| `entityExists` | `(world: World, eid: EntityId) => boolean` | stable |
|
|
389
390
|
| `getEntityIndex` | `(eid: EntityId) => number` | stable |
|
|
390
|
-
| `getEntityGeneration` | `(eid: EntityId) => number` | stable |
|
|
391
|
-
| `packEntity` | `(index: number, generation: number) => EntityId` | stable |
|
|
391
|
+
| `getEntityGeneration` | `(eid: EntityId) => number` | stable(自 0.3.0)— 回傳 8-bit generation 欄位;使用預設 24/8 佈局 |
|
|
392
|
+
| `packEntity` | `(index: number, generation: number) => EntityId` | stable(自 0.3.0)— 使用預設 24/8 佈局打包 index + generation |
|
|
393
|
+
| `refOf` | `<T>(world: World, eid: EntityId) => EntityRef<T>` | stable(自 0.3.0)— 建立 ABA-safe ref;entity 已死亡時拋出 `EntityNotAliveError` |
|
|
394
|
+
| `deref` | `<T>(world: World, ref: EntityRef<T>) => EntityId \| null` | stable(自 0.3.0)— 回傳存活的 `EntityId`,否則回傳 `null`;絕不拋出 |
|
|
395
|
+
| `aliveRef` | `<T>(world: World, ref: EntityRef<T>) => boolean` | stable(自 0.3.0)— `deref` 的布林 guard 形式;絕不拋出 |
|
|
396
|
+
| `EntityRef` | `interface EntityRef<T> { id: EntityId; worldId: number }` | stable(自 0.3.0)— 不透明的 ABA-safe 參照;僅限記憶體內使用 |
|
|
397
|
+
| `EntityNotAliveError` | `class EntityNotAliveError extends Error { eid: number }` | stable(自 0.3.0)— `refOf` 在 entity 不存活時拋出 |
|
|
392
398
|
|
|
393
399
|
### Component — `aiecsjs`
|
|
394
400
|
|
|
@@ -469,15 +475,16 @@ const Types = { i8, u8, i16, u16, i32, u32, f32, f64, eid, bool } as const
|
|
|
469
475
|
| `attachWorld` | `(buffer, opts?) => World` | experimental |
|
|
470
476
|
| `detachWorld` | `(world) => void` | experimental |
|
|
471
477
|
|
|
472
|
-
### Relations — `aiecsjs/relations
|
|
478
|
+
### Relations — `aiecsjs/relations`
|
|
473
479
|
|
|
474
480
|
| 函式 | Signature | 穩定度 |
|
|
475
481
|
|---|---|---|
|
|
476
|
-
| `defineRelation` | `<T>(opts?) => Relation<T>` |
|
|
477
|
-
| `addRelation` | `(world, src, rel, tgt, data?) => void` |
|
|
478
|
-
| `removeRelation` | `(world, src, rel, tgt) => void` |
|
|
479
|
-
| `getRelationTargets` | `(world, src, rel) => readonly EntityId[]` |
|
|
480
|
-
| `
|
|
482
|
+
| `defineRelation` | `<T>(opts?) => Relation<T>` | stable |
|
|
483
|
+
| `addRelation` | `(world, src, rel, tgt, data?) => void` | stable |
|
|
484
|
+
| `removeRelation` | `(world, src, rel, tgt) => void` | stable |
|
|
485
|
+
| `getRelationTargets` | `(world, src, rel) => readonly EntityId[]` | stable |
|
|
486
|
+
| `getRelationData` | `<T>(world, src, rel, tgt) => T \| undefined` | stable(自 0.4.0) |
|
|
487
|
+
| `ChildOf`(常數) | `Relation` | stable |
|
|
481
488
|
|
|
482
489
|
### 工具 — `aiecsjs`
|
|
483
490
|
|
|
@@ -856,7 +863,7 @@ if (VERSION.startsWith('0.')) {
|
|
|
856
863
|
|
|
857
864
|
### 穩定度契約
|
|
858
865
|
|
|
859
|
-
請見 [`STABILITY_ZHTW.md`](./STABILITY_ZHTW.md)。簡言之:從 `aiecsjs`(根目錄)匯入者在 0.x minor 內為 **stable
|
|
866
|
+
請見 [`STABILITY_ZHTW.md`](./STABILITY_ZHTW.md)。簡言之:從 `aiecsjs`(根目錄)匯入者在 0.x minor 內為 **stable**。`aiecsjs/relations` 自 0.4.0 起為 **stable**;`aiecsjs/worker` 仍為 **experimental**。任何 `aiecsjs/internal` 內的東西為 **internal**,請勿引用。
|
|
860
867
|
|
|
861
868
|
### 遙測 / 隱私
|
|
862
869
|
|
|
@@ -901,7 +908,7 @@ A:將在首次穩定發佈時上架。在那之前,文件即契約。
|
|
|
901
908
|
|
|
902
909
|
- **最大實體數** 由 `indexBits` × `generationBits` 決定。預設 24 + 8 = 16M 實體 × 各 256 次回收。
|
|
903
910
|
- **0.1 沒有自動系統排程器 / 平行執行**。系統以 `pipe()` 順序在單執行緒執行(你可以自行啟動更多 worker)。
|
|
904
|
-
- **Relations API
|
|
911
|
+
- **Relations API(`aiecsjs/relations`)自 0.4.0 起穩定。** wildcard relation 查詢與關係圖序列化仍屬未來工作。
|
|
905
912
|
- **AoS 元件** 無法跨 Worker 透過 SAB 共享。
|
|
906
913
|
- **網路 delta 序列器** wire format 在 0.1 為 experimental,可能改變。
|
|
907
914
|
- **WebGPU 整合為單向**(CPU → GPU)。無 compute-shader 系統生成。
|
package/STABILITY.md
CHANGED
|
@@ -113,17 +113,20 @@ The entire subpath is **experimental** in 0.x. **In 0.x the implementation is a
|
|
|
113
113
|
| `attachWorld` | experimental | 0.1.0 | |
|
|
114
114
|
| `detachWorld` | experimental | 0.1.0 | |
|
|
115
115
|
|
|
116
|
-
### `aiecsjs/relations` (
|
|
116
|
+
### `aiecsjs/relations` (stable sub-path since 0.4.0)
|
|
117
117
|
|
|
118
|
-
The
|
|
118
|
+
The relations sub-path is **stable** as of 0.4.0. The graph API (`defineRelation`, `addRelation`, `removeRelation`, `getRelationTargets`, `getRelationData`) and the built-in `ChildOf` relation are frozen for the 1.x track.
|
|
119
|
+
|
|
120
|
+
**Raw slot-keying ABA semantic:** relation storage keys edges by raw entity slot index (`entityId & indexMask`), not by the full packed EntityId (which includes a generation counter). If entity A is destroyed and a different entity B is later created occupying the same slot, B will inherit A's outgoing and incoming edges unless the destroy cleanup hook ran. The cleanup hook fires automatically when `destroyEntity` is called, so normal usage is safe. Callers holding cached EntityId values across destroy/recreate cycles should validate liveness with `entityExists` before reading relation data if ABA is a concern.
|
|
119
121
|
|
|
120
122
|
| Export | Stability | Since | Notes |
|
|
121
123
|
|---|---|---|---|
|
|
122
|
-
| `defineRelation` |
|
|
123
|
-
| `addRelation` |
|
|
124
|
-
| `removeRelation` |
|
|
125
|
-
| `getRelationTargets` |
|
|
126
|
-
| `ChildOf` (constant) |
|
|
124
|
+
| `defineRelation` | stable | 0.1.0 | |
|
|
125
|
+
| `addRelation` | stable | 0.1.0 | |
|
|
126
|
+
| `removeRelation` | stable | 0.1.0 | |
|
|
127
|
+
| `getRelationTargets` | stable | 0.1.0 | |
|
|
128
|
+
| `ChildOf` (constant) | stable | 0.1.0 | Built-in exclusive relation. |
|
|
129
|
+
| `getRelationData` | stable | 0.4.0 | Returns the data payload attached via `addRelation`, or `undefined` if no such edge or no data was stored. Subject to the raw slot-keying ABA semantic described above. |
|
|
127
130
|
|
|
128
131
|
### `aiecsjs/internal/*`
|
|
129
132
|
|
|
@@ -136,7 +139,7 @@ Everything under this prefix is **internal**. It exists for the implementation's
|
|
|
136
139
|
| 0.1.x | Core surface (world, entity, component, query, system, loop, commands, observers, serialize) | Initial publish; all marked experimental at the package level but per-export stable where listed. |
|
|
137
140
|
| 0.2.0 | Safety + alignment | Prototype-pollution hardening, observer `{ signal? }`, `disposeWorld` alias, `getEntityGeneration` / `packEntity` re-labelled experimental, `verify:llms` gate. See [CHANGELOG.md](./CHANGELOG.md#020---2026-05-28). |
|
|
138
141
|
| 0.3.x | EntityRef + generation packing | ABA-safe; `getEntityGeneration` / `packEntity` → stable. |
|
|
139
|
-
| 0.4
|
|
142
|
+
| 0.4.0 | Relations stabilisation | `aiecsjs/relations` graduated to stable; `getRelationData` added. `aiecsjs/worker` remains experimental (true SAB shared-memory columns deferred). |
|
|
140
143
|
| 0.6+ | Multi-World snapshot diff transport (placeholder) | experimental — design TBD. |
|
|
141
144
|
| 1.0.0 | API freeze | All `stable` exports frozen for 1.x. |
|
|
142
145
|
|
package/STABILITY_ZHTW.md
CHANGED
|
@@ -25,7 +25,7 @@ aiecsjs 遵循 [semver](https://semver.org/)。在 **0.x** 系列內:
|
|
|
25
25
|
| 匯出 | 穩定度 | 起始版本 | 備註 |
|
|
26
26
|
|---|---|---|---|
|
|
27
27
|
| `createWorld` | stable | 0.1.0 | |
|
|
28
|
-
| `destroyWorld` |
|
|
28
|
+
| `destroyWorld` | **deprecated** | 0.1.0 | 請改用 `disposeWorld`。預計於 1.0 移除。 |
|
|
29
29
|
| `resetWorld` | stable | 0.1.0 | |
|
|
30
30
|
| `getWorldSize` | stable | 0.1.0 | |
|
|
31
31
|
| `getWorldCapacity` | stable | 0.1.0 | |
|
|
@@ -112,17 +112,20 @@ aiecsjs 遵循 [semver](https://semver.org/)。在 **0.x** 系列內:
|
|
|
112
112
|
| `attachWorld` | experimental | 0.1.0 | |
|
|
113
113
|
| `detachWorld` | experimental | 0.1.0 | |
|
|
114
114
|
|
|
115
|
-
### `aiecsjs/relations`(
|
|
115
|
+
### `aiecsjs/relations`(stable sub-path,自 0.4.0 起)
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
relations sub-path 自 **0.4.0** 起為 **stable**。圖形 API(`defineRelation`、`addRelation`、`removeRelation`、`getRelationTargets`、`getRelationData`)以及內建 `ChildOf` 關係已為 1.x 凍結。
|
|
118
|
+
|
|
119
|
+
**原始 slot 鍵值的 ABA 語意:** relation storage 以原始 entity slot index(`entityId & indexMask`)作為 edge 的鍵,而非包含 generation counter 的完整 packed EntityId。若 entity A 被銷毀後,另一個 entity B 在同一個 slot 被建立,B 將繼承 A 的所有進出 edge——除非 destroy cleanup hook 已執行(呼叫 `destroyEntity` 時會自動執行)。一般使用下是安全的;若在 destroy/recreate 循環中持有快取的 EntityId,讀取 relation data 前應以 `entityExists` 確認活躍狀態。
|
|
118
120
|
|
|
119
121
|
| 匯出 | 穩定度 | 起始版本 | 備註 |
|
|
120
122
|
|---|---|---|---|
|
|
121
|
-
| `defineRelation` |
|
|
122
|
-
| `addRelation` |
|
|
123
|
-
| `removeRelation` |
|
|
124
|
-
| `getRelationTargets` |
|
|
125
|
-
| `ChildOf`(常數) |
|
|
123
|
+
| `defineRelation` | stable | 0.1.0 | |
|
|
124
|
+
| `addRelation` | stable | 0.1.0 | |
|
|
125
|
+
| `removeRelation` | stable | 0.1.0 | |
|
|
126
|
+
| `getRelationTargets` | stable | 0.1.0 | |
|
|
127
|
+
| `ChildOf`(常數) | stable | 0.1.0 | 內建獨佔關係。 |
|
|
128
|
+
| `getRelationData` | stable | 0.4.0 | 回傳透過 `addRelation` 附加的 data payload,或在 edge 不存在 / 未儲存 data 時回傳 `undefined`。受上述原始 slot 鍵值 ABA 語意影響。 |
|
|
126
129
|
|
|
127
130
|
### `aiecsjs/internal/*`
|
|
128
131
|
|
|
@@ -135,7 +138,7 @@ aiecsjs 遵循 [semver](https://semver.org/)。在 **0.x** 系列內:
|
|
|
135
138
|
| 0.1.x | 核心表面(world、entity、component、query、system、loop、commands、observers、serialize) | 初次發佈;package 整體標 experimental,但各 export 表中列為 stable 者皆穩定。 |
|
|
136
139
|
| 0.2.0 | 安全與生態對齊 | 原型污染強化、observer `{ signal? }`、`disposeWorld` 別名、`getEntityGeneration` / `packEntity` 改 experimental、`verify:llms` gate。詳見 [CHANGELOG.md](./CHANGELOG.md#020---2026-05-28)。 |
|
|
137
140
|
| 0.3.x | EntityRef + generation packing | ABA-safe;`getEntityGeneration` / `packEntity` → stable。 |
|
|
138
|
-
| 0.4
|
|
141
|
+
| 0.4.0 | Relations 穩定化 | `aiecsjs/relations` 升為 stable;新增 `getRelationData`。`aiecsjs/worker` 維持 experimental(真正 SAB 共享記憶體欄位延後)。 |
|
|
139
142
|
| 0.6+ | Multi-World snapshot diff transport(佔位) | experimental — 設計待定。 |
|
|
140
143
|
| 1.0.0 | API 凍結 | 所有 `stable` 匯出於 1.x 系列凍結。 |
|
|
141
144
|
|
package/api.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://yshengliao.github.io/aiecsjs/api-schema.json",
|
|
3
3
|
"name": "aiecsjs",
|
|
4
|
-
"version": "0.
|
|
5
|
-
"generated": "2026-05-
|
|
4
|
+
"version": "0.4.0",
|
|
5
|
+
"generated": "2026-05-29",
|
|
6
6
|
"stability": "experimental",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": "https://github.com/yshengliao/aiecsjs",
|
|
@@ -84,13 +84,14 @@
|
|
|
84
84
|
},
|
|
85
85
|
{
|
|
86
86
|
"name": "aiecsjs/relations",
|
|
87
|
-
"description": "Entity relations and hierarchies
|
|
87
|
+
"description": "Entity relations and hierarchies. Stable since 0.4.0; the graph API and built-in ChildOf relation are frozen for the 1.x track.",
|
|
88
88
|
"exports": [
|
|
89
89
|
"defineRelation",
|
|
90
90
|
"addRelation",
|
|
91
91
|
"removeRelation",
|
|
92
92
|
"getRelationTargets",
|
|
93
|
-
"ChildOf"
|
|
93
|
+
"ChildOf",
|
|
94
|
+
"getRelationData"
|
|
94
95
|
]
|
|
95
96
|
}
|
|
96
97
|
],
|
|
@@ -1184,11 +1185,11 @@
|
|
|
1184
1185
|
"name": "defineRelation",
|
|
1185
1186
|
"kind": "function",
|
|
1186
1187
|
"module": "aiecsjs/relations",
|
|
1187
|
-
"since": "0.
|
|
1188
|
-
"stability": "
|
|
1188
|
+
"since": "0.1.0",
|
|
1189
|
+
"stability": "stable",
|
|
1189
1190
|
"deprecated": false,
|
|
1190
1191
|
"signature": "function defineRelation<T = void>(options?: { exclusive?: boolean }): Relation<T>",
|
|
1191
|
-
"summary": "Declare a relation type between two entities.
|
|
1192
|
+
"summary": "Declare a relation type between two entities. Pass `exclusive: true` for one-to-one relations (e.g. ChildOf).",
|
|
1192
1193
|
"params": [
|
|
1193
1194
|
{
|
|
1194
1195
|
"name": "options",
|
|
@@ -1210,8 +1211,8 @@
|
|
|
1210
1211
|
"name": "addRelation",
|
|
1211
1212
|
"kind": "function",
|
|
1212
1213
|
"module": "aiecsjs/relations",
|
|
1213
|
-
"since": "0.
|
|
1214
|
-
"stability": "
|
|
1214
|
+
"since": "0.1.0",
|
|
1215
|
+
"stability": "stable",
|
|
1215
1216
|
"deprecated": false,
|
|
1216
1217
|
"signature": "function addRelation<T>(world: World, source: EntityId, rel: Relation<T>, target: EntityId, data?: T): void",
|
|
1217
1218
|
"summary": "Add a relation edge from source to target.",
|
|
@@ -1229,8 +1230,8 @@
|
|
|
1229
1230
|
"name": "removeRelation",
|
|
1230
1231
|
"kind": "function",
|
|
1231
1232
|
"module": "aiecsjs/relations",
|
|
1232
|
-
"since": "0.
|
|
1233
|
-
"stability": "
|
|
1233
|
+
"since": "0.1.0",
|
|
1234
|
+
"stability": "stable",
|
|
1234
1235
|
"deprecated": false,
|
|
1235
1236
|
"signature": "function removeRelation(world: World, source: EntityId, rel: Relation, target: EntityId): void",
|
|
1236
1237
|
"summary": "Remove a single relation edge.",
|
|
@@ -1243,15 +1244,39 @@
|
|
|
1243
1244
|
"name": "getRelationTargets",
|
|
1244
1245
|
"kind": "function",
|
|
1245
1246
|
"module": "aiecsjs/relations",
|
|
1246
|
-
"since": "0.
|
|
1247
|
-
"stability": "
|
|
1247
|
+
"since": "0.1.0",
|
|
1248
|
+
"stability": "stable",
|
|
1248
1249
|
"deprecated": false,
|
|
1249
1250
|
"signature": "function getRelationTargets(world: World, source: EntityId, rel: Relation): readonly EntityId[]",
|
|
1250
1251
|
"summary": "Enumerate targets reachable from source by this relation.",
|
|
1251
1252
|
"params": [],
|
|
1252
1253
|
"returns": { "type": "readonly EntityId[]", "description": "Target entity IDs." },
|
|
1253
1254
|
"examples": [],
|
|
1254
|
-
"seeAlso": ["defineRelation"]
|
|
1255
|
+
"seeAlso": ["defineRelation", "getRelationData"]
|
|
1256
|
+
},
|
|
1257
|
+
{
|
|
1258
|
+
"name": "getRelationData",
|
|
1259
|
+
"kind": "function",
|
|
1260
|
+
"module": "aiecsjs/relations",
|
|
1261
|
+
"since": "0.4.0",
|
|
1262
|
+
"stability": "stable",
|
|
1263
|
+
"deprecated": false,
|
|
1264
|
+
"signature": "function getRelationData<T>(world: World, source: EntityId, rel: Relation<T>, target: EntityId): T | undefined",
|
|
1265
|
+
"summary": "Read the data payload attached to a relation edge. Returns the value passed as `data` to `addRelation`, or `undefined` if no such edge exists or no data was stored. Subject to raw slot-keying: edges are keyed by slot index, not generation; validate liveness with `entityExists` if slot recycling is a concern.",
|
|
1266
|
+
"params": [
|
|
1267
|
+
{ "name": "world", "type": "World", "optional": false, "description": "Target world." },
|
|
1268
|
+
{ "name": "source", "type": "EntityId", "optional": false, "description": "Source entity." },
|
|
1269
|
+
{ "name": "rel", "type": "Relation<T>", "optional": false, "description": "Relation handle." },
|
|
1270
|
+
{ "name": "target", "type": "EntityId", "optional": false, "description": "Target entity." }
|
|
1271
|
+
],
|
|
1272
|
+
"returns": { "type": "T | undefined", "description": "Data payload, or undefined." },
|
|
1273
|
+
"examples": [
|
|
1274
|
+
{
|
|
1275
|
+
"title": "Read relation payload",
|
|
1276
|
+
"code": "const data = getRelationData(world, alice, Likes, bob) // { since: 2024 } or undefined"
|
|
1277
|
+
}
|
|
1278
|
+
],
|
|
1279
|
+
"seeAlso": ["addRelation", "removeRelation", "getRelationTargets"]
|
|
1255
1280
|
},
|
|
1256
1281
|
{
|
|
1257
1282
|
"name": "isWorld",
|
|
@@ -1575,9 +1600,9 @@
|
|
|
1575
1600
|
"kind": "constant",
|
|
1576
1601
|
"module": "aiecsjs/relations",
|
|
1577
1602
|
"type": "Relation",
|
|
1578
|
-
"stability": "
|
|
1579
|
-
"since": "0.
|
|
1580
|
-
"description": "Built-in parent-child relation.
|
|
1603
|
+
"stability": "stable",
|
|
1604
|
+
"since": "0.1.0",
|
|
1605
|
+
"description": "Built-in exclusive parent-child relation. Each entity may have at most one parent at a time."
|
|
1581
1606
|
}
|
|
1582
1607
|
]
|
|
1583
1608
|
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';var chunkSJDWI3OZ_cjs=require('./chunk-SJDWI3OZ.cjs');var d="AIEC",B=1;function P(n,o){let e=D(n);return o?.components&&(e.entities=e.entities.map(t=>({eid:t.eid,components:t.components.filter(i=>o.components.some(c=>c.__id===i.id))}))),x(e)}function Q(n,o){let e=N(n,o);return J(e)}function D(n){let o=chunkSJDWI3OZ_cjs.i(n),e=[];for(let t=1;t<o.capacity;t++){let i=o.entityArchetype[t]??0;if(i===0)continue;let c=o.archetypes[i];if(!c)continue;let r=o.generations[t]??0,a=chunkSJDWI3OZ_cjs.r(t,r,o.options);if(!c.entityRow.has(a))continue;let s=o.options.maskWordCount,l=t*s,p=[];for(let m=0;m<s;m++){let u=o.entityMask[l+m]??0;for(;u!==0;){let b=u&-u,_=(m<<5)+(31-Math.clz32(b)),f=o.componentInfoByBit[_];if(f){let S=o.componentStorageByBit[_],w=null;if(f.kind==="soa"&&S?.soa){let y={};for(let h of f.fields){let g=S.soa[h.name];if(g)if(h.vectorLen===1)y[h.name]=g[t];else {let L=[],M=t*h.vectorLen;for(let C=0;C<h.vectorLen;C++)L.push(g[M+C]??0);y[h.name]=L;}}w=y;}else f.kind==="aos"&&S?.aos?w=S.aos[t]??null:w=true;p.push({kind:f.kind,id:f.id,data:w});}u&=u-1;}}e.push({eid:t,components:p});}return {version:o.version,capacity:o.capacity,entities:e}}function J(n){let o=chunkSJDWI3OZ_cjs.k({initialCapacity:n.capacity}),e=new Map;for(let t of n.entities){let i=chunkSJDWI3OZ_cjs.u(o);e.set(t.eid,i);}for(let t of n.entities){let i=e.get(t.eid);for(let c of t.components){let r=chunkSJDWI3OZ_cjs.P(c.id);if(!r)continue;let a=A(r);a&&chunkSJDWI3OZ_cjs.I(o,i,a,c.data);}}return o}function A(n){if(n.kind==="soa")return {__kind:"soa",__id:n.id,__schema:n.schema??{}};if(n.kind==="aos"){let o=n.factory??(()=>({}));return {__kind:"aos",__id:n.id,__factory:o}}return {__kind:"tag",__id:n.id}}function x(n){let o=JSON.stringify(n),e=new TextEncoder().encode(o),t=new TextEncoder().encode(chunkSJDWI3OZ_cjs.a),c=12+t.length+4+e.length,r=new Uint8Array(c),a=new DataView(r.buffer),s=0;return r[s++]=d.charCodeAt(0),r[s++]=d.charCodeAt(1),r[s++]=d.charCodeAt(2),r[s++]=d.charCodeAt(3),a.setUint32(s,B,true),s+=4,a.setUint32(s,t.length,true),s+=4,r.set(t,s),s+=t.length,a.setUint32(s,e.length,true),s+=4,r.set(e,s),r}function N(n,o){if(n.length<12)throw new Error("aiecsjs: bytes too short to be a valid snapshot");let e=new DataView(n.buffer,n.byteOffset,n.byteLength);if(n[0]!==d.charCodeAt(0)||n[1]!==d.charCodeAt(1)||n[2]!==d.charCodeAt(2)||n[3]!==d.charCodeAt(3))throw new Error("aiecsjs: invalid magic bytes");let t=4,i=e.getUint32(t,true);t+=4;let c=o?.onUnknownVersion??"throw";if(i!==B&&c==="throw")throw new Error(`aiecsjs: format version ${i} not supported`);let r=64*1024*1024;if(t+4>n.length)throw new Error("aiecsjs: snapshot truncated before verLen");let a=e.getUint32(t,true);if(t+=4,a>r||t+a>n.length)throw new Error(`aiecsjs: snapshot verLen=${a} out of bounds`);if(t+=a,t+4>n.length)throw new Error("aiecsjs: snapshot truncated before jsonLen");let s=e.getUint32(t,true);if(t+=4,s>r||t+s>n.length)throw new Error(`aiecsjs: snapshot jsonLen=${s} out of bounds`);let l=n.subarray(t,t+s),p=new TextDecoder().decode(l);return JSON.parse(p)}function Y(n,o){let e={world:n,components:o?.components??chunkSJDWI3OZ_cjs.Q().map(t=>A(t)).filter(Boolean),lastSnapshot:null};return {capture(){let t=D(e.world),i;return e.lastSnapshot?i=V(e.lastSnapshot,t):i=t,e.lastSnapshot=t,x(i)},apply(t,i){let c=N(i),r=chunkSJDWI3OZ_cjs.i(t);for(let a of c.entities)if(!(a.eid>=r.capacity)){if(!chunkSJDWI3OZ_cjs.w(t,a.eid))for(;r.size<a.eid;)chunkSJDWI3OZ_cjs.u(t);for(let s of a.components){let l=chunkSJDWI3OZ_cjs.P(s.id);if(!l)continue;let p=A(l);p&&chunkSJDWI3OZ_cjs.I(t,a.eid,p,s.data);}}},reset(){e.lastSnapshot=null;}}}function V(n,o){let e=new Map(n.entities.map(i=>[i.eid,i])),t=[];for(let i of o.entities){let c=e.get(i.eid);if(!c){t.push(i);continue}let r=JSON.stringify(c.components),a=JSON.stringify(i.components);r!==a&&t.push(i);}return {version:o.version,capacity:o.capacity,entities:t}}exports.a=P;exports.b=Q;exports.c=D;exports.d=J;exports.e=Y;//# sourceMappingURL=chunk-B777DQR7.cjs.map
|
|
2
|
+
//# sourceMappingURL=chunk-B777DQR7.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/serialize.ts"],"names":["MAGIC","FORMAT_VERSION","serializeWorld","world","options","snapshot","toJSON","e","c","comp","packBinary","deserializeWorld","bytes","unpackBinary","fromJSON","state","getWorldState","entities","idx","archId","arch","gen","packedEid","packEid","w","base","components","wi","word","lsb","bit","info","storage","data","obj","f","col","arr","baseI","i","createWorld","eidMap","eid","createEntity","getComponentByInternalId","handle","getComponentHandle","addComponent","factory","json","jsonBytes","versionBytes","VERSION","total","out","view","off","formatVersion","onUnknown","MAX_FIELD_LEN","verLen","jsonLen","createDeltaSerializer","listAllComponents","current","delta","computeDelta","targetWorld","deltaBytes","targetState","entityExists","prev","curr","prevByEid","changed","prevE","prevSig","currSig"],"mappings":"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","file":"chunk-B777DQR7.cjs","sourcesContent":["// aiecsjs/serialize — binary, JSON, and delta serializers.\n\nimport { testBit } from './internal/bitmask.js'\nimport {\n addComponent,\n defineComponent,\n defineObjectComponent,\n defineTag,\n getComponentByInternalId,\n getComponentInfo,\n listAllComponents,\n} from './internal/component.js'\nimport { createEntity, destroyEntity, entityExists, packEid } from './internal/entity.js'\nimport type {\n ComponentInfo,\n ComponentLike,\n DeltaSerializer,\n DeserializeOptions,\n EntityId,\n SerializeOptions,\n World,\n WorldSnapshot,\n} from './internal/types.js'\nimport { createWorld, getWorldState } from './internal/world.js'\nimport { VERSION } from './version.js'\n\nconst MAGIC = 'AIEC'\nconst FORMAT_VERSION = 1\n\nexport function serializeWorld(world: World, options?: SerializeOptions): Uint8Array {\n const snapshot = toJSON(world)\n if (options?.components) {\n snapshot.entities = snapshot.entities.map((e) => ({\n eid: e.eid,\n components: e.components.filter((c) =>\n options.components!.some((comp) => comp.__id === c.id),\n ),\n }))\n }\n return packBinary(snapshot)\n}\n\nexport function deserializeWorld(bytes: Uint8Array, options?: DeserializeOptions): World {\n const snapshot = unpackBinary(bytes, options)\n return fromJSON(snapshot)\n}\n\n/**\n * Serialize a world snapshot to a plain JSON-compatible object.\n *\n * Note: `EntityRef` is in-memory only — not preserved across serialize/deserialize.\n * Generation counters reset on world load. Stale refs from before serialization\n * will deref to null after loading the snapshot into a new world.\n */\nexport function toJSON(world: World): WorldSnapshot {\n const state = getWorldState(world)\n const entities: WorldSnapshot['entities'] = []\n // Iterate by raw slot index; snapshot stores raw idx in the `eid` field\n // (wire format unchanged — idx is used for load-side entity re-creation).\n for (let idx = 1; idx < state.capacity; idx++) {\n const archId = state.entityArchetype[idx] ?? 0\n if (archId === 0) continue // slot unused\n\n const arch = state.archetypes[archId]\n if (!arch) continue\n\n // Build the packed eid from idx + current generation to do alive check\n const gen = state.generations[idx] ?? 0\n const packedEid = packEid(idx, gen, state.options)\n if (!arch.entityRow.has(packedEid)) continue\n\n const w = state.options.maskWordCount\n const base = idx * w\n const components: WorldSnapshot['entities'][0]['components'] = []\n for (let wi = 0; wi < w; wi++) {\n let word = state.entityMask[base + wi] ?? 0\n while (word !== 0) {\n const lsb = word & -word\n const bit = (wi << 5) + (31 - Math.clz32(lsb))\n const info = state.componentInfoByBit[bit]\n if (info) {\n const storage = state.componentStorageByBit[bit]\n let data: unknown = null\n if (info.kind === 'soa' && storage?.soa) {\n const obj: Record<string, unknown> = {}\n for (const f of info.fields) {\n const col = storage.soa[f.name]\n if (!col) continue\n if (f.vectorLen === 1) {\n obj[f.name] = col[idx]\n } else {\n const arr: number[] = []\n const baseI = idx * f.vectorLen\n for (let i = 0; i < f.vectorLen; i++) arr.push(col[baseI + i] ?? 0)\n obj[f.name] = arr\n }\n }\n data = obj\n } else if (info.kind === 'aos' && storage?.aos) {\n data = storage.aos[idx] ?? null\n } else {\n data = true\n }\n components.push({ kind: info.kind, id: info.id, data })\n }\n word &= word - 1\n }\n }\n // Wire format stores raw idx (not packed) for cross-session portability\n entities.push({ eid: idx, components })\n }\n return {\n version: state.version,\n capacity: state.capacity,\n entities,\n }\n}\n\nexport function fromJSON(snapshot: WorldSnapshot): World {\n const world = createWorld({ initialCapacity: snapshot.capacity })\n const eidMap = new Map<number, EntityId>()\n for (const e of snapshot.entities) {\n const eid = createEntity(world)\n eidMap.set(e.eid, eid)\n }\n for (const e of snapshot.entities) {\n const eid = eidMap.get(e.eid)!\n for (const comp of e.components) {\n const info = getComponentByInternalId(comp.id)\n if (!info) {\n // Component missing — silently skip; future version may throw based on options\n continue\n }\n const handle = getComponentHandle(info)\n if (handle) {\n addComponent(world, eid, handle, comp.data as any)\n }\n }\n }\n return world\n}\n\n// Reconstruct a component handle from its ComponentInfo. Since defineComponent\n// returns plain handles { __kind, __id, __schema }, we can synthesize them.\nfunction getComponentHandle(info: ComponentInfo): ComponentLike | null {\n if (info.kind === 'soa') {\n return { __kind: 'soa', __id: info.id, __schema: info.schema ?? {} } as ComponentLike\n }\n if (info.kind === 'aos') {\n const factory = info.factory ?? (() => ({}))\n return { __kind: 'aos', __id: info.id, __factory: factory } as ComponentLike\n }\n return { __kind: 'tag', __id: info.id } as ComponentLike\n}\n\n// --- Binary packing (wrapped JSON for 0.1) ---\n\nfunction packBinary(snapshot: WorldSnapshot): Uint8Array {\n const json = JSON.stringify(snapshot)\n const jsonBytes = new TextEncoder().encode(json)\n const versionBytes = new TextEncoder().encode(VERSION)\n const headerSize = 4 + 4 + 4 + versionBytes.length + 4\n const total = headerSize + jsonBytes.length\n const out = new Uint8Array(total)\n const view = new DataView(out.buffer)\n let off = 0\n out[off++] = MAGIC.charCodeAt(0)\n out[off++] = MAGIC.charCodeAt(1)\n out[off++] = MAGIC.charCodeAt(2)\n out[off++] = MAGIC.charCodeAt(3)\n view.setUint32(off, FORMAT_VERSION, true)\n off += 4\n view.setUint32(off, versionBytes.length, true)\n off += 4\n out.set(versionBytes, off)\n off += versionBytes.length\n view.setUint32(off, jsonBytes.length, true)\n off += 4\n out.set(jsonBytes, off)\n return out\n}\n\nfunction unpackBinary(bytes: Uint8Array, options?: DeserializeOptions): WorldSnapshot {\n if (bytes.length < 12) throw new Error('aiecsjs: bytes too short to be a valid snapshot')\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)\n if (\n bytes[0] !== MAGIC.charCodeAt(0) ||\n bytes[1] !== MAGIC.charCodeAt(1) ||\n bytes[2] !== MAGIC.charCodeAt(2) ||\n bytes[3] !== MAGIC.charCodeAt(3)\n ) {\n throw new Error('aiecsjs: invalid magic bytes')\n }\n let off = 4\n const formatVersion = view.getUint32(off, true)\n off += 4\n const onUnknown = options?.onUnknownVersion ?? 'throw'\n if (formatVersion !== FORMAT_VERSION && onUnknown === 'throw') {\n throw new Error(`aiecsjs: format version ${formatVersion} not supported`)\n }\n\n // SECURITY: explicit bounds checks on every length field. DataView itself\n // throws on out-of-range reads, but an attacker who controls the bytes can\n // still craft a `verLen` that skips past data of their choosing — we make\n // each step's invariant explicit and impose a sane cap to short-circuit\n // pathological payloads early.\n const MAX_FIELD_LEN = 64 * 1024 * 1024 // 64 MiB\n if (off + 4 > bytes.length) {\n throw new Error('aiecsjs: snapshot truncated before verLen')\n }\n const verLen = view.getUint32(off, true)\n off += 4\n if (verLen > MAX_FIELD_LEN || off + verLen > bytes.length) {\n throw new Error(`aiecsjs: snapshot verLen=${verLen} out of bounds`)\n }\n off += verLen // skip the aiecsjs version string\n\n if (off + 4 > bytes.length) {\n throw new Error('aiecsjs: snapshot truncated before jsonLen')\n }\n const jsonLen = view.getUint32(off, true)\n off += 4\n if (jsonLen > MAX_FIELD_LEN || off + jsonLen > bytes.length) {\n throw new Error(`aiecsjs: snapshot jsonLen=${jsonLen} out of bounds`)\n }\n const jsonBytes = bytes.subarray(off, off + jsonLen)\n const json = new TextDecoder().decode(jsonBytes)\n return JSON.parse(json) as WorldSnapshot\n}\n\n// --- Delta serializer ---\n\ninterface DeltaState {\n world: World\n components: ComponentLike[]\n lastSnapshot: WorldSnapshot | null\n}\n\nexport function createDeltaSerializer(world: World, options?: SerializeOptions): DeltaSerializer {\n const state: DeltaState = {\n world,\n components:\n options?.components ??\n listAllComponents()\n .map((i) => getComponentHandle(i)!)\n .filter(Boolean),\n lastSnapshot: null,\n }\n return {\n capture(): Uint8Array {\n const current = toJSON(state.world)\n let delta: WorldSnapshot\n if (!state.lastSnapshot) {\n delta = current\n } else {\n // Compute simple delta: entities with changed components\n delta = computeDelta(state.lastSnapshot, current)\n }\n state.lastSnapshot = current\n return packBinary(delta)\n },\n apply(targetWorld: World, deltaBytes: Uint8Array): void {\n const snapshot = unpackBinary(deltaBytes)\n // Apply: ensure entities exist, then add/update their components\n const targetState = getWorldState(targetWorld)\n for (const e of snapshot.entities) {\n if (e.eid >= targetState.capacity) continue\n if (!entityExists(targetWorld, e.eid as EntityId)) {\n // Create the entity. For simplicity, we just spawn until we have enough.\n while (targetState.size < e.eid) {\n createEntity(targetWorld)\n }\n }\n for (const comp of e.components) {\n const info = getComponentByInternalId(comp.id)\n if (!info) continue\n const handle = getComponentHandle(info)\n if (handle) addComponent(targetWorld, e.eid as EntityId, handle, comp.data as any)\n }\n }\n },\n reset(): void {\n state.lastSnapshot = null\n },\n }\n}\n\nfunction computeDelta(prev: WorldSnapshot, curr: WorldSnapshot): WorldSnapshot {\n const prevByEid = new Map(prev.entities.map((e) => [e.eid, e]))\n const changed: WorldSnapshot['entities'] = []\n for (const e of curr.entities) {\n const prevE = prevByEid.get(e.eid)\n if (!prevE) {\n changed.push(e)\n continue\n }\n const prevSig = JSON.stringify(prevE.components)\n const currSig = JSON.stringify(e.components)\n if (prevSig !== currSig) changed.push(e)\n }\n return { version: curr.version, capacity: curr.capacity, entities: changed }\n}\n"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import {i,r,k,u,P as P$1,I,a,Q as Q$1,w}from'./chunk-F7KNZ27O.js';var d="AIEC",B=1;function P(n,o){let e=D(n);return o?.components&&(e.entities=e.entities.map(t=>({eid:t.eid,components:t.components.filter(i=>o.components.some(c=>c.__id===i.id))}))),x(e)}function Q(n,o){let e=N(n,o);return J(e)}function D(n){let o=i(n),e=[];for(let t=1;t<o.capacity;t++){let i=o.entityArchetype[t]??0;if(i===0)continue;let c=o.archetypes[i];if(!c)continue;let r$1=o.generations[t]??0,a=r(t,r$1,o.options);if(!c.entityRow.has(a))continue;let s=o.options.maskWordCount,l=t*s,p=[];for(let m=0;m<s;m++){let u=o.entityMask[l+m]??0;for(;u!==0;){let b=u&-u,_=(m<<5)+(31-Math.clz32(b)),f=o.componentInfoByBit[_];if(f){let S=o.componentStorageByBit[_],w=null;if(f.kind==="soa"&&S?.soa){let y={};for(let h of f.fields){let g=S.soa[h.name];if(g)if(h.vectorLen===1)y[h.name]=g[t];else {let L=[],M=t*h.vectorLen;for(let C=0;C<h.vectorLen;C++)L.push(g[M+C]??0);y[h.name]=L;}}w=y;}else f.kind==="aos"&&S?.aos?w=S.aos[t]??null:w=true;p.push({kind:f.kind,id:f.id,data:w});}u&=u-1;}}e.push({eid:t,components:p});}return {version:o.version,capacity:o.capacity,entities:e}}function J(n){let o=k({initialCapacity:n.capacity}),e=new Map;for(let t of n.entities){let i=u(o);e.set(t.eid,i);}for(let t of n.entities){let i=e.get(t.eid);for(let c of t.components){let r=P$1(c.id);if(!r)continue;let a=A(r);a&&I(o,i,a,c.data);}}return o}function A(n){if(n.kind==="soa")return {__kind:"soa",__id:n.id,__schema:n.schema??{}};if(n.kind==="aos"){let o=n.factory??(()=>({}));return {__kind:"aos",__id:n.id,__factory:o}}return {__kind:"tag",__id:n.id}}function x(n){let o=JSON.stringify(n),e=new TextEncoder().encode(o),t=new TextEncoder().encode(a),c=12+t.length+4+e.length,r=new Uint8Array(c),a$1=new DataView(r.buffer),s=0;return r[s++]=d.charCodeAt(0),r[s++]=d.charCodeAt(1),r[s++]=d.charCodeAt(2),r[s++]=d.charCodeAt(3),a$1.setUint32(s,B,true),s+=4,a$1.setUint32(s,t.length,true),s+=4,r.set(t,s),s+=t.length,a$1.setUint32(s,e.length,true),s+=4,r.set(e,s),r}function N(n,o){if(n.length<12)throw new Error("aiecsjs: bytes too short to be a valid snapshot");let e=new DataView(n.buffer,n.byteOffset,n.byteLength);if(n[0]!==d.charCodeAt(0)||n[1]!==d.charCodeAt(1)||n[2]!==d.charCodeAt(2)||n[3]!==d.charCodeAt(3))throw new Error("aiecsjs: invalid magic bytes");let t=4,i=e.getUint32(t,true);t+=4;let c=o?.onUnknownVersion??"throw";if(i!==B&&c==="throw")throw new Error(`aiecsjs: format version ${i} not supported`);let r=64*1024*1024;if(t+4>n.length)throw new Error("aiecsjs: snapshot truncated before verLen");let a=e.getUint32(t,true);if(t+=4,a>r||t+a>n.length)throw new Error(`aiecsjs: snapshot verLen=${a} out of bounds`);if(t+=a,t+4>n.length)throw new Error("aiecsjs: snapshot truncated before jsonLen");let s=e.getUint32(t,true);if(t+=4,s>r||t+s>n.length)throw new Error(`aiecsjs: snapshot jsonLen=${s} out of bounds`);let l=n.subarray(t,t+s),p=new TextDecoder().decode(l);return JSON.parse(p)}function Y(n,o){let e={world:n,components:o?.components??Q$1().map(t=>A(t)).filter(Boolean),lastSnapshot:null};return {capture(){let t=D(e.world),i;return e.lastSnapshot?i=V(e.lastSnapshot,t):i=t,e.lastSnapshot=t,x(i)},apply(t,i$1){let c=N(i$1),r=i(t);for(let a of c.entities)if(!(a.eid>=r.capacity)){if(!w(t,a.eid))for(;r.size<a.eid;)u(t);for(let s of a.components){let l=P$1(s.id);if(!l)continue;let p=A(l);p&&I(t,a.eid,p,s.data);}}},reset(){e.lastSnapshot=null;}}}function V(n,o){let e=new Map(n.entities.map(i=>[i.eid,i])),t=[];for(let i of o.entities){let c=e.get(i.eid);if(!c){t.push(i);continue}let r=JSON.stringify(c.components),a=JSON.stringify(i.components);r!==a&&t.push(i);}return {version:o.version,capacity:o.capacity,entities:t}}export{P as a,Q as b,D as c,J as d,Y as e};//# sourceMappingURL=chunk-CTESP3XL.js.map
|
|
2
|
+
//# sourceMappingURL=chunk-CTESP3XL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/serialize.ts"],"names":["MAGIC","FORMAT_VERSION","serializeWorld","world","options","snapshot","toJSON","e","c","comp","packBinary","deserializeWorld","bytes","unpackBinary","fromJSON","state","getWorldState","entities","idx","archId","arch","gen","packedEid","packEid","w","base","components","wi","word","lsb","bit","info","storage","data","obj","f","col","arr","baseI","i","createWorld","eidMap","eid","createEntity","getComponentByInternalId","handle","getComponentHandle","addComponent","factory","json","jsonBytes","versionBytes","VERSION","total","out","view","off","formatVersion","onUnknown","MAX_FIELD_LEN","verLen","jsonLen","createDeltaSerializer","listAllComponents","current","delta","computeDelta","targetWorld","deltaBytes","targetState","entityExists","prev","curr","prevByEid","changed","prevE","prevSig","currSig"],"mappings":"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","file":"chunk-CTESP3XL.js","sourcesContent":["// aiecsjs/serialize — binary, JSON, and delta serializers.\n\nimport { testBit } from './internal/bitmask.js'\nimport {\n addComponent,\n defineComponent,\n defineObjectComponent,\n defineTag,\n getComponentByInternalId,\n getComponentInfo,\n listAllComponents,\n} from './internal/component.js'\nimport { createEntity, destroyEntity, entityExists, packEid } from './internal/entity.js'\nimport type {\n ComponentInfo,\n ComponentLike,\n DeltaSerializer,\n DeserializeOptions,\n EntityId,\n SerializeOptions,\n World,\n WorldSnapshot,\n} from './internal/types.js'\nimport { createWorld, getWorldState } from './internal/world.js'\nimport { VERSION } from './version.js'\n\nconst MAGIC = 'AIEC'\nconst FORMAT_VERSION = 1\n\nexport function serializeWorld(world: World, options?: SerializeOptions): Uint8Array {\n const snapshot = toJSON(world)\n if (options?.components) {\n snapshot.entities = snapshot.entities.map((e) => ({\n eid: e.eid,\n components: e.components.filter((c) =>\n options.components!.some((comp) => comp.__id === c.id),\n ),\n }))\n }\n return packBinary(snapshot)\n}\n\nexport function deserializeWorld(bytes: Uint8Array, options?: DeserializeOptions): World {\n const snapshot = unpackBinary(bytes, options)\n return fromJSON(snapshot)\n}\n\n/**\n * Serialize a world snapshot to a plain JSON-compatible object.\n *\n * Note: `EntityRef` is in-memory only — not preserved across serialize/deserialize.\n * Generation counters reset on world load. Stale refs from before serialization\n * will deref to null after loading the snapshot into a new world.\n */\nexport function toJSON(world: World): WorldSnapshot {\n const state = getWorldState(world)\n const entities: WorldSnapshot['entities'] = []\n // Iterate by raw slot index; snapshot stores raw idx in the `eid` field\n // (wire format unchanged — idx is used for load-side entity re-creation).\n for (let idx = 1; idx < state.capacity; idx++) {\n const archId = state.entityArchetype[idx] ?? 0\n if (archId === 0) continue // slot unused\n\n const arch = state.archetypes[archId]\n if (!arch) continue\n\n // Build the packed eid from idx + current generation to do alive check\n const gen = state.generations[idx] ?? 0\n const packedEid = packEid(idx, gen, state.options)\n if (!arch.entityRow.has(packedEid)) continue\n\n const w = state.options.maskWordCount\n const base = idx * w\n const components: WorldSnapshot['entities'][0]['components'] = []\n for (let wi = 0; wi < w; wi++) {\n let word = state.entityMask[base + wi] ?? 0\n while (word !== 0) {\n const lsb = word & -word\n const bit = (wi << 5) + (31 - Math.clz32(lsb))\n const info = state.componentInfoByBit[bit]\n if (info) {\n const storage = state.componentStorageByBit[bit]\n let data: unknown = null\n if (info.kind === 'soa' && storage?.soa) {\n const obj: Record<string, unknown> = {}\n for (const f of info.fields) {\n const col = storage.soa[f.name]\n if (!col) continue\n if (f.vectorLen === 1) {\n obj[f.name] = col[idx]\n } else {\n const arr: number[] = []\n const baseI = idx * f.vectorLen\n for (let i = 0; i < f.vectorLen; i++) arr.push(col[baseI + i] ?? 0)\n obj[f.name] = arr\n }\n }\n data = obj\n } else if (info.kind === 'aos' && storage?.aos) {\n data = storage.aos[idx] ?? null\n } else {\n data = true\n }\n components.push({ kind: info.kind, id: info.id, data })\n }\n word &= word - 1\n }\n }\n // Wire format stores raw idx (not packed) for cross-session portability\n entities.push({ eid: idx, components })\n }\n return {\n version: state.version,\n capacity: state.capacity,\n entities,\n }\n}\n\nexport function fromJSON(snapshot: WorldSnapshot): World {\n const world = createWorld({ initialCapacity: snapshot.capacity })\n const eidMap = new Map<number, EntityId>()\n for (const e of snapshot.entities) {\n const eid = createEntity(world)\n eidMap.set(e.eid, eid)\n }\n for (const e of snapshot.entities) {\n const eid = eidMap.get(e.eid)!\n for (const comp of e.components) {\n const info = getComponentByInternalId(comp.id)\n if (!info) {\n // Component missing — silently skip; future version may throw based on options\n continue\n }\n const handle = getComponentHandle(info)\n if (handle) {\n addComponent(world, eid, handle, comp.data as any)\n }\n }\n }\n return world\n}\n\n// Reconstruct a component handle from its ComponentInfo. Since defineComponent\n// returns plain handles { __kind, __id, __schema }, we can synthesize them.\nfunction getComponentHandle(info: ComponentInfo): ComponentLike | null {\n if (info.kind === 'soa') {\n return { __kind: 'soa', __id: info.id, __schema: info.schema ?? {} } as ComponentLike\n }\n if (info.kind === 'aos') {\n const factory = info.factory ?? (() => ({}))\n return { __kind: 'aos', __id: info.id, __factory: factory } as ComponentLike\n }\n return { __kind: 'tag', __id: info.id } as ComponentLike\n}\n\n// --- Binary packing (wrapped JSON for 0.1) ---\n\nfunction packBinary(snapshot: WorldSnapshot): Uint8Array {\n const json = JSON.stringify(snapshot)\n const jsonBytes = new TextEncoder().encode(json)\n const versionBytes = new TextEncoder().encode(VERSION)\n const headerSize = 4 + 4 + 4 + versionBytes.length + 4\n const total = headerSize + jsonBytes.length\n const out = new Uint8Array(total)\n const view = new DataView(out.buffer)\n let off = 0\n out[off++] = MAGIC.charCodeAt(0)\n out[off++] = MAGIC.charCodeAt(1)\n out[off++] = MAGIC.charCodeAt(2)\n out[off++] = MAGIC.charCodeAt(3)\n view.setUint32(off, FORMAT_VERSION, true)\n off += 4\n view.setUint32(off, versionBytes.length, true)\n off += 4\n out.set(versionBytes, off)\n off += versionBytes.length\n view.setUint32(off, jsonBytes.length, true)\n off += 4\n out.set(jsonBytes, off)\n return out\n}\n\nfunction unpackBinary(bytes: Uint8Array, options?: DeserializeOptions): WorldSnapshot {\n if (bytes.length < 12) throw new Error('aiecsjs: bytes too short to be a valid snapshot')\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)\n if (\n bytes[0] !== MAGIC.charCodeAt(0) ||\n bytes[1] !== MAGIC.charCodeAt(1) ||\n bytes[2] !== MAGIC.charCodeAt(2) ||\n bytes[3] !== MAGIC.charCodeAt(3)\n ) {\n throw new Error('aiecsjs: invalid magic bytes')\n }\n let off = 4\n const formatVersion = view.getUint32(off, true)\n off += 4\n const onUnknown = options?.onUnknownVersion ?? 'throw'\n if (formatVersion !== FORMAT_VERSION && onUnknown === 'throw') {\n throw new Error(`aiecsjs: format version ${formatVersion} not supported`)\n }\n\n // SECURITY: explicit bounds checks on every length field. DataView itself\n // throws on out-of-range reads, but an attacker who controls the bytes can\n // still craft a `verLen` that skips past data of their choosing — we make\n // each step's invariant explicit and impose a sane cap to short-circuit\n // pathological payloads early.\n const MAX_FIELD_LEN = 64 * 1024 * 1024 // 64 MiB\n if (off + 4 > bytes.length) {\n throw new Error('aiecsjs: snapshot truncated before verLen')\n }\n const verLen = view.getUint32(off, true)\n off += 4\n if (verLen > MAX_FIELD_LEN || off + verLen > bytes.length) {\n throw new Error(`aiecsjs: snapshot verLen=${verLen} out of bounds`)\n }\n off += verLen // skip the aiecsjs version string\n\n if (off + 4 > bytes.length) {\n throw new Error('aiecsjs: snapshot truncated before jsonLen')\n }\n const jsonLen = view.getUint32(off, true)\n off += 4\n if (jsonLen > MAX_FIELD_LEN || off + jsonLen > bytes.length) {\n throw new Error(`aiecsjs: snapshot jsonLen=${jsonLen} out of bounds`)\n }\n const jsonBytes = bytes.subarray(off, off + jsonLen)\n const json = new TextDecoder().decode(jsonBytes)\n return JSON.parse(json) as WorldSnapshot\n}\n\n// --- Delta serializer ---\n\ninterface DeltaState {\n world: World\n components: ComponentLike[]\n lastSnapshot: WorldSnapshot | null\n}\n\nexport function createDeltaSerializer(world: World, options?: SerializeOptions): DeltaSerializer {\n const state: DeltaState = {\n world,\n components:\n options?.components ??\n listAllComponents()\n .map((i) => getComponentHandle(i)!)\n .filter(Boolean),\n lastSnapshot: null,\n }\n return {\n capture(): Uint8Array {\n const current = toJSON(state.world)\n let delta: WorldSnapshot\n if (!state.lastSnapshot) {\n delta = current\n } else {\n // Compute simple delta: entities with changed components\n delta = computeDelta(state.lastSnapshot, current)\n }\n state.lastSnapshot = current\n return packBinary(delta)\n },\n apply(targetWorld: World, deltaBytes: Uint8Array): void {\n const snapshot = unpackBinary(deltaBytes)\n // Apply: ensure entities exist, then add/update their components\n const targetState = getWorldState(targetWorld)\n for (const e of snapshot.entities) {\n if (e.eid >= targetState.capacity) continue\n if (!entityExists(targetWorld, e.eid as EntityId)) {\n // Create the entity. For simplicity, we just spawn until we have enough.\n while (targetState.size < e.eid) {\n createEntity(targetWorld)\n }\n }\n for (const comp of e.components) {\n const info = getComponentByInternalId(comp.id)\n if (!info) continue\n const handle = getComponentHandle(info)\n if (handle) addComponent(targetWorld, e.eid as EntityId, handle, comp.data as any)\n }\n }\n },\n reset(): void {\n state.lastSnapshot = null\n },\n }\n}\n\nfunction computeDelta(prev: WorldSnapshot, curr: WorldSnapshot): WorldSnapshot {\n const prevByEid = new Map(prev.entities.map((e) => [e.eid, e]))\n const changed: WorldSnapshot['entities'] = []\n for (const e of curr.entities) {\n const prevE = prevByEid.get(e.eid)\n if (!prevE) {\n changed.push(e)\n continue\n }\n const prevSig = JSON.stringify(prevE.components)\n const currSig = JSON.stringify(e.components)\n if (prevSig !== currSig) changed.push(e)\n }\n return { version: curr.version, capacity: curr.capacity, entities: changed }\n}\n"]}
|