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/docs/MIGRATION_ZHTW.md
DELETED
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
# 移轉指引
|
|
2
|
-
|
|
3
|
-
[English](MIGRATION.md) | [繁體中文](MIGRATION_ZHTW.md)
|
|
4
|
-
|
|
5
|
-
從其他 JavaScript ECS 函式庫切換到 `aiecsjs` 的具體名稱對照與心態調整筆記。
|
|
6
|
-
|
|
7
|
-
## 從 bitECS 0.4 移轉
|
|
8
|
-
|
|
9
|
-
bitECS 與 aiecsjs 共享最多 DNA:都是函式式、都用 TypedArray 欄位、都用 `pipe` 組合系統。差異雖真實但不大。
|
|
10
|
-
|
|
11
|
-
### 名稱對照
|
|
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?)` ← **參數順序!** |
|
|
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([...]))`(無 `world` 參數) |
|
|
24
|
-
| `exitQuery(query)` | `exitQuery(defineQuery([...]))` |
|
|
25
|
-
| `Not(Comp)` | `defineQuery({ all: [...], none: [Comp] })` |
|
|
26
|
-
| `pipe(s1, s2)(world)` | `pipe(s1, s2)(world, ctx)`(ctx 會被串接) |
|
|
27
|
-
| `defineSerializer(...)` | `createDeltaSerializer(world, { components })` |
|
|
28
|
-
| `createRelation(...)` | `defineRelation(...)`(目標 0.2) |
|
|
29
|
-
| `withVersioning(bits)` | `createWorld({ indexBits, generationBits })` |
|
|
30
|
-
| `observe(world, query, ...)` | `observe(world, query, event, handler)` |
|
|
31
|
-
|
|
32
|
-
### 心態調整
|
|
33
|
-
|
|
34
|
-
**儲存模型。** bitECS 用每元件 SparseSet + bitmask。aiecsjs 用 archetype 表格。效能特性不同:
|
|
35
|
-
|
|
36
|
-
- 每幀 add/remove 一個 tag 在 **bitECS 較便宜**(sparse set O(1) toggle)。
|
|
37
|
-
- 對 1 萬實體做熱查詢迭代在 **aiecsjs 較便宜**(連續 archetype 欄位)。
|
|
38
|
-
- 經常切換的 tag,請改用穩定元件內的 `boolean` 欄位,不要 `add`/`removeComponent`。
|
|
39
|
-
|
|
40
|
-
**參數順序。** 移植時最常出 bug 的點:
|
|
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
|
-
aiecsjs 順序為 `(world, eid, component, init)` — 實體在前,因為它才是操作主體。
|
|
51
|
-
|
|
52
|
-
**查詢迭代。** bitECS 是 query 呼叫直接回傳實體陣列。aiecsjs 將定義與執行分離:
|
|
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
|
-
aiecsjs 寫法較短,且 callback 內可取得已對型的欄位 view。
|
|
71
|
-
|
|
72
|
-
**實體版本控制。** 兩者都支援。bitECS 透過 `withVersioning(bits)`;aiecsjs 直接在 `WorldOptions` 中取 `indexBits` 與 `generationBits`。
|
|
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
|
-
### 系統移植範例
|
|
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
|
-
## 從 miniplex 移轉
|
|
109
|
-
|
|
110
|
-
miniplex 是 OO 且以實體 shape 為主;aiecsjs 是函式式且以元件宣告為主。移植需要一點心態調整,但若你需要 TypedArray 效能或多執行緒支援就值得。
|
|
111
|
-
|
|
112
|
-
### 名稱對照
|
|
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` + 多次 `addComponent` |
|
|
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 |
|
|
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)` | (在 `forEachEntity` callback 內過濾) |
|
|
127
|
-
| `<Entities of={query}>`(miniplex-react) | (尚未支援,見 roadmap) |
|
|
128
|
-
|
|
129
|
-
### 心態調整
|
|
130
|
-
|
|
131
|
-
**元件需預先宣告。** miniplex 中,元件是你賦值就存在的物件屬性名。aiecsjs 中,元件必須先宣告:
|
|
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
|
-
收益是 TypedArray 欄位(快速迭代)與 SAB 安全儲存。代價是元件需要事先宣告。
|
|
146
|
-
|
|
147
|
-
**異質參考。** 若 miniplex 實體有 `mesh: THREE.Mesh` 屬性,aiecsjs 中請用 `defineObjectComponent`:
|
|
148
|
-
|
|
149
|
-
```ts
|
|
150
|
-
const MeshRef = defineObjectComponent<{ mesh: THREE.Mesh | null }>(() => ({ mesh: null }))
|
|
151
|
-
addComponent(world, e, MeshRef, { mesh: someMesh })
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
但記住:AoS 元件僅限主執行緒。
|
|
155
|
-
|
|
156
|
-
**iteration callback vs. iterator。** miniplex 的 `for (const e of query)` 方便但每幀分配 iterator。`forEachEntity(world, query, fn)` 是熱路徑;只有需要 `for...of` 語義時才使用 `iterQuery`。
|
|
157
|
-
|
|
158
|
-
### 系統移植範例
|
|
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
|
-
## 從 ECSY 移轉
|
|
183
|
-
|
|
184
|
-
ECSY 已[封存](https://github.com/ecsyjs/ecsy)(2025 年 4 月)。移轉到 aiecsjs 並不困難,因為兩者皆為 archetype-style。ECSY 的 OO 慣用可直接對應到 aiecsjs 的函式式 API。
|
|
185
|
-
|
|
186
|
-
### 名稱對照
|
|
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)` | (在 `defineComponent` 時自動完成) |
|
|
194
|
-
| `world.registerSystem(S)` | (無 — `pipe` 決定順序) |
|
|
195
|
-
| `world.execute(dt, time)` | `tick(world, dt)`,其中 `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)`(aiecsjs 永遠是可變的) |
|
|
201
|
-
| `queries.foo.added` | `enterQuery(fooQ)` |
|
|
202
|
-
| `queries.foo.removed` | `exitQuery(fooQ)` |
|
|
203
|
-
| `queries.foo.changed` | (用 `onSet` observer 或自行追蹤) |
|
|
204
|
-
|
|
205
|
-
### 心態調整
|
|
206
|
-
|
|
207
|
-
**不用寫 `class System`。** 系統是函式不是 class。捨棄 `extends System`、`execute`、`static queries` — 在模組頂層定義 query,並傳給 `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 欄位 vs. 元件實例。** ECSY 元件是 class 實例,欄位如 `pos.x`。aiecsjs SoA 元件是欄位對應表;以實體 ID 索引:`pos.x[e]`。
|
|
241
|
-
|
|
242
|
-
**沒有 `priority` 或排程 DSL。** aiecsjs 系統以 `pipe()` 順序執行。若你依賴 ECSY 的 `priority` 排序,直接把 pipe 順序寫對即可。
|
|
243
|
-
|
|
244
|
-
**`Types.Number` → `Types.f64`(或 `f32`)。** ECSY 的數值型別為雙精度;aiecsjs 讓你選擇寬度。一般遊戲資料用 `f32`,真的需要才用 `f64`。
|
|
245
|
-
|
|
246
|
-
## 移轉常見陷阱(任一函式庫)
|
|
247
|
-
|
|
248
|
-
1. **忘了用 `pipe(...)` 串系統** — 手動逐一呼叫各系統卻沒把 world 參考帶過去。請用 `pipe` 串好,再 `tick(world, ctx)`。
|
|
249
|
-
2. **在系統內呼叫 `defineComponent`** — 元件是 identity-based,必須是 module-level 常數。
|
|
250
|
-
3. **快取 `getComponent()` 回傳值** — 實體 archetype 改變後該 view 已失效。每幀請重新取得。
|
|
251
|
-
4. **對 `runQuery` 結果用 `for...of` 迭代** — `runQuery` 每次呼叫都分配陣列。熱路徑請改用 `forEachEntity`。
|
|
252
|
-
5. **嘗試跨 Worker 共享 AoS 元件** — 僅 SoA 元件能存於 SharedArrayBuffer。多執行緒前請先把 AoS 換成 SoA。
|