archetype-ecs-lib 0.6.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/lib/ecs/Archetype.d.ts +7 -0
- package/lib/ecs/Archetype.js +7 -0
- package/lib/ecs/Commands.d.ts +12 -4
- package/lib/ecs/Commands.js +10 -32
- package/lib/ecs/Schedule.d.ts +1 -1
- package/lib/ecs/Schedule.js +51 -47
- package/lib/ecs/Signature.d.ts +4 -0
- package/lib/ecs/Signature.js +19 -0
- package/lib/ecs/Types.d.ts +65 -23
- package/lib/ecs/Types.js +12 -0
- package/lib/ecs/World.d.ts +37 -14
- package/lib/ecs/World.js +359 -114
- package/lib/ecs/stats/StatsOverlay.js +4 -1
- package/package.json +9 -8
package/README.md
CHANGED
|
@@ -14,7 +14,8 @@ A tiny **archetype-based ECS** (Entity Component System) for TypeScript.
|
|
|
14
14
|
Exports are defined in `index.ts`:
|
|
15
15
|
- `Types`, `TypeRegistry`, `Commands`, `World`, `Schedule`
|
|
16
16
|
|
|
17
|
-
> :exclamation: The full documentation is at [https://piratejl.github.io/archetype-ecs-lib/](https://piratejl.github.io/archetype-ecs-lib/)
|
|
17
|
+
> :exclamation: The full documentation is at [https://piratejl.github.io/archetype-ecs-lib/](https://piratejl.github.io/archetype-ecs-lib/)
|
|
18
|
+
> Can view the [Changelog here](https://piratejl.github.io/archetype-ecs-lib/changelog/)
|
|
18
19
|
|
|
19
20
|
---
|
|
20
21
|
|
|
@@ -55,7 +56,7 @@ world.addSystem((w) => {
|
|
|
55
56
|
world.update(1 / 60);
|
|
56
57
|
```
|
|
57
58
|
|
|
58
|
-
> Note: `SystemFn` is typed as `(world: WorldApi, dt) => void
|
|
59
|
+
> Note: `SystemFn` is typed as `(world: WorldApi, dt) => void`.
|
|
59
60
|
|
|
60
61
|
---
|
|
61
62
|
|
package/lib/ecs/Archetype.d.ts
CHANGED
|
@@ -4,6 +4,13 @@ export declare class Archetype {
|
|
|
4
4
|
readonly sig: Signature;
|
|
5
5
|
private readonly cols;
|
|
6
6
|
readonly entities: Entity[];
|
|
7
|
+
/**
|
|
8
|
+
* Archetype graph edges: cache destination archetype for single-component add/remove transitions.
|
|
9
|
+
* Key: TypeId being added/removed. Value: destination Archetype after the transition.
|
|
10
|
+
* Populated lazily in World.add() / World.remove().
|
|
11
|
+
*/
|
|
12
|
+
readonly addEdges: Map<number, Archetype>;
|
|
13
|
+
readonly removeEdges: Map<number, Archetype>;
|
|
7
14
|
constructor(id: number, sig: Signature);
|
|
8
15
|
/**
|
|
9
16
|
* Adds a new row. The caller must push per-column values in the same order.
|
package/lib/ecs/Archetype.js
CHANGED
|
@@ -35,6 +35,13 @@ var Archetype = /** @class */ (function () {
|
|
|
35
35
|
this.cols = new Map();
|
|
36
36
|
// Entity handles stored per row
|
|
37
37
|
this.entities = [];
|
|
38
|
+
/**
|
|
39
|
+
* Archetype graph edges: cache destination archetype for single-component add/remove transitions.
|
|
40
|
+
* Key: TypeId being added/removed. Value: destination Archetype after the transition.
|
|
41
|
+
* Populated lazily in World.add() / World.remove().
|
|
42
|
+
*/
|
|
43
|
+
this.addEdges = new Map();
|
|
44
|
+
this.removeEdges = new Map();
|
|
38
45
|
this.id = id;
|
|
39
46
|
this.sig = sig;
|
|
40
47
|
try {
|
package/lib/ecs/Commands.d.ts
CHANGED
|
@@ -10,21 +10,29 @@ export type Command = {
|
|
|
10
10
|
e: Entity;
|
|
11
11
|
ctor: ComponentCtor<any>;
|
|
12
12
|
value: any;
|
|
13
|
+
} | {
|
|
14
|
+
k: "addMany";
|
|
15
|
+
e: Entity;
|
|
16
|
+
items: ComponentCtorBundleItem[];
|
|
13
17
|
} | {
|
|
14
18
|
k: "remove";
|
|
15
19
|
e: Entity;
|
|
16
20
|
ctor: ComponentCtor<any>;
|
|
21
|
+
} | {
|
|
22
|
+
k: "removeMany";
|
|
23
|
+
e: Entity;
|
|
24
|
+
ctors: ComponentCtor<any>[];
|
|
17
25
|
};
|
|
18
26
|
export declare class Commands implements CommandsApi {
|
|
19
27
|
private q;
|
|
20
28
|
spawn(init?: (e: Entity) => void): void;
|
|
21
|
-
|
|
29
|
+
spawnWith(...items: ComponentCtorBundleItem[]): void;
|
|
22
30
|
despawn(e: Entity): void;
|
|
23
|
-
|
|
31
|
+
despawnMany(entities: Entity[]): void;
|
|
24
32
|
add<T>(e: Entity, ctor: ComponentCtor<T>, value: T): void;
|
|
25
|
-
|
|
33
|
+
addMany(e: Entity, ...items: ComponentCtorBundleItem[]): void;
|
|
26
34
|
remove<T>(e: Entity, ctor: ComponentCtor<T>): void;
|
|
27
|
-
|
|
35
|
+
removeMany(e: Entity, ...ctors: ComponentCtor<any>[]): void;
|
|
28
36
|
hasPending(): boolean;
|
|
29
37
|
drain(): Command[];
|
|
30
38
|
}
|
package/lib/ecs/Commands.js
CHANGED
|
@@ -35,7 +35,7 @@ var Commands = /** @class */ (function () {
|
|
|
35
35
|
Commands.prototype.spawn = function (init) {
|
|
36
36
|
this.q.push({ k: "spawn", init: init });
|
|
37
37
|
};
|
|
38
|
-
Commands.prototype.
|
|
38
|
+
Commands.prototype.spawnWith = function () {
|
|
39
39
|
var _this = this;
|
|
40
40
|
var items = [];
|
|
41
41
|
for (var _i = 0; _i < arguments.length; _i++) {
|
|
@@ -62,7 +62,7 @@ var Commands = /** @class */ (function () {
|
|
|
62
62
|
Commands.prototype.despawn = function (e) {
|
|
63
63
|
this.q.push({ k: "despawn", e: e });
|
|
64
64
|
};
|
|
65
|
-
Commands.prototype.
|
|
65
|
+
Commands.prototype.despawnMany = function (entities) {
|
|
66
66
|
var e_2, _a;
|
|
67
67
|
try {
|
|
68
68
|
for (var entities_1 = __values(entities), entities_1_1 = entities_1.next(); !entities_1_1.done; entities_1_1 = entities_1.next()) {
|
|
@@ -81,48 +81,26 @@ var Commands = /** @class */ (function () {
|
|
|
81
81
|
Commands.prototype.add = function (e, ctor, value) {
|
|
82
82
|
this.q.push({ k: "add", e: e, ctor: ctor, value: value });
|
|
83
83
|
};
|
|
84
|
-
Commands.prototype.
|
|
85
|
-
var e_3, _a;
|
|
84
|
+
Commands.prototype.addMany = function (e) {
|
|
86
85
|
var items = [];
|
|
87
86
|
for (var _i = 1; _i < arguments.length; _i++) {
|
|
88
87
|
items[_i - 1] = arguments[_i];
|
|
89
88
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
this.add(e, ctor, value);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
catch (e_3_1) { e_3 = { error: e_3_1 }; }
|
|
97
|
-
finally {
|
|
98
|
-
try {
|
|
99
|
-
if (items_2_1 && !items_2_1.done && (_a = items_2.return)) _a.call(items_2);
|
|
100
|
-
}
|
|
101
|
-
finally { if (e_3) throw e_3.error; }
|
|
102
|
-
}
|
|
89
|
+
if (items.length === 0)
|
|
90
|
+
return;
|
|
91
|
+
this.q.push({ k: "addMany", e: e, items: items });
|
|
103
92
|
};
|
|
104
93
|
Commands.prototype.remove = function (e, ctor) {
|
|
105
94
|
this.q.push({ k: "remove", e: e, ctor: ctor });
|
|
106
95
|
};
|
|
107
|
-
Commands.prototype.
|
|
108
|
-
var e_4, _a;
|
|
96
|
+
Commands.prototype.removeMany = function (e) {
|
|
109
97
|
var ctors = [];
|
|
110
98
|
for (var _i = 1; _i < arguments.length; _i++) {
|
|
111
99
|
ctors[_i - 1] = arguments[_i];
|
|
112
100
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
this.remove(e, ctor);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
catch (e_4_1) { e_4 = { error: e_4_1 }; }
|
|
120
|
-
finally {
|
|
121
|
-
try {
|
|
122
|
-
if (ctors_1_1 && !ctors_1_1.done && (_a = ctors_1.return)) _a.call(ctors_1);
|
|
123
|
-
}
|
|
124
|
-
finally { if (e_4) throw e_4.error; }
|
|
125
|
-
}
|
|
101
|
+
if (ctors.length === 0)
|
|
102
|
+
return;
|
|
103
|
+
this.q.push({ k: "removeMany", e: e, ctors: ctors });
|
|
126
104
|
};
|
|
127
105
|
Commands.prototype.hasPending = function () {
|
|
128
106
|
return this.q.length > 0;
|
package/lib/ecs/Schedule.d.ts
CHANGED
|
@@ -124,7 +124,7 @@ export declare class Schedule {
|
|
|
124
124
|
* - phases explicitly registered via `add()` keep insertion order when unconstrained
|
|
125
125
|
* - remaining ties fall back to lexicographic order
|
|
126
126
|
*
|
|
127
|
-
* @param
|
|
127
|
+
* @param phases Phase -> systems mapping from the World.
|
|
128
128
|
* @returns A valid phase order that satisfies all constraints.
|
|
129
129
|
* @throws If constraints contain a cycle.
|
|
130
130
|
* @internal
|
package/lib/ecs/Schedule.js
CHANGED
|
@@ -177,67 +177,72 @@ var Schedule = /** @class */ (function () {
|
|
|
177
177
|
if (worldInstance._getSystemCount() > 0) {
|
|
178
178
|
worldInstance._warnAboutLifecycleConflict("Schedule.run");
|
|
179
179
|
}
|
|
180
|
-
var
|
|
180
|
+
var scheduleSystems = worldInstance._scheduleSystems;
|
|
181
|
+
var phases = (_c = phaseOrder !== null && phaseOrder !== void 0 ? phaseOrder : this.phaseOrder) !== null && _c !== void 0 ? _c : this._computePhaseOrder(scheduleSystems);
|
|
181
182
|
if (!phases || phases.length === 0) {
|
|
182
183
|
throw new Error('Schedule.run requires a phase order (pass it as an argument or call schedule.setOrder([...]))');
|
|
183
184
|
}
|
|
184
185
|
var frameStart = worldInstance._profBeginFrame(dt);
|
|
185
186
|
try {
|
|
186
|
-
|
|
187
|
-
var
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
var
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
error.message
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
187
|
+
try {
|
|
188
|
+
for (var phases_1 = __values(phases), phases_1_1 = phases_1.next(); !phases_1_1.done; phases_1_1 = phases_1.next()) {
|
|
189
|
+
var phase = phases_1_1.value;
|
|
190
|
+
var phaseStart = performance.now();
|
|
191
|
+
var list = scheduleSystems.get(phase);
|
|
192
|
+
// Run systems only if they exist for this phase
|
|
193
|
+
if (list) {
|
|
194
|
+
try {
|
|
195
|
+
for (var list_1 = (e_2 = void 0, __values(list)), list_1_1 = list_1.next(); !list_1_1.done; list_1_1 = list_1.next()) {
|
|
196
|
+
var fn = list_1_1.value;
|
|
197
|
+
var sysStart = performance.now();
|
|
198
|
+
try {
|
|
199
|
+
fn(world, dt);
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
var sysName = fn.name && fn.name.length > 0 ? fn.name : "<anonymous>";
|
|
203
|
+
var msg = error.message !== undefined && typeof error.message === 'string' ?
|
|
204
|
+
error.message : JSON.stringify(error);
|
|
205
|
+
var e = new Error("[phase=".concat(phase, " system=").concat(sysName, "] ").concat(msg));
|
|
206
|
+
e.cause = error;
|
|
207
|
+
throw e;
|
|
208
|
+
}
|
|
209
|
+
finally {
|
|
210
|
+
var sysName = fn.name && fn.name.length > 0 ? fn.name : "<anonymous>";
|
|
211
|
+
worldInstance._profAddSystem("".concat(phase, ":").concat(sysName), performance.now() - sysStart);
|
|
212
|
+
}
|
|
206
213
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
214
|
+
}
|
|
215
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
216
|
+
finally {
|
|
217
|
+
try {
|
|
218
|
+
if (list_1_1 && !list_1_1.done && (_b = list_1.return)) _b.call(list_1);
|
|
210
219
|
}
|
|
220
|
+
finally { if (e_2) throw e_2.error; }
|
|
211
221
|
}
|
|
212
222
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
223
|
+
if (this.boundaryMode !== "manual") {
|
|
224
|
+
// apply deferred commands between phases
|
|
225
|
+
if (world.cmd().hasPending()) {
|
|
226
|
+
world.flush();
|
|
217
227
|
}
|
|
218
|
-
|
|
228
|
+
// deliver events emitted in this phase to the next phase
|
|
229
|
+
world.swapEvents();
|
|
219
230
|
}
|
|
231
|
+
worldInstance._profAddPhase(phase, performance.now() - phaseStart);
|
|
220
232
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
// deliver events emitted in this phase to the next phase
|
|
227
|
-
world.swapEvents();
|
|
233
|
+
}
|
|
234
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
235
|
+
finally {
|
|
236
|
+
try {
|
|
237
|
+
if (phases_1_1 && !phases_1_1.done && (_a = phases_1.return)) _a.call(phases_1);
|
|
228
238
|
}
|
|
229
|
-
|
|
239
|
+
finally { if (e_1) throw e_1.error; }
|
|
230
240
|
}
|
|
231
241
|
}
|
|
232
|
-
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
233
242
|
finally {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
finally { if (e_1) throw e_1.error; }
|
|
243
|
+
worldInstance._profEndFrame(frameStart);
|
|
244
|
+
worldInstance.updateOverlay(worldInstance.stats(), worldInstance.statsHistory());
|
|
238
245
|
}
|
|
239
|
-
worldInstance._profEndFrame(frameStart);
|
|
240
|
-
worldInstance.updateOverlay(worldInstance.stats(), worldInstance.statsHistory());
|
|
241
246
|
};
|
|
242
247
|
/**
|
|
243
248
|
* Record a phase ordering constraint `before -> after`.
|
|
@@ -266,15 +271,14 @@ var Schedule = /** @class */ (function () {
|
|
|
266
271
|
* - phases explicitly registered via `add()` keep insertion order when unconstrained
|
|
267
272
|
* - remaining ties fall back to lexicographic order
|
|
268
273
|
*
|
|
269
|
-
* @param
|
|
274
|
+
* @param phases Phase -> systems mapping from the World.
|
|
270
275
|
* @returns A valid phase order that satisfies all constraints.
|
|
271
276
|
* @throws If constraints contain a cycle.
|
|
272
277
|
* @internal
|
|
273
278
|
*/
|
|
274
|
-
Schedule.prototype._computePhaseOrder = function (
|
|
279
|
+
Schedule.prototype._computePhaseOrder = function (phases) {
|
|
275
280
|
var e_3, _a, e_4, _b, e_5, _c, e_6, _d, e_7, _e, e_8, _f, e_9, _g, e_10, _h, e_11, _j;
|
|
276
281
|
var _k, _l;
|
|
277
|
-
var phases = world._scheduleSystems;
|
|
278
282
|
// Collect all known phases (defined or referenced by constraints)
|
|
279
283
|
var all = new Set();
|
|
280
284
|
try {
|
package/lib/ecs/Signature.d.ts
CHANGED
|
@@ -6,3 +6,7 @@ export declare function subtractSignature(sig: Signature, remove: TypeId): TypeI
|
|
|
6
6
|
* True if `need` is a subset of `have`. Both must be sorted ascending.
|
|
7
7
|
*/
|
|
8
8
|
export declare function signatureHasAll(have: Signature, need: Signature): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* True if `have` contains at least one element from `any`. Both must be sorted ascending.
|
|
11
|
+
*/
|
|
12
|
+
export declare function signatureHasAny(have: Signature, any: Signature): boolean;
|
package/lib/ecs/Signature.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.signatureKey = signatureKey;
|
|
|
4
4
|
exports.mergeSignature = mergeSignature;
|
|
5
5
|
exports.subtractSignature = subtractSignature;
|
|
6
6
|
exports.signatureHasAll = signatureHasAll;
|
|
7
|
+
exports.signatureHasAny = signatureHasAny;
|
|
7
8
|
function signatureKey(sig) {
|
|
8
9
|
// canonical: sorted ascending, joined
|
|
9
10
|
return sig.join(",");
|
|
@@ -48,3 +49,21 @@ function signatureHasAll(have, need) {
|
|
|
48
49
|
}
|
|
49
50
|
return j === need.length;
|
|
50
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* True if `have` contains at least one element from `any`. Both must be sorted ascending.
|
|
54
|
+
*/
|
|
55
|
+
function signatureHasAny(have, any) {
|
|
56
|
+
var i = 0;
|
|
57
|
+
var j = 0;
|
|
58
|
+
while (i < have.length && j < any.length) {
|
|
59
|
+
var a = have[i];
|
|
60
|
+
var b = any[j];
|
|
61
|
+
if (a === b)
|
|
62
|
+
return true;
|
|
63
|
+
if (a < b)
|
|
64
|
+
i++;
|
|
65
|
+
else
|
|
66
|
+
j++;
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
package/lib/ecs/Types.d.ts
CHANGED
|
@@ -18,12 +18,47 @@ export type Signature = ReadonlyArray<TypeId>;
|
|
|
18
18
|
*/
|
|
19
19
|
export type ComponentCtor<T> = (new (...args: any[]) => T) | ((...args: any[]) => T);
|
|
20
20
|
export type ComponentCtorBundleItem<T = any> = readonly [ComponentCtor<T>, T];
|
|
21
|
+
/**
|
|
22
|
+
* A reusable, named group of component/value pairs.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* const PhysicsBundle = bundle([Position, { x: 0, y: 0 }], [Velocity, { x: 0, y: 0 }]);
|
|
27
|
+
* world.spawnWith(...PhysicsBundle);
|
|
28
|
+
* world.cmd().spawnWith(...PhysicsBundle);
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export type Bundle = readonly ComponentCtorBundleItem[];
|
|
32
|
+
/**
|
|
33
|
+
* Create a typed, reusable bundle (a list of component/value pairs).
|
|
34
|
+
* Bundles can be spread into `spawnWith`, `addMany`, or `cmd().spawnWith`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function bundle(...items: ComponentCtorBundleItem[]): Bundle;
|
|
21
37
|
/**
|
|
22
38
|
* Internal numeric id for a component "type".
|
|
23
39
|
* (We keep it numeric so signatures can be sorted quickly.)
|
|
24
40
|
*/
|
|
25
41
|
export type TypeId = number;
|
|
26
42
|
export type SystemFn = (world: WorldApi, dt: number) => void;
|
|
43
|
+
/**
|
|
44
|
+
* Optional filter applied to any query.
|
|
45
|
+
*
|
|
46
|
+
* - `without`: exclude archetypes that have ANY of these component types.
|
|
47
|
+
* - `with`: require these component types to be present without returning their values.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* // Entities with Position but NOT Dead
|
|
52
|
+
* world.query(Position, { without: [Dead] })
|
|
53
|
+
*
|
|
54
|
+
* // Entities with Position AND Active marker, but NOT Frozen
|
|
55
|
+
* world.query(Position, { with: [Active], without: [Frozen] })
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export type QueryFilter = {
|
|
59
|
+
readonly without?: ReadonlyArray<ComponentCtor<any>>;
|
|
60
|
+
readonly with?: ReadonlyArray<ComponentCtor<any>>;
|
|
61
|
+
};
|
|
27
62
|
export type SnapshotCodec<T, D = unknown> = Readonly<{
|
|
28
63
|
/**
|
|
29
64
|
* Stable key written into the snapshot payload.
|
|
@@ -63,13 +98,13 @@ export type WorldSnapshot = Readonly<{
|
|
|
63
98
|
*/
|
|
64
99
|
export interface CommandsApi {
|
|
65
100
|
spawn(init?: (e: Entity) => void): void;
|
|
66
|
-
|
|
101
|
+
spawnWith(...items: ComponentCtorBundleItem[]): void;
|
|
67
102
|
despawn(e: Entity): void;
|
|
68
|
-
|
|
103
|
+
despawnMany(entities: Entity[]): void;
|
|
69
104
|
add<T>(e: Entity, ctor: ComponentCtor<T>, value: T): void;
|
|
70
|
-
|
|
105
|
+
addMany(e: Entity, ...items: ComponentCtorBundleItem[]): void;
|
|
71
106
|
remove<T>(e: Entity, ctor: ComponentCtor<T>): void;
|
|
72
|
-
|
|
107
|
+
removeMany(e: Entity, ...ctors: ComponentCtor<any>[]): void;
|
|
73
108
|
hasPending(): boolean;
|
|
74
109
|
}
|
|
75
110
|
export type QueryRow1<A> = {
|
|
@@ -186,14 +221,15 @@ export type WorldStatsHistory = Readonly<{
|
|
|
186
221
|
export interface WorldApi {
|
|
187
222
|
stats(): WorldStats;
|
|
188
223
|
statsHistory(): WorldStatsHistory;
|
|
189
|
-
/** @internal Phase -> systems mapping for Schedule */
|
|
190
|
-
readonly _scheduleSystems: Map<string, SystemFn[]>;
|
|
191
|
-
/** @internal Returns the number of systems registered via addSystem() */
|
|
192
|
-
_getSystemCount(): number;
|
|
193
224
|
/** Enables/disables profiling (system/phase timing). */
|
|
194
225
|
setProfilingEnabled(enabled: boolean): void;
|
|
195
226
|
/** Set how many frames of the profiling history to keep (default: 120). */
|
|
196
227
|
setProfilingHistorySize(frames: number): void;
|
|
228
|
+
/**
|
|
229
|
+
* Destroy this world, releasing all archetypes, entities, resources, and event channels.
|
|
230
|
+
* Any further use of the world after calling this will throw.
|
|
231
|
+
*/
|
|
232
|
+
destroy(): void;
|
|
197
233
|
cmd(): CommandsApi;
|
|
198
234
|
addSystem(fn: SystemFn): this;
|
|
199
235
|
update(dt: number): void;
|
|
@@ -254,7 +290,7 @@ export interface WorldApi {
|
|
|
254
290
|
*/
|
|
255
291
|
swapEvents(): void;
|
|
256
292
|
spawn(): Entity;
|
|
257
|
-
|
|
293
|
+
spawnWith(...items: ComponentCtorBundleItem[]): Entity;
|
|
258
294
|
despawn(e: Entity): void;
|
|
259
295
|
despawnMany(entities: Entity[]): void;
|
|
260
296
|
isAlive(e: Entity): boolean;
|
|
@@ -265,25 +301,31 @@ export interface WorldApi {
|
|
|
265
301
|
has<T>(e: Entity, ctor: ComponentCtor<T>): boolean;
|
|
266
302
|
get<T>(e: Entity, ctor: ComponentCtor<T>): T | undefined;
|
|
267
303
|
set<T>(e: Entity, ctor: ComponentCtor<T>, value: T): void;
|
|
268
|
-
query<A>(c1: ComponentCtor<A
|
|
269
|
-
query<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B
|
|
270
|
-
query<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C
|
|
271
|
-
query<A, B, C, D>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D
|
|
272
|
-
query<A, B, C, D, E>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E
|
|
273
|
-
query<A, B, C, D, E, F>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, c6: ComponentCtor<F
|
|
274
|
-
query(...
|
|
275
|
-
queryTables<A>(c1: ComponentCtor<A
|
|
276
|
-
queryTables<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B
|
|
277
|
-
queryTables<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C
|
|
278
|
-
queryTables<A, B, C, D>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D
|
|
279
|
-
queryTables<A, B, C, D, E>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E
|
|
280
|
-
queryTables<A, B, C, D, E, F>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, c6: ComponentCtor<F
|
|
281
|
-
queryTables(...
|
|
304
|
+
query<A>(c1: ComponentCtor<A>, filter?: QueryFilter): Iterable<QueryRow1<A>>;
|
|
305
|
+
query<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, filter?: QueryFilter): Iterable<QueryRow2<A, B>>;
|
|
306
|
+
query<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, filter?: QueryFilter): Iterable<QueryRow3<A, B, C>>;
|
|
307
|
+
query<A, B, C, D>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, filter?: QueryFilter): Iterable<QueryRow4<A, B, C, D>>;
|
|
308
|
+
query<A, B, C, D, E>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, filter?: QueryFilter): Iterable<QueryRow5<A, B, C, D, E>>;
|
|
309
|
+
query<A, B, C, D, E, F>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, c6: ComponentCtor<F>, filter?: QueryFilter): Iterable<QueryRow6<A, B, C, D, E, F>>;
|
|
310
|
+
query(...args: any[]): Iterable<any>;
|
|
311
|
+
queryTables<A>(c1: ComponentCtor<A>, filter?: QueryFilter): Iterable<QueryTable1<A>>;
|
|
312
|
+
queryTables<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, filter?: QueryFilter): Iterable<QueryTable2<A, B>>;
|
|
313
|
+
queryTables<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, filter?: QueryFilter): Iterable<QueryTable3<A, B, C>>;
|
|
314
|
+
queryTables<A, B, C, D>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, filter?: QueryFilter): Iterable<QueryTable4<A, B, C, D>>;
|
|
315
|
+
queryTables<A, B, C, D, E>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, filter?: QueryFilter): Iterable<QueryTable5<A, B, C, D, E>>;
|
|
316
|
+
queryTables<A, B, C, D, E, F>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, c6: ComponentCtor<F>, filter?: QueryFilter): Iterable<QueryTable6<A, B, C, D, E, F>>;
|
|
317
|
+
queryTables(...args: any[]): Iterable<any>;
|
|
282
318
|
queryEach<A>(c1: ComponentCtor<A>, fn: (e: Entity, c1: A) => void): void;
|
|
319
|
+
queryEach<A>(c1: ComponentCtor<A>, filter: QueryFilter, fn: (e: Entity, c1: A) => void): void;
|
|
283
320
|
queryEach<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, fn: (e: Entity, c1: A, c2: B) => void): void;
|
|
321
|
+
queryEach<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, filter: QueryFilter, fn: (e: Entity, c1: A, c2: B) => void): void;
|
|
284
322
|
queryEach<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, fn: (e: Entity, c1: A, c2: B, c3: C) => void): void;
|
|
323
|
+
queryEach<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, filter: QueryFilter, fn: (e: Entity, c1: A, c2: B, c3: C) => void): void;
|
|
285
324
|
queryEach<A, B, C, D>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, fn: (e: Entity, c1: A, c2: B, c3: C, c4: D) => void): void;
|
|
325
|
+
queryEach<A, B, C, D>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, filter: QueryFilter, fn: (e: Entity, c1: A, c2: B, c3: C, c4: D) => void): void;
|
|
286
326
|
queryEach<A, B, C, D, E>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, fn: (e: Entity, c1: A, c2: B, c3: C, c4: D, c5: E) => void): void;
|
|
327
|
+
queryEach<A, B, C, D, E>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, filter: QueryFilter, fn: (e: Entity, c1: A, c2: B, c3: C, c4: D, c5: E) => void): void;
|
|
287
328
|
queryEach<A, B, C, D, E, F>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, c6: ComponentCtor<F>, fn: (e: Entity, c1: A, c2: B, c3: C, c4: D, c5: E, c6: F) => void): void;
|
|
329
|
+
queryEach<A, B, C, D, E, F>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, c6: ComponentCtor<F>, filter: QueryFilter, fn: (e: Entity, c1: A, c2: B, c3: C, c4: D, c5: E, c6: F) => void): void;
|
|
288
330
|
queryEach(...args: any[]): void;
|
|
289
331
|
}
|
package/lib/ecs/Types.js
CHANGED
|
@@ -1,2 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.bundle = bundle;
|
|
4
|
+
/**
|
|
5
|
+
* Create a typed, reusable bundle (a list of component/value pairs).
|
|
6
|
+
* Bundles can be spread into `spawnWith`, `addMany`, or `cmd().spawnWith`.
|
|
7
|
+
*/
|
|
8
|
+
function bundle() {
|
|
9
|
+
var items = [];
|
|
10
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
11
|
+
items[_i] = arguments[_i];
|
|
12
|
+
}
|
|
13
|
+
return items;
|
|
14
|
+
}
|
package/lib/ecs/World.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Commands } from "./Commands";
|
|
2
2
|
import { EventChannel } from "./Events";
|
|
3
|
-
import type { ComponentCtor, ComponentCtorBundleItem, Entity, QueryRow1, QueryRow2, QueryRow3, QueryRow4, QueryRow5, QueryRow6, QueryTable1, QueryTable2, QueryTable3, QueryTable4, QueryTable5, QueryTable6, SnapshotCodec, SystemFn, WorldApi, WorldSnapshot, WorldStats, WorldStatsHistory } from "./Types";
|
|
3
|
+
import type { ComponentCtor, ComponentCtorBundleItem, Entity, QueryFilter, QueryRow1, QueryRow2, QueryRow3, QueryRow4, QueryRow5, QueryRow6, QueryTable1, QueryTable2, QueryTable3, QueryTable4, QueryTable5, QueryTable6, SnapshotCodec, SystemFn, WorldApi, WorldSnapshot, WorldStats, WorldStatsHistory } from "./Types";
|
|
4
4
|
import { StatsOverlay, type StatsOverlayOptions } from "./stats/StatsOverlay";
|
|
5
5
|
export declare class World extends StatsOverlay implements WorldApi {
|
|
6
6
|
private readonly entities;
|
|
@@ -12,8 +12,15 @@ export declare class World extends StatsOverlay implements WorldApi {
|
|
|
12
12
|
private readonly resources;
|
|
13
13
|
private readonly eventChannels;
|
|
14
14
|
private readonly snapshotStore;
|
|
15
|
+
/**
|
|
16
|
+
* Query result cache: maps a sorted query signature key → matching archetypes.
|
|
17
|
+
* `checked` tracks how many archetypes were scanned when the entry was last updated.
|
|
18
|
+
* Because archetypes are append-only, new ones are scanned incrementally on next use.
|
|
19
|
+
*/
|
|
20
|
+
private readonly _queryCache;
|
|
15
21
|
/** @internal Phase -> systems mapping for Schedule */
|
|
16
22
|
readonly _scheduleSystems: Map<string, SystemFn[]>;
|
|
23
|
+
private _destroyed;
|
|
17
24
|
constructor(options?: {
|
|
18
25
|
statsOverlayOptions: StatsOverlayOptions;
|
|
19
26
|
});
|
|
@@ -50,6 +57,7 @@ export declare class World extends StatsOverlay implements WorldApi {
|
|
|
50
57
|
*/
|
|
51
58
|
update(dt: number): void;
|
|
52
59
|
flush(): void;
|
|
60
|
+
destroy(): void;
|
|
53
61
|
registerComponentSnapshot<T, D = unknown>(key: ComponentCtor<T>, codec: SnapshotCodec<T, D>): this;
|
|
54
62
|
unregisterComponentSnapshot<T>(key: ComponentCtor<T>): boolean;
|
|
55
63
|
registerResourceSnapshot<T, D = unknown>(key: ComponentCtor<T>, codec: SnapshotCodec<T, D>): this;
|
|
@@ -69,7 +77,7 @@ export declare class World extends StatsOverlay implements WorldApi {
|
|
|
69
77
|
/** @internal Called by Schedule at phase boundaries */
|
|
70
78
|
swapEvents(): void;
|
|
71
79
|
spawn(): Entity;
|
|
72
|
-
|
|
80
|
+
spawnWith(...items: ComponentCtorBundleItem[]): Entity;
|
|
73
81
|
isAlive(e: Entity): boolean;
|
|
74
82
|
despawn(e: Entity): void;
|
|
75
83
|
despawnMany(entities: Entity[]): void;
|
|
@@ -84,34 +92,49 @@ export declare class World extends StatsOverlay implements WorldApi {
|
|
|
84
92
|
* Query all entities having all required component types.
|
|
85
93
|
* Iterates archetypes (tables) and yields SoA columns for cache-friendly loops.
|
|
86
94
|
*/
|
|
87
|
-
query<A>(c1: ComponentCtor<A
|
|
88
|
-
query<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B
|
|
89
|
-
query<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C
|
|
90
|
-
query<A, B, C, D>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D
|
|
91
|
-
query<A, B, C, D, E>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E
|
|
92
|
-
query<A, B, C, D, E, F>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, c6: ComponentCtor<F
|
|
95
|
+
query<A>(c1: ComponentCtor<A>, filter?: QueryFilter): Iterable<QueryRow1<A>>;
|
|
96
|
+
query<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, filter?: QueryFilter): Iterable<QueryRow2<A, B>>;
|
|
97
|
+
query<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, filter?: QueryFilter): Iterable<QueryRow3<A, B, C>>;
|
|
98
|
+
query<A, B, C, D>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, filter?: QueryFilter): Iterable<QueryRow4<A, B, C, D>>;
|
|
99
|
+
query<A, B, C, D, E>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, filter?: QueryFilter): Iterable<QueryRow5<A, B, C, D, E>>;
|
|
100
|
+
query<A, B, C, D, E, F>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, c6: ComponentCtor<F>, filter?: QueryFilter): Iterable<QueryRow6<A, B, C, D, E, F>>;
|
|
93
101
|
/**
|
|
94
102
|
* Table query: yields one item per matching archetype (SoA columns + entity array).
|
|
95
103
|
* This avoids allocating one object per entity row.
|
|
96
104
|
*/
|
|
97
|
-
queryTables<A>(c1: ComponentCtor<A
|
|
98
|
-
queryTables<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B
|
|
99
|
-
queryTables<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C
|
|
100
|
-
queryTables<A, B, C, D>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D
|
|
101
|
-
queryTables<A, B, C, D, E>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E
|
|
102
|
-
queryTables<A, B, C, D, E, F>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, c6: ComponentCtor<F
|
|
105
|
+
queryTables<A>(c1: ComponentCtor<A>, filter?: QueryFilter): Iterable<QueryTable1<A>>;
|
|
106
|
+
queryTables<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, filter?: QueryFilter): Iterable<QueryTable2<A, B>>;
|
|
107
|
+
queryTables<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, filter?: QueryFilter): Iterable<QueryTable3<A, B, C>>;
|
|
108
|
+
queryTables<A, B, C, D>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, filter?: QueryFilter): Iterable<QueryTable4<A, B, C, D>>;
|
|
109
|
+
queryTables<A, B, C, D, E>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, filter?: QueryFilter): Iterable<QueryTable5<A, B, C, D, E>>;
|
|
110
|
+
queryTables<A, B, C, D, E, F>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, c6: ComponentCtor<F>, filter?: QueryFilter): Iterable<QueryTable6<A, B, C, D, E, F>>;
|
|
103
111
|
/**
|
|
104
112
|
* Callback query: calls `fn` for each matching entity row (no yield object allocations).
|
|
105
113
|
*/
|
|
106
114
|
queryEach<A>(c1: ComponentCtor<A>, fn: (e: Entity, c1: A) => void): void;
|
|
115
|
+
queryEach<A>(c1: ComponentCtor<A>, filter: QueryFilter, fn: (e: Entity, c1: A) => void): void;
|
|
107
116
|
queryEach<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, fn: (e: Entity, c1: A, c2: B) => void): void;
|
|
117
|
+
queryEach<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, filter: QueryFilter, fn: (e: Entity, c1: A, c2: B) => void): void;
|
|
108
118
|
queryEach<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, fn: (e: Entity, c1: A, c2: B, c3: C) => void): void;
|
|
119
|
+
queryEach<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, filter: QueryFilter, fn: (e: Entity, c1: A, c2: B, c3: C) => void): void;
|
|
109
120
|
queryEach<A, B, C, D>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, fn: (e: Entity, c1: A, c2: B, c3: C, c4: D) => void): void;
|
|
121
|
+
queryEach<A, B, C, D>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, filter: QueryFilter, fn: (e: Entity, c1: A, c2: B, c3: C, c4: D) => void): void;
|
|
110
122
|
queryEach<A, B, C, D, E>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, fn: (e: Entity, c1: A, c2: B, c3: C, c4: D, c5: E) => void): void;
|
|
123
|
+
queryEach<A, B, C, D, E>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, filter: QueryFilter, fn: (e: Entity, c1: A, c2: B, c3: C, c4: D, c5: E) => void): void;
|
|
111
124
|
queryEach<A, B, C, D, E, F>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, c6: ComponentCtor<F>, fn: (e: Entity, c1: A, c2: B, c3: C, c4: D, c5: E, c6: F) => void): void;
|
|
125
|
+
queryEach<A, B, C, D, E, F>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, c6: ComponentCtor<F>, filter: QueryFilter, fn: (e: Entity, c1: A, c2: B, c3: C, c4: D, c5: E, c6: F) => void): void;
|
|
112
126
|
private _snapshotRuntime;
|
|
113
127
|
private _resetArchetypes;
|
|
128
|
+
private static _isQueryFilter;
|
|
129
|
+
private static _splitArgs;
|
|
130
|
+
/**
|
|
131
|
+
* Returns the list of archetypes matching `needSorted` (a sorted, deduped TypeId array).
|
|
132
|
+
* Results are cached per query signature. Because archetypes are append-only, stale entries
|
|
133
|
+
* are extended incrementally — only newly-added archetypes are re-scanned.
|
|
134
|
+
*/
|
|
135
|
+
private _matchingArchetypes;
|
|
114
136
|
private static _buildQueryTypeIds;
|
|
137
|
+
private _ensureNotDestroyed;
|
|
115
138
|
private _ensureNotIterating;
|
|
116
139
|
private _getOrCreateArchetype;
|
|
117
140
|
private _removeFromArchetype;
|