@oasys/oecs 0.1.2 → 0.2.1

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.
Files changed (41) hide show
  1. package/README.md +172 -164
  2. package/dist/archetype.d.ts +50 -13
  3. package/dist/archetype.d.ts.map +1 -1
  4. package/dist/component.d.ts +27 -10
  5. package/dist/component.d.ts.map +1 -1
  6. package/dist/ecs.d.ts +104 -0
  7. package/dist/ecs.d.ts.map +1 -0
  8. package/dist/entity.d.ts.map +1 -1
  9. package/dist/event.d.ts +2 -2
  10. package/dist/event.d.ts.map +1 -1
  11. package/dist/index.cjs +1 -1
  12. package/dist/index.d.ts +8 -7
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +673 -321
  15. package/dist/query.d.ts +28 -31
  16. package/dist/query.d.ts.map +1 -1
  17. package/dist/ref.d.ts +19 -0
  18. package/dist/ref.d.ts.map +1 -0
  19. package/dist/resource.d.ts +23 -0
  20. package/dist/resource.d.ts.map +1 -0
  21. package/dist/schedule.d.ts +3 -3
  22. package/dist/schedule.d.ts.map +1 -1
  23. package/dist/store.d.ts +48 -29
  24. package/dist/store.d.ts.map +1 -1
  25. package/dist/system.d.ts +2 -2
  26. package/dist/system.d.ts.map +1 -1
  27. package/dist/type_primitives/assertions.d.ts +1 -0
  28. package/dist/type_primitives/assertions.d.ts.map +1 -1
  29. package/dist/type_primitives/bitset/bitset.d.ts +2 -2
  30. package/dist/type_primitives/bitset/bitset.d.ts.map +1 -1
  31. package/dist/type_primitives/sparse_map/sparse_map.d.ts.map +1 -1
  32. package/dist/type_primitives/sparse_set/sparse_set.d.ts.map +1 -1
  33. package/dist/type_primitives/typed_arrays/typed_arrays.d.ts +9 -0
  34. package/dist/type_primitives/typed_arrays/typed_arrays.d.ts.map +1 -1
  35. package/dist/utils/constants.d.ts +20 -0
  36. package/dist/utils/constants.d.ts.map +1 -0
  37. package/dist/utils/error.d.ts +2 -9
  38. package/dist/utils/error.d.ts.map +1 -1
  39. package/package.json +3 -2
  40. package/dist/world.d.ts +0 -73
  41. package/dist/world.d.ts.map +0 -1
package/README.md CHANGED
@@ -2,27 +2,49 @@
2
2
 
3
3
  A fast, minimal archetype-based Entity Component System written in TypeScript.
4
4
 
5
- - **Zero-copy archetype transitions** — component data is indexed by entity, not archetype row. Moving between archetypes copies only the relevant column data.
6
- - **Structure-of-Arrays (SoA)** — each component field is a contiguous `number[]` column, enabling tight inner loops.
7
- - **Phantom-typed components** — `ComponentDef<["x", "y"]>` is just a number at runtime, but enforces field names at compile time.
8
- - **Batch iteration** — `query.each()` calls your function once per archetype with column arrays and entity count. You write the inner loop.
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.
14
+
15
+ ## Installation
16
+
17
+ Using npm (or equivalent):
18
+
19
+ ```
20
+ npm install @oasys/oecs
21
+ ```
11
22
 
12
23
  ## Quick start
13
24
 
