@oasys/oecs 0.1.2 → 0.2.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 +131 -164
- package/dist/archetype.d.ts +50 -13
- package/dist/archetype.d.ts.map +1 -1
- package/dist/component.d.ts +27 -10
- package/dist/component.d.ts.map +1 -1
- package/dist/ecs.d.ts +104 -0
- package/dist/ecs.d.ts.map +1 -0
- package/dist/entity.d.ts.map +1 -1
- package/dist/event.d.ts +2 -2
- package/dist/event.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +8 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +673 -321
- package/dist/query.d.ts +28 -31
- package/dist/query.d.ts.map +1 -1
- package/dist/ref.d.ts +19 -0
- package/dist/ref.d.ts.map +1 -0
- package/dist/resource.d.ts +23 -0
- package/dist/resource.d.ts.map +1 -0
- package/dist/schedule.d.ts +3 -3
- package/dist/schedule.d.ts.map +1 -1
- package/dist/store.d.ts +48 -29
- package/dist/store.d.ts.map +1 -1
- package/dist/system.d.ts +2 -2
- package/dist/system.d.ts.map +1 -1
- package/dist/type_primitives/assertions.d.ts +1 -0
- package/dist/type_primitives/assertions.d.ts.map +1 -1
- package/dist/type_primitives/bitset/bitset.d.ts +2 -2
- package/dist/type_primitives/bitset/bitset.d.ts.map +1 -1
- package/dist/type_primitives/sparse_map/sparse_map.d.ts.map +1 -1
- package/dist/type_primitives/sparse_set/sparse_set.d.ts.map +1 -1
- package/dist/type_primitives/typed_arrays/typed_arrays.d.ts +9 -0
- package/dist/type_primitives/typed_arrays/typed_arrays.d.ts.map +1 -1
- package/dist/utils/constants.d.ts +20 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/error.d.ts +2 -9
- package/dist/utils/error.d.ts.map +1 -1
- package/package.json +2 -1
- package/dist/world.d.ts +0 -73
- package/dist/world.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -2,27 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
A fast, minimal archetype-based Entity Component System written in TypeScript.
|
|
4
4
|
|
|
5
|
-
- **
|
|
6
|
-
- **
|
|
7
|
-
- **
|
|
8
|
-
- **
|
|
5
|
+
- **Structure-of-Arrays (SoA)** — each component field is a contiguous typed array column (`Float64Array`, `Int32Array`, etc.), enabling cache-friendly inner loops.
|
|
6
|
+
- **Phantom-typed components** — `ComponentDef<{x:"f64",y:"f64"}>` is just a number at runtime, but enforces field names and types at compile time.
|
|
7
|
+
- **Batch iteration** — `for..of` over a query yields non-empty archetypes. Access SoA columns via `get_column()` and write the inner loop.
|
|
8
|
+
- **Single-entity refs** — `ctx.ref(Pos, entity)` gives you a cached accessor with prototype-backed getters/setters.
|
|
9
|
+
- **Resources** — typed global singletons (time, input, config) with live readers.
|
|
10
|
+
- **Events & signals** — fire-and-forget SoA channels, auto-cleared each frame.
|
|
9
11
|
- **Deferred structural changes** — add/remove component and destroy entity are buffered during system execution and flushed between phases.
|
|
10
12
|
- **Topological system ordering** — systems within a phase are sorted by before/after constraints using Kahn's algorithm.
|
|
13
|
+
- **Fixed timestep** — configurable fixed update loop with accumulator and interpolation alpha.
|
|
11
14
|
|
|
12
15
|
## Quick start
|
|
13
16
|
|
|
14
17
|
```ts
|
|
15
|
-
import {
|
|
18
|
+
import { ECS, SCHEDULE } from "@oasys/oecs-typed2";
|
|
16
19
|
|
|
17
|
-
const world = new
|
|
20
|
+
const world = new ECS();
|
|
18
21
|
|
|
19
|
-
//
|
|
20
|
-
const Pos = world.register_component(
|
|
22
|
+
// Record syntax — per-field type control
|
|
23
|
+
const Pos = world.register_component({ x: "f64", y: "f64" });
|
|
24
|
+
|
|
25
|
+
// Array shorthand — uniform type, defaults to "f64"
|
|
21
26
|
const Vel = world.register_component(["vx", "vy"] as const);
|
|
22
27
|
|
|
23
28
|
// Tags have no fields
|
|
24
29
|
const IsEnemy = world.register_tag();
|
|
25
30
|
|
|
31
|
+
// Resources are global singletons
|
|
32
|
+
const Time = world.register_resource(["delta", "elapsed"] as const, {
|
|
33
|
+
delta: 0,
|
|
34
|
+
elapsed: 0,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Events are fire-and-forget messages
|
|
38
|
+
const Damage = world.register_event(["target", "amount"] as const);
|
|
39
|
+
|
|
26
40
|
// Create entities and attach components
|
|
27
41
|
const e = world.create_entity();
|
|
28
42
|
world.add_component(e, Pos, { x: 0, y: 0 });
|
|
@@ -32,12 +46,17 @@ world.add_component(e, IsEnemy);
|
|
|
32
46
|
// Register a system with a typed query
|
|
33
47
|
const moveSys = world.register_system(
|
|
34
48
|
(q, _ctx, dt) => {
|
|
35
|
-
|
|
49
|
+
for (const arch of q) {
|
|
50
|
+
const px = arch.get_column(Pos, "x");
|
|
51
|
+
const py = arch.get_column(Pos, "y");
|
|
52
|
+
const vx = arch.get_column(Vel, "vx");
|
|
53
|
+
const vy = arch.get_column(Vel, "vy");
|
|
54
|
+
const n = arch.entity_count;
|
|
36
55
|
for (let i = 0; i < n; i++) {
|
|
37
|
-
|
|
38
|
-
|
|
56
|
+
px[i] += vx[i] * dt;
|
|
57
|
+
py[i] += vy[i] * dt;
|
|
39
58
|
}
|
|
40
|
-
}
|
|
59
|
+
}
|
|
41
60
|
},
|
|
42
61
|
(qb) => qb.every(Pos, Vel),
|
|
43
62
|
);
|
|
@@ -50,245 +69,193 @@ world.startup();
|
|
|
50
69
|
|
|
51
70
|
// Game loop
|
|
52
71
|
function frame(dt: number) {
|
|
72
|
+
world.set_resource(Time, { delta: dt, elapsed: performance.now() / 1000 });
|
|
53
73
|
world.update(dt);
|
|
54
74
|
}
|
|
55
75
|
```
|
|
56
76
|
|
|
57
77
|
## Components
|
|
58
78
|
|
|
59
|
-
Components
|
|
79
|
+
Components map field names to typed array tags. All field values are `number`, but storage uses the specified typed array (`Float64Array`, `Int32Array`, etc.) for cache-friendly iteration.
|
|
60
80
|
|
|
61
81
|
```ts
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
const
|
|
82
|
+
// Record syntax — per-field type control
|
|
83
|
+
const Position = world.register_component({ x: "f64", y: "f64" });
|
|
84
|
+
const Health = world.register_component({ current: "i32", max: "i32" });
|
|
85
|
+
|
|
86
|
+
// Array shorthand — all fields default to "f64"
|
|
87
|
+
const Vel = world.register_component(["vx", "vy"] as const);
|
|
88
|
+
|
|
89
|
+
// Tags — no fields
|
|
90
|
+
const IsEnemy = world.register_tag();
|
|
65
91
|
```
|
|
66
92
|
|
|
67
|
-
|
|
93
|
+
Supported typed array tags: `"f32"`, `"f64"`, `"i8"`, `"i16"`, `"i32"`, `"u8"`, `"u16"`, `"u32"`.
|
|
68
94
|
|
|
69
|
-
|
|
95
|
+
Add components individually or in batch (single archetype transition):
|
|
70
96
|
|
|
71
97
|
```ts
|
|
72
|
-
const e = world.create_entity();
|
|
73
|
-
|
|
74
|
-
// Data components require a values object
|
|
75
98
|
world.add_component(e, Position, { x: 10, y: 20 });
|
|
76
|
-
world.add_component(e, Health, { current: 100, max: 100 });
|
|
77
|
-
|
|
78
|
-
// Tags require no values
|
|
79
|
-
world.add_component(e, IsEnemy);
|
|
80
|
-
|
|
81
|
-
// Add multiple at once (single archetype transition)
|
|
82
99
|
world.add_components(e, [
|
|
83
100
|
{ def: Position, values: { x: 10, y: 20 } },
|
|
84
101
|
{ def: Health, values: { current: 100, max: 100 } },
|
|
85
102
|
]);
|
|
86
103
|
```
|
|
87
104
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
```ts
|
|
91
|
-
world.remove_component(e, Health);
|
|
92
|
-
world.has_component(e, IsEnemy); // true
|
|
93
|
-
```
|
|
105
|
+
See [docs/api/components.md](docs/api/components.md) for full API.
|
|
94
106
|
|
|
95
107
|
## Queries
|
|
96
108
|
|
|
97
|
-
Queries are live views over all archetypes matching a component mask.
|
|
98
|
-
|
|
99
|
-
### Batch iteration with `each()`
|
|
100
|
-
|
|
101
|
-
`each()` calls your function once per matching archetype, passing column-group objects and the entity count. You write the inner loop.
|
|
109
|
+
Queries are live views over all archetypes matching a component mask.
|
|
102
110
|
|
|
103
111
|
```ts
|
|
104
112
|
const q = world.query(Position, Velocity);
|
|
105
113
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
114
|
+
// Iterate non-empty archetypes, access SoA columns, write the inner loop
|
|
115
|
+
for (const arch of q) {
|
|
116
|
+
const px = arch.get_column(Position, "x");
|
|
117
|
+
const py = arch.get_column(Position, "y");
|
|
118
|
+
const vx = arch.get_column(Velocity, "vx");
|
|
119
|
+
const vy = arch.get_column(Velocity, "vy");
|
|
120
|
+
const n = arch.entity_count;
|
|
109
121
|
for (let i = 0; i < n; i++) {
|
|
110
|
-
|
|
111
|
-
|
|
122
|
+
px[i] += vx[i];
|
|
123
|
+
py[i] += vy[i];
|
|
112
124
|
}
|
|
113
|
-
}
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
### Query chaining
|
|
117
|
-
|
|
118
|
-
```ts
|
|
119
|
-
// Extend required components
|
|
120
|
-
const q = world.query(Position).and(Velocity);
|
|
121
|
-
|
|
122
|
-
// Exclude archetypes that have a component
|
|
123
|
-
const alive = world.query(Position).not(Dead);
|
|
124
|
-
|
|
125
|
-
// Require at least one of these
|
|
126
|
-
const damaged = world.query(Health).or(Poison, Fire);
|
|
125
|
+
}
|
|
127
126
|
|
|
128
|
-
//
|
|
127
|
+
// Chaining
|
|
129
128
|
const targets = world.query(Position).and(Health).not(Shield).or(IsEnemy, IsBoss);
|
|
130
129
|
```
|
|
131
130
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
For dynamic component access, iterate archetypes directly:
|
|
135
|
-
|
|
136
|
-
```ts
|
|
137
|
-
for (const arch of world.query(Position)) {
|
|
138
|
-
const px = arch.get_column(Position, "x");
|
|
139
|
-
const py = arch.get_column(Position, "y");
|
|
140
|
-
const count = arch.entity_count;
|
|
141
|
-
|
|
142
|
-
for (let i = 0; i < count; i++) {
|
|
143
|
-
px[i] += 1;
|
|
144
|
-
py[i] += 1;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
```
|
|
131
|
+
See [docs/api/queries.md](docs/api/queries.md) for full API.
|
|
148
132
|
|
|
149
133
|
## Systems
|
|
150
134
|
|
|
151
135
|
Systems are plain functions registered with a query and scheduled into lifecycle phases.
|
|
152
136
|
|
|
153
|
-
### Registration
|
|
154
|
-
|
|
155
|
-
Two registration styles:
|
|
156
|
-
|
|
157
137
|
```ts
|
|
158
|
-
//
|
|
138
|
+
// With a typed query
|
|
159
139
|
const moveSys = world.register_system(
|
|
160
|
-
(q, ctx, dt) => {
|
|
161
|
-
q.each((pos, vel, n) => {
|
|
162
|
-
for (let i = 0; i < n; i++) {
|
|
163
|
-
pos.x[i] += vel.vx[i] * dt;
|
|
164
|
-
pos.y[i] += vel.vy[i] * dt;
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
},
|
|
140
|
+
(q, ctx, dt) => { for (const arch of q) { /* ... */ } },
|
|
168
141
|
(qb) => qb.every(Pos, Vel),
|
|
169
142
|
);
|
|
170
143
|
|
|
171
|
-
//
|
|
144
|
+
// Without a query
|
|
172
145
|
const logSys = world.register_system({
|
|
173
|
-
fn(ctx, dt) {
|
|
174
|
-
console.log("frame", dt);
|
|
175
|
-
},
|
|
146
|
+
fn(ctx, dt) { console.log("frame", dt); },
|
|
176
147
|
});
|
|
177
148
|
```
|
|
178
149
|
|
|
179
|
-
|
|
150
|
+
Inside systems, use `ctx` for deferred structural changes and per-entity access:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
const e = ctx.create_entity();
|
|
154
|
+
ctx.add_component(e, Pos, { x: 0, y: 0 });
|
|
155
|
+
ctx.destroy_entity(someEntity);
|
|
156
|
+
ctx.remove_component(entity, Health);
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
See [docs/api/systems.md](docs/api/systems.md) for full API.
|
|
180
160
|
|
|
181
|
-
|
|
161
|
+
## Resources
|
|
162
|
+
|
|
163
|
+
Resources are typed global singletons — time, input state, camera config.
|
|
182
164
|
|
|
183
165
|
```ts
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
const e = ctx.create_entity();
|
|
187
|
-
ctx.add_component(e, Position, { x: 0, y: 0 });
|
|
188
|
-
ctx.destroy_entity(someOtherEntity);
|
|
189
|
-
ctx.remove_component(anotherEntity, Health);
|
|
190
|
-
// Changes are applied after all systems in this phase complete
|
|
191
|
-
},
|
|
166
|
+
const Time = world.register_resource(["delta", "elapsed"] as const, {
|
|
167
|
+
delta: 0, elapsed: 0,
|
|
192
168
|
});
|
|
169
|
+
|
|
170
|
+
// Write
|
|
171
|
+
world.set_resource(Time, { delta: dt, elapsed: total });
|
|
172
|
+
|
|
173
|
+
// Read — scalar values, not arrays
|
|
174
|
+
const time = world.resource(Time);
|
|
175
|
+
time.delta; // number
|
|
176
|
+
time.elapsed; // number
|
|
193
177
|
```
|
|
194
178
|
|
|
195
|
-
|
|
179
|
+
See [docs/api/resources.md](docs/api/resources.md) for full API.
|
|
180
|
+
|
|
181
|
+
## Events & Signals
|
|
196
182
|
|
|
197
|
-
|
|
183
|
+
Events are fire-and-forget SoA channels, auto-cleared each frame.
|
|
198
184
|
|
|
199
185
|
```ts
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
186
|
+
// Data events carry fields
|
|
187
|
+
const Damage = world.register_event(["target", "amount"] as const);
|
|
188
|
+
ctx.emit(Damage, { target: entityId, amount: 50 });
|
|
189
|
+
|
|
190
|
+
const dmg = ctx.read(Damage);
|
|
191
|
+
for (let i = 0; i < dmg.length; i++) {
|
|
192
|
+
dmg.target[i]; // number
|
|
193
|
+
dmg.amount[i]; // number
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Signals carry no data — just a count
|
|
197
|
+
const OnReset = world.register_signal();
|
|
198
|
+
ctx.emit(OnReset);
|
|
199
|
+
if (ctx.read(OnReset).length > 0) { /* fired */ }
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
See [docs/api/events.md](docs/api/events.md) for full API.
|
|
203
|
+
|
|
204
|
+
## Refs
|
|
205
|
+
|
|
206
|
+
Refs provide cached single-entity field access — faster than `get_field`/`set_field` for repeated access.
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
const pos = ctx.ref(Pos, entity);
|
|
210
|
+
const vel = ctx.ref(Vel, entity);
|
|
211
|
+
pos.x += vel.vx * dt;
|
|
212
|
+
pos.y += vel.vy * dt;
|
|
206
213
|
```
|
|
207
214
|
|
|
215
|
+
See [docs/api/refs.md](docs/api/refs.md) for full API.
|
|
216
|
+
|
|
208
217
|
## Schedule
|
|
209
218
|
|
|
210
|
-
|
|
219
|
+
Seven lifecycle phases, executed in order:
|
|
211
220
|
|
|
212
221
|
| Phase | When | Use case |
|
|
213
222
|
|---|---|---|
|
|
214
|
-
| `PRE_STARTUP` | Once, before startup | Resource loading
|
|
223
|
+
| `PRE_STARTUP` | Once, before startup | Resource loading |
|
|
215
224
|
| `STARTUP` | Once | Initial entity spawning |
|
|
216
|
-
| `POST_STARTUP` | Once, after startup | Validation
|
|
217
|
-
| `
|
|
218
|
-
| `
|
|
225
|
+
| `POST_STARTUP` | Once, after startup | Validation |
|
|
226
|
+
| `FIXED_UPDATE` | Every tick (fixed dt) | Physics, simulation |
|
|
227
|
+
| `PRE_UPDATE` | Every frame, first | Input handling |
|
|
228
|
+
| `UPDATE` | Every frame | Game logic |
|
|
219
229
|
| `POST_UPDATE` | Every frame, last | Rendering, cleanup |
|
|
220
230
|
|
|
221
|
-
Deferred changes are flushed automatically after each phase.
|
|
222
|
-
|
|
223
231
|
```ts
|
|
224
232
|
world.add_systems(SCHEDULE.UPDATE, moveSys, physicsSys);
|
|
225
233
|
world.add_systems(SCHEDULE.POST_UPDATE, renderSys);
|
|
226
|
-
```
|
|
227
234
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
```ts
|
|
235
|
+
// Ordering constraints
|
|
231
236
|
world.add_systems(SCHEDULE.UPDATE, moveSys, {
|
|
232
237
|
system: physicsSys,
|
|
233
238
|
ordering: { after: [moveSys] },
|
|
234
239
|
});
|
|
235
|
-
|
|
236
|
-
world.add_systems(SCHEDULE.UPDATE, {
|
|
237
|
-
system: aiSys,
|
|
238
|
-
ordering: { before: [moveSys] },
|
|
239
|
-
});
|
|
240
240
|
```
|
|
241
241
|
|
|
242
|
-
|
|
242
|
+
See [docs/api/schedule.md](docs/api/schedule.md) for full API.
|
|
243
243
|
|
|
244
244
|
## Entity lifecycle
|
|
245
245
|
|
|
246
246
|
```ts
|
|
247
247
|
const e = world.create_entity();
|
|
248
|
-
|
|
249
248
|
world.is_alive(e); // true
|
|
250
|
-
|
|
251
|
-
world.
|
|
252
|
-
world.flush(); // apply now
|
|
253
|
-
|
|
249
|
+
world.destroy_entity_deferred(e); // deferred
|
|
250
|
+
world.flush();
|
|
254
251
|
world.is_alive(e); // false
|
|
255
252
|
```
|
|
256
253
|
|
|
257
|
-
Entity IDs are generational: destroying an entity increments its slot's generation, so stale IDs are
|
|
258
|
-
|
|
259
|
-
## Lifecycle hooks
|
|
260
|
-
|
|
261
|
-
Systems can define lifecycle hooks:
|
|
262
|
-
|
|
263
|
-
```ts
|
|
264
|
-
const sys = world.register_system({
|
|
265
|
-
fn(ctx, dt) { /* runs every frame */ },
|
|
266
|
-
|
|
267
|
-
on_added(store) {
|
|
268
|
-
// Called once during world.startup()
|
|
269
|
-
},
|
|
270
|
-
|
|
271
|
-
on_removed() {
|
|
272
|
-
// Called when system is unregistered via world.remove_system()
|
|
273
|
-
},
|
|
274
|
-
|
|
275
|
-
dispose() {
|
|
276
|
-
// Called during world.dispose()
|
|
277
|
-
},
|
|
278
|
-
});
|
|
279
|
-
```
|
|
254
|
+
Entity IDs are generational: destroying an entity increments its slot's generation, so stale IDs are detected as dead.
|
|
280
255
|
|
|
281
256
|
## Dev / Prod modes
|
|
282
257
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
- Bounds checking on entity IDs
|
|
286
|
-
- Validation on branded type construction
|
|
287
|
-
- Duplicate system detection
|
|
288
|
-
- Circular dependency detection
|
|
289
|
-
- Helpful error messages with context
|
|
290
|
-
|
|
291
|
-
All dev-only checks are tree-shaken in production builds.
|
|
258
|
+
`__DEV__` compile-time flags enable bounds checking, dead entity detection, and duplicate system detection. Circular dependency detection is always active (not tree-shaken). All other dev checks are tree-shaken in production builds.
|
|
292
259
|
|
|
293
260
|
## Development
|
|
294
261
|
|
|
@@ -302,4 +269,4 @@ pnpm tsc --noEmit # type check
|
|
|
302
269
|
|
|
303
270
|
## Architecture
|
|
304
271
|
|
|
305
|
-
See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for
|
|
272
|
+
See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for internal design details.
|
package/dist/archetype.d.ts
CHANGED
|
@@ -1,41 +1,52 @@
|
|
|
1
|
-
import { Brand, BitSet } from './type_primitives';
|
|
2
|
-
import { ComponentID,
|
|
1
|
+
import { Brand, GrowableTypedArray, AnyTypedArray, TypedArrayTag, BitSet } from './type_primitives';
|
|
2
|
+
import { ComponentID, ComponentDef, ComponentSchema, TagToTypedArray } from './component';
|
|
3
3
|
import { EntityID } from './entity';
|
|
4
4
|
export type ArchetypeID = Brand<number, "archetype_id">;
|
|
5
5
|
export declare const as_archetype_id: (value: number) => ArchetypeID;
|
|
6
6
|
export interface ArchetypeEdge {
|
|
7
7
|
add: ArchetypeID | null;
|
|
8
8
|
remove: ArchetypeID | null;
|
|
9
|
+
/** Pre-computed column mapping for add direction: this → add target. */
|
|
10
|
+
add_map: Int16Array | null;
|
|
11
|
+
/** Pre-computed column mapping for remove direction: this → remove target. */
|
|
12
|
+
remove_map: Int16Array | null;
|
|
9
13
|
}
|
|
10
14
|
export interface ArchetypeColumnLayout {
|
|
11
15
|
component_id: ComponentID;
|
|
12
16
|
field_names: string[];
|
|
13
17
|
field_index: Record<string, number>;
|
|
18
|
+
field_types: TypedArrayTag[];
|
|
14
19
|
}
|
|
15
20
|
interface ArchetypeColumnGroup {
|
|
16
21
|
layout: ArchetypeColumnLayout;
|
|
17
|
-
columns:
|
|
18
|
-
record: Record<string, number[]>;
|
|
22
|
+
columns: GrowableTypedArray<AnyTypedArray>[];
|
|
19
23
|
}
|
|
20
24
|
export declare class Archetype {
|
|
21
25
|
readonly id: ArchetypeID;
|
|
22
26
|
readonly mask: BitSet;
|
|
23
27
|
readonly has_columns: boolean;
|
|
24
|
-
|
|
28
|
+
private readonly _entity_ids;
|
|
25
29
|
length: number;
|
|
26
|
-
private edges;
|
|
30
|
+
private readonly edges;
|
|
31
|
+
readonly _flat_columns: GrowableTypedArray<AnyTypedArray>[];
|
|
32
|
+
readonly _col_offset: number[];
|
|
33
|
+
readonly _field_count: number[];
|
|
34
|
+
private readonly _field_index;
|
|
35
|
+
private readonly _field_names;
|
|
27
36
|
readonly column_groups: (ArchetypeColumnGroup | undefined)[];
|
|
28
|
-
|
|
29
|
-
constructor(id: ArchetypeID, mask: BitSet, layouts?: ArchetypeColumnLayout[]);
|
|
37
|
+
readonly _column_ids: number[];
|
|
38
|
+
constructor(id: ArchetypeID, mask: BitSet, layouts?: ArchetypeColumnLayout[], initial_capacity?: number);
|
|
30
39
|
get entity_count(): number;
|
|
31
|
-
|
|
40
|
+
/** Raw entity ID buffer. Valid data at indices 0..entity_count-1. */
|
|
41
|
+
get entity_ids(): Uint32Array;
|
|
42
|
+
get entity_list(): Uint32Array;
|
|
32
43
|
has_component(id: ComponentID): boolean;
|
|
33
44
|
matches(required: BitSet): boolean;
|
|
34
45
|
/** Get a single field's column. Valid data: indices 0..entity_count-1. */
|
|
35
|
-
get_column<
|
|
36
|
-
/** Get all columns for a component as { fieldName: number[] }. */
|
|
37
|
-
get_column_group<F extends ComponentFields>(def: ComponentDef<F>): ColumnsForSchema<F>;
|
|
46
|
+
get_column<S extends ComponentSchema, K extends string & keyof S>(def: ComponentDef<S>, field: K): TagToTypedArray[S[K]];
|
|
38
47
|
write_fields(row: number, component_id: ComponentID, values: Record<string, number>): void;
|
|
48
|
+
/** Fast positional write: values[i] → field[i] in declaration order. No string lookup. */
|
|
49
|
+
write_fields_positional(row: number, component_id: ComponentID, values: ArrayLike<number>): void;
|
|
39
50
|
read_field(row: number, component_id: ComponentID, field: string): number;
|
|
40
51
|
/** Copy all shared component columns from source archetype at src_row into dst_row. */
|
|
41
52
|
copy_shared_from(source: Archetype, src_row: number, dst_row: number): void;
|
|
@@ -47,15 +58,41 @@ export declare class Archetype {
|
|
|
47
58
|
/**
|
|
48
59
|
* Remove entity at row via swap-and-pop. Swaps the last entity into the
|
|
49
60
|
* vacated row to keep data dense. Returns the entity_index of the swapped
|
|
50
|
-
* entity (so Store can update its row), or
|
|
61
|
+
* entity (so Store can update its row), or NO_SWAP if no swap was needed.
|
|
51
62
|
*/
|
|
52
63
|
remove_entity(row: number): number;
|
|
53
64
|
/** Tag-optimized add: skip column push entirely (no data to store). */
|
|
54
65
|
add_entity_tag(entity_id: EntityID): number;
|
|
55
66
|
/** Tag-optimized remove via swap-and-pop: skip column swap/pop entirely. */
|
|
56
67
|
remove_entity_tag(row: number): number;
|
|
68
|
+
/**
|
|
69
|
+
* Move an entity from src archetype into this archetype in a single pass.
|
|
70
|
+
* Combines add_entity + copy_shared_from + remove_entity(src).
|
|
71
|
+
* Uses a pre-computed transition map for branchless column copy.
|
|
72
|
+
* Writes dst_row to _move_result[0], swapped entity index to _move_result[1].
|
|
73
|
+
*/
|
|
74
|
+
move_entity_from(src: Archetype, src_row: number, entity_id: EntityID, transition_map: Int16Array): void;
|
|
75
|
+
/**
|
|
76
|
+
* Move an entity from src into this archetype (tag-only: no columns to copy).
|
|
77
|
+
* Writes dst_row to _move_result[0], swapped entity index to _move_result[1].
|
|
78
|
+
*/
|
|
79
|
+
move_entity_from_tag(src: Archetype, src_row: number, entity_id: EntityID): void;
|
|
80
|
+
/**
|
|
81
|
+
* Bulk-move ALL entities from src into this archetype using TypedArray.set().
|
|
82
|
+
* Much faster than per-entity move_entity_from when the entire source is moving.
|
|
83
|
+
* After this call, src is empty. Returns the starting dst_row for the batch.
|
|
84
|
+
*/
|
|
85
|
+
bulk_move_all_from(src: Archetype, transition_map: Int16Array): number;
|
|
57
86
|
get_edge(component_id: ComponentID): ArchetypeEdge | undefined;
|
|
58
87
|
set_edge(component_id: ComponentID, edge: ArchetypeEdge): void;
|
|
59
88
|
}
|
|
89
|
+
/** Reusable result buffer for move_entity_from/move_entity_from_tag. [dst_row, swapped_index] */
|
|
90
|
+
export declare const _move_result: [number, number];
|
|
91
|
+
/**
|
|
92
|
+
* Build a transition map from src archetype to dst archetype.
|
|
93
|
+
* For each column in dst, stores the index of the corresponding column in src,
|
|
94
|
+
* or -1 if the column is new (no source).
|
|
95
|
+
*/
|
|
96
|
+
export declare function build_transition_map(src: Archetype, dst: Archetype): Int16Array;
|
|
60
97
|
export {};
|
|
61
98
|
//# sourceMappingURL=archetype.d.ts.map
|
package/dist/archetype.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archetype.d.ts","sourceRoot":"","sources":["../src/archetype.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;KAmBK;AAEL,OAAO,EACL,KAAK,
|
|
1
|
+
{"version":3,"file":"archetype.d.ts","sourceRoot":"","sources":["../src/archetype.ts"],"names":[],"mappings":"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"}
|
package/dist/component.d.ts
CHANGED
|
@@ -1,18 +1,35 @@
|
|
|
1
|
-
import { Brand } from './type_primitives';
|
|
1
|
+
import { Brand, TypedArrayTag } from './type_primitives';
|
|
2
2
|
export type ComponentID = Brand<number, "component_id">;
|
|
3
3
|
export declare const as_component_id: (value: number) => ComponentID;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
/** Core schema type: maps field names to typed array tags. */
|
|
5
|
+
export type ComponentSchema = Readonly<Record<string, TypedArrayTag>>;
|
|
6
|
+
/** Compile-time tag → TypedArray mapping. */
|
|
7
|
+
export type TagToTypedArray = {
|
|
8
|
+
f32: Float32Array;
|
|
9
|
+
f64: Float64Array;
|
|
10
|
+
i8: Int8Array;
|
|
11
|
+
i16: Int16Array;
|
|
12
|
+
i32: Int32Array;
|
|
13
|
+
u8: Uint8Array;
|
|
14
|
+
u16: Uint16Array;
|
|
15
|
+
u32: Uint32Array;
|
|
16
|
+
};
|
|
17
|
+
/** Maps schema fields to their value object: { x: number, y: number }. */
|
|
18
|
+
export type FieldValues<S extends ComponentSchema> = {
|
|
19
|
+
readonly [K in keyof S]: number;
|
|
8
20
|
};
|
|
9
|
-
/** Maps
|
|
10
|
-
export type ColumnsForSchema<
|
|
11
|
-
readonly [K in
|
|
21
|
+
/** Maps schema fields to their specific typed array columns. */
|
|
22
|
+
export type ColumnsForSchema<S extends ComponentSchema> = {
|
|
23
|
+
readonly [K in keyof S]: TagToTypedArray[S[K]];
|
|
24
|
+
};
|
|
25
|
+
export type ComponentFields = readonly string[];
|
|
26
|
+
/** Maps component fields to column arrays (used by events — always Float64Array). */
|
|
27
|
+
export type ColumnsForFields<F extends ComponentFields> = {
|
|
28
|
+
readonly [K in F[number]]: Float64Array;
|
|
12
29
|
};
|
|
13
30
|
declare const __schema: unique symbol;
|
|
14
|
-
export type ComponentDef<
|
|
15
|
-
readonly [__schema]:
|
|
31
|
+
export type ComponentDef<S extends ComponentSchema = ComponentSchema> = ComponentID & {
|
|
32
|
+
readonly [__schema]: S;
|
|
16
33
|
};
|
|
17
34
|
export {};
|
|
18
35
|
//# sourceMappingURL=component.d.ts.map
|
package/dist/component.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;KAuBK;AAEL,OAAO,EACL,KAAK,EAGL,KAAK,aAAa,EACnB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;AACxD,eAAO,MAAM,eAAe,UAAW,MAAM,gBAK1C,CAAC;AAEJ,8DAA8D;AAC9D,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;AAEtE,6CAA6C;AAC7C,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,YAAY,CAAC;IAClB,GAAG,EAAE,YAAY,CAAC;IAClB,EAAE,EAAE,SAAS,CAAC;IACd,GAAG,EAAE,UAAU,CAAC;IAChB,GAAG,EAAE,UAAU,CAAC;IAChB,EAAE,EAAE,UAAU,CAAC;IACf,GAAG,EAAE,WAAW,CAAC;IACjB,GAAG,EAAE,WAAW,CAAC;CAClB,CAAC;AAEF,0EAA0E;AAC1E,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,eAAe,IAAI;IACnD,QAAQ,EAAE,CAAC,IAAI,MAAM,CAAC,GAAG,MAAM;CAChC,CAAC;AAEF,gEAAgE;AAChE,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,eAAe,IAAI;IACxD,QAAQ,EAAE,CAAC,IAAI,MAAM,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAC/C,CAAC;AAGF,MAAM,MAAM,eAAe,GAAG,SAAS,MAAM,EAAE,CAAC;AAEhD,qFAAqF;AACrF,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,eAAe,IAAI;IACxD,QAAQ,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,YAAY;CACxC,CAAC;AAMF,OAAO,CAAC,MAAM,QAAQ,EAAE,OAAO,MAAM,CAAC;AAEtC,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,eAAe,GAAG,eAAe,IAClE,WAAW,GAAG;IAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;CAAE,CAAC"}
|