@oasys/oecs 0.2.0 → 0.3.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/LICENSE +1 -1
- package/README.md +183 -155
- package/dist/archetype.d.ts +12 -9
- package/dist/archetype.d.ts.map +1 -1
- package/dist/component.d.ts +10 -0
- package/dist/component.d.ts.map +1 -1
- package/dist/ecs.d.ts +14 -14
- package/dist/ecs.d.ts.map +1 -1
- package/dist/entity.d.ts.map +1 -1
- package/dist/event.d.ts +6 -0
- package/dist/event.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +8 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +621 -459
- package/dist/query.d.ts +36 -16
- package/dist/query.d.ts.map +1 -1
- package/dist/ref.d.ts +4 -0
- package/dist/ref.d.ts.map +1 -1
- package/dist/resource.d.ts +21 -21
- package/dist/resource.d.ts.map +1 -1
- package/dist/schedule.d.ts +8 -6
- package/dist/schedule.d.ts.map +1 -1
- package/dist/store.d.ts +13 -6
- package/dist/store.d.ts.map +1 -1
- 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/binary_heap/binary_heap.d.ts +33 -0
- package/dist/type_primitives/binary_heap/binary_heap.d.ts.map +1 -0
- package/dist/type_primitives/bitset/bitset.d.ts +5 -0
- package/dist/type_primitives/bitset/bitset.d.ts.map +1 -1
- package/dist/type_primitives/error.d.ts +2 -1
- package/dist/type_primitives/error.d.ts.map +1 -1
- package/dist/type_primitives/index.d.ts +2 -0
- package/dist/type_primitives/index.d.ts.map +1 -1
- package/dist/type_primitives/topological_sort/topological_sort.d.ts +25 -0
- package/dist/type_primitives/topological_sort/topological_sort.d.ts.map +1 -0
- package/dist/type_primitives/typed_arrays/typed_arrays.d.ts +2 -0
- package/dist/type_primitives/typed_arrays/typed_arrays.d.ts.map +1 -1
- package/dist/utils/arrays.d.ts.map +1 -1
- package/dist/utils/constants.d.ts +0 -8
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/error.d.ts +4 -1
- package/dist/utils/error.d.ts.map +1 -1
- package/package.json +2 -2
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,272 +1,300 @@
|
|
|
1
|
-
#
|
|
1
|
+
# oecs
|
|
2
2
|
|
|
3
|
-
A fast, minimal archetype-based Entity Component System
|
|
3
|
+
A fast, minimal, archetype-based Entity Component System for TypeScript.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- **
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Archetype-based SoA storage.** Entities sharing a component set share contiguous typed-array columns — cache-friendly loops, no per-entity object allocation.
|
|
8
|
+
- **Phantom-typed components.** `ComponentDef<{ x: "f64", y: "f64" }>` is a branded integer at runtime and a fully-typed schema at compile time. Misspelled fields are compile errors.
|
|
9
|
+
- **Callback iteration.** `query.for_each(arch => ...)` yields non-empty archetypes; you write the row loop over typed-array columns.
|
|
10
|
+
- **Tick-based change detection.** Each `(archetype, component)` tracks a change tick. `query.changed(Pos).for_each(...)` visits only archetypes mutated since the system's last run.
|
|
11
|
+
- **Key-based events and resources.** `event_key<F>` / `resource_key<T>` create module-scope symbol handles carrying their schema as a phantom — import the key anywhere.
|
|
12
|
+
- **Cached single-entity refs.** `ctx.ref` / `ctx.ref_mut` give ergonomic `pos.x += vel.vx * dt` that compiles to a direct typed-array index op.
|
|
13
|
+
- **Deferred structural changes.** `ctx.add_component` / `ctx.remove_component` / `ctx.destroy_entity` buffer until the schedule flushes between phases, so iterators stay valid.
|
|
14
|
+
- **Topological scheduler.** Per-phase Kahn's-algorithm sort over a binary heap, with insertion order as a deterministic tiebreaker.
|
|
15
|
+
- **Fixed timestep.** Accumulator loop with configurable `fixed_timestep` and spiral-of-death protection.
|
|
16
|
+
- **Reusable primitives.** `BitSet`, `SparseSet`, `SparseMap`, `GrowableTypedArray`, `BinaryHeap`, `topological_sort` all exported.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pnpm add @oasys/oecs
|
|
22
|
+
```
|
|
14
23
|
|
|
15
24
|
## Quick start
|
|
16
25
|
|
|
17
26
|
```ts
|
|
18
|
-
import { ECS, SCHEDULE } from "@oasys/oecs
|
|
27
|
+
import { ECS, SCHEDULE, event_key, resource_key } from "@oasys/oecs";
|
|
28
|
+
|
|
29
|
+
// Keys — module scope, phantom-typed
|
|
30
|
+
const Time = resource_key<{ delta: number; elapsed: number }>("Time");
|
|
31
|
+
const DamageEvent = event_key<readonly ["target", "amount"]>("Damage");
|
|
19
32
|
|
|
20
33
|
const world = new ECS();
|
|
21
34
|
|
|
22
|
-
//
|
|
35
|
+
// Components
|
|
23
36
|
const Pos = world.register_component({ x: "f64", y: "f64" });
|
|
24
|
-
|
|
25
|
-
// Array shorthand — uniform type, defaults to "f64"
|
|
26
37
|
const Vel = world.register_component(["vx", "vy"] as const);
|
|
27
38
|
|
|
28
|
-
//
|
|
29
|
-
|
|
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
|
+
// Resources & events
|
|
40
|
+
world.register_resource(Time, { delta: 0, elapsed: 0 });
|
|
41
|
+
world.register_event(DamageEvent, ["target", "amount"] as const);
|
|
39
42
|
|
|
40
|
-
//
|
|
43
|
+
// Entities
|
|
41
44
|
const e = world.create_entity();
|
|
42
|
-
world.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
world.add_components(e, [
|
|
46
|
+
{ def: Pos, values: { x: 0, y: 0 } },
|
|
47
|
+
{ def: Vel, values: { vx: 100, vy: 50 } },
|
|
48
|
+
]);
|
|
45
49
|
|
|
46
|
-
//
|
|
50
|
+
// System — query resolved once at registration
|
|
47
51
|
const moveSys = world.register_system(
|
|
48
|
-
(q,
|
|
49
|
-
|
|
50
|
-
const px = arch.
|
|
51
|
-
const py = arch.
|
|
52
|
+
(q, ctx, dt) => {
|
|
53
|
+
q.for_each((arch) => {
|
|
54
|
+
const px = arch.get_column_mut(Pos, "x", ctx.world_tick);
|
|
55
|
+
const py = arch.get_column_mut(Pos, "y", ctx.world_tick);
|
|
52
56
|
const vx = arch.get_column(Vel, "vx");
|
|
53
57
|
const vy = arch.get_column(Vel, "vy");
|
|
54
|
-
|
|
55
|
-
for (let i = 0; i < n; i++) {
|
|
58
|
+
for (let i = 0; i < arch.entity_count; i++) {
|
|
56
59
|
px[i] += vx[i] * dt;
|
|
57
60
|
py[i] += vy[i] * dt;
|
|
58
61
|
}
|
|
59
|
-
}
|
|
62
|
+
});
|
|
60
63
|
},
|
|
61
64
|
(qb) => qb.every(Pos, Vel),
|
|
62
65
|
);
|
|
63
66
|
|
|
64
|
-
// Schedule the system
|
|
65
67
|
world.add_systems(SCHEDULE.UPDATE, moveSys);
|
|
66
|
-
|
|
67
|
-
// Initialize
|
|
68
68
|
world.startup();
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
function frame(
|
|
72
|
-
|
|
70
|
+
let last = performance.now();
|
|
71
|
+
function frame() {
|
|
72
|
+
const now = performance.now();
|
|
73
|
+
const dt = (now - last) / 1000;
|
|
74
|
+
last = now;
|
|
75
|
+
const t = world.resource(Time);
|
|
76
|
+
world.set_resource(Time, { delta: dt, elapsed: t.elapsed + dt });
|
|
73
77
|
world.update(dt);
|
|
78
|
+
requestAnimationFrame(frame);
|
|
74
79
|
}
|
|
80
|
+
requestAnimationFrame(frame);
|
|
75
81
|
```
|
|
76
82
|
|
|
77
|
-
##
|
|
78
|
-
|
|
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.
|
|
83
|
+
## World options
|
|
80
84
|
|
|
81
85
|
```ts
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const Vel = world.register_component(["vx", "vy"] as const);
|
|
88
|
-
|
|
89
|
-
// Tags — no fields
|
|
90
|
-
const IsEnemy = world.register_tag();
|
|
86
|
+
const world = new ECS({
|
|
87
|
+
initial_capacity: 4096,
|
|
88
|
+
fixed_timestep: 1 / 50,
|
|
89
|
+
max_fixed_steps: 4,
|
|
90
|
+
});
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
| Option | Type | Default | Description |
|
|
94
|
+
| ------------------ | -------- | ------- | ----------- |
|
|
95
|
+
| `initial_capacity` | `number` | `1024` | Starting size of each archetype's entity-ID and column buffers. Buffers double on overflow; pick close to your expected per-archetype entity count to avoid early reallocations. |
|
|
96
|
+
| `fixed_timestep` | `number` | `1/60` | Interval (seconds) at which `SCHEDULE.FIXED_UPDATE` systems run. |
|
|
97
|
+
| `max_fixed_steps` | `number` | `5` | Hard cap on fixed-update iterations per frame. Protects against spiral of death. |
|
|
98
|
+
|
|
99
|
+
## Components
|
|
94
100
|
|
|
95
|
-
|
|
101
|
+
Records give per-field type control; array shorthand defaults to `f64`. Tags have no fields.
|
|
96
102
|
|
|
97
103
|
```ts
|
|
98
|
-
world.
|
|
104
|
+
const Pos = world.register_component({ x: "f64", y: "f64" });
|
|
105
|
+
const Health = world.register_component({ current: "i32", max: "i32" });
|
|
106
|
+
const Vel = world.register_component(["vx", "vy"] as const);
|
|
107
|
+
const IsEnemy = world.register_tag();
|
|
108
|
+
|
|
99
109
|
world.add_components(e, [
|
|
100
|
-
{ def:
|
|
101
|
-
{ def:
|
|
110
|
+
{ def: Pos, values: { x: 0, y: 0 } },
|
|
111
|
+
{ def: Vel, values: { vx: 1, vy: 0 } },
|
|
112
|
+
{ def: IsEnemy },
|
|
102
113
|
]);
|
|
103
114
|
```
|
|
104
115
|
|
|
105
|
-
|
|
116
|
+
Supported tags: `f32`, `f64`, `i8`, `i16`, `i32`, `u8`, `u16`, `u32`.
|
|
117
|
+
|
|
118
|
+
See [docs/api/components.md](docs/api/components.md).
|
|
106
119
|
|
|
107
120
|
## Queries
|
|
108
121
|
|
|
109
|
-
|
|
122
|
+
Live, cached views over matching archetypes. Iterate with `for_each`.
|
|
110
123
|
|
|
111
124
|
```ts
|
|
112
|
-
const q = world.query(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const vy = arch.get_column(Velocity, "vy");
|
|
120
|
-
const n = arch.entity_count;
|
|
121
|
-
for (let i = 0; i < n; i++) {
|
|
122
|
-
px[i] += vx[i];
|
|
123
|
-
py[i] += vy[i];
|
|
124
|
-
}
|
|
125
|
-
}
|
|
125
|
+
const q = world.query(Pos, Vel);
|
|
126
|
+
|
|
127
|
+
q.for_each((arch) => {
|
|
128
|
+
const px = arch.get_column(Pos, "x");
|
|
129
|
+
const py = arch.get_column(Pos, "y");
|
|
130
|
+
for (let i = 0; i < arch.entity_count; i++) { /* ... */ }
|
|
131
|
+
});
|
|
126
132
|
|
|
127
|
-
// Chaining
|
|
128
|
-
const targets = world.query(
|
|
133
|
+
// Chaining returns new cached queries
|
|
134
|
+
const targets = world.query(Pos).and(Health).not(Shield).any_of(IsEnemy, IsBoss);
|
|
135
|
+
|
|
136
|
+
// Change detection — only archetypes whose Pos column changed since last run
|
|
137
|
+
q.changed(Pos).for_each((arch) => { /* ... */ });
|
|
129
138
|
```
|
|
130
139
|
|
|
131
|
-
See [docs/api/queries.md](docs/api/queries.md)
|
|
140
|
+
See [docs/api/queries.md](docs/api/queries.md) and [docs/api/change-detection.md](docs/api/change-detection.md).
|
|
132
141
|
|
|
133
142
|
## Systems
|
|
134
143
|
|
|
135
|
-
Systems are plain functions
|
|
144
|
+
Systems are plain functions. Three registration shapes all return a `SystemDescriptor`.
|
|
136
145
|
|
|
137
146
|
```ts
|
|
138
|
-
//
|
|
147
|
+
// Bare function
|
|
148
|
+
const logSys = world.register_system((ctx, dt) => { /* ... */ });
|
|
149
|
+
|
|
150
|
+
// Function + query builder (query resolved once at registration)
|
|
139
151
|
const moveSys = world.register_system(
|
|
140
|
-
(q, ctx, dt) => {
|
|
152
|
+
(q, ctx, dt) => { q.for_each((arch) => { /* ... */ }); },
|
|
141
153
|
(qb) => qb.every(Pos, Vel),
|
|
142
154
|
);
|
|
143
155
|
|
|
144
|
-
//
|
|
145
|
-
const
|
|
146
|
-
|
|
156
|
+
// Full config — lifecycle hooks, name
|
|
157
|
+
const spawnSys = world.register_system({
|
|
158
|
+
name: "spawn",
|
|
159
|
+
fn(ctx, dt) { /* every frame */ },
|
|
160
|
+
on_added(ctx) { /* once during world.startup() */ },
|
|
161
|
+
dispose() { /* during world.dispose() */ },
|
|
147
162
|
});
|
|
148
163
|
```
|
|
149
164
|
|
|
150
|
-
|
|
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
|
-
```
|
|
165
|
+
`SystemContext` exposes deferred structural ops, per-entity access, events, resources, and tick bookkeeping (`ctx.world_tick`, `ctx.last_run_tick`).
|
|
158
166
|
|
|
159
|
-
See [docs/api/systems.md](docs/api/systems.md)
|
|
167
|
+
See [docs/api/systems.md](docs/api/systems.md).
|
|
160
168
|
|
|
161
169
|
## Resources
|
|
162
170
|
|
|
163
|
-
|
|
171
|
+
Global singletons keyed by `ResourceKey<T>`. Values can be any type — objects, typed arrays, class instances.
|
|
164
172
|
|
|
165
173
|
```ts
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
});
|
|
174
|
+
import { resource_key } from "@oasys/oecs";
|
|
175
|
+
|
|
176
|
+
const Time = resource_key<{ delta: number; elapsed: number }>("Time");
|
|
177
|
+
const Assets = resource_key<Map<string, ImageBitmap>>("Assets");
|
|
169
178
|
|
|
170
|
-
|
|
171
|
-
world.
|
|
179
|
+
world.register_resource(Time, { delta: 0, elapsed: 0 });
|
|
180
|
+
world.register_resource(Assets, new Map());
|
|
172
181
|
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
time.delta; // number
|
|
176
|
-
time.elapsed; // number
|
|
182
|
+
const t = world.resource(Time); // typed as { delta, elapsed }
|
|
183
|
+
world.set_resource(Time, { delta: 0.016, elapsed: 0 }); // swap in a new value
|
|
177
184
|
```
|
|
178
185
|
|
|
179
|
-
See [docs/api/resources.md](docs/api/resources.md)
|
|
186
|
+
See [docs/api/resources.md](docs/api/resources.md).
|
|
180
187
|
|
|
181
|
-
## Events
|
|
188
|
+
## Events
|
|
182
189
|
|
|
183
|
-
|
|
190
|
+
Fire-and-forget SoA channels. Data events carry typed fields; signals carry only a count. Cleared at the end of each `world.update(dt)`.
|
|
184
191
|
|
|
185
192
|
```ts
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
193
|
+
import { event_key, signal_key } from "@oasys/oecs";
|
|
194
|
+
|
|
195
|
+
const DamageEvent = event_key<readonly ["target", "amount"]>("Damage");
|
|
196
|
+
const GameOver = signal_key("GameOver");
|
|
189
197
|
|
|
190
|
-
|
|
198
|
+
world.register_event(DamageEvent, ["target", "amount"] as const);
|
|
199
|
+
world.register_signal(GameOver);
|
|
200
|
+
|
|
201
|
+
ctx.emit(DamageEvent, { target: victimId, amount: 25 });
|
|
202
|
+
ctx.emit(GameOver);
|
|
203
|
+
|
|
204
|
+
const dmg = ctx.read(DamageEvent);
|
|
191
205
|
for (let i = 0; i < dmg.length; i++) {
|
|
192
|
-
dmg.target[i]; // number
|
|
193
|
-
dmg.amount[i]; // number
|
|
206
|
+
dmg.target[i]; dmg.amount[i]; // number columns
|
|
194
207
|
}
|
|
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 */ }
|
|
208
|
+
if (ctx.read(GameOver).length > 0) { /* fired */ }
|
|
200
209
|
```
|
|
201
210
|
|
|
202
|
-
See [docs/api/events.md](docs/api/events.md)
|
|
211
|
+
See [docs/api/events.md](docs/api/events.md).
|
|
203
212
|
|
|
204
213
|
## Refs
|
|
205
214
|
|
|
206
|
-
|
|
215
|
+
Cached single-entity handles — resolve archetype + row + column once, then read/write fields by name.
|
|
207
216
|
|
|
208
217
|
```ts
|
|
209
|
-
const pos = ctx.
|
|
210
|
-
const vel = ctx.ref(Vel, entity);
|
|
218
|
+
const pos = ctx.ref_mut(Pos, entity); // writable; bumps Pos change tick
|
|
219
|
+
const vel = ctx.ref(Vel, entity); // readonly
|
|
211
220
|
pos.x += vel.vx * dt;
|
|
212
221
|
pos.y += vel.vy * dt;
|
|
213
222
|
```
|
|
214
223
|
|
|
215
|
-
|
|
224
|
+
Prefer `ctx.ref` by default; reach for `ctx.ref_mut` at the point of mutation. Do not hold refs across archetype transitions or phase flushes.
|
|
225
|
+
|
|
226
|
+
See [docs/api/refs.md](docs/api/refs.md).
|
|
216
227
|
|
|
217
228
|
## Schedule
|
|
218
229
|
|
|
219
|
-
Seven
|
|
230
|
+
Seven phases run in a fixed order:
|
|
220
231
|
|
|
221
|
-
| Phase
|
|
222
|
-
|
|
223
|
-
| `PRE_STARTUP`
|
|
224
|
-
| `STARTUP`
|
|
225
|
-
| `POST_STARTUP` | Once, after
|
|
226
|
-
| `FIXED_UPDATE` |
|
|
227
|
-
| `PRE_UPDATE`
|
|
228
|
-
| `UPDATE`
|
|
229
|
-
| `POST_UPDATE`
|
|
232
|
+
| Phase | When | Typical use |
|
|
233
|
+
| -------------- | -------------------------------- | ----------------------- |
|
|
234
|
+
| `PRE_STARTUP` | Once, before `STARTUP` | Resource loading |
|
|
235
|
+
| `STARTUP` | Once | Initial entity spawning |
|
|
236
|
+
| `POST_STARTUP` | Once, after `STARTUP` | Validation |
|
|
237
|
+
| `FIXED_UPDATE` | Zero+ times per frame (fixed dt) | Physics, simulation |
|
|
238
|
+
| `PRE_UPDATE` | Every frame, first | Input, time |
|
|
239
|
+
| `UPDATE` | Every frame | Game logic, AI |
|
|
240
|
+
| `POST_UPDATE` | Every frame, last | Rendering, cleanup |
|
|
230
241
|
|
|
231
242
|
```ts
|
|
232
|
-
world.add_systems(SCHEDULE.UPDATE, moveSys,
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
// Ordering constraints
|
|
236
|
-
world.add_systems(SCHEDULE.UPDATE, moveSys, {
|
|
237
|
-
system: physicsSys,
|
|
238
|
-
ordering: { after: [moveSys] },
|
|
243
|
+
world.add_systems(SCHEDULE.UPDATE, moveSys, damageSys, {
|
|
244
|
+
system: deathSys,
|
|
245
|
+
ordering: { after: [damageSys] },
|
|
239
246
|
});
|
|
240
247
|
```
|
|
241
248
|
|
|
242
|
-
|
|
249
|
+
Within a phase, systems are topologically sorted by `before` / `after` constraints. `ctx.flush()` runs automatically between phases.
|
|
250
|
+
|
|
251
|
+
See [docs/api/schedule.md](docs/api/schedule.md).
|
|
243
252
|
|
|
244
253
|
## Entity lifecycle
|
|
245
254
|
|
|
246
255
|
```ts
|
|
247
256
|
const e = world.create_entity();
|
|
248
|
-
world.is_alive(e);
|
|
249
|
-
world.destroy_entity_deferred(e);
|
|
257
|
+
world.is_alive(e); // true
|
|
258
|
+
world.destroy_entity_deferred(e);
|
|
250
259
|
world.flush();
|
|
251
|
-
world.is_alive(e);
|
|
260
|
+
world.is_alive(e); // false
|
|
252
261
|
```
|
|
253
262
|
|
|
254
|
-
|
|
263
|
+
`EntityID` is a packed 31-bit integer (20-bit slot index, 11-bit generation). Destroying an entity bumps its slot's generation, so stale handles are detected as dead. Inside systems, use `ctx.create_entity()` (immediate) and `ctx.destroy_entity(e)` (deferred).
|
|
255
264
|
|
|
256
|
-
|
|
265
|
+
See [docs/api/entities.md](docs/api/entities.md).
|
|
257
266
|
|
|
258
|
-
|
|
267
|
+
## Dev vs Prod modes
|
|
268
|
+
|
|
269
|
+
A compile-time `__DEV__` flag gates runtime sanity checks: bounds checks, liveness checks, duplicate-system detection, and registration validation. These are tree-shaken out of production bundles by the Vite build. Scheduler cycle detection is always active and throws `ECS_ERROR.CIRCULAR_SYSTEM_DEPENDENCY` on the first offending run.
|
|
259
270
|
|
|
260
271
|
## Development
|
|
261
272
|
|
|
262
273
|
```bash
|
|
263
274
|
pnpm install
|
|
264
|
-
pnpm test
|
|
265
|
-
pnpm bench
|
|
266
|
-
pnpm build
|
|
267
|
-
pnpm tsc --noEmit
|
|
275
|
+
pnpm test # vitest
|
|
276
|
+
pnpm bench # vitest bench
|
|
277
|
+
pnpm build # vite library build
|
|
278
|
+
pnpm tsc --noEmit # type check
|
|
268
279
|
```
|
|
269
280
|
|
|
270
|
-
##
|
|
271
|
-
|
|
272
|
-
|
|
281
|
+
## Guides
|
|
282
|
+
|
|
283
|
+
- [Getting Started](docs/GETTING_STARTED.md) — step-by-step tutorial.
|
|
284
|
+
- [Best Practices](docs/BEST_PRACTICES.md) — component design, query patterns, pitfalls.
|
|
285
|
+
- [Architecture](docs/ARCHITECTURE.md) — data layout, flush model, cache invalidation.
|
|
286
|
+
- API reference:
|
|
287
|
+
[components](docs/api/components.md) ·
|
|
288
|
+
[entities](docs/api/entities.md) ·
|
|
289
|
+
[queries](docs/api/queries.md) ·
|
|
290
|
+
[systems](docs/api/systems.md) ·
|
|
291
|
+
[schedule](docs/api/schedule.md) ·
|
|
292
|
+
[resources](docs/api/resources.md) ·
|
|
293
|
+
[events](docs/api/events.md) ·
|
|
294
|
+
[refs](docs/api/refs.md) ·
|
|
295
|
+
[change detection](docs/api/change-detection.md) ·
|
|
296
|
+
[type primitives](docs/api/type-primitives.md)
|
|
297
|
+
|
|
298
|
+
## License
|
|
299
|
+
|
|
300
|
+
MIT
|
package/dist/archetype.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Brand, GrowableTypedArray, AnyTypedArray, TypedArrayTag, BitSet } from './type_primitives';
|
|
2
|
-
import { ComponentID, ComponentDef, ComponentSchema, TagToTypedArray } from './component';
|
|
2
|
+
import { ComponentID, ComponentDef, ComponentSchema, TagToTypedArray, ReadonlyColumn, ReadonlyUint32Array } 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;
|
|
@@ -35,21 +35,24 @@ export declare class Archetype {
|
|
|
35
35
|
private readonly _field_names;
|
|
36
36
|
readonly column_groups: (ArchetypeColumnGroup | undefined)[];
|
|
37
37
|
readonly _column_ids: number[];
|
|
38
|
+
readonly _changed_tick: number[];
|
|
38
39
|
constructor(id: ArchetypeID, mask: BitSet, layouts?: ArchetypeColumnLayout[], initial_capacity?: number);
|
|
39
40
|
get entity_count(): number;
|
|
40
41
|
/** Raw entity ID buffer. Valid data at indices 0..entity_count-1. */
|
|
41
|
-
get entity_ids():
|
|
42
|
+
get entity_ids(): ReadonlyUint32Array;
|
|
42
43
|
get entity_list(): Uint32Array;
|
|
43
44
|
has_component(id: ComponentID): boolean;
|
|
44
45
|
matches(required: BitSet): boolean;
|
|
45
|
-
/** Get a single field's column. Valid data: indices 0..entity_count-1. */
|
|
46
|
-
get_column<S extends ComponentSchema, K extends string & keyof S>(def: ComponentDef<S>, field: K):
|
|
47
|
-
|
|
46
|
+
/** Get a single field's column (read-only). Valid data: indices 0..entity_count-1. */
|
|
47
|
+
get_column<S extends ComponentSchema, K extends string & keyof S>(def: ComponentDef<S>, field: K): ReadonlyColumn;
|
|
48
|
+
/** Get a single field's column (mutable). Marks the component as changed at the given tick. */
|
|
49
|
+
get_column_mut<S extends ComponentSchema, K extends string & keyof S>(def: ComponentDef<S>, field: K, tick: number): TagToTypedArray[S[K]];
|
|
50
|
+
write_fields(row: number, component_id: ComponentID, values: Record<string, number>, tick: number): void;
|
|
48
51
|
/** 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
|
|
52
|
+
write_fields_positional(row: number, component_id: ComponentID, values: ArrayLike<number>, tick: number): void;
|
|
50
53
|
read_field(row: number, component_id: ComponentID, field: string): number;
|
|
51
54
|
/** Copy all shared component columns from source archetype at src_row into dst_row. */
|
|
52
|
-
copy_shared_from(source: Archetype, src_row: number, dst_row: number): void;
|
|
55
|
+
copy_shared_from(source: Archetype, src_row: number, dst_row: number, tick: number): void;
|
|
53
56
|
/**
|
|
54
57
|
* Add an entity. Pushes zeroes into all columns and returns the assigned row.
|
|
55
58
|
* Store is responsible for tracking entity_index → row.
|
|
@@ -71,7 +74,7 @@ export declare class Archetype {
|
|
|
71
74
|
* Uses a pre-computed transition map for branchless column copy.
|
|
72
75
|
* Writes dst_row to _move_result[0], swapped entity index to _move_result[1].
|
|
73
76
|
*/
|
|
74
|
-
move_entity_from(src: Archetype, src_row: number, entity_id: EntityID, transition_map: Int16Array): void;
|
|
77
|
+
move_entity_from(src: Archetype, src_row: number, entity_id: EntityID, transition_map: Int16Array, tick: number): void;
|
|
75
78
|
/**
|
|
76
79
|
* Move an entity from src into this archetype (tag-only: no columns to copy).
|
|
77
80
|
* Writes dst_row to _move_result[0], swapped entity index to _move_result[1].
|
|
@@ -82,7 +85,7 @@ export declare class Archetype {
|
|
|
82
85
|
* Much faster than per-entity move_entity_from when the entire source is moving.
|
|
83
86
|
* After this call, src is empty. Returns the starting dst_row for the batch.
|
|
84
87
|
*/
|
|
85
|
-
bulk_move_all_from(src: Archetype, transition_map: Int16Array): number;
|
|
88
|
+
bulk_move_all_from(src: Archetype, transition_map: Int16Array, tick: number): number;
|
|
86
89
|
get_edge(component_id: ComponentID): ArchetypeEdge | undefined;
|
|
87
90
|
set_edge(component_id: ComponentID, edge: ArchetypeEdge): void;
|
|
88
91
|
}
|
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,EAGL,kBAAkB,EAGlB,KAAK,aAAa,EAClB,KAAK,aAAa,EACnB,MAAM,
|
|
1
|
+
{"version":3,"file":"archetype.d.ts","sourceRoot":"","sources":["../src/archetype.ts"],"names":[],"mappings":"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"}
|
package/dist/component.d.ts
CHANGED
|
@@ -31,5 +31,15 @@ declare const __schema: unique symbol;
|
|
|
31
31
|
export type ComponentDef<S extends ComponentSchema = ComponentSchema> = ComponentID & {
|
|
32
32
|
readonly [__schema]: S;
|
|
33
33
|
};
|
|
34
|
+
/** Compile-time readonly view of a typed array column. Prevents index writes. */
|
|
35
|
+
export interface ReadonlyColumn {
|
|
36
|
+
readonly [index: number]: number;
|
|
37
|
+
readonly length: number;
|
|
38
|
+
}
|
|
39
|
+
/** Compile-time readonly view of a Uint32Array. Prevents index writes. */
|
|
40
|
+
export interface ReadonlyUint32Array {
|
|
41
|
+
readonly [index: number]: number;
|
|
42
|
+
readonly length: number;
|
|
43
|
+
}
|
|
34
44
|
export {};
|
|
35
45
|
//# 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;;;;;;;;;;;;;;;;;;;;;;;KAuBK;AAEL,OAAO,EACL,KAAK,EAGL,KAAK,aAAa,EACnB,MAAM,
|
|
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,mBAAmB,CAAC;AAE3B,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,IAAI,WAAW,GAAG;IACpF,QAAQ,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;CACxB,CAAC;AAEF,iFAAiF;AACjF,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,0EAA0E;AAC1E,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB"}
|
package/dist/ecs.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ import { Archetype } from './archetype';
|
|
|
3
3
|
import { SystemContext, Query, QueryBuilder, QueryResolver } from './query';
|
|
4
4
|
import { EntityID } from './entity';
|
|
5
5
|
import { ComponentDef, ComponentSchema, ComponentFields, FieldValues } from './component';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { EventKey, EventReader } from './event';
|
|
7
|
+
import { ResourceKey } from './resource';
|
|
8
8
|
import { SystemFn, SystemConfig, SystemDescriptor } from './system';
|
|
9
9
|
import { BitSet, TypedArrayTag } from './type_primitives';
|
|
10
10
|
export interface WorldOptions {
|
|
@@ -18,6 +18,7 @@ export declare class ECS implements QueryResolver {
|
|
|
18
18
|
private readonly ctx;
|
|
19
19
|
private readonly systems;
|
|
20
20
|
private next_system_id;
|
|
21
|
+
private _tick;
|
|
21
22
|
private _fixed_timestep;
|
|
22
23
|
private _accumulator;
|
|
23
24
|
private _max_fixed_steps;
|
|
@@ -32,15 +33,12 @@ export declare class ECS implements QueryResolver {
|
|
|
32
33
|
readonly [K in F[number]]: T;
|
|
33
34
|
}>;
|
|
34
35
|
register_tag(): ComponentDef<Record<string, never>>;
|
|
35
|
-
register_event<F extends readonly string[]>(fields: F):
|
|
36
|
-
register_signal(
|
|
37
|
-
register_resource<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
set_resource<F extends ComponentFields>(def: ResourceDef<F>, values: {
|
|
42
|
-
readonly [K in F[number]]: number;
|
|
43
|
-
}): void;
|
|
36
|
+
register_event<F extends readonly string[]>(key: EventKey<F>, fields: F): void;
|
|
37
|
+
register_signal(key: EventKey<readonly []>): void;
|
|
38
|
+
register_resource<T>(key: ResourceKey<T>, value: T): void;
|
|
39
|
+
resource<T>(key: ResourceKey<T>): T;
|
|
40
|
+
set_resource<T>(key: ResourceKey<T>, value: T): void;
|
|
41
|
+
has_resource<T>(key: ResourceKey<T>): boolean;
|
|
44
42
|
create_entity(): EntityID;
|
|
45
43
|
destroy_entity_deferred(id: EntityID): void;
|
|
46
44
|
is_alive(id: EntityID): boolean;
|
|
@@ -67,11 +65,13 @@ export declare class ECS implements QueryResolver {
|
|
|
67
65
|
batch_remove_component(src_arch: Archetype, def: ComponentDef): void;
|
|
68
66
|
get_field<S extends ComponentSchema>(entity_id: EntityID, def: ComponentDef<S>, field: string & keyof S): number;
|
|
69
67
|
set_field<S extends ComponentSchema>(entity_id: EntityID, def: ComponentDef<S>, field: string & keyof S, value: number): void;
|
|
70
|
-
emit(
|
|
71
|
-
emit<F extends ComponentFields>(
|
|
68
|
+
emit(key: EventKey<readonly []>): void;
|
|
69
|
+
emit<F extends ComponentFields>(key: EventKey<F>, values: {
|
|
72
70
|
readonly [K in F[number]]: number;
|
|
73
71
|
}): void;
|
|
72
|
+
read<F extends ComponentFields>(key: EventKey<F>): EventReader<F>;
|
|
74
73
|
query<T extends ComponentDef[]>(...defs: T): Query<T>;
|
|
74
|
+
_get_last_run_tick(): number;
|
|
75
75
|
/** QueryResolver implementation — creates or retrieves a cached Query. */
|
|
76
76
|
_resolve_query(include: BitSet, exclude: BitSet | null, any_of: BitSet | null, defs: readonly ComponentDef[]): Query<any>;
|
|
77
77
|
private _find_cached;
|
|
@@ -83,7 +83,7 @@ export declare class ECS implements QueryResolver {
|
|
|
83
83
|
*
|
|
84
84
|
* // Function + query builder
|
|
85
85
|
* world.register_system(
|
|
86
|
-
* (q, ctx, dt) => {
|
|
86
|
+
* (q, ctx, dt) => { q.for_each((arch) => { ... }); },
|
|
87
87
|
* (qb) => qb.every(Pos, Vel),
|
|
88
88
|
* );
|
|
89
89
|
*
|