14
25
  ```ts
15
- import { World, SCHEDULE } from "@oasys/oecs";
26
+ import { ECS, SCHEDULE } from "@oasys/oecs";
27
+
28
+ const world = new ECS();
16
29
 
17
- const world = new World();
30
+ // Record syntax per-field type control
31
+ const Pos = world.register_component({ x: "f64", y: "f64" });
18
32
 
19
- // Define components as field-name arrays
20
- const Pos = world.register_component(["x", "y"] as const);
33
+ // Array shorthand uniform type, defaults to "f64"
21
34
  const Vel = world.register_component(["vx", "vy"] as const);
22
35
 
23
36
  // Tags have no fields
24
37
  const IsEnemy = world.register_tag();
25
38
 
39
+ // Resources are global singletons
40
+ const Time = world.register_resource(["delta", "elapsed"] as const, {
41
+ delta: 0,
42
+ elapsed: 0,
43
+ });
44
+
45
+ // Events are fire-and-forget messages
46
+ const Damage = world.register_event(["target", "amount"] as const);
47
+
26
48
  // Create entities and attach components
27
49
  const e = world.create_entity();
28
50
  world.add_component(e, Pos, { x: 0, y: 0 });
@@ -32,12 +54,17 @@ world.add_component(e, IsEnemy);
32
54
  // Register a system with a typed query
33
55
  const moveSys = world.register_system(
34
56
  (q, _ctx, dt) => {
35
- q.each((pos, vel, n) => {
57
+ for (const arch of q) {
58
+ const px = arch.get_column(Pos, "x");
59
+ const py = arch.get_column(Pos, "y");
60
+ const vx = arch.get_column(Vel, "vx");
61
+ const vy = arch.get_column(Vel, "vy");
62
+ const n = arch.entity_count;
36
63
  for (let i = 0; i < n; i++) {
37
- pos.x[i] += vel.vx[i] * dt;
38
- pos.y[i] += vel.vy[i] * dt;
64
+ px[i] += vx[i] * dt;
65
+ py[i] += vy[i] * dt;
39
66
  }
40
- });
67
+ }
41
68
  },
42
69
  (qb) => qb.every(Pos, Vel),
43
70
  );
@@ -50,125 +77,105 @@ world.startup();
50
77
 
51
78
  // Game loop
52
79
  function frame(dt: number) {
80
+ world.set_resource(Time, { delta: dt, elapsed: performance.now() / 1000 });
53
81
  world.update(dt);
54
82
  }
55
83
  ```
56
84
 
57
- ## Components
85
+ ## World Options
58
86
 
59
- Components are defined as readonly string arrays of field names. All field values are `number`.
87
+ `ECS` accepts an optional configuration object:
60
88
 
61
89
  ```ts
62
- const Position = world.register_component(["x", "y"] as const);
63
- const Health = world.register_component(["current", "max"] as const);
64
- const IsEnemy = world.register_tag(); // no fields
90
+ const world = new ECS({
91
+ initial_capacity: 4096, // pre-allocate archetype storage (default: 1024, grows automatically)
92
+ fixed_timestep: 1 / 50, // fixed update interval in seconds (default: 1/60)
93
+ max_fixed_steps: 4, // cap fixed updates per frame to prevent spiral of death (default: 5)
94
+ });
65
95
  ```
66
96
 
67
- The `as const` is required so TypeScript infers the literal tuple type `["x", "y"]` instead of `string[]`. This enables type-safe field access throughout the API.
97
+ | Option | Type | Default | Description |
98
+ |---|---|---|---|
99
+ | `initial_capacity` | `number` | `1024` | Starting size for every archetype's backing typed arrays (entity IDs and all SoA component columns). Arrays double when exceeded — set this close to your expected entity count per archetype to avoid early re-allocations. |
100
+ | `fixed_timestep` | `number` | `1/60` | Interval (seconds) for `FIXED_UPDATE` systems. |
101
+ | `max_fixed_steps` | `number` | `5` | Maximum `FIXED_UPDATE` iterations per frame. |
102
+
103
+ ## Components
68
104
 
69
- ### Adding components
105
+ 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.
70
106
 
71
107
  ```ts
72
- const e = world.create_entity();
108
+ // Record syntax — per-field type control
109
+ const Position = world.register_component({ x: "f64", y: "f64" });
110
+ const Health = world.register_component({ current: "i32", max: "i32" });
73
111
 
74
- // Data components require a values object
75
- world.add_component(e, Position, { x: 10, y: 20 });
76
- world.add_component(e, Health, { current: 100, max: 100 });
112
+ // Array shorthand all fields default to "f64"
113
+ const Vel = world.register_component(["vx", "vy"] as const);
77
114
 
78
- // Tags require no values
79
- world.add_component(e, IsEnemy);
115
+ // Tags no fields
116
+ const IsEnemy = world.register_tag();
117
+ ```
118
+
119
+ Supported typed array tags: `"f32"`, `"f64"`, `"i8"`, `"i16"`, `"i32"`, `"u8"`, `"u16"`, `"u32"`.
120
+
121
+ Add components individually or via `add_components` (single archetype transition):
80
122
 
