@oasys/oecs 0.1.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 +21 -0
- package/README.md +305 -0
- package/dist/archetype.d.ts +61 -0
- package/dist/archetype.d.ts.map +1 -0
- package/dist/component.d.ts +18 -0
- package/dist/component.d.ts.map +1 -0
- package/dist/entity.d.ts +11 -0
- package/dist/entity.d.ts.map +1 -0
- package/dist/event.d.ts +24 -0
- package/dist/event.d.ts.map +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1182 -0
- package/dist/query.d.ts +73 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/schedule.d.ts +40 -0
- package/dist/schedule.d.ts.map +1 -0
- package/dist/store.d.ts +92 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/system.d.ts +16 -0
- package/dist/system.d.ts.map +1 -0
- package/dist/type_primitives/assertions.d.ts +14 -0
- package/dist/type_primitives/assertions.d.ts.map +1 -0
- package/dist/type_primitives/bitset/bitset.d.ts +37 -0
- package/dist/type_primitives/bitset/bitset.d.ts.map +1 -0
- package/dist/type_primitives/brand.d.ts +18 -0
- package/dist/type_primitives/brand.d.ts.map +1 -0
- package/dist/type_primitives/error.d.ts +10 -0
- package/dist/type_primitives/error.d.ts.map +1 -0
- package/dist/type_primitives/index.d.ts +8 -0
- package/dist/type_primitives/index.d.ts.map +1 -0
- package/dist/type_primitives/sparse_map/sparse_map.d.ts +26 -0
- package/dist/type_primitives/sparse_map/sparse_map.d.ts.map +1 -0
- package/dist/type_primitives/sparse_set/sparse_set.d.ts +24 -0
- package/dist/type_primitives/sparse_set/sparse_set.d.ts.map +1 -0
- package/dist/type_primitives/typed_arrays/typed_arrays.d.ts +79 -0
- package/dist/type_primitives/typed_arrays/typed_arrays.d.ts.map +1 -0
- package/dist/utils/arrays.d.ts +6 -0
- package/dist/utils/arrays.d.ts.map +1 -0
- package/dist/utils/error.d.ts +28 -0
- package/dist/utils/error.d.ts.map +1 -0
- package/dist/world.d.ts +63 -0
- package/dist/world.d.ts.map +1 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Oasys
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# OECS
|
|
2
|
+
|
|
3
|
+
A fast, minimal archetype-based Entity Component System written in TypeScript.
|
|
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.
|
|
9
|
+
- **Deferred structural changes** — add/remove component and destroy entity are buffered during system execution and flushed between phases.
|
|
10
|
+
- **Topological system ordering** — systems within a phase are sorted by before/after constraints using Kahn's algorithm.
|
|
11
|
+
|
|
12
|
+
## Quick start
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { World, SCHEDULE } from "@oasys/oecs";
|
|
16
|
+
|
|
17
|
+
const world = new World();
|
|
18
|
+
|
|
19
|
+
// Define components as field-name arrays
|
|
20
|
+
const Pos = world.register_component(["x", "y"] as const);
|
|
21
|
+
const Vel = world.register_component(["vx", "vy"] as const);
|
|
22
|
+
|
|
23
|
+
// Tags have no fields
|
|
24
|
+
const IsEnemy = world.register_tag();
|
|
25
|
+
|
|
26
|
+
// Create entities and attach components
|
|
27
|
+
const e = world.create_entity();
|
|
28
|
+
world.add_component(e, Pos, { x: 0, y: 0 });
|
|
29
|
+
world.add_component(e, Vel, { vx: 100, vy: 50 });
|
|
30
|
+
world.add_component(e, IsEnemy);
|
|
31
|
+
|
|
32
|
+
// Register a system with a typed query
|
|
33
|
+
const moveSys = world.register_system(
|
|
34
|
+
(q, _ctx, dt) => {
|
|
35
|
+
q.each((pos, vel, n) => {
|
|
36
|
+
for (let i = 0; i < n; i++) {
|
|
37
|
+
pos.x[i] += vel.vx[i] * dt;
|
|
38
|
+
pos.y[i] += vel.vy[i] * dt;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
(qb) => qb.every(Pos, Vel),
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// Schedule the system
|
|
46
|
+
world.add_systems(SCHEDULE.UPDATE, moveSys);
|
|
47
|
+
|
|
48
|
+
// Initialize
|
|
49
|
+
world.startup();
|
|
50
|
+
|
|
51
|
+
// Game loop
|
|
52
|
+
function frame(dt: number) {
|
|
53
|
+
world.update(dt);
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Components
|
|
58
|
+
|
|
59
|
+
Components are defined as readonly string arrays of field names. All field values are `number`.
|
|
60
|
+
|
|
61
|
+
```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
|
|
65
|
+
```
|
|
66
|
+
|
|
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.
|
|
68
|
+
|
|
69
|
+
### Adding components
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
const e = world.create_entity();
|
|
73
|
+
|
|
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 });
|
|
77
|
+
|
|
78
|
+
// Tags require no values
|
|
79
|
+
world.add_component(e, IsEnemy);
|
|
80
|
+
|
|
81
|
+
// Add multiple at once (single archetype transition)
|
|
82
|
+
world.add_components(e, [
|
|
83
|
+
{ def: Position, values: { x: 10, y: 20 } },
|
|
84
|
+
{ def: Health, values: { current: 100, max: 100 } },
|
|
85
|
+
]);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Removing and checking components
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
world.remove_component(e, Health);
|
|
92
|
+
world.has_component(e, IsEnemy); // true
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Queries
|
|
96
|
+
|
|
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.
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
const q = world.query(Position, Velocity);
|
|
105
|
+
|
|
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)) {
|
|
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
|
+
```
|
|
148
|
+
|
|
149
|
+
## Systems
|
|
150
|
+
|
|
151
|
+
Systems are plain functions registered with a query and scheduled into lifecycle phases.
|
|
152
|
+
|
|
153
|
+
### Registration
|
|
154
|
+
|
|
155
|
+
Two registration styles:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
// Style 1: Function + query builder (typed, preferred)
|
|
159
|
+
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
|
+
},
|
|
168
|
+
(qb) => qb.every(Pos, Vel),
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
// Style 2: Config object (for systems that don't need a query)
|
|
172
|
+
const logSys = world.register_system({
|
|
173
|
+
fn(ctx, dt) {
|
|
174
|
+
console.log("frame", dt);
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Deferred operations
|
|
180
|
+
|
|
181
|
+
Inside systems, use `ctx` (SystemContext) for structural changes. These are buffered and applied between phases.
|
|
182
|
+
|
|
183
|
+
```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
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Per-entity field access
|
|
196
|
+
|
|
197
|
+
For reading/writing individual entity fields (not batch):
|
|
198
|
+
|
|
199
|
+
```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
|
+
},
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Schedule
|
|
209
|
+
|
|
210
|
+
Six lifecycle phases, executed in order:
|
|
211
|
+
|
|
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 |
|
|
220
|
+
|
|
221
|
+
Deferred changes are flushed automatically after each phase.
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
world.add_systems(SCHEDULE.UPDATE, moveSys, physicsSys);
|
|
225
|
+
world.add_systems(SCHEDULE.POST_UPDATE, renderSys);
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Ordering constraints
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
world.add_systems(SCHEDULE.UPDATE, moveSys, {
|
|
232
|
+
system: physicsSys,
|
|
233
|
+
ordering: { after: [moveSys] },
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
world.add_systems(SCHEDULE.UPDATE, {
|
|
237
|
+
system: aiSys,
|
|
238
|
+
ordering: { before: [moveSys] },
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Systems with ordering constraints are topologically sorted. Systems with no constraints run in registration order.
|
|
243
|
+
|
|
244
|
+
## Entity lifecycle
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
const e = world.create_entity();
|
|
248
|
+
|
|
249
|
+
world.is_alive(e); // true
|
|
250
|
+
|
|
251
|
+
world.destroy_entity(e); // deferred
|
|
252
|
+
world.flush(); // apply now
|
|
253
|
+
|
|
254
|
+
world.is_alive(e); // false
|
|
255
|
+
```
|
|
256
|
+
|
|
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
|
+
```
|
|
280
|
+
|
|
281
|
+
## Dev / Prod modes
|
|
282
|
+
|
|
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.
|
|
292
|
+
|
|
293
|
+
## Development
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
pnpm install
|
|
297
|
+
pnpm test # vitest in watch mode
|
|
298
|
+
pnpm bench # run benchmarks
|
|
299
|
+
pnpm build # vite library build
|
|
300
|
+
pnpm tsc --noEmit # type check
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Architecture
|
|
304
|
+
|
|
305
|
+
See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for a thorough explanation of the internal design.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Brand, BitSet } from './type_primitives';
|
|
2
|
+
import { ComponentID, ComponentFields, ComponentDef, ColumnsForSchema } from './component';
|
|
3
|
+
import { EntityID } from './entity';
|
|
4
|
+
export type ArchetypeID = Brand<number, "archetype_id">;
|
|
5
|
+
export declare const as_archetype_id: (value: number) => ArchetypeID;
|
|
6
|
+
export interface ArchetypeEdge {
|
|
7
|
+
add: ArchetypeID | null;
|
|
8
|
+
remove: ArchetypeID | null;
|
|
9
|
+
}
|
|
10
|
+
export interface ArchetypeColumnLayout {
|
|
11
|
+
component_id: ComponentID;
|
|
12
|
+
field_names: string[];
|
|
13
|
+
field_index: Record<string, number>;
|
|
14
|
+
}
|
|
15
|
+
interface ArchetypeColumnGroup {
|
|
16
|
+
layout: ArchetypeColumnLayout;
|
|
17
|
+
columns: number[][];
|
|
18
|
+
record: Record<string, number[]>;
|
|
19
|
+
}
|
|
20
|
+
export declare class Archetype {
|
|
21
|
+
readonly id: ArchetypeID;
|
|
22
|
+
readonly mask: BitSet;
|
|
23
|
+
readonly has_columns: boolean;
|
|
24
|
+
entity_ids: EntityID[];
|
|
25
|
+
length: number;
|
|
26
|
+
private edges;
|
|
27
|
+
readonly column_groups: (ArchetypeColumnGroup | undefined)[];
|
|
28
|
+
private _column_ids;
|
|
29
|
+
constructor(id: ArchetypeID, mask: BitSet, layouts?: ArchetypeColumnLayout[]);
|
|
30
|
+
get entity_count(): number;
|
|
31
|
+
get entity_list(): readonly EntityID[];
|
|
32
|
+
has_component(id: ComponentID): boolean;
|
|
33
|
+
matches(required: BitSet): boolean;
|
|
34
|
+
/** 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>;
|
|
38
|
+
write_fields(row: number, component_id: ComponentID, values: Record<string, number>): void;
|
|
39
|
+
read_field(row: number, component_id: ComponentID, field: string): number;
|
|
40
|
+
/** Copy all shared component columns from source archetype at src_row into dst_row. */
|
|
41
|
+
copy_shared_from(source: Archetype, src_row: number, dst_row: number): void;
|
|
42
|
+
/**
|
|
43
|
+
* Add an entity. Pushes zeroes into all columns and returns the assigned row.
|
|
44
|
+
* Store is responsible for tracking entity_index → row.
|
|
45
|
+
*/
|
|
46
|
+
add_entity(entity_id: EntityID): number;
|
|
47
|
+
/**
|
|
48
|
+
* Remove entity at row via swap-and-pop. Swaps the last entity into the
|
|
49
|
+
* 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.
|
|
51
|
+
*/
|
|
52
|
+
remove_entity(row: number): number;
|
|
53
|
+
/** Tag-optimized add: skip column push entirely (no data to store). */
|
|
54
|
+
add_entity_tag(entity_id: EntityID): number;
|
|
55
|
+
/** Tag-optimized remove via swap-and-pop: skip column swap/pop entirely. */
|
|
56
|
+
remove_entity_tag(row: number): number;
|
|
57
|
+
get_edge(component_id: ComponentID): ArchetypeEdge | undefined;
|
|
58
|
+
set_edge(component_id: ComponentID, edge: ArchetypeEdge): void;
|
|
59
|
+
}
|
|
60
|
+
export {};
|
|
61
|
+
//# sourceMappingURL=archetype.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archetype.d.ts","sourceRoot":"","sources":["../src/archetype.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Brand } from './type_primitives';
|
|
2
|
+
export type ComponentID = Brand<number, "component_id">;
|
|
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;
|
|
8
|
+
};
|
|
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[];
|
|
12
|
+
};
|
|
13
|
+
declare const __schema: unique symbol;
|
|
14
|
+
export type ComponentDef<F extends ComponentFields = ComponentFields> = ComponentID & {
|
|
15
|
+
readonly [__schema]: F;
|
|
16
|
+
};
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=component.d.ts.map
|
|
@@ -0,0 +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"}
|
package/dist/entity.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Brand } from './type_primitives';
|
|
2
|
+
export type EntityID = Brand<number, "entity_id">;
|
|
3
|
+
export declare const INDEX_BITS = 20;
|
|
4
|
+
export declare const INDEX_MASK: number;
|
|
5
|
+
export declare const MAX_INDEX: number;
|
|
6
|
+
export declare const GENERATION_BITS: number;
|
|
7
|
+
export declare const MAX_GENERATION: number;
|
|
8
|
+
export declare const create_entity_id: (index: number, generation: number) => EntityID;
|
|
9
|
+
export declare const get_entity_index: (id: EntityID) => number;
|
|
10
|
+
export declare const get_entity_generation: (id: EntityID) => number;
|
|
11
|
+
//# sourceMappingURL=entity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entity.d.ts","sourceRoot":"","sources":["../src/entity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;KAkBK;AAEL,OAAO,EAAE,KAAK,EAAe,MAAM,iBAAiB,CAAC;AAGrD,MAAM,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAElD,eAAO,MAAM,UAAU,KAAK,CAAC;AAC7B,eAAO,MAAM,UAAU,QAAwB,CAAC;AAChD,eAAO,MAAM,SAAS,QAAa,CAAC;AACpC,eAAO,MAAM,eAAe,QAAkB,CAAC;AAC/C,eAAO,MAAM,cAAc,QAA6B,CAAC;AAEzD,eAAO,MAAM,gBAAgB,UACpB,MAAM,cACD,MAAM,KACjB,QAWF,CAAC;AAEF,eAAO,MAAM,gBAAgB,OAAQ,QAAQ,KAAG,MAAyB,CAAC;AAE1E,eAAO,MAAM,qBAAqB,OAAQ,QAAQ,KAAG,MACvB,CAAC"}
|
package/dist/event.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Brand } from './type_primitives';
|
|
2
|
+
import { ComponentFields, ColumnsForSchema } from './component';
|
|
3
|
+
export type EventID = Brand<number, "event_id">;
|
|
4
|
+
export declare const as_event_id: (value: number) => EventID;
|
|
5
|
+
declare const __event_schema: unique symbol;
|
|
6
|
+
export type EventDef<F extends ComponentFields = ComponentFields> = EventID & {
|
|
7
|
+
readonly [__event_schema]: F;
|
|
8
|
+
};
|
|
9
|
+
/** Reader view over an event channel's SoA columns. */
|
|
10
|
+
export type EventReader<F extends ComponentFields> = {
|
|
11
|
+
length: number;
|
|
12
|
+
} & ColumnsForSchema<F>;
|
|
13
|
+
export declare class EventChannel {
|
|
14
|
+
readonly field_names: string[];
|
|
15
|
+
readonly columns: number[][];
|
|
16
|
+
readonly reader: EventReader<any>;
|
|
17
|
+
constructor(field_names: string[]);
|
|
18
|
+
emit(values: Record<string, number>): void;
|
|
19
|
+
/** Emit a signal (zero-field event). */
|
|
20
|
+
emit_signal(): void;
|
|
21
|
+
clear(): void;
|
|
22
|
+
}
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=event.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../src/event.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;KAyBK;AAEL,OAAO,EACL,KAAK,EAGN,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAErE,MAAM,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAChD,eAAO,MAAM,WAAW,UAAW,MAAM,YAKtC,CAAC;AAGJ,OAAO,CAAC,MAAM,cAAc,EAAE,OAAO,MAAM,CAAC;AAE5C,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,eAAe,GAAG,eAAe,IAC9D,OAAO,GAAG;IAAE,QAAQ,CAAC,CAAC,cAAc,CAAC,EAAE,CAAC,CAAA;CAAE,CAAC;AAE7C,uDAAuD;AACvD,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,eAAe,IAAI;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC;AAE9F,qBAAa,YAAY;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC;gBAEtB,WAAW,EAAE,MAAM,EAAE;IAejC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI;IAS1C,wCAAwC;IACxC,WAAW,IAAI,IAAI;IAInB,KAAK,IAAI,IAAI;CAOd"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var $=Object.defineProperty;var z=(a,t,e)=>t in a?$(a,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):a[t]=e;var l=(a,t,e)=>z(a,typeof t!="symbol"?t+"":t,e);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class Y extends Error{constructor(t,e,s){super(t),this.is_operational=e,this.context=s,this.name=this.constructor.name,Error.captureStackTrace(this,this.constructor)}}var u=(a=>(a.EID_MAX_INDEX_OVERFLOW="EID_MAX_INDEX_OVERFLOW",a.EID_MAX_GEN_OVERFLOW="EID_MAX_GEN_OVERFLOW",a.COMPONENT_NOT_REGISTERED="COMPONENT_NOT_REGISTERED",a.ENTITY_NOT_ALIVE="ENTITY_NOT_ALIVE",a.CIRCULAR_SYSTEM_DEPENDENCY="CIRCULAR_SYSTEM_DEPENDENCY",a.DUPLICATE_SYSTEM="DUPLICATE_SYSTEM",a.ARCHETYPE_NOT_FOUND="ARCHETYPE_NOT_FOUND",a))(u||{});class g extends Y{constructor(t,e,s){super(e??t,!0,s),this.category=t}}var C=(a=>(a.ASSERTION_FAIL_CONDITION="ASSERTION_FAIL_CONDITION",a.VALIDATION_FAIL_CONDITION="VALIDATION_FAIL_CONDITION",a))(C||{});class B extends Y{constructor(t,e,s){super(e,!1,s),this.category=t}}const P=a=>Number.isInteger(a)&&a>=0;function b(a,t,e){if(process.env.NODE_ENV!=="production"&&!t(a))throw new B(C.VALIDATION_FAIL_CONDITION,`Expected value to meet validation: ${e}`);return a}const Q=4;class T{constructor(t){l(this,"_words");this._words=t??new Array(Q).fill(0)}has(t){const e=t>>>5;return e>=this._words.length?!1:(this._words[e]&1<<(t&31))!==0}set(t){const e=t>>>5;e>=this._words.length&&this.grow(e+1),this._words[e]|=1<<(t&31)}clear(t){const e=t>>>5;e>=this._words.length||(this._words[e]&=~(1<<(t&31)))}overlaps(t){const e=this._words,s=t._words,n=e.length<s.length?e.length:s.length;for(let r=0;r<n;r++)if((e[r]&s[r])!==0)return!0;return!1}contains(t){const e=t._words,s=this._words,n=s.length;for(let r=0;r<e.length;r++){const o=e[r];if(o!==0&&(r>=n||(s[r]&o)!==o))return!1}return!0}equals(t){const e=this._words,s=t._words,n=e.length>s.length?e.length:s.length;for(let r=0;r<n;r++){const o=r<e.length?e[r]:0,_=r<s.length?s[r]:0;if(o!==_)return!1}return!0}copy(){return new T(this._words.slice())}copy_with_set(t){const e=t>>>5,s=e+1,n=this._words.length>s?this._words.length:s,r=new Array(n).fill(0);for(let o=0;o<this._words.length;o++)r[o]=this._words[o];return r[e]|=1<<(t&31),new T(r)}copy_with_clear(t){const e=this._words.slice(),s=t>>>5;return s<e.length&&(e[s]&=~(1<<(t&31))),new T(e)}hash(){let t=2166136261;const e=this._words;let s=e.length-1;for(;s>=0&&e[s]===0;)s--;for(let n=0;n<=s;n++)t^=e[n],t=Math.imul(t,16777619);return t}for_each(t){const e=this._words;for(let s=0;s<e.length;s++){let n=e[s];if(n===0)continue;const r=s<<5;for(;n!==0;){const o=n&-n>>>0,_=31-Math.clz32(o);t(r+_),n^=o}}}grow(t){let e=this._words.length;for(;e<t;)e*=2;const s=new Array(e).fill(0);for(let n=0;n<this._words.length;n++)s[n]=this._words[n];this._words=s}}const D=20,S=(1<<D)-1,K=S,J=31-D,k=(1<<J)-1,Z=(a,t)=>{if(process.env.NODE_ENV!=="production"){if(a<0||a>K)throw new g(u.EID_MAX_INDEX_OVERFLOW);if(t<0||t>k)throw new g(u.EID_MAX_GEN_OVERFLOW)}return t<<D|a},m=a=>a&S,R=a=>a>>D,H=a=>b(a,P,"ComponentID must be a non-negative integer"),tt=a=>b(a,P,"EventID must be a non-negative integer");class et{constructor(t){l(this,"field_names");l(this,"columns");l(this,"reader");this.field_names=t,this.columns=[];for(let s=0;s<t.length;s++)this.columns.push([]);const e={length:0};for(let s=0;s<t.length;s++)e[t[s]]=this.columns[s];this.reader=e}emit(t){const e=this.field_names,s=this.columns;for(let n=0;n<e.length;n++)s[n].push(t[e[n]]);this.reader.length++}emit_signal(){this.reader.length++}clear(){this.reader.length=0;const t=this.columns;for(let e=0;e<t.length;e++)t[e].length=0}}const st=a=>b(a,P,"ArchetypeID must be a non-negative integer");class nt{constructor(t,e,s){l(this,"id");l(this,"mask");l(this,"has_columns");l(this,"entity_ids",[]);l(this,"length",0);l(this,"edges",[]);l(this,"column_groups",[]);l(this,"_column_ids",[]);if(this.id=t,this.mask=e,s)for(let n=0;n<s.length;n++){const r=s[n],o=new Array(r.field_names.length);for(let h=0;h<r.field_names.length;h++)o[h]=[];const _=Object.create(null);for(let h=0;h<r.field_names.length;h++)_[r.field_names[h]]=o[h];this.column_groups[r.component_id]={layout:r,columns:o,record:_},this._column_ids.push(r.component_id)}this.has_columns=this._column_ids.length>0}get entity_count(){return this.length}get entity_list(){return this.entity_ids}has_component(t){return this.mask.has(t)}matches(t){return this.mask.contains(t)}get_column(t,e){const s=this.column_groups[t];if(process.env.NODE_ENV!=="production"&&!s)throw new g(u.COMPONENT_NOT_REGISTERED,`Component ${t} not in archetype ${this.id}`);const n=s.layout.field_index[e];if(process.env.NODE_ENV!=="production"&&n===void 0)throw new g(u.COMPONENT_NOT_REGISTERED,`Field "${e}" does not exist on component`);return s.columns[n]}get_column_group(t){const e=this.column_groups[t];return e?e.record:{}}write_fields(t,e,s){const n=this.column_groups[e];if(!n)return;const{field_names:r}=n.layout;for(let o=0;o<r.length;o++)n.columns[o][t]=s[r[o]]}read_field(t,e,s){const n=this.column_groups[e];if(!n)return NaN;const r=n.layout.field_index[s];return r===void 0?NaN:n.columns[r][t]??NaN}copy_shared_from(t,e,s){const n=t.column_groups,r=this._column_ids;for(let o=0;o<r.length;o++){const _=r[o],h=n[_];if(!h)continue;const i=this.column_groups[_];for(let c=0;c<i.columns.length;c++)i.columns[c][s]=h.columns[c][e]}}add_entity(t){const e=this.length;this.entity_ids.push(t);const s=this._column_ids;for(let n=0;n<s.length;n++){const r=this.column_groups[s[n]];for(let o=0;o<r.columns.length;o++)r.columns[o].push(0)}return this.length++,e}remove_entity(t){const e=this.length-1;let s=-1;const n=this._column_ids;if(t!==e){this.entity_ids[t]=this.entity_ids[e],s=m(this.entity_ids[t]);for(let r=0;r<n.length;r++){const o=this.column_groups[n[r]];for(let _=0;_<o.columns.length;_++)o.columns[_][t]=o.columns[_][e],o.columns[_].pop()}}else for(let r=0;r<n.length;r++){const o=this.column_groups[n[r]];for(let _=0;_<o.columns.length;_++)o.columns[_].pop()}return this.entity_ids.pop(),this.length--,s}add_entity_tag(t){const e=this.length;return this.entity_ids.push(t),this.length++,e}remove_entity_tag(t){const e=this.length-1;let s=-1;return t!==e&&(this.entity_ids[t]=this.entity_ids[e],s=m(this.entity_ids[t])),this.entity_ids.pop(),this.length--,s}get_edge(t){return this.edges[t]}set_edge(t,e){this.edges[t]=e}}function j(a,t,e){const s=a.get(t);s!==void 0?s.push(e):a.set(t,[e])}const w=-1,M=Object.freeze(Object.create(null));class rt{constructor(){l(this,"entity_generations",[]);l(this,"entity_high_water",0);l(this,"entity_free_indices",[]);l(this,"entity_alive_count",0);l(this,"component_metas",[]);l(this,"component_count",0);l(this,"event_channels",[]);l(this,"event_count",0);l(this,"archetypes",[]);l(this,"archetype_map",new Map);l(this,"next_archetype_id",0);l(this,"component_index",new Map);l(this,"registered_queries",[]);l(this,"empty_archetype_id");l(this,"entity_archetype",[]);l(this,"entity_row",[]);l(this,"pending_destroy",[]);l(this,"pending_add_ids",[]);l(this,"pending_add_defs",[]);l(this,"pending_add_values",[]);l(this,"pending_remove_ids",[]);l(this,"pending_remove_defs",[]);this.empty_archetype_id=this.arch_get_or_create_from_mask(new T)}arch_get(t){if(process.env.NODE_ENV!=="production"&&(t<0||t>=this.archetypes.length))throw new g(u.ARCHETYPE_NOT_FOUND,`Archetype with ID ${t} not found`);return this.archetypes[t]}arch_get_or_create_from_mask(t){const e=t.hash(),s=this.archetype_map.get(e);if(s!==void 0){for(let h=0;h<s.length;h++)if(this.archetypes[s[h]].mask.equals(t))return s[h]}const n=st(this.next_archetype_id++),r=[];t.for_each(h=>{const i=h,c=this.component_metas[i];c&&c.field_names.length>0&&r.push({component_id:i,field_names:c.field_names,field_index:c.field_index})});const o=new nt(n,t,r);this.archetypes.push(o),j(this.archetype_map,e,n),t.for_each(h=>{const i=h;let c=this.component_index.get(i);c||(c=new Set,this.component_index.set(i,c)),c.add(n)});const _=this.registered_queries;for(let h=0;h<_.length;h++){const i=_[h];o.matches(i.include_mask)&&(!i.exclude_mask||!o.mask.overlaps(i.exclude_mask))&&(!i.any_of_mask||o.mask.overlaps(i.any_of_mask))&&i.result.push(o)}return n}arch_resolve_add(t,e){const s=this.arch_get(t);if(s.mask.has(e))return t;const n=s.get_edge(e);if((n==null?void 0:n.add)!=null)return n.add;const r=this.arch_get_or_create_from_mask(s.mask.copy_with_set(e));return this.arch_cache_edge(s,this.arch_get(r),e),r}arch_resolve_remove(t,e){const s=this.arch_get(t);if(!s.mask.has(e))return t;const n=s.get_edge(e);if((n==null?void 0:n.remove)!=null)return n.remove;const r=this.arch_get_or_create_from_mask(s.mask.copy_with_clear(e));return this.arch_cache_edge(this.arch_get(r),s,e),r}arch_cache_edge(t,e,s){const n=t.get_edge(s)??{add:null,remove:null};n.add=e.id,t.set_edge(s,n);const r=e.get_edge(s)??{add:null,remove:null};r.remove=t.id,e.set_edge(s,r)}create_entity(){let t,e;this.entity_free_indices.length>0?(t=this.entity_free_indices.pop(),e=this.entity_generations[t]):(t=this.entity_high_water++,this.entity_generations[t]=0,e=0),this.entity_alive_count++;const s=Z(t,e);return this.entity_archetype[t]=this.empty_archetype_id,this.entity_row[t]=w,s}destroy_entity(t){if(!this.is_alive(t)){if(process.env.NODE_ENV!=="production")throw new g(u.ENTITY_NOT_ALIVE);return}const e=m(t),s=this.entity_row[e];if(s!==w){const o=this.arch_get(this.entity_archetype[e]).remove_entity(s);o!==-1&&(this.entity_row[o]=s)}this.entity_archetype[e]=w,this.entity_row[e]=w;const n=R(t);if(process.env.NODE_ENV!=="production"&&n>=k)throw new g(u.EID_MAX_GEN_OVERFLOW);this.entity_generations[e]=n+1&k,this.entity_free_indices.push(e),this.entity_alive_count--}is_alive(t){const e=m(t);return e<this.entity_high_water&&this.entity_generations[e]===R(t)}get entity_count(){return this.entity_alive_count}destroy_entity_deferred(t){if(process.env.NODE_ENV!=="production"&&!this.is_alive(t))throw new g(u.ENTITY_NOT_ALIVE);this.pending_destroy.push(t)}flush_destroyed(){const t=this.pending_destroy;if(t.length===0)return;const e=this.entity_archetype,s=this.entity_row,n=this.entity_generations,r=this.archetypes,o=this.entity_high_water;for(let _=0;_<t.length;_++){const h=t[_],i=h&S,c=h>>D;if(i>=o||n[i]!==c)continue;const d=s[i];if(d!==w){const f=r[e[i]],p=f.has_columns?f.remove_entity(d):f.remove_entity_tag(d);p!==-1&&(s[p]=d)}if(e[i]=w,s[i]=w,process.env.NODE_ENV!=="production"&&c>=k)throw new g(u.EID_MAX_GEN_OVERFLOW);n[i]=c+1&k,this.entity_free_indices.push(i),this.entity_alive_count--}t.length=0}get pending_destroy_count(){return this.pending_destroy.length}add_component_deferred(t,e,s){if(process.env.NODE_ENV!=="production"&&!this.is_alive(t))throw new g(u.ENTITY_NOT_ALIVE);this.pending_add_ids.push(t),this.pending_add_defs.push(e),this.pending_add_values.push(s??M)}remove_component_deferred(t,e){if(process.env.NODE_ENV!=="production"&&!this.is_alive(t))throw new g(u.ENTITY_NOT_ALIVE);this.pending_remove_ids.push(t),this.pending_remove_defs.push(e)}flush_structural(){this.pending_add_ids.length>0&&this._flush_adds(),this.pending_remove_ids.length>0&&this._flush_removes()}_flush_adds(){const t=this.pending_add_ids,e=this.pending_add_defs,s=this.pending_add_values,n=t.length,r=this.entity_archetype,o=this.entity_row,_=this.entity_generations,h=this.archetypes,i=this.component_metas,c=this.entity_high_water;for(let d=0;d<n;d++){const f=t[d],p=f&S,v=f>>D;if(p>=c||_[p]!==v)continue;const O=r[p],E=e[d],y=h[O];if(y.mask.has(E)){i[E].field_names.length>0&&y.write_fields(o[p],E,s[d]);continue}const A=this.arch_resolve_add(O,E),N=h[A],I=o[p],x=!N.has_columns&&!y.has_columns,V=x?N.add_entity_tag(f):N.add_entity(f);if(I!==w){x||N.copy_shared_from(y,I,V);const q=x?y.remove_entity_tag(I):y.remove_entity(I);q!==-1&&(o[q]=I)}i[E].field_names.length>0&&N.write_fields(V,E,s[d]),r[p]=A,o[p]=V}t.length=0,e.length=0,s.length=0}_flush_removes(){const t=this.pending_remove_ids,e=this.pending_remove_defs,s=t.length,n=this.entity_archetype,r=this.entity_row,o=this.entity_generations,_=this.archetypes,h=this.entity_high_water;for(let i=0;i<s;i++){const c=t[i],d=c&S,f=c>>D;if(d>=h||o[d]!==f)continue;const p=n[d],v=e[i],O=_[p];if(!O.mask.has(v))continue;const E=this.arch_resolve_remove(p,v),y=_[E],A=r[d],N=!y.has_columns&&!O.has_columns,I=N?y.add_entity_tag(c):y.add_entity(c);N||y.copy_shared_from(O,A,I);const x=N?O.remove_entity_tag(A):O.remove_entity(A);x!==-1&&(r[x]=A),n[d]=E,r[d]=I}t.length=0,e.length=0}get pending_structural_count(){return this.pending_add_ids.length+this.pending_remove_ids.length}register_component(t){const e=H(this.component_count++),s=t,n=Object.create(null);for(let r=0;r<s.length;r++)n[s[r]]=r;return this.component_metas.push({field_names:s,field_index:n}),e}add_component(t,e,s){if(!this.is_alive(t)){if(process.env.NODE_ENV!=="production")throw new g(u.ENTITY_NOT_ALIVE);return}const n=m(t),r=this.entity_archetype[n],o=this.arch_get(r);if(o.has_component(e)){o.write_fields(this.entity_row[n],e,s);return}const _=this.arch_resolve_add(r,e),h=this.arch_get(_),i=this.entity_row[n],c=h.add_entity(t);if(i!==w){h.copy_shared_from(o,i,c);const d=o.remove_entity(i);d!==-1&&(this.entity_row[d]=i)}h.write_fields(c,e,s),this.entity_archetype[n]=_,this.entity_row[n]=c}add_components(t,e){if(!this.is_alive(t)){if(process.env.NODE_ENV!=="production")throw new g(u.ENTITY_NOT_ALIVE);return}const s=m(t),n=this.entity_archetype[s];let r=n;for(let o=0;o<e.length;o++)r=this.arch_resolve_add(r,e[o].def);if(r!==n){const o=this.arch_get(n),_=this.arch_get(r),h=this.entity_row[s],i=_.add_entity(t);if(h!==w){_.copy_shared_from(o,h,i);const c=o.remove_entity(h);c!==-1&&(this.entity_row[c]=h)}for(let c=0;c<e.length;c++)_.write_fields(i,e[c].def,e[c].values??M);this.entity_archetype[s]=r,this.entity_row[s]=i}else{const o=this.arch_get(n),_=this.entity_row[s];for(let h=0;h<e.length;h++)o.write_fields(_,e[h].def,e[h].values??M)}}remove_component(t,e){if(!this.is_alive(t)){if(process.env.NODE_ENV!=="production")throw new g(u.ENTITY_NOT_ALIVE);return}const s=m(t),n=this.entity_archetype[s],r=this.arch_get(n);if(!r.has_component(e))return;const o=this.arch_resolve_remove(n,e),_=this.arch_get(o),h=this.entity_row[s],i=_.add_entity(t);_.copy_shared_from(r,h,i);const c=r.remove_entity(h);c!==-1&&(this.entity_row[c]=h),this.entity_archetype[s]=o,this.entity_row[s]=i}remove_components(t,e){if(!this.is_alive(t)){if(process.env.NODE_ENV!=="production")throw new g(u.ENTITY_NOT_ALIVE);return}const s=m(t),n=this.entity_archetype[s];let r=n;for(let d=0;d<e.length;d++)r=this.arch_resolve_remove(r,e[d]);if(r===n)return;const o=this.arch_get(n),_=this.arch_get(r),h=this.entity_row[s],i=_.add_entity(t);_.copy_shared_from(o,h,i);const c=o.remove_entity(h);c!==-1&&(this.entity_row[c]=h),this.entity_archetype[s]=r,this.entity_row[s]=i}has_component(t,e){if(!this.is_alive(t)){if(process.env.NODE_ENV!=="production")throw new g(u.ENTITY_NOT_ALIVE);return!1}const s=m(t);return this.arch_get(this.entity_archetype[s]).has_component(e)}get_entity_archetype(t){return this.arch_get(this.entity_archetype[m(t)])}get_entity_row(t){return this.entity_row[m(t)]}get_matching_archetypes(t,e,s){const n=t._words;let r=!1;for(let i=0;i<n.length;i++)if(n[i]!==0){r=!0;break}if(!r)return this.archetypes.filter(i=>(!e||!i.mask.overlaps(e))&&(!s||i.mask.overlaps(s)));let o,_=!1;for(let i=0;i<n.length;i++){let c=n[i];if(c===0)continue;const d=i<<5;for(;c!==0;){const f=c&-c>>>0,p=d+(31-Math.clz32(f));c^=f;const v=this.component_index.get(p);if(!v||v.size===0){_=!0;break}(!o||v.size<o.size)&&(o=v)}if(_)break}if(_||!o)return[];const h=[];for(const i of o){const c=this.arch_get(i);c.matches(t)&&(!e||!c.mask.overlaps(e))&&(!s||c.mask.overlaps(s))&&h.push(c)}return h}register_query(t,e,s){const n=this.get_matching_archetypes(t,e,s);return this.registered_queries.push({include_mask:t.copy(),exclude_mask:e?e.copy():null,any_of_mask:s?s.copy():null,result:n}),n}get archetype_count(){return this.archetypes.length}register_event(t){const e=tt(this.event_count++),s=new et(t);return this.event_channels.push(s),e}emit_event(t,e){this.event_channels[t].emit(e)}emit_signal(t){this.event_channels[t].emit_signal()}get_event_reader(t){return this.event_channels[t].reader}clear_events(){const t=this.event_channels;for(let e=0;e<t.length;e++)t[e].clear()}}var F=(a=>(a.PRE_STARTUP="PRE_STARTUP",a.STARTUP="STARTUP",a.POST_STARTUP="POST_STARTUP",a.PRE_UPDATE="PRE_UPDATE",a.UPDATE="UPDATE",a.POST_UPDATE="POST_UPDATE",a))(F||{});const L=["PRE_STARTUP","STARTUP","POST_STARTUP"],U=["PRE_UPDATE","UPDATE","POST_UPDATE"];class ot{constructor(){l(this,"label_systems",new Map);l(this,"sorted_cache",new Map);l(this,"system_index",new Map);l(this,"next_insertion_order",0);for(let t=0;t<L.length;t++)this.label_systems.set(L[t],[]);for(let t=0;t<U.length;t++)this.label_systems.set(U[t],[])}add_systems(t,...e){for(const s of e){const n="system"in s?s.system:s,r="system"in s?s.ordering:void 0;if(process.env.NODE_ENV!=="production"&&this.system_index.has(n))throw new g(u.DUPLICATE_SYSTEM,`System ${n.id} is already scheduled`);const o={descriptor:n,insertion_order:this.next_insertion_order++,before:new Set((r==null?void 0:r.before)??[]),after:new Set((r==null?void 0:r.after)??[])};this.label_systems.get(t).push(o),this.system_index.set(n,t),this.sorted_cache.delete(t)}}remove_system(t){const e=this.system_index.get(t);if(e===void 0)return;const s=this.label_systems.get(e),n=s.findIndex(r=>r.descriptor===t);if(n!==-1){const r=s.length-1;n!==r&&(s[n]=s[r]),s.pop();for(const o of s)o.before.delete(t),o.after.delete(t)}this.system_index.delete(t),this.sorted_cache.delete(e)}run_startup(t){for(const e of L)this.run_label(e,t,0)}run_update(t,e){for(const s of U)this.run_label(s,t,e)}get_all_systems(){const t=[];for(const e of this.label_systems.values())for(const s of e)t.push(s.descriptor);return t}has_system(t){return this.system_index.has(t)}clear(){for(const t of this.label_systems.values())t.length=0;this.sorted_cache.clear(),this.system_index.clear()}run_label(t,e,s){const n=this.get_sorted(t);for(let r=0;r<n.length;r++)n[r].fn(e,s);e.flush()}get_sorted(t){const e=this.sorted_cache.get(t);if(e!==void 0)return e;const s=this.label_systems.get(t),n=this.topological_sort(s,t);return this.sorted_cache.set(t,n),n}topological_sort(t,e){if(t.length===0)return[];const s=new Map,n=new Map,r=new Map,o=new Set;for(const i of t)s.set(i.descriptor,new Set),n.set(i.descriptor,0),r.set(i.descriptor,i.insertion_order),o.add(i.descriptor);for(const i of t){for(const c of i.before)o.has(c)&&(s.get(i.descriptor).add(c),n.set(c,n.get(c)+1));for(const c of i.after)o.has(c)&&(s.get(c).add(i.descriptor),n.set(i.descriptor,n.get(i.descriptor)+1))}let _=[];for(const i of t)n.get(i.descriptor)===0&&_.push(i.descriptor);_.sort((i,c)=>r.get(c)-r.get(i));const h=[];for(;_.length>0;){const i=_.pop();h.push(i);for(const c of s.get(i)){const d=n.get(c)-1;n.set(c,d),d===0&&_.push(c)}_.sort((c,d)=>r.get(d)-r.get(c))}if(h.length!==t.length){const i=new Set(h),c=t.filter(d=>!i.has(d.descriptor)).map(d=>`system_${d.descriptor.id}`);throw new g(u.CIRCULAR_SYSTEM_DEPENDENCY,`Circular system dependency detected in ${e}: [${c.join(", ")}]`)}return h}}const it=Object.freeze(Object.create(null));class X{constructor(t,e,s,n,r,o){l(this,"_archetypes");l(this,"_defs");l(this,"_resolver");l(this,"_include");l(this,"_exclude");l(this,"_any_of");l(this,"_args_buf");this._archetypes=t,this._defs=e,this._resolver=s,this._include=n,this._exclude=r,this._any_of=o,this._args_buf=new Array(e.length+1)}get length(){return this._archetypes.length}count(){const t=this._archetypes;let e=0;for(let s=0;s<t.length;s++)e+=t[s].entity_count;return e}get archetypes(){return this._archetypes}*[Symbol.iterator](){const t=this._archetypes;for(let e=0;e<t.length;e++)t[e].entity_count>0&&(yield t[e])}each(t){const e=this._archetypes,s=this._defs,n=this._args_buf;for(let r=0;r<e.length;r++){const o=e[r],_=o.entity_count;if(_!==0){for(let h=0;h<s.length;h++)n[h]=o.get_column_group(s[h]);n[s.length]=_,t.apply(null,n)}}}and(...t){const e=this._include.copy(),s=this._defs.slice();for(let n=0;n<t.length;n++)e.has(t[n])||(e.set(t[n]),s.push(t[n]));return this._resolver._resolve_query(e,this._exclude,this._any_of,s)}not(...t){const e=this._exclude?this._exclude.copy():new T;for(let s=0;s<t.length;s++)e.set(t[s]);return this._resolver._resolve_query(this._include,e,this._any_of,this._defs)}or(...t){const e=this._any_of?this._any_of.copy():new T;for(let s=0;s<t.length;s++)e.set(t[s]);return this._resolver._resolve_query(this._include,this._exclude,e,this._defs)}}class G{constructor(t){this._resolver=t}every(...t){const e=new T;for(let s=0;s<t.length;s++)e.set(t[s]);return this._resolver._resolve_query(e,null,null,t)}}class W{constructor(t){l(this,"store");this.store=t}create_entity(){return this.store.create_entity()}get_field(t,e,s){const n=this.store.get_entity_archetype(e),r=this.store.get_entity_row(e);return n.read_field(r,t,s)}set_field(t,e,s,n){const r=this.store.get_entity_archetype(e),o=this.store.get_entity_row(e),_=r.get_column(t,s);_[o]=n}destroy_entity(t){return this.store.destroy_entity_deferred(t),this}flush_destroyed(){this.store.flush_destroyed()}add_component(t,e,s){return this.store.add_component_deferred(t,e,s??it),this}remove_component(t,e){return this.store.remove_component_deferred(t,e),this}flush(){this.store.flush_structural(),this.store.flush_destroyed()}emit(t,e){e===void 0?this.store.emit_signal(t):this.store.emit_event(t,e)}read(t){return this.store.get_event_reader(t)}}const _t=a=>b(a,P,"SystemID must be a non-negative integer"),ct=Object.freeze(Object.create(null));class ht{constructor(){l(this,"store");l(this,"schedule");l(this,"ctx");l(this,"systems",new Set);l(this,"next_system_id",0);l(this,"query_cache",new Map);l(this,"scratch_mask",new T);this.store=new rt,this.schedule=new ot,this.ctx=new W(this.store)}register_component(t){return this.store.register_component(t)}register_tag(){return this.store.register_component([])}register_event(t){return this.store.register_event(t)}register_signal(){return this.store.register_event([])}create_entity(){return this.store.create_entity()}destroy_entity(t){this.store.destroy_entity_deferred(t)}is_alive(t){return this.store.is_alive(t)}get entity_count(){return this.store.entity_count}add_component(t,e,s){this.store.add_component(t,e,s??ct)}add_components(t,e){this.store.add_components(t,e)}remove_component(t,e){return this.store.remove_component(t,e),this}remove_components(t,...e){this.store.remove_components(t,e)}has_component(t,e){return this.store.has_component(t,e)}get_field(t,e,s){const n=this.store.get_entity_archetype(e),r=this.store.get_entity_row(e);return n.read_field(r,t,s)}emit(t,e){e===void 0?this.store.emit_signal(t):this.store.emit_event(t,e)}query(...t){const e=this.scratch_mask;e._words.fill(0);for(let s=0;s<t.length;s++)e.set(t[s]);return this._resolve_query(e.copy(),null,null,t)}_resolve_query(t,e,s,n){const r=t.hash(),o=e?e.hash():0,_=s?s.hash():0,h=r^Math.imul(o,2654435769)^Math.imul(_,1367130551)|0,i=this._find_cached(h,t,e,s);if(i!==void 0)return i.query;const c=this.store.register_query(t,e??void 0,s??void 0),d=new X(c,n,this,t.copy(),(e==null?void 0:e.copy())??null,(s==null?void 0:s.copy())??null);return j(this.query_cache,h,{include_mask:t.copy(),exclude_mask:(e==null?void 0:e.copy())??null,any_of_mask:(s==null?void 0:s.copy())??null,query:d}),d}_find_cached(t,e,s,n){const r=this.query_cache.get(t);if(r)for(let o=0;o<r.length;o++){const _=r[o];if(!(!_.include_mask.equals(e)||!(s===null?_.exclude_mask===null:_.exclude_mask!==null&&_.exclude_mask.equals(s))||!(n===null?_.any_of_mask===null:_.any_of_mask!==null&&_.any_of_mask.equals(n))))return _}}register_system(t,e){let s;if(typeof t=="function"){const o=e(new G(this)),_=this.ctx;s={fn:(h,i)=>t(o,_,i)}}else s=t;const n=_t(this.next_system_id++),r=Object.freeze(Object.assign({id:n},s));return this.systems.add(r),r}add_systems(t,...e){return this.schedule.add_systems(t,...e),this}remove_system(t){var e;this.schedule.remove_system(t),(e=t.on_removed)==null||e.call(t),this.systems.delete(t)}get system_count(){return this.systems.size}startup(){var t;for(const e of this.systems.values())(t=e.on_added)==null||t.call(e,this.store);this.schedule.run_startup(this.ctx)}update(t){this.store.clear_events(),this.schedule.run_update(this.ctx,t)}flush(){this.ctx.flush()}dispose(){var t,e;for(const s of this.systems.values())(t=s.dispose)==null||t.call(s),(e=s.on_removed)==null||e.call(s);this.systems.clear(),this.schedule.clear()}}exports.Query=X;exports.QueryBuilder=G;exports.SCHEDULE=F;exports.SystemContext=W;exports.World=ht;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { World } from './world';
|
|
2
|
+
export { Query, QueryBuilder, SystemContext } from './query';
|
|
3
|
+
export { SCHEDULE } from './schedule';
|
|
4
|
+
export type { EntityID } from './entity';
|
|
5
|
+
export type { ComponentDef, ComponentFields, FieldValues, ColumnsForSchema, } from './component';
|
|
6
|
+
export type { EventDef, EventReader } from './event';
|
|
7
|
+
export type { SystemFn, SystemConfig, SystemDescriptor, } from './system';
|
|
8
|
+
export type { SystemEntry, SystemOrdering } from './schedule';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7D,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGtC,YAAY,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACzC,YAAY,EACV,YAAY,EACZ,eAAe,EACf,WAAW,EACX,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACrD,YAAY,EACV,QAAQ,EACR,YAAY,EACZ,gBAAgB,GACjB,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|