aiecsjs 0.4.0 → 0.5.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/README.md +1 -1
- package/README_ZHTW.md +4 -4
- package/api.json +1 -1
- package/dist/{chunk-B777DQR7.cjs → chunk-4QBOZQZR.cjs} +2 -2
- package/dist/{chunk-B777DQR7.cjs.map → chunk-4QBOZQZR.cjs.map} +1 -1
- package/dist/{chunk-SQWZUC2Q.js → chunk-AROBEZEB.js} +2 -2
- package/dist/{chunk-SQWZUC2Q.js.map → chunk-AROBEZEB.js.map} +1 -1
- package/dist/{chunk-SJDWI3OZ.cjs → chunk-IJ4BTSN2.cjs} +2 -2
- package/dist/{chunk-SJDWI3OZ.cjs.map → chunk-IJ4BTSN2.cjs.map} +1 -1
- package/dist/{chunk-CTESP3XL.js → chunk-JLU2PG6H.js} +2 -2
- package/dist/{chunk-CTESP3XL.js.map → chunk-JLU2PG6H.js.map} +1 -1
- package/dist/{chunk-F7KNZ27O.js → chunk-XDLAI4YT.js} +2 -2
- package/dist/{chunk-F7KNZ27O.js.map → chunk-XDLAI4YT.js.map} +1 -1
- package/dist/{chunk-RHH5JA74.cjs → chunk-Z6ATHRHC.cjs} +2 -2
- package/dist/{chunk-RHH5JA74.cjs.map → chunk-Z6ATHRHC.cjs.map} +1 -1
- package/dist/commands.cjs +1 -1
- package/dist/commands.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +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 +23 -5
- package/package.json +5 -8
- package/CHANGELOG.md +0 -292
- package/STABILITY.md +0 -156
- package/STABILITY_ZHTW.md +0 -155
- package/docs/MIGRATION.md +0 -252
- package/docs/MIGRATION_ZHTW.md +0 -252
package/STABILITY.md
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
# Stability Contract
|
|
2
|
-
|
|
3
|
-
[English](STABILITY.md) | [繁體中文](STABILITY_ZHTW.md)
|
|
4
|
-
|
|
5
|
-
This document is the per-export stability promise for `aiecsjs`. It is the contract AI tools and human users can rely on when pinning versions and writing import paths.
|
|
6
|
-
|
|
7
|
-
## Policy
|
|
8
|
-
|
|
9
|
-
aiecsjs follows [semver](https://semver.org/). Within the **0.x** series:
|
|
10
|
-
- **`stable`** exports do not change in breaking ways across minor versions (e.g. 0.1 → 0.2).
|
|
11
|
-
- **`experimental`** exports may change shape, name, or behaviour in any minor release. Pin the exact version if you depend on them.
|
|
12
|
-
- **`internal`** is not part of the API. May change in any patch release. Do not import.
|
|
13
|
-
- **`deprecated`** still works as documented but is scheduled for removal. The deprecation notice states the target version.
|
|
14
|
-
|
|
15
|
-
At **1.0**, the `stable` surface freezes for the entire 1.x series.
|
|
16
|
-
|
|
17
|
-
The full machine-readable export list lives in [`api.json`](./api.json), with the `stability` and `since` fields on every entry.
|
|
18
|
-
|
|
19
|
-
## By module
|
|
20
|
-
|
|
21
|
-
The **root** entry (`aiecsjs`) is the stable core: world, entity, component, query, system. Everything under a sub-path (`aiecsjs/<name>`) is a **utility or adapter sub-path** — useful but non-essential, decoupled from the core, and importable a la carte. Tree-shakers should be able to drop any sub-path the application does not import.
|
|
22
|
-
|
|
23
|
-
### `aiecsjs` (root core)
|
|
24
|
-
|
|
25
|
-
| Export | Stability | Since | Notes |
|
|
26
|
-
|---|---|---|---|
|
|
27
|
-
| `createWorld` | stable | 0.1.0 | |
|
|
28
|
-
| `disposeWorld` | stable | 0.2.0 | Alias for `destroyWorld`; aligns with the ai*js ecosystem `dispose()` convention. Prefer this name in new code. |
|
|
29
|
-
| `destroyWorld` | **deprecated** | 0.1.0 | Use `disposeWorld` instead. Scheduled for removal in 1.0. |
|
|
30
|
-
| `resetWorld` | stable | 0.1.0 | |
|
|
31
|
-
| `getWorldSize` | stable | 0.1.0 | |
|
|
32
|
-
| `getWorldCapacity` | stable | 0.1.0 | |
|
|
33
|
-
| `createEntity` | stable | 0.1.0 | |
|
|
34
|
-
| `destroyEntity` | stable | 0.1.0 | |
|
|
35
|
-
| `entityExists` | stable | 0.1.0 | |
|
|
36
|
-
| `getEntityIndex` | stable | 0.1.0 | |
|
|
37
|
-
| `getEntityGeneration` | stable | 0.3.0 | Returns real generation value packed into EntityId (default 24-bit index, 8-bit generation). For non-default `createWorld({ indexBits, generationBits })`, use `EntityRef` + `deref` instead. |
|
|
38
|
-
| `packEntity` | stable | 0.3.0 | Packs index + generation into an EntityId using default 24/8 bit layout. For non-default bit sizes, use `EntityRef` + `deref` instead. |
|
|
39
|
-
| `refOf` | stable | 0.3.0 | Throws `EntityNotAliveError` for dead entity. |
|
|
40
|
-
| `deref` | stable | 0.3.0 | Returns null for stale / cross-world refs; never throws. |
|
|
41
|
-
| `aliveRef` | stable | 0.3.0 | Boolean guard form of `deref`; never throws. |
|
|
42
|
-
| `EntityRef` (type) | stable | 0.3.0 | In-memory only; not serializable. |
|
|
43
|
-
| `EntityNotAliveError` | stable | 0.3.0 | Thrown only by `refOf`. |
|
|
44
|
-
| `defineComponent` | stable | 0.1.0 | |
|
|
45
|
-
| `defineTag` | stable | 0.1.0 | |
|
|
46
|
-
| `defineObjectComponent` | stable | 0.1.0 | AoS components are main-thread only; not SAB-shareable. |
|
|
47
|
-
| `addComponent` | stable | 0.1.0 | Argument order `(world, eid, component, init?)` is final. |
|
|
48
|
-
| `removeComponent` | stable | 0.1.0 | |
|
|
49
|
-
| `hasComponent` | stable | 0.1.0 | |
|
|
50
|
-
| `getComponent` | stable | 0.1.0 | |
|
|
51
|
-
| `setComponent` | stable | 0.1.0 | |
|
|
52
|
-
| `Types` | stable | 0.1.0 | Constant map; field names are part of the contract. |
|
|
53
|
-
| `defineQuery` | stable | 0.1.0 | |
|
|
54
|
-
| `runQuery` | stable | 0.1.0 | |
|
|
55
|
-
| `forEachEntity` | stable | 0.1.0 | |
|
|
56
|
-
| `iterQuery` | stable | 0.1.0 | |
|
|
57
|
-
| `enterQuery` | stable | 0.1.0 | |
|
|
58
|
-
| `exitQuery` | stable | 0.1.0 | |
|
|
59
|
-
| `queryArchetypes` | **experimental** | 0.1.0 | `Archetype.id` is opaque-internal; the shape of `Archetype` may grow. |
|
|
60
|
-
| `pipe` | stable | 0.1.0 | |
|
|
61
|
-
| `VERSION` | stable | 0.1.0 | |
|
|
62
|
-
| `IS_SAB_SUPPORTED` | stable | 0.1.0 | |
|
|
63
|
-
| `isWorld` | stable | 0.1.0 | |
|
|
64
|
-
| `isEntity` | stable | 0.1.0 | |
|
|
65
|
-
|
|
66
|
-
### `aiecsjs/loop` (utility sub-path)
|
|
67
|
-
|
|
68
|
-
Fixed-timestep accumulator loop. Drop this sub-path if you already drive frame updates yourself (PixiJS `Ticker`, requestAnimationFrame, server-side simulation).
|
|
69
|
-
|
|
70
|
-
| Export | Stability | Since | Notes |
|
|
71
|
-
|---|---|---|---|
|
|
72
|
-
| `createLoop` | stable | 0.1.0 | |
|
|
73
|
-
|
|
74
|
-
### `aiecsjs/commands` (utility sub-path)
|
|
75
|
-
|
|
76
|
-
Deferred structural mutations so systems can mutate world structure mid-iteration without invalidating queries.
|
|
77
|
-
|
|
78
|
-
| Export | Stability | Since | Notes |
|
|
79
|
-
|---|---|---|---|
|
|
80
|
-
| `createCommandBuffer` | stable | 0.1.0 | |
|
|
81
|
-
| `flush` | stable | 0.1.0 | |
|
|
82
|
-
| `withCommandBuffer` | stable | 0.1.0 | |
|
|
83
|
-
|
|
84
|
-
### `aiecsjs/observers` (utility sub-path)
|
|
85
|
-
|
|
86
|
-
Component lifecycle hooks. The core does not require observers; install this sub-path only if a system needs add/remove/set callbacks.
|
|
87
|
-
|
|
88
|
-
| Export | Stability | Since | Notes |
|
|
89
|
-
|---|---|---|---|
|
|
90
|
-
| `observe` | stable | 0.1.0 | Accepts `{ signal?: AbortSignal }` since 0.2.0. |
|
|
91
|
-
| `onAdd` | stable | 0.1.0 | Accepts `{ signal?: AbortSignal }` since 0.2.0. |
|
|
92
|
-
| `onRemove` | stable | 0.1.0 | Accepts `{ signal?: AbortSignal }` since 0.2.0. |
|
|
93
|
-
| `onSet` | stable | 0.1.0 | Low-level mutation hook; NOT a reactive value-predicate query. Accepts `{ signal?: AbortSignal }` since 0.2.0. |
|
|
94
|
-
|
|
95
|
-
### `aiecsjs/serialize` (utility sub-path)
|
|
96
|
-
|
|
97
|
-
| Export | Stability | Since | Notes |
|
|
98
|
-
|---|---|---|---|
|
|
99
|
-
| `serializeWorld` | stable | 0.1.0 | Binary format includes a version stamp. |
|
|
100
|
-
| `deserializeWorld` | stable | 0.1.0 | |
|
|
101
|
-
| `toJSON` | stable | 0.1.0 | |
|
|
102
|
-
| `fromJSON` | stable | 0.1.0 | |
|
|
103
|
-
| `createDeltaSerializer` | **experimental** | 0.1.0 | Wire format may change before 1.0. |
|
|
104
|
-
|
|
105
|
-
### `aiecsjs/worker` (experimental adapter sub-path)
|
|
106
|
-
|
|
107
|
-
The entire subpath is **experimental** in 0.x. **In 0.x the implementation is a snapshot-copy transport** — serialize the world into the SAB on send, deserialize into a fresh world on adopt. It is not true shared-memory column aliasing. The API surface matches the documented contract; true shared columns are targeted for **0.3+**. Snapshot layout and capability flags may change.
|
|
108
|
-
|
|
109
|
-
| Export | Stability | Since | Notes |
|
|
110
|
-
|---|---|---|---|
|
|
111
|
-
| `transferableSnapshot` | experimental | 0.1.0 | |
|
|
112
|
-
| `adoptSnapshot` | experimental | 0.1.0 | |
|
|
113
|
-
| `attachWorld` | experimental | 0.1.0 | |
|
|
114
|
-
| `detachWorld` | experimental | 0.1.0 | |
|
|
115
|
-
|
|
116
|
-
### `aiecsjs/relations` (stable sub-path since 0.4.0)
|
|
117
|
-
|
|
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.
|
|
121
|
-
|
|
122
|
-
| Export | Stability | Since | Notes |
|
|
123
|
-
|---|---|---|---|
|
|
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. |
|
|
130
|
-
|
|
131
|
-
### `aiecsjs/internal/*`
|
|
132
|
-
|
|
133
|
-
Everything under this prefix is **internal**. It exists for the implementation's own use and may break in any release. Do not import.
|
|
134
|
-
|
|
135
|
-
## Roadmap
|
|
136
|
-
|
|
137
|
-
| Version | Focus | Stability shift |
|
|
138
|
-
|---|---|---|
|
|
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. |
|
|
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). |
|
|
141
|
-
| 0.3.x | EntityRef + generation packing | ABA-safe; `getEntityGeneration` / `packEntity` → stable. |
|
|
142
|
-
| 0.4.0 | Relations stabilisation | `aiecsjs/relations` graduated to stable; `getRelationData` added. `aiecsjs/worker` remains experimental (true SAB shared-memory columns deferred). |
|
|
143
|
-
| 0.6+ | Multi-World snapshot diff transport (placeholder) | experimental — design TBD. |
|
|
144
|
-
| 1.0.0 | API freeze | All `stable` exports frozen for 1.x. |
|
|
145
|
-
|
|
146
|
-
## How to check stability at runtime
|
|
147
|
-
|
|
148
|
-
```ts
|
|
149
|
-
import { VERSION } from 'aiecsjs'
|
|
150
|
-
|
|
151
|
-
if (VERSION.startsWith('0.')) {
|
|
152
|
-
console.warn('aiecsjs is in pre-1.0; API surface may shift')
|
|
153
|
-
}
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
For programmatic introspection, parse [`api.json`](./api.json) — each entry has `stability` and `since` fields.
|
package/STABILITY_ZHTW.md
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
# 穩定度契約
|
|
2
|
-
|
|
3
|
-
[English](STABILITY.md) | [繁體中文](STABILITY_ZHTW.md)
|
|
4
|
-
|
|
5
|
-
本文件為 `aiecsjs` 中各匯出的穩定度承諾。AI 工具與人類使用者可依此契約來鎖定版本與書寫 import 路徑。
|
|
6
|
-
|
|
7
|
-
## 規範
|
|
8
|
-
|
|
9
|
-
aiecsjs 遵循 [semver](https://semver.org/)。在 **0.x** 系列內:
|
|
10
|
-
- **`stable`** 匯出於 minor 版本之間(例如 0.1 → 0.2)不會破壞性變更。
|
|
11
|
-
- **`experimental`** 匯出可能在任何 minor 釋出時改變形狀、命名或行為。若有依賴請鎖定明確版本。
|
|
12
|
-
- **`internal`** 不屬於 API 表面,可能在任何 patch 釋出時改變。請勿匯入。
|
|
13
|
-
- **`deprecated`** 仍可正常運作但已排入移除計畫。棄用通知中會載明目標版本。
|
|
14
|
-
|
|
15
|
-
至 **1.0** 時,`stable` 表面為整個 1.x 系列凍結。
|
|
16
|
-
|
|
17
|
-
完整機器可讀的匯出清單位於 [`api.json`](./api.json),每筆都有 `stability` 與 `since` 欄位。
|
|
18
|
-
|
|
19
|
-
## 依模組分類
|
|
20
|
-
|
|
21
|
-
**根目錄** entry(`aiecsjs`)為穩定核心:world、entity、component、query、system。任何 sub-path(`aiecsjs/<名稱>`)為 **utility 或 adapter sub-path** — 實用但非核心,與 core 解耦,可單獨 a la carte import。Tree-shaker 應能丟掉未引用的任何 sub-path。
|
|
22
|
-
|
|
23
|
-
### `aiecsjs`(根核心)
|
|
24
|
-
|
|
25
|
-
| 匯出 | 穩定度 | 起始版本 | 備註 |
|
|
26
|
-
|---|---|---|---|
|
|
27
|
-
| `createWorld` | stable | 0.1.0 | |
|
|
28
|
-
| `destroyWorld` | **deprecated** | 0.1.0 | 請改用 `disposeWorld`。預計於 1.0 移除。 |
|
|
29
|
-
| `resetWorld` | stable | 0.1.0 | |
|
|
30
|
-
| `getWorldSize` | stable | 0.1.0 | |
|
|
31
|
-
| `getWorldCapacity` | stable | 0.1.0 | |
|
|
32
|
-
| `createEntity` | stable | 0.1.0 | |
|
|
33
|
-
| `destroyEntity` | stable | 0.1.0 | |
|
|
34
|
-
| `entityExists` | stable | 0.1.0 | |
|
|
35
|
-
| `getEntityIndex` | stable | 0.1.0 | |
|
|
36
|
-
| `getEntityGeneration` | stable | 0.3.0 | 回傳 EntityId 中實際打包的 generation 值(預設 24-bit index、8-bit generation)。若使用非預設 `createWorld({ indexBits, generationBits })`,請改用 `EntityRef` + `deref`。 |
|
|
37
|
-
| `packEntity` | stable | 0.3.0 | 使用預設 24/8 bit 佈局將 index + generation 打包為 EntityId。若使用非預設 bit 大小,請改用 `EntityRef` + `deref`。 |
|
|
38
|
-
| `refOf` | stable | 0.3.0 | entity 不存活時拋出 `EntityNotAliveError`。 |
|
|
39
|
-
| `deref` | stable | 0.3.0 | 對過期或跨 world 的 ref 回傳 null;絕不拋出錯誤。 |
|
|
40
|
-
| `aliveRef` | stable | 0.3.0 | `deref` 的布林 guard 形式;絕不拋出錯誤。 |
|
|
41
|
-
| `EntityRef`(type) | stable | 0.3.0 | 僅限記憶體內使用;不可序列化。 |
|
|
42
|
-
| `EntityNotAliveError` | stable | 0.3.0 | 僅由 `refOf` 拋出。 |
|
|
43
|
-
| `defineComponent` | stable | 0.1.0 | |
|
|
44
|
-
| `defineTag` | stable | 0.1.0 | |
|
|
45
|
-
| `defineObjectComponent` | stable | 0.1.0 | AoS 元件僅限主執行緒;不可跨 SAB 共享。 |
|
|
46
|
-
| `addComponent` | stable | 0.1.0 | 參數順序 `(world, eid, component, init?)` 為最終定義。 |
|
|
47
|
-
| `removeComponent` | stable | 0.1.0 | |
|
|
48
|
-
| `hasComponent` | stable | 0.1.0 | |
|
|
49
|
-
| `getComponent` | stable | 0.1.0 | |
|
|
50
|
-
| `setComponent` | stable | 0.1.0 | |
|
|
51
|
-
| `Types` | stable | 0.1.0 | 常數對應表;欄位名稱屬於契約。 |
|
|
52
|
-
| `defineQuery` | stable | 0.1.0 | |
|
|
53
|
-
| `runQuery` | stable | 0.1.0 | |
|
|
54
|
-
| `forEachEntity` | stable | 0.1.0 | |
|
|
55
|
-
| `iterQuery` | stable | 0.1.0 | |
|
|
56
|
-
| `enterQuery` | stable | 0.1.0 | |
|
|
57
|
-
| `exitQuery` | stable | 0.1.0 | |
|
|
58
|
-
| `queryArchetypes` | **experimental** | 0.1.0 | `Archetype.id` 為 opaque 內部值;`Archetype` 的形狀可能增加欄位。 |
|
|
59
|
-
| `pipe` | stable | 0.1.0 | |
|
|
60
|
-
| `VERSION` | stable | 0.1.0 | |
|
|
61
|
-
| `IS_SAB_SUPPORTED` | stable | 0.1.0 | |
|
|
62
|
-
| `isWorld` | stable | 0.1.0 | |
|
|
63
|
-
| `isEntity` | stable | 0.1.0 | |
|
|
64
|
-
|
|
65
|
-
### `aiecsjs/loop`(utility sub-path)
|
|
66
|
-
|
|
67
|
-
定步長累加迴圈。若應用層已自有 frame 更新驅動(PixiJS `Ticker`、requestAnimationFrame、伺服端模擬),可直接略過本 sub-path。
|
|
68
|
-
|
|
69
|
-
| 匯出 | 穩定度 | 起始版本 | 備註 |
|
|
70
|
-
|---|---|---|---|
|
|
71
|
-
| `createLoop` | stable | 0.1.0 | |
|
|
72
|
-
|
|
73
|
-
### `aiecsjs/commands`(utility sub-path)
|
|
74
|
-
|
|
75
|
-
延後的結構性變更,讓系統可在迭代中安全改動 world 結構而不會讓查詢失效。
|
|
76
|
-
|
|
77
|
-
| 匯出 | 穩定度 | 起始版本 | 備註 |
|
|
78
|
-
|---|---|---|---|
|
|
79
|
-
| `createCommandBuffer` | stable | 0.1.0 | |
|
|
80
|
-
| `flush` | stable | 0.1.0 | |
|
|
81
|
-
| `withCommandBuffer` | stable | 0.1.0 | |
|
|
82
|
-
|
|
83
|
-
### `aiecsjs/observers`(utility sub-path)
|
|
84
|
-
|
|
85
|
-
元件生命週期 hook。Core 不依賴 observers;只在系統需要 add/remove/set callback 時才安裝本 sub-path。
|
|
86
|
-
|
|
87
|
-
| 匯出 | 穩定度 | 起始版本 | 備註 |
|
|
88
|
-
|---|---|---|---|
|
|
89
|
-
| `observe` | stable | 0.1.0 | |
|
|
90
|
-
| `onAdd` | stable | 0.1.0 | |
|
|
91
|
-
| `onRemove` | stable | 0.1.0 | |
|
|
92
|
-
| `onSet` | stable | 0.1.0 | |
|
|
93
|
-
|
|
94
|
-
### `aiecsjs/serialize`(utility sub-path)
|
|
95
|
-
|
|
96
|
-
| 匯出 | 穩定度 | 起始版本 | 備註 |
|
|
97
|
-
|---|---|---|---|
|
|
98
|
-
| `serializeWorld` | stable | 0.1.0 | 二進位格式包含版本戳記。 |
|
|
99
|
-
| `deserializeWorld` | stable | 0.1.0 | |
|
|
100
|
-
| `toJSON` | stable | 0.1.0 | |
|
|
101
|
-
| `fromJSON` | stable | 0.1.0 | |
|
|
102
|
-
| `createDeltaSerializer` | **experimental** | 0.1.0 | Wire format 在 1.0 之前可能改變。 |
|
|
103
|
-
|
|
104
|
-
### `aiecsjs/worker`(experimental adapter sub-path)
|
|
105
|
-
|
|
106
|
-
整個 subpath 在 0.x 為 **experimental**。**0.x 的實作為 snapshot-copy 傳輸**——傳送時將 world 序列化進 SAB,接收端反序列化成全新 world,並非真正的共享記憶體欄位 aliasing。API 表面符合文件契約;真正的共享欄位預計 **0.3+** 推出。Snapshot 佈局與能力旗標可能改變。
|
|
107
|
-
|
|
108
|
-
| 匯出 | 穩定度 | 起始版本 | 備註 |
|
|
109
|
-
|---|---|---|---|
|
|
110
|
-
| `transferableSnapshot` | experimental | 0.1.0 | |
|
|
111
|
-
| `adoptSnapshot` | experimental | 0.1.0 | |
|
|
112
|
-
| `attachWorld` | experimental | 0.1.0 | |
|
|
113
|
-
| `detachWorld` | experimental | 0.1.0 | |
|
|
114
|
-
|
|
115
|
-
### `aiecsjs/relations`(stable sub-path,自 0.4.0 起)
|
|
116
|
-
|
|
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` 確認活躍狀態。
|
|
120
|
-
|
|
121
|
-
| 匯出 | 穩定度 | 起始版本 | 備註 |
|
|
122
|
-
|---|---|---|---|
|
|
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 語意影響。 |
|
|
129
|
-
|
|
130
|
-
### `aiecsjs/internal/*`
|
|
131
|
-
|
|
132
|
-
此前綴底下的所有內容皆為 **internal**。它存在於實作自用,可能在任何 release 變更。請勿匯入。
|
|
133
|
-
|
|
134
|
-
## Roadmap
|
|
135
|
-
|
|
136
|
-
| 版本 | 焦點 | 穩定度變動 |
|
|
137
|
-
|---|---|---|
|
|
138
|
-
| 0.1.x | 核心表面(world、entity、component、query、system、loop、commands、observers、serialize) | 初次發佈;package 整體標 experimental,但各 export 表中列為 stable 者皆穩定。 |
|
|
139
|
-
| 0.2.0 | 安全與生態對齊 | 原型污染強化、observer `{ signal? }`、`disposeWorld` 別名、`getEntityGeneration` / `packEntity` 改 experimental、`verify:llms` gate。詳見 [CHANGELOG.md](./CHANGELOG.md#020---2026-05-28)。 |
|
|
140
|
-
| 0.3.x | EntityRef + generation packing | ABA-safe;`getEntityGeneration` / `packEntity` → stable。 |
|
|
141
|
-
| 0.4.0 | Relations 穩定化 | `aiecsjs/relations` 升為 stable;新增 `getRelationData`。`aiecsjs/worker` 維持 experimental(真正 SAB 共享記憶體欄位延後)。 |
|
|
142
|
-
| 0.6+ | Multi-World snapshot diff transport(佔位) | experimental — 設計待定。 |
|
|
143
|
-
| 1.0.0 | API 凍結 | 所有 `stable` 匯出於 1.x 系列凍結。 |
|
|
144
|
-
|
|
145
|
-
## 在執行時檢查穩定度
|
|
146
|
-
|
|
147
|
-
```ts
|
|
148
|
-
import { VERSION } from 'aiecsjs'
|
|
149
|
-
|
|
150
|
-
if (VERSION.startsWith('0.')) {
|
|
151
|
-
console.warn('aiecsjs 處於 1.0 前;API 表面可能調整')
|
|
152
|
-
}
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
如需程式化檢視,請解析 [`api.json`](./api.json) — 每筆都有 `stability` 與 `since` 欄位。
|
package/docs/MIGRATION.md
DELETED
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
# Migration Guide
|
|
2
|
-
|
|
3
|
-
[English](MIGRATION.md) | [繁體中文](MIGRATION_ZHTW.md)
|
|
4
|
-
|
|
5
|
-
Concrete name-mapping tables and mental-model notes for switching to `aiecsjs` from other JavaScript ECS libraries.
|
|
6
|
-
|
|
7
|
-
## From bitECS 0.4
|
|
8
|
-
|
|
9
|
-
bitECS and aiecsjs share the most DNA: both are functional, both use TypedArray columns, both compose systems with `pipe`. The differences are real but small.
|
|
10
|
-
|
|
11
|
-
### Name mappings
|
|
12
|
-
|
|
13
|
-
| bitECS 0.4 | aiecsjs 0.1 |
|
|
14
|
-
|---|---|
|
|
15
|
-
| `createWorld()` | `createWorld()` |
|
|
16
|
-
| `defineComponent({ x: Types.f32 })` | `defineComponent({ x: Types.f32 })` |
|
|
17
|
-
| `addComponent(world, Comp, eid)` | `addComponent(world, eid, Comp, init?)` ← **arg order!** |
|
|
18
|
-
| `removeComponent(world, Comp, eid)` | `removeComponent(world, eid, Comp)` |
|
|
19
|
-
| `hasComponent(world, Comp, eid)` | `hasComponent(world, eid, Comp)` |
|
|
20
|
-
| `addEntity(world)` | `createEntity(world)` |
|
|
21
|
-
| `removeEntity(world, eid)` | `destroyEntity(world, eid)` |
|
|
22
|
-
| `defineQuery([Comp])(world)` | `forEachEntity(world, defineQuery([Comp]), fn)` |
|
|
23
|
-
| `enterQuery(query)` | `enterQuery(defineQuery([...]))` (no `world` arg) |
|
|
24
|
-
| `exitQuery(query)` | `exitQuery(defineQuery([...]))` |
|
|
25
|
-
| `Not(Comp)` | `defineQuery({ all: [...], none: [Comp] })` |
|
|
26
|
-
| `pipe(s1, s2)(world)` | `pipe(s1, s2)(world, ctx)` (ctx threaded through) |
|
|
27
|
-
| `defineSerializer(...)` | `createDeltaSerializer(world, { components })` |
|
|
28
|
-
| `createRelation(...)` | `defineRelation(...)` (target 0.2) |
|
|
29
|
-
| `withVersioning(bits)` | `createWorld({ indexBits, generationBits })` |
|
|
30
|
-
| `observe(world, query, ...)` | `observe(world, query, event, handler)` |
|
|
31
|
-
|
|
32
|
-
### Mental shifts
|
|
33
|
-
|
|
34
|
-
**Storage model.** bitECS uses per-component SparseSet + bitmask. aiecsjs uses archetype tables. The performance characteristics differ:
|
|
35
|
-
|
|
36
|
-
- Adding/removing a tag every frame is **cheaper in bitECS** (sparse set has O(1) toggle).
|
|
37
|
-
- Iterating a hot query over 10k entities is **cheaper in aiecsjs** (contiguous archetype columns).
|
|
38
|
-
- For tags you toggle often, store a `boolean` field in a stable component instead of `add`/`removeComponent`.
|
|
39
|
-
|
|
40
|
-
**Argument order.** This is the #1 source of bugs when porting:
|
|
41
|
-
|
|
42
|
-
```ts
|
|
43
|
-
// bitECS:
|
|
44
|
-
addComponent(world, Position, eid)
|
|
45
|
-
|
|
46
|
-
// aiecsjs:
|
|
47
|
-
addComponent(world, eid, Position, { x: 0, y: 0 })
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
The aiecsjs order is `(world, eid, component, init)` — entity first because it's the subject of the operation.
|
|
51
|
-
|
|
52
|
-
**Query iteration.** bitECS returns the entity array from the query function call. aiecsjs separates query definition from execution:
|
|
53
|
-
|
|
54
|
-
```ts
|
|
55
|
-
// bitECS:
|
|
56
|
-
const movers = defineQuery([Position, Velocity])
|
|
57
|
-
const eids = movers(world)
|
|
58
|
-
for (let i = 0; i < eids.length; i++) {
|
|
59
|
-
const e = eids[i]
|
|
60
|
-
Position.x[e] += Velocity.x[e]
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// aiecsjs:
|
|
64
|
-
const movers = defineQuery([Position, Velocity])
|
|
65
|
-
forEachEntity(world, movers, (e, pos, vel) => {
|
|
66
|
-
pos.x[e] += vel.x[e]
|
|
67
|
-
})
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
The aiecsjs version is shorter and gets typed column views as callback arguments.
|
|
71
|
-
|
|
72
|
-
**Entity versioning.** Both support it. bitECS exposes `withVersioning(bits)`; aiecsjs takes `indexBits` and `generationBits` directly in `WorldOptions`.
|
|
73
|
-
|
|
74
|
-
```ts
|
|
75
|
-
// bitECS:
|
|
76
|
-
const world = createWorld(withVersioning(8))
|
|
77
|
-
|
|
78
|
-
// aiecsjs:
|
|
79
|
-
const world = createWorld({ indexBits: 24, generationBits: 8 })
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### Porting a system
|
|
83
|
-
|
|
84
|
-
bitECS:
|
|
85
|
-
```ts
|
|
86
|
-
const movementSystem = (world) => {
|
|
87
|
-
const ents = movers(world)
|
|
88
|
-
for (let i = 0; i < ents.length; i++) {
|
|
89
|
-
const eid = ents[i]
|
|
90
|
-
Position.x[eid] += Velocity.x[eid]
|
|
91
|
-
Position.y[eid] += Velocity.y[eid]
|
|
92
|
-
}
|
|
93
|
-
return world
|
|
94
|
-
}
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
aiecsjs:
|
|
98
|
-
```ts
|
|
99
|
-
const movementSystem = (world, dt = 1) => {
|
|
100
|
-
forEachEntity(world, movers, (e, pos, vel) => {
|
|
101
|
-
pos.x[e] += vel.x[e] * dt
|
|
102
|
-
pos.y[e] += vel.y[e] * dt
|
|
103
|
-
})
|
|
104
|
-
return world
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
## From miniplex
|
|
109
|
-
|
|
110
|
-
miniplex is object-oriented and entity-shape-driven; aiecsjs is functional and component-declaration-driven. The port is a small mental adjustment but worthwhile if you need TypedArray performance or multi-thread support.
|
|
111
|
-
|
|
112
|
-
### Name mappings
|
|
113
|
-
|
|
114
|
-
| miniplex 2.0 | aiecsjs 0.1 |
|
|
115
|
-
|---|---|
|
|
116
|
-
| `const world = new World<Entity>()` | `const world = createWorld()` |
|
|
117
|
-
| `world.add({ position: {x, y}, velocity: {x, y} })` | `createEntity` + multiple `addComponent` calls |
|
|
118
|
-
| `world.with('position', 'velocity')` | `defineQuery([Position, Velocity])` |
|
|
119
|
-
| `world.archetype('position', 'velocity')` | `defineQuery([Position, Velocity])` |
|
|
120
|
-
| `query.entities` | `runQuery(world, query)` |
|
|
121
|
-
| `for (const e of query)` | `forEachEntity(world, query, fn)` |
|
|
122
|
-
| `query.onEntityAdded.add(fn)` | `enterQuery(query)` + observe in a system |
|
|
123
|
-
| `query.onEntityRemoved.add(fn)` | `exitQuery(query)` |
|
|
124
|
-
| `world.remove(entity)` | `destroyEntity(world, eid)` |
|
|
125
|
-
| `world.queue.add(...)`, `world.queue.flush()` | `withCommandBuffer(world, cb => cb.create() ...)` |
|
|
126
|
-
| `world.where(predicate)` | (filter inside `forEachEntity` callback) |
|
|
127
|
-
| `<Entities of={query}>` (miniplex-react) | (not yet — see roadmap) |
|
|
128
|
-
|
|
129
|
-
### Mental shifts
|
|
130
|
-
|
|
131
|
-
**Component declaration up front.** In miniplex, components are object property names that exist if you assign them. In aiecsjs, components must be declared:
|
|
132
|
-
|
|
133
|
-
```ts
|
|
134
|
-
// miniplex:
|
|
135
|
-
const e = world.add({ position: { x: 0, y: 0 }, velocity: { x: 1, y: 0 } })
|
|
136
|
-
|
|
137
|
-
// aiecsjs:
|
|
138
|
-
const Position = defineComponent({ x: Types.f32, y: Types.f32 })
|
|
139
|
-
const Velocity = defineComponent({ x: Types.f32, y: Types.f32 })
|
|
140
|
-
const e = createEntity(world)
|
|
141
|
-
addComponent(world, e, Position, { x: 0, y: 0 })
|
|
142
|
-
addComponent(world, e, Velocity, { x: 1, y: 0 })
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
The win is TypedArray-backed columns (fast iteration) and SAB-safe storage. The cost is the upfront component declarations.
|
|
146
|
-
|
|
147
|
-
**Heterogeneous references.** If your miniplex entities have `mesh: THREE.Mesh` properties, use `defineObjectComponent` in aiecsjs:
|
|
148
|
-
|
|
149
|
-
```ts
|
|
150
|
-
const MeshRef = defineObjectComponent<{ mesh: THREE.Mesh | null }>(() => ({ mesh: null }))
|
|
151
|
-
addComponent(world, e, MeshRef, { mesh: someMesh })
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
But remember: AoS components are main-thread only.
|
|
155
|
-
|
|
156
|
-
**Iteration callbacks vs. iterators.** miniplex's `for (const e of query)` is convenient but allocates the iterator each frame. `forEachEntity(world, query, fn)` is the hot path; reach for `iterQuery` only when you need `for...of` semantics.
|
|
157
|
-
|
|
158
|
-
### Porting a system
|
|
159
|
-
|
|
160
|
-
miniplex:
|
|
161
|
-
```ts
|
|
162
|
-
const movement = (dt: number) => {
|
|
163
|
-
for (const e of world.with('position', 'velocity')) {
|
|
164
|
-
e.position.x += e.velocity.x * dt
|
|
165
|
-
e.position.y += e.velocity.y * dt
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
aiecsjs:
|
|
171
|
-
```ts
|
|
172
|
-
const movers = defineQuery([Position, Velocity])
|
|
173
|
-
const movement = (world, dt) => {
|
|
174
|
-
forEachEntity(world, movers, (e, pos, vel) => {
|
|
175
|
-
pos.x[e] += vel.x[e] * dt
|
|
176
|
-
pos.y[e] += vel.y[e] * dt
|
|
177
|
-
})
|
|
178
|
-
return world
|
|
179
|
-
}
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
## From ECSY
|
|
183
|
-
|
|
184
|
-
ECSY is [archived](https://github.com/ecsyjs/ecsy) (April 2025). Migration to aiecsjs is straightforward because both are archetype-style ECS. ECSY's OO ergonomics map cleanly to aiecsjs's functional API.
|
|
185
|
-
|
|
186
|
-
### Name mappings
|
|
187
|
-
|
|
188
|
-
| ECSY | aiecsjs 0.1 |
|
|
189
|
-
|---|---|
|
|
190
|
-
| `class C extends Component { static schema = { x: { type: Types.Number } } }` | `defineComponent({ x: Types.f64 })` |
|
|
191
|
-
| `class Tag extends TagComponent {}` | `defineTag()` |
|
|
192
|
-
| `class S extends System { static queries = { foo: { components: [...] } }; execute(dt) { this.queries.foo.results.forEach(...) } }` | `const fooQ = defineQuery([...])`; `const S = (world, dt) => { forEachEntity(world, fooQ, fn); return world }` |
|
|
193
|
-
| `world.registerComponent(C)` | (implicit on `defineComponent`) |
|
|
194
|
-
| `world.registerSystem(S)` | (none — `pipe` orders systems) |
|
|
195
|
-
| `world.execute(dt, time)` | `tick(world, dt)` where `tick = pipe(S1, S2, ...)` |
|
|
196
|
-
| `world.createEntity()` | `createEntity(world)` |
|
|
197
|
-
| `entity.addComponent(C, data)` | `addComponent(world, eid, C, data)` |
|
|
198
|
-
| `entity.removeComponent(C)` | `removeComponent(world, eid, C)` |
|
|
199
|
-
| `entity.getComponent(C)` | `getComponent(world, eid, C)` |
|
|
200
|
-
| `entity.getMutableComponent(C)` | `getComponent(world, eid, C)` (always mutable in aiecsjs) |
|
|
201
|
-
| `queries.foo.added` | `enterQuery(fooQ)` |
|
|
202
|
-
| `queries.foo.removed` | `exitQuery(fooQ)` |
|
|
203
|
-
| `queries.foo.changed` | (use `onSet` observer or own change tracking) |
|
|
204
|
-
|
|
205
|
-
### Mental shifts
|
|
206
|
-
|
|
207
|
-
**No `class System`.** Systems are functions, not classes. Drop `extends System`, `execute`, and `static queries` — define a query at module top-level and pass it to `forEachEntity`.
|
|
208
|
-
|
|
209
|
-
```ts
|
|
210
|
-
// ECSY:
|
|
211
|
-
class MovementSystem extends System {
|
|
212
|
-
static queries = { movers: { components: [Position, Velocity] } }
|
|
213
|
-
execute(dt: number) {
|
|
214
|
-
this.queries.movers.results.forEach((e) => {
|
|
215
|
-
const pos = e.getMutableComponent(Position)
|
|
216
|
-
const vel = e.getComponent(Velocity)
|
|
217
|
-
pos.x += vel.x * dt
|
|
218
|
-
pos.y += vel.y * dt
|
|
219
|
-
})
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
world.registerSystem(MovementSystem)
|
|
223
|
-
world.execute(1/60)
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
```ts
|
|
227
|
-
// aiecsjs:
|
|
228
|
-
const movers = defineQuery([Position, Velocity])
|
|
229
|
-
const movement = (world, dt) => {
|
|
230
|
-
forEachEntity(world, movers, (e, pos, vel) => {
|
|
231
|
-
pos.x[e] += vel.x[e] * dt
|
|
232
|
-
pos.y[e] += vel.y[e] * dt
|
|
233
|
-
})
|
|
234
|
-
return world
|
|
235
|
-
}
|
|
236
|
-
const tick = pipe(movement)
|
|
237
|
-
tick(world, 1/60)
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
**SoA columns vs. component instances.** ECSY components are class instances with fields like `pos.x`. aiecsjs SoA components are column maps; you index by entity ID: `pos.x[e]`.
|
|
241
|
-
|
|
242
|
-
**No `priority` or scheduling DSL.** aiecsjs systems run in `pipe()` order. If you depended on ECSY's `priority` for ordering, just write the pipe in the right order.
|
|
243
|
-
|
|
244
|
-
**`Types.Number` → `Types.f64` (or `f32`).** ECSY's numeric type is double-precision; aiecsjs lets you pick the width. Use `f32` for game data, `f64` only if you genuinely need it.
|
|
245
|
-
|
|
246
|
-
## Common pitfalls when migrating (from any library)
|
|
247
|
-
|
|
248
|
-
1. **Forgetting to `pipe(...)` system function** — calling each system manually and forgetting to thread the world reference. Compose with `pipe` once and call `tick(world, ctx)`.
|
|
249
|
-
2. **Calling `defineComponent` inside a system** — components are identity-based and must be module-level constants.
|
|
250
|
-
3. **Caching `getComponent()` return value** — after an entity's archetype changes, the view is stale. Re-fetch each frame.
|
|
251
|
-
4. **Iterating with `for...of` on `runQuery` result** — `runQuery` allocates an array each call. Use `forEachEntity` in hot paths.
|
|
252
|
-
5. **Trying to share AoS components across Workers** — only SoA components live in SharedArrayBuffer. Replace AoS with SoA before going multi-thread.
|