81
- // Add multiple at once (single archetype transition)
123
+ ```ts
124
+ world.add_component(e, Position, { x: 10, y: 20 });
82
125
  world.add_components(e, [
83
126
  { def: Position, values: { x: 10, y: 20 } },
84
127
  { def: Health, values: { current: 100, max: 100 } },
85
128
  ]);
86
129
  ```
87
130
 
88
- ### Removing and checking components
89
-
90
- ```ts
91
- world.remove_component(e, Health);
92
- world.has_component(e, IsEnemy); // true
93
- ```
131
+ See [docs/api/components.md](docs/api/components.md) for full API.
94
132
 
95
133
  ## Queries
96
134
 
97
- Queries are live views over all archetypes matching a component mask. They update automatically as new archetypes are created.
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.
135
+ Queries are live views over all archetypes matching a component mask.
102
136
 
103
137
  ```ts
104
138
  const q = world.query(Position, Velocity);
105
139
 
106
- q.each((pos, vel, n) => {
107
- // pos.x, pos.y, vel.vx, vel.vy are all number[]
108
- // n is the entity count in this archetype
109
- for (let i = 0; i < n; i++) {
110
- pos.x[i] += vel.vx[i];
111
- pos.y[i] += vel.vy[i];
112
- }
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);
127
-
128
- // Combine
129
- const targets = world.query(Position).and(Health).not(Shield).or(IsEnemy, IsBoss);
130
- ```
131
-
132
- ### Manual iteration
133
-
134
- For dynamic component access, iterate archetypes directly:
135
-
136
- ```ts
137
- for (const arch of world.query(Position)) {
140
+ // Iterate non-empty archetypes, access SoA columns, write the inner loop
141
+ for (const arch of q) {
138
142
  const px = arch.get_column(Position, "x");
139
143
  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;
144
+ const vx = arch.get_column(Velocity, "vx");
145
+ const vy = arch.get_column(Velocity, "vy");
146
+ const n = arch.entity_count;
147
+ for (let i = 0; i < n; i++) {
148
+ px[i] += vx[i];
149
+ py[i] += vy[i];
145
150
  }
146
151
  }
152
+
153
+ // Chaining
154
+ const targets = world
155
+ .query(Position)
156
+ .and(Health)
157
+ .not(Shield)
158
+ .any_of(IsEnemy, IsBoss);
147
159
  ```
148
160
 
161
+ See [docs/api/queries.md](docs/api/queries.md) for full API.
162
+
149
163
  ## Systems
150
164
 
151
165
  Systems are plain functions registered with a query and scheduled into lifecycle phases.
152
166
 
153
- ### Registration
154
-
155
- Two registration styles:
156
-
157
167
  ```ts
158
- // Style 1: Function + query builder (typed, preferred)
168
+ // With a typed query
159
169
  const moveSys = world.register_system(
160
170
  (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
- });
171
+ for (const arch of q) {
172
+ /* ... */
173
+ }
167
174
  },
168
175
  (qb) => qb.every(Pos, Vel),
169
176
  );
170
177
 
171
- // Style 2: Config object (for systems that don't need a query)
178
+ // Without a query
172
179
  const logSys = world.register_system({
173
180
  fn(ctx, dt) {
174
181
  console.log("frame", dt);
@@ -176,119 +183,118 @@ const logSys = world.register_system({
176
183
  });
177
184
  ```
178
185
 
179
- ### Deferred operations
180
-
181
- Inside systems, use `ctx` (SystemContext) for structural changes. These are buffered and applied between phases.
186
+ Inside systems, use `ctx` for deferred structural changes and per-entity access:
182
187
 
183
188
  ```ts
184
- const spawnSys = world.register_system({
185
- fn(ctx, _dt) {
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
- },
192
- });
189
+ const e = ctx.create_entity();
190
+ ctx.add_component(e, Pos, { x: 0, y: 0 });
191
+ ctx.destroy_entity(someEntity);
192
+ ctx.remove_component(entity, Health);
193
193
  ```
194
194
 
195
- ### Per-entity field access
195
+ See [docs/api/systems.md](docs/api/systems.md) for full API.
196
196
 
197
- For reading/writing individual entity fields (not batch):
197
+ ## Resources
198
+
199
+ Resources are typed global singletons — time, input state, camera config.
198
200
 
199
201
  ```ts
200
- const damageSys = world.register_system({
201
- fn(ctx, _dt) {
202
- const hp = ctx.get_field(Health, targetEntity, "current");
203
- ctx.set_field(Health, targetEntity, "current", hp - 10);
204
- },
202
+ const Time = world.register_resource(["delta", "elapsed"] as const, {
203
+ delta: 0,
204
+ elapsed: 0,
205
205
  });
206
+
207
+ // Write
208
+ world.set_resource(Time, { delta: dt, elapsed: total });
209
+
210
+ // Read — scalar values, not arrays
211
+ const time = world.resource(Time);
212
+ time.delta; // number
213
+ time.elapsed; // number
206
214
  ```
207
215
 
208
- ## Schedule
216
+ See [docs/api/resources.md](docs/api/resources.md) for full API.
217
+
218
+ ## Events & Signals
209
219
 
210
- Six lifecycle phases, executed in order:
220
+ Events are fire-and-forget SoA channels, auto-cleared each frame.
211
221
 
212
- | Phase | When | Use case |
213
- |---|---|---|
214
- | `PRE_STARTUP` | Once, before startup | Resource loading, allocation |
215
- | `STARTUP` | Once | Initial entity spawning |
216
- | `POST_STARTUP` | Once, after startup | Validation, index building |
217
- | `PRE_UPDATE` | Every frame, first | Input handling, time management |
218
- | `UPDATE` | Every frame | Game logic, physics, AI |
219
- | `POST_UPDATE` | Every frame, last | Rendering, cleanup |
222
+ ```ts
223
+ // Data events carry fields
224
+ const Damage = world.register_event(["target", "amount"] as const);
225
+ ctx.emit(Damage, { target: entityId, amount: 50 });
226
+
227
+ const dmg = ctx.read(Damage);
228
+ for (let i = 0; i < dmg.length; i++) {
229
+ dmg.target[i]; // number
230
+ dmg.amount[i]; // number
231
+ }
232
+
233
+ // Signals carry no data — just a count
234
+ const OnReset = world.register_signal();
235
+ ctx.emit(OnReset);
236
+ if (ctx.read(OnReset).length > 0) {
237
+ /* fired */
238
+ }
239
+ ```
240
+
241
+ See [docs/api/events.md](docs/api/events.md) for full API.
220
242
 
221
- Deferred changes are flushed automatically after each phase.
243
+ ## Refs
244
+
245
+ Refs provide cached single-entity field access — faster than `get_field`/`set_field` for repeated access.
222
246
 
223
247
  ```ts
224
- world.add_systems(SCHEDULE.UPDATE, moveSys, physicsSys);
225
- world.add_systems(SCHEDULE.POST_UPDATE, renderSys);
248
+ const pos = ctx.ref(Pos, entity);
249
+ const vel = ctx.ref(Vel, entity);
250
+ pos.x += vel.vx * dt;
251
+ pos.y += vel.vy * dt;
226
252
  ```
227
253
 
228
- ### Ordering constraints
254
+ See [docs/api/refs.md](docs/api/refs.md) for full API.
255
+
256
+ ## Schedule
257
+
258
+ Seven lifecycle phases, executed in order:
259
+
260
+ | Phase | When | Use case |
261
+ | -------------- | --------------------- | ----------------------- |
262
+ | `PRE_STARTUP` | Once, before startup | Resource loading |
263
+ | `STARTUP` | Once | Initial entity spawning |
264
+ | `POST_STARTUP` | Once, after startup | Validation |
265
+ | `FIXED_UPDATE` | Every tick (fixed dt) | Physics, simulation |
266
+ | `PRE_UPDATE` | Every frame, first | Input handling |
267
+ | `UPDATE` | Every frame | Game logic |
268
+ | `POST_UPDATE` | Every frame, last | Rendering, cleanup |
229
269
 
230
270
  ```ts
271
+ world.add_systems(SCHEDULE.UPDATE, moveSys, physicsSys);
272
+ world.add_systems(SCHEDULE.POST_UPDATE, renderSys);
273
+
274
+ // Ordering constraints
231
275
  world.add_systems(SCHEDULE.UPDATE, moveSys, {
232
276
  system: physicsSys,
233
277
  ordering: { after: [moveSys] },
234
278
  });
235
-
236
- world.add_systems(SCHEDULE.UPDATE, {
237
- system: aiSys,
238
- ordering: { before: [moveSys] },
239
- });
240
279
  ```
241
280
 
242
- Systems with ordering constraints are topologically sorted. Systems with no constraints run in registration order.
281
+ See [docs/api/schedule.md](docs/api/schedule.md) for full API.
243
282
 
244
283
  ## Entity lifecycle
245
284
 
246
285
  ```ts
247
286
  const e = world.create_entity();
248
-
249
287
  world.is_alive(e); // true
250
-
251
- world.destroy_entity(e); // deferred
252
- world.flush(); // apply now
253
-
288
+ world.destroy_entity_deferred(e); // deferred
289
+ world.flush();
254
290
  world.is_alive(e); // false
255
291
  ```
256
292
 
257
- Entity IDs are generational: destroying an entity increments its slot's generation, so stale IDs are safely detected as dead.
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
- ```
293
+ Entity IDs are generational: destroying an entity increments its slot's generation, so stale IDs are detected as dead.
280
294
 
281
295
  ## Dev / Prod modes
282
296
 
283
- The codebase uses `__DEV__` compile-time flags. In development builds, you get:
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.
297
+ `__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
298
 
293
299
  ## Development
294
300
 
@@ -300,6 +306,8 @@ pnpm build # vite library build
300
306
  pnpm tsc --noEmit # type check
301
307
  ```
302
308
 
303
- ## Architecture
309
+ ## Guides
304
310
 
305
- See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for a thorough explanation of the internal design.
311
+ - [Getting Started](docs/GETTING_STARTED.md) step-by-step tutorial
312
+ - [Best Practices](docs/BEST_PRACTICES.md) — performance tips and patterns
313
+ - [Architecture](docs/ARCHITECTURE.md) — internal design details
@@ -1,41 +1,52 @@
1
- import { Brand, BitSet } from './type_primitives';
2
- import { ComponentID, ComponentFields, ComponentDef, ColumnsForSchema } from './component';
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: number[][];
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
- entity_ids: EntityID[];
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
- private _column_ids;
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
- get entity_list(): readonly EntityID[];
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<F extends ComponentFields, Field extends F[number]>(def: ComponentDef<F>, field: Field): number[];
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 -1 if no swap was needed.
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
@@ -1 +1 @@
1
- {"version":3,"file":"archetype.d.ts","sourceRoot":"","sources":["../src/archetype.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"archetype.d.ts","sourceRoot":"","sources":["../src/archetype.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;KAmBK;AAEL,OAAO,EACL,KAAK,EAGL,kBAAkB,EAGlB,KAAK,aAAa,EAClB,KAAK,aAAa,EACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EACV,WAAW,EACX,YAAY,EACZ,eAAe,EACf,eAAe,EAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAoB,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAG3D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAE9C,MAAM,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;AAExD,eAAO,MAAM,eAAe,UAAW,MAAM,gBAK1C,CAAC;AAEJ,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,WAAW,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3B,wEAAwE;IACxE,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;IAC3B,8EAA8E;IAC9E,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,WAAW,CAAC;IAC1B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,WAAW,EAAE,aAAa,EAAE,CAAC;CAC9B;AAED,UAAU,oBAAoB;IAC5B,MAAM,EAAE,qBAAqB,CAAC;IAC9B,OAAO,EAAE,kBAAkB,CAAC,aAAa,CAAC,EAAE,CAAC;CAC9C;AAED,qBAAa,SAAS;IACpB,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAE9B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsB;IAC3C,MAAM,EAAE,MAAM,CAAK;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuB;IAI7C,QAAQ,CAAC,aAAa,EAAE,kBAAkB,CAAC,aAAa,CAAC,EAAE,CAAM;IAEjE,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,CAAM;IAEpC,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,CAAM;IAErC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAgC;IAE7D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkB;IAG/C,QAAQ,CAAC,aAAa,EAAE,CAAC,oBAAoB,GAAG,SAAS,CAAC,EAAE,CAAM;IAElE,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,CAAM;gBAGlC,EAAE,EAAE,WAAW,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,qBAAqB,EAAE,EACjC,gBAAgB,GAAE,MAAgC;IAgCpD,IAAW,YAAY,IAAI,MAAM,CAEhC;IAED,qEAAqE;IACrE,IAAW,UAAU,IAAI,WAAW,CAEnC;IAED,IAAW,WAAW,IAAI,WAAW,CAEpC;IAEM,aAAa,CAAC,EAAE,EAAE,WAAW,GAAG,OAAO;IAIvC,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAIzC,0EAA0E;IACnE,UAAU,CAAC,CAAC,SAAS,eAAe,EAAE,CAAC,SAAS,MAAM,GAAG,MAAM,CAAC,EACrE,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC,EACpB,KAAK,EAAE,CAAC,GACP,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAsBjB,YAAY,CACjB,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,WAAW,EACzB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,IAAI;IAWP,0FAA0F;IACnF,uBAAuB,CAC5B,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,WAAW,EACzB,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,GACxB,IAAI;IAUA,UAAU,CACf,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,WAAW,EACzB,KAAK,EAAE,MAAM,GACZ,MAAM;IAST,uFAAuF;IAChF,gBAAgB,CACrB,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd,IAAI;IAkBP;;;OAGG;IACI,UAAU,CAAC,SAAS,EAAE,QAAQ,GAAG,MAAM;IAW9C;;;;OAIG;IACI,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAuBzC,uEAAuE;IAChE,cAAc,CAAC,SAAS,EAAE,QAAQ,GAAG,MAAM;IAOlD,4EAA4E;IACrE,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAe7C;;;;;OAKG;IACI,gBAAgB,CACrB,GAAG,EAAE,SAAS,EACd,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,QAAQ,EACnB,cAAc,EAAE,UAAU,GACzB,IAAI;IAwBP;;;OAGG;IACI,oBAAoB,CACzB,GAAG,EAAE,SAAS,EACd,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,QAAQ,GAClB,IAAI;IAWP;;;;OAIG;IACI,kBAAkB,CACvB,GAAG,EAAE,SAAS,EACd,cAAc,EAAE,UAAU,GACzB,MAAM;IAiCF,QAAQ,CAAC,YAAY,EAAE,WAAW,GAAG,aAAa,GAAG,SAAS;IAI9D,QAAQ,CAAC,YAAY,EAAE,WAAW,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI;CAGtE;AAED,iGAAiG;AACjG,eAAO,MAAM,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,CAAgB,CAAC;AAE3D;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,GAAG,UAAU,CA6B/E"}
@@ -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
- export type ComponentFields = readonly string[];
5
- /** Maps component fields to their value object: { x: number, y: number }. */
6
- export type FieldValues<F extends ComponentFields> = {
7
- readonly [K in F[number]]: number;
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 component fields to column arrays: { x: number[], y: number[] }. */
10
- export type ColumnsForSchema<F extends ComponentFields> = {
11
- readonly [K in F[number]]: number[];
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<F extends ComponentFields = ComponentFields> = ComponentID & {
15
- readonly [__schema]: F;
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
@@ -1 +1 @@
1
- {"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;KAkBK;AAEL,OAAO,EACL,KAAK,EAGN,MAAM,iBAAiB,CAAC;AAEzB,MAAM,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;AACxD,eAAO,MAAM,eAAe,UAAW,MAAM,gBAK1C,CAAC;AAEJ,MAAM,MAAM,eAAe,GAAG,SAAS,MAAM,EAAE,CAAC;AAEhD,6EAA6E;AAC7E,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,eAAe,IAAI;IACnD,QAAQ,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,MAAM;CAClC,CAAC;AAEF,4EAA4E;AAC5E,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,eAAe,IAAI;IACxD,QAAQ,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE;CACpC,CAAC;AAKF,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"}
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"}