@codehz/ecs 0.5.1 → 0.5.2
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/{world.d.mts → builder.d.mts} +103 -465
- package/index.d.mts +1 -1
- package/index.mjs +1 -1
- package/package.json +1 -1
- package/testing.d.mts +2 -2
- package/testing.mjs +2 -2
- package/testing.mjs.map +1 -1
- package/world.mjs +1192 -1540
- package/world.mjs.map +1 -1
package/world.mjs
CHANGED
|
@@ -1,101 +1,4 @@
|
|
|
1
|
-
//#region src/
|
|
2
|
-
var BitSet = class {
|
|
3
|
-
data;
|
|
4
|
-
_length;
|
|
5
|
-
constructor(length) {
|
|
6
|
-
this._length = length;
|
|
7
|
-
const numWords = Math.ceil(length / 32);
|
|
8
|
-
this.data = new Uint32Array(numWords);
|
|
9
|
-
}
|
|
10
|
-
get length() {
|
|
11
|
-
return this._length;
|
|
12
|
-
}
|
|
13
|
-
has(index) {
|
|
14
|
-
if (index < 0 || index >= this._length) return false;
|
|
15
|
-
const word = index >>> 5;
|
|
16
|
-
const bit = index & 31;
|
|
17
|
-
return (this.data[word] >>> bit & 1) !== 0;
|
|
18
|
-
}
|
|
19
|
-
set(index) {
|
|
20
|
-
if (index < 0 || index >= this._length) return;
|
|
21
|
-
const word = index >>> 5;
|
|
22
|
-
const bit = index & 31;
|
|
23
|
-
this.data[word] |= 1 << bit;
|
|
24
|
-
}
|
|
25
|
-
clear(index) {
|
|
26
|
-
if (index < 0 || index >= this._length) return;
|
|
27
|
-
const word = index >>> 5;
|
|
28
|
-
const bit = index & 31;
|
|
29
|
-
this.data[word] &= ~(1 << bit);
|
|
30
|
-
}
|
|
31
|
-
setRange(lo, hi) {
|
|
32
|
-
if (lo > hi) return;
|
|
33
|
-
if (lo < 0) lo = 0;
|
|
34
|
-
if (hi >= this._length) hi = this._length - 1;
|
|
35
|
-
const firstWord = lo >>> 5;
|
|
36
|
-
const lastWord = hi >>> 5;
|
|
37
|
-
const loBit = lo & 31;
|
|
38
|
-
const hiBit = hi & 31;
|
|
39
|
-
const maskFor = (a, b) => {
|
|
40
|
-
const width = b - a + 1;
|
|
41
|
-
if (width <= 0) return 0;
|
|
42
|
-
if (width >= 32) return 4294967295;
|
|
43
|
-
return (1 << width) - 1 << a >>> 0;
|
|
44
|
-
};
|
|
45
|
-
if (firstWord === lastWord) {
|
|
46
|
-
const mask = maskFor(loBit, hiBit);
|
|
47
|
-
this.data[firstWord] = (this.data[firstWord] | mask) >>> 0;
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
const firstMask = maskFor(loBit, 31);
|
|
51
|
-
this.data[firstWord] = (this.data[firstWord] | firstMask) >>> 0;
|
|
52
|
-
for (let w = firstWord + 1; w <= lastWord - 1; w++) this.data[w] = 4294967295;
|
|
53
|
-
const lastMask = maskFor(0, hiBit);
|
|
54
|
-
this.data[lastWord] = (this.data[lastWord] | lastMask) >>> 0;
|
|
55
|
-
}
|
|
56
|
-
anyClearInRange(lo, hi) {
|
|
57
|
-
if (lo > hi) return false;
|
|
58
|
-
if (lo < 0) lo = 0;
|
|
59
|
-
if (hi >= this._length) hi = this._length - 1;
|
|
60
|
-
const firstWord = lo >>> 5;
|
|
61
|
-
const lastWord = hi >>> 5;
|
|
62
|
-
const loBit = lo & 31;
|
|
63
|
-
const hiBit = hi & 31;
|
|
64
|
-
const maskFor = (a, b) => {
|
|
65
|
-
const width = b - a + 1;
|
|
66
|
-
if (width <= 0) return 0;
|
|
67
|
-
if (width >= 32) return 4294967295;
|
|
68
|
-
return (1 << width) - 1 << a >>> 0;
|
|
69
|
-
};
|
|
70
|
-
if (firstWord === lastWord) {
|
|
71
|
-
const mask = maskFor(loBit, hiBit);
|
|
72
|
-
return (this.data[firstWord] & mask) >>> 0 !== mask >>> 0;
|
|
73
|
-
}
|
|
74
|
-
const firstMask = maskFor(loBit, 31);
|
|
75
|
-
if ((this.data[firstWord] & firstMask) >>> 0 !== firstMask >>> 0) return true;
|
|
76
|
-
for (let w = firstWord + 1; w <= lastWord - 1; w++) if (this.data[w] !== 4294967295) return true;
|
|
77
|
-
const lastMask = maskFor(0, hiBit);
|
|
78
|
-
if ((this.data[lastWord] & lastMask) >>> 0 !== lastMask >>> 0) return true;
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
reset() {
|
|
82
|
-
this.data.fill(0);
|
|
83
|
-
}
|
|
84
|
-
*[Symbol.iterator]() {
|
|
85
|
-
for (let wordIndex = 0; wordIndex < this.data.length; wordIndex++) {
|
|
86
|
-
let word = this.data[wordIndex];
|
|
87
|
-
if (word === 0) continue;
|
|
88
|
-
const baseIndex = wordIndex * 32;
|
|
89
|
-
for (let bit = 0; bit < 32 && baseIndex + bit < this._length; bit++) {
|
|
90
|
-
if (word & 1) yield baseIndex + bit;
|
|
91
|
-
word >>>= 1;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
//#endregion
|
|
98
|
-
//#region src/entity.ts
|
|
1
|
+
//#region src/core/entity-types.ts
|
|
99
2
|
const COMPONENT_ID_MAX = 1023;
|
|
100
3
|
const ENTITY_ID_START = 1024;
|
|
101
4
|
/**
|
|
@@ -104,34 +7,11 @@ const ENTITY_ID_START = 1024;
|
|
|
104
7
|
const RELATION_SHIFT = 2 ** 42;
|
|
105
8
|
const WILDCARD_TARGET_ID = 0;
|
|
106
9
|
/**
|
|
107
|
-
* Internal function to decode a relation ID into raw component and target IDs
|
|
108
|
-
* @param id The EntityId to decode
|
|
109
|
-
* @returns Object with componentId and targetId, or null if not a relation
|
|
110
|
-
*/
|
|
111
|
-
function decodeRelationRaw(id) {
|
|
112
|
-
if (id >= 0) return null;
|
|
113
|
-
const absId = -id;
|
|
114
|
-
return {
|
|
115
|
-
componentId: Math.floor(absId / RELATION_SHIFT),
|
|
116
|
-
targetId: absId % RELATION_SHIFT
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
10
|
* Check if a component ID is valid (1-1023)
|
|
121
11
|
*/
|
|
122
12
|
function isValidComponentId(componentId) {
|
|
123
13
|
return componentId >= 1 && componentId <= COMPONENT_ID_MAX;
|
|
124
14
|
}
|
|
125
|
-
function relation(componentId, targetId) {
|
|
126
|
-
if (!isComponentId(componentId)) throw new Error("First argument must be a valid component ID");
|
|
127
|
-
let actualTargetId;
|
|
128
|
-
if (targetId === "*") actualTargetId = WILDCARD_TARGET_ID;
|
|
129
|
-
else {
|
|
130
|
-
if (!isEntityId(targetId) && !isComponentId(targetId)) throw new Error("Second argument must be a valid entity ID, component ID, or '*'");
|
|
131
|
-
actualTargetId = targetId;
|
|
132
|
-
}
|
|
133
|
-
return -(componentId * RELATION_SHIFT + actualTargetId);
|
|
134
|
-
}
|
|
135
15
|
/**
|
|
136
16
|
* Check if an ID is a component ID
|
|
137
17
|
*/
|
|
@@ -150,6 +30,32 @@ function isEntityId(id) {
|
|
|
150
30
|
function isRelationId(id) {
|
|
151
31
|
return id < 0;
|
|
152
32
|
}
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/core/entity-relation.ts
|
|
36
|
+
/**
|
|
37
|
+
* Internal function to decode a relation ID into raw component and target IDs
|
|
38
|
+
* @param id The EntityId to decode
|
|
39
|
+
* @returns Object with componentId and targetId, or null if not a relation
|
|
40
|
+
*/
|
|
41
|
+
function decodeRelationRaw(id) {
|
|
42
|
+
if (id >= 0) return null;
|
|
43
|
+
const absId = -id;
|
|
44
|
+
return {
|
|
45
|
+
componentId: Math.floor(absId / RELATION_SHIFT),
|
|
46
|
+
targetId: absId % RELATION_SHIFT
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function relation(componentId, targetId) {
|
|
50
|
+
if (!isComponentId(componentId)) throw new Error("First argument must be a valid component ID");
|
|
51
|
+
let actualTargetId;
|
|
52
|
+
if (targetId === "*") actualTargetId = WILDCARD_TARGET_ID;
|
|
53
|
+
else {
|
|
54
|
+
if (!isEntityId(targetId) && !isComponentId(targetId)) throw new Error("Second argument must be a valid entity ID, component ID, or '*'");
|
|
55
|
+
actualTargetId = targetId;
|
|
56
|
+
}
|
|
57
|
+
return -(componentId * RELATION_SHIFT + actualTargetId);
|
|
58
|
+
}
|
|
153
59
|
/**
|
|
154
60
|
* Check if an ID is a wildcard relation id
|
|
155
61
|
*/
|
|
@@ -192,7 +98,7 @@ function decodeRelationId(relationId) {
|
|
|
192
98
|
function getIdType(id) {
|
|
193
99
|
if (isComponentId(id)) return "component";
|
|
194
100
|
if (isEntityId(id)) return "entity";
|
|
195
|
-
if (
|
|
101
|
+
if (id < 0) try {
|
|
196
102
|
const decoded = decodeRelationId(id);
|
|
197
103
|
if (decoded.type !== "wildcard" && !isEntityId(decoded.targetId) && !isComponentId(decoded.targetId)) return "invalid";
|
|
198
104
|
switch (decoded.type) {
|
|
@@ -213,7 +119,7 @@ function getIdType(id) {
|
|
|
213
119
|
function getDetailedIdType(id) {
|
|
214
120
|
if (isComponentId(id)) return { type: "component" };
|
|
215
121
|
if (isEntityId(id)) return { type: "entity" };
|
|
216
|
-
if (
|
|
122
|
+
if (id < 0) try {
|
|
217
123
|
const decoded = decodeRelationId(id);
|
|
218
124
|
if (decoded.type !== "wildcard" && !isEntityId(decoded.targetId) && !isComponentId(decoded.targetId)) return { type: "invalid" };
|
|
219
125
|
let type;
|
|
@@ -239,6 +145,32 @@ function getDetailedIdType(id) {
|
|
|
239
145
|
return { type: "invalid" };
|
|
240
146
|
}
|
|
241
147
|
/**
|
|
148
|
+
* Get the componentId from a relation ID without fully decoding the relation.
|
|
149
|
+
* Returns undefined for non-relation IDs or invalid component IDs.
|
|
150
|
+
*/
|
|
151
|
+
function getComponentIdFromRelationId(id) {
|
|
152
|
+
const decoded = decodeRelationRaw(id);
|
|
153
|
+
if (decoded === null || !isValidComponentId(decoded.componentId)) return void 0;
|
|
154
|
+
return decoded.componentId;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Get the targetId from a relation ID without fully decoding the relation.
|
|
158
|
+
* Returns undefined for non-relation IDs.
|
|
159
|
+
*/
|
|
160
|
+
function getTargetIdFromRelationId(id) {
|
|
161
|
+
return decodeRelationRaw(id)?.targetId;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Check if an ID is an entity-relation (relation targeting an entity, not a component or wildcard)
|
|
165
|
+
*/
|
|
166
|
+
function isEntityRelation(id) {
|
|
167
|
+
const decoded = decodeRelationRaw(id);
|
|
168
|
+
return decoded !== null && decoded.targetId >= ENTITY_ID_START;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/core/entity-manager.ts
|
|
173
|
+
/**
|
|
242
174
|
* Entity ID Manager for automatic allocation and freelist recycling
|
|
243
175
|
*/
|
|
244
176
|
var EntityIdManager = class {
|
|
@@ -330,80 +262,180 @@ var ComponentIdAllocator = class {
|
|
|
330
262
|
return this.nextId <= COMPONENT_ID_MAX;
|
|
331
263
|
}
|
|
332
264
|
};
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
* @example
|
|
344
|
-
* // Just a name
|
|
345
|
-
* const Position = component<Position>("Position");
|
|
346
|
-
*
|
|
347
|
-
* // With options
|
|
348
|
-
* const ChildOf = component({ exclusive: true, cascadeDelete: true });
|
|
349
|
-
*
|
|
350
|
-
* // With name and options
|
|
351
|
-
* const ChildOf = component({ name: "ChildOf", exclusive: true });
|
|
352
|
-
*/
|
|
353
|
-
function component(nameOrOptions) {
|
|
354
|
-
const id = globalComponentIdAllocator.allocate();
|
|
355
|
-
let name;
|
|
356
|
-
let options;
|
|
357
|
-
if (typeof nameOrOptions === "string") name = nameOrOptions;
|
|
358
|
-
else if (typeof nameOrOptions === "object" && nameOrOptions !== null) {
|
|
359
|
-
options = nameOrOptions;
|
|
360
|
-
name = options.name;
|
|
265
|
+
|
|
266
|
+
//#endregion
|
|
267
|
+
//#region src/utils/bit-set.ts
|
|
268
|
+
var BitSet = class {
|
|
269
|
+
data;
|
|
270
|
+
_length;
|
|
271
|
+
constructor(length) {
|
|
272
|
+
this._length = length;
|
|
273
|
+
const numWords = Math.ceil(length / 32);
|
|
274
|
+
this.data = new Uint32Array(numWords);
|
|
361
275
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
componentNames[id] = name;
|
|
365
|
-
ComponentIdForNames.set(name, id);
|
|
276
|
+
get length() {
|
|
277
|
+
return this._length;
|
|
366
278
|
}
|
|
367
|
-
|
|
368
|
-
if (
|
|
369
|
-
|
|
370
|
-
|
|
279
|
+
has(index) {
|
|
280
|
+
if (index < 0 || index >= this._length) return false;
|
|
281
|
+
const word = index >>> 5;
|
|
282
|
+
const bit = index & 31;
|
|
283
|
+
return (this.data[word] >>> bit & 1) !== 0;
|
|
371
284
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
285
|
+
set(index) {
|
|
286
|
+
if (index < 0 || index >= this._length) return;
|
|
287
|
+
const word = index >>> 5;
|
|
288
|
+
const bit = index & 31;
|
|
289
|
+
this.data[word] |= 1 << bit;
|
|
290
|
+
}
|
|
291
|
+
clear(index) {
|
|
292
|
+
if (index < 0 || index >= this._length) return;
|
|
293
|
+
const word = index >>> 5;
|
|
294
|
+
const bit = index & 31;
|
|
295
|
+
this.data[word] &= ~(1 << bit);
|
|
296
|
+
}
|
|
297
|
+
setRange(lo, hi) {
|
|
298
|
+
if (lo > hi) return;
|
|
299
|
+
if (lo < 0) lo = 0;
|
|
300
|
+
if (hi >= this._length) hi = this._length - 1;
|
|
301
|
+
const firstWord = lo >>> 5;
|
|
302
|
+
const lastWord = hi >>> 5;
|
|
303
|
+
const loBit = lo & 31;
|
|
304
|
+
const hiBit = hi & 31;
|
|
305
|
+
const maskFor = (a, b) => {
|
|
306
|
+
const width = b - a + 1;
|
|
307
|
+
if (width <= 0) return 0;
|
|
308
|
+
if (width >= 32) return 4294967295;
|
|
309
|
+
return (1 << width) - 1 << a >>> 0;
|
|
310
|
+
};
|
|
311
|
+
if (firstWord === lastWord) {
|
|
312
|
+
const mask = maskFor(loBit, hiBit);
|
|
313
|
+
this.data[firstWord] = (this.data[firstWord] | mask) >>> 0;
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const firstMask = maskFor(loBit, 31);
|
|
317
|
+
this.data[firstWord] = (this.data[firstWord] | firstMask) >>> 0;
|
|
318
|
+
for (let w = firstWord + 1; w <= lastWord - 1; w++) this.data[w] = 4294967295;
|
|
319
|
+
const lastMask = maskFor(0, hiBit);
|
|
320
|
+
this.data[lastWord] = (this.data[lastWord] | lastMask) >>> 0;
|
|
321
|
+
}
|
|
322
|
+
anyClearInRange(lo, hi) {
|
|
323
|
+
if (lo > hi) return false;
|
|
324
|
+
if (lo < 0) lo = 0;
|
|
325
|
+
if (hi >= this._length) hi = this._length - 1;
|
|
326
|
+
const firstWord = lo >>> 5;
|
|
327
|
+
const lastWord = hi >>> 5;
|
|
328
|
+
const loBit = lo & 31;
|
|
329
|
+
const hiBit = hi & 31;
|
|
330
|
+
const maskFor = (a, b) => {
|
|
331
|
+
const width = b - a + 1;
|
|
332
|
+
if (width <= 0) return 0;
|
|
333
|
+
if (width >= 32) return 4294967295;
|
|
334
|
+
return (1 << width) - 1 << a >>> 0;
|
|
335
|
+
};
|
|
336
|
+
if (firstWord === lastWord) {
|
|
337
|
+
const mask = maskFor(loBit, hiBit);
|
|
338
|
+
return (this.data[firstWord] & mask) >>> 0 !== mask >>> 0;
|
|
339
|
+
}
|
|
340
|
+
const firstMask = maskFor(loBit, 31);
|
|
341
|
+
if ((this.data[firstWord] & firstMask) >>> 0 !== firstMask >>> 0) return true;
|
|
342
|
+
for (let w = firstWord + 1; w <= lastWord - 1; w++) if (this.data[w] !== 4294967295) return true;
|
|
343
|
+
const lastMask = maskFor(0, hiBit);
|
|
344
|
+
if ((this.data[lastWord] & lastMask) >>> 0 !== lastMask >>> 0) return true;
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
reset() {
|
|
348
|
+
this.data.fill(0);
|
|
349
|
+
}
|
|
350
|
+
*[Symbol.iterator]() {
|
|
351
|
+
for (let wordIndex = 0; wordIndex < this.data.length; wordIndex++) {
|
|
352
|
+
let word = this.data[wordIndex];
|
|
353
|
+
if (word === 0) continue;
|
|
354
|
+
const baseIndex = wordIndex * 32;
|
|
355
|
+
for (let bit = 0; bit < 32 && baseIndex + bit < this._length; bit++) {
|
|
356
|
+
if (word & 1) yield baseIndex + bit;
|
|
357
|
+
word >>>= 1;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
//#endregion
|
|
364
|
+
//#region src/core/component-registry.ts
|
|
365
|
+
const globalComponentIdAllocator = new ComponentIdAllocator();
|
|
366
|
+
const ComponentIdForNames = /* @__PURE__ */ new Map();
|
|
367
|
+
const componentNames = new Array(COMPONENT_ID_MAX + 1);
|
|
368
|
+
const exclusiveFlags = new BitSet(COMPONENT_ID_MAX + 1);
|
|
369
|
+
const cascadeDeleteFlags = new BitSet(COMPONENT_ID_MAX + 1);
|
|
370
|
+
const dontFragmentFlags = new BitSet(COMPONENT_ID_MAX + 1);
|
|
371
|
+
/**
|
|
372
|
+
* Allocate a new component ID from the global allocator.
|
|
373
|
+
* @param nameOrOptions Optional name for the component (for serialization/debugging) or options object
|
|
374
|
+
* @returns The allocated component ID
|
|
375
|
+
* @example
|
|
376
|
+
* // Just a name
|
|
377
|
+
* const Position = component<Position>("Position");
|
|
378
|
+
*
|
|
379
|
+
* // With options
|
|
380
|
+
* const ChildOf = component({ exclusive: true, cascadeDelete: true });
|
|
381
|
+
*
|
|
382
|
+
* // With name and options
|
|
383
|
+
* const ChildOf = component({ name: "ChildOf", exclusive: true });
|
|
384
|
+
*/
|
|
385
|
+
function component(nameOrOptions) {
|
|
386
|
+
const id = globalComponentIdAllocator.allocate();
|
|
387
|
+
let name;
|
|
388
|
+
let options;
|
|
389
|
+
if (typeof nameOrOptions === "string") name = nameOrOptions;
|
|
390
|
+
else if (typeof nameOrOptions === "object" && nameOrOptions !== null) {
|
|
391
|
+
options = nameOrOptions;
|
|
392
|
+
name = options.name;
|
|
393
|
+
}
|
|
394
|
+
if (name) {
|
|
395
|
+
if (ComponentIdForNames.has(name)) throw new Error(`Component name "${name}" is already registered`);
|
|
396
|
+
componentNames[id] = name;
|
|
397
|
+
ComponentIdForNames.set(name, id);
|
|
398
|
+
}
|
|
399
|
+
if (options) {
|
|
400
|
+
if (options.exclusive) exclusiveFlags.set(id);
|
|
401
|
+
if (options.cascadeDelete) cascadeDeleteFlags.set(id);
|
|
402
|
+
if (options.dontFragment) dontFragmentFlags.set(id);
|
|
403
|
+
}
|
|
404
|
+
return id;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Get a component ID by its registered name
|
|
408
|
+
* @param name The component name
|
|
409
|
+
* @returns The component ID if found, undefined otherwise
|
|
410
|
+
*/
|
|
411
|
+
function getComponentIdByName(name) {
|
|
412
|
+
return ComponentIdForNames.get(name);
|
|
413
|
+
}
|
|
414
|
+
/** Get a component name by its ID
|
|
415
|
+
* @param id The component ID
|
|
416
|
+
* @returns The component name if found, undefined otherwise
|
|
417
|
+
*/
|
|
418
|
+
function getComponentNameById(id) {
|
|
419
|
+
return componentNames[id];
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Check if a component is marked as exclusive
|
|
423
|
+
* @param id The component ID
|
|
424
|
+
* @returns true if the component is exclusive, false otherwise
|
|
425
|
+
*/
|
|
426
|
+
function isExclusiveComponent(id) {
|
|
427
|
+
return exclusiveFlags.has(id);
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Check if a component is marked as dontFragment
|
|
431
|
+
* @param id The component ID
|
|
432
|
+
* @returns true if the component is dontFragment, false otherwise
|
|
433
|
+
*/
|
|
434
|
+
function isDontFragmentComponent(id) {
|
|
435
|
+
return dontFragmentFlags.has(id);
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Generic function to check relation flags with specific target conditions
|
|
407
439
|
* @param id The entity/relation ID to check
|
|
408
440
|
* @param flagBitSet The bitset for the flag
|
|
409
441
|
* @param targetCondition Function to check target ID condition
|
|
@@ -443,920 +475,803 @@ function isDontFragmentWildcard(id) {
|
|
|
443
475
|
function isCascadeDeleteRelation(id) {
|
|
444
476
|
return checkRelationFlag(id, cascadeDeleteFlags, (targetId) => targetId !== WILDCARD_TARGET_ID && targetId >= ENTITY_ID_START);
|
|
445
477
|
}
|
|
446
|
-
/**
|
|
447
|
-
* Get the componentId from a relation ID without fully decoding the relation.
|
|
448
|
-
* Returns undefined for non-relation IDs or invalid component IDs.
|
|
449
|
-
*/
|
|
450
|
-
function getComponentIdFromRelationId(id) {
|
|
451
|
-
const decoded = decodeRelationRaw(id);
|
|
452
|
-
if (decoded === null || !isValidComponentId(decoded.componentId)) return void 0;
|
|
453
|
-
return decoded.componentId;
|
|
454
|
-
}
|
|
455
|
-
/**
|
|
456
|
-
* Get the targetId from a relation ID without fully decoding the relation.
|
|
457
|
-
* Returns undefined for non-relation IDs.
|
|
458
|
-
*/
|
|
459
|
-
function getTargetIdFromRelationId(id) {
|
|
460
|
-
return decodeRelationRaw(id)?.targetId;
|
|
461
|
-
}
|
|
462
|
-
/**
|
|
463
|
-
* Check if an ID is an entity-relation (relation targeting an entity, not a component or wildcard)
|
|
464
|
-
*/
|
|
465
|
-
function isEntityRelation(id) {
|
|
466
|
-
const decoded = decodeRelationRaw(id);
|
|
467
|
-
return decoded !== null && decoded.targetId >= ENTITY_ID_START;
|
|
468
|
-
}
|
|
469
478
|
|
|
470
479
|
//#endregion
|
|
471
|
-
//#region src/
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
480
|
+
//#region src/core/builder.ts
|
|
481
|
+
var EntityBuilder = class {
|
|
482
|
+
world;
|
|
483
|
+
components = [];
|
|
484
|
+
constructor(world) {
|
|
485
|
+
this.world = world;
|
|
486
|
+
}
|
|
487
|
+
with(componentId, value) {
|
|
488
|
+
this.components.push({
|
|
489
|
+
type: "component",
|
|
490
|
+
id: componentId,
|
|
491
|
+
value
|
|
492
|
+
});
|
|
493
|
+
return this;
|
|
494
|
+
}
|
|
495
|
+
withTag(componentId) {
|
|
496
|
+
this.components.push({
|
|
497
|
+
type: "component",
|
|
498
|
+
id: componentId,
|
|
499
|
+
value: void 0
|
|
500
|
+
});
|
|
501
|
+
return this;
|
|
502
|
+
}
|
|
503
|
+
withRelation(componentId, targetEntity, value) {
|
|
504
|
+
this.components.push({
|
|
505
|
+
type: "relation",
|
|
506
|
+
componentId,
|
|
507
|
+
targetId: targetEntity,
|
|
508
|
+
value
|
|
509
|
+
});
|
|
510
|
+
return this;
|
|
511
|
+
}
|
|
512
|
+
withRelationTag(componentId, targetEntity) {
|
|
513
|
+
this.components.push({
|
|
514
|
+
type: "relation",
|
|
515
|
+
componentId,
|
|
516
|
+
targetId: targetEntity,
|
|
517
|
+
value: void 0
|
|
518
|
+
});
|
|
519
|
+
return this;
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Create an entity and enqueue components to be applied. This method
|
|
523
|
+
* does NOT call `world.sync()` automatically; callers must invoke
|
|
524
|
+
* `world.sync()` to apply deferred commands.
|
|
525
|
+
* (Previously auto-synced; now a breaking change — buildDeferred() removed.)
|
|
526
|
+
*/
|
|
527
|
+
build() {
|
|
528
|
+
const entity = this.world.new();
|
|
529
|
+
for (const def of this.components) if (def.type === "component") this.world.set(entity, def.id, def.value);
|
|
530
|
+
else {
|
|
531
|
+
const relationId = relation(def.componentId, def.targetId);
|
|
532
|
+
this.world.set(entity, relationId, def.value);
|
|
533
|
+
}
|
|
534
|
+
return entity;
|
|
535
|
+
}
|
|
536
|
+
};
|
|
475
537
|
|
|
476
538
|
//#endregion
|
|
477
|
-
//#region src/
|
|
478
|
-
/**
|
|
479
|
-
* Utility functions for ECS library
|
|
480
|
-
*/
|
|
539
|
+
//#region src/commands/changeset.ts
|
|
481
540
|
/**
|
|
482
|
-
*
|
|
483
|
-
* @param cache The cache map
|
|
484
|
-
* @param key The cache key
|
|
485
|
-
* @param compute Function to compute the value if not cached
|
|
486
|
-
* @returns The cached or computed value
|
|
541
|
+
* @internal Represents a set of component changes to be applied to an entity
|
|
487
542
|
*/
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
value = compute();
|
|
492
|
-
cache.set(key, value);
|
|
493
|
-
}
|
|
494
|
-
return value;
|
|
495
|
-
}
|
|
496
|
-
/**
|
|
497
|
-
* Get a value from cache or create and cache it if not present, allowing side effects during creation
|
|
498
|
-
* @param cache The cache map
|
|
499
|
-
* @param key The cache key
|
|
500
|
-
* @param create Function to create the value if not cached (can have side effects)
|
|
501
|
-
* @returns The cached or created value
|
|
502
|
-
*/
|
|
503
|
-
function getOrCreateWithSideEffect(cache, key, create) {
|
|
504
|
-
let value = cache.get(key);
|
|
505
|
-
if (value === void 0) {
|
|
506
|
-
value = create();
|
|
507
|
-
cache.set(key, value);
|
|
508
|
-
}
|
|
509
|
-
return value;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
//#endregion
|
|
513
|
-
//#region src/archetype.ts
|
|
514
|
-
/**
|
|
515
|
-
* Special value to represent missing component data
|
|
516
|
-
*/
|
|
517
|
-
const MISSING_COMPONENT = Symbol("missing component");
|
|
518
|
-
/**
|
|
519
|
-
* Archetype class for ECS architecture
|
|
520
|
-
* Represents a group of entities that share the same set of components
|
|
521
|
-
* Optimized for fast iteration and component access
|
|
522
|
-
*/
|
|
523
|
-
var Archetype = class {
|
|
524
|
-
/**
|
|
525
|
-
* The component types that define this archetype
|
|
526
|
-
*/
|
|
527
|
-
componentTypes;
|
|
528
|
-
/**
|
|
529
|
-
* List of entities in this archetype
|
|
530
|
-
*/
|
|
531
|
-
entities = [];
|
|
532
|
-
/**
|
|
533
|
-
* Component data storage - maps component type to array of component data
|
|
534
|
-
* Each array index corresponds to the entity index in the entities array
|
|
535
|
-
*/
|
|
536
|
-
componentData = /* @__PURE__ */ new Map();
|
|
537
|
-
/**
|
|
538
|
-
* Reverse mapping from entity to its index in this archetype
|
|
539
|
-
*/
|
|
540
|
-
entityToIndex = /* @__PURE__ */ new Map();
|
|
541
|
-
/**
|
|
542
|
-
* Reference to dontFragment relations storage from World
|
|
543
|
-
* This allows entities with different relation targets to share the same archetype
|
|
544
|
-
* Stored in World to avoid migration overhead when entities change archetypes
|
|
545
|
-
*/
|
|
546
|
-
dontFragmentRelations;
|
|
543
|
+
var ComponentChangeset = class {
|
|
544
|
+
adds = /* @__PURE__ */ new Map();
|
|
545
|
+
removes = /* @__PURE__ */ new Set();
|
|
547
546
|
/**
|
|
548
|
-
*
|
|
549
|
-
* For regular components: data array
|
|
550
|
-
* For wildcards: matching relation types array
|
|
547
|
+
* Add a component to the changeset
|
|
551
548
|
*/
|
|
552
|
-
|
|
549
|
+
set(componentType, component$1) {
|
|
550
|
+
this.adds.set(componentType, component$1);
|
|
551
|
+
this.removes.delete(componentType);
|
|
552
|
+
}
|
|
553
553
|
/**
|
|
554
|
-
*
|
|
555
|
-
* @param componentTypes The component types that define this archetype
|
|
556
|
-
* @param dontFragmentRelations Reference to the World's dontFragmentRelations storage
|
|
554
|
+
* Remove a component from the changeset
|
|
557
555
|
*/
|
|
558
|
-
|
|
559
|
-
this.
|
|
560
|
-
this.
|
|
561
|
-
for (const componentType of this.componentTypes) this.componentData.set(componentType, []);
|
|
556
|
+
delete(componentType) {
|
|
557
|
+
this.removes.add(componentType);
|
|
558
|
+
this.adds.delete(componentType);
|
|
562
559
|
}
|
|
563
560
|
/**
|
|
564
|
-
*
|
|
561
|
+
* Check if the changeset has any changes
|
|
565
562
|
*/
|
|
566
|
-
|
|
567
|
-
return this.
|
|
563
|
+
hasChanges() {
|
|
564
|
+
return this.adds.size > 0 || this.removes.size > 0;
|
|
568
565
|
}
|
|
569
566
|
/**
|
|
570
|
-
*
|
|
571
|
-
* @param componentTypes The component types to check
|
|
567
|
+
* Clear all changes
|
|
572
568
|
*/
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
return this.componentTypes.every((type, index) => type === sortedTypes[index]);
|
|
569
|
+
clear() {
|
|
570
|
+
this.adds.clear();
|
|
571
|
+
this.removes.clear();
|
|
577
572
|
}
|
|
578
573
|
/**
|
|
579
|
-
*
|
|
580
|
-
* @param entityId The entity to add
|
|
581
|
-
* @param componentData Map of component type to component data (includes both regular and dontFragment components)
|
|
574
|
+
* Merge another changeset into this one
|
|
582
575
|
*/
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
this.entityToIndex.set(entityId, index);
|
|
588
|
-
for (const componentType of this.componentTypes) {
|
|
589
|
-
const data = componentData.get(componentType);
|
|
590
|
-
this.getComponentData(componentType).push(!componentData.has(componentType) ? MISSING_COMPONENT : data);
|
|
576
|
+
merge(other) {
|
|
577
|
+
for (const [componentType, component$1] of other.adds) {
|
|
578
|
+
this.adds.set(componentType, component$1);
|
|
579
|
+
this.removes.delete(componentType);
|
|
591
580
|
}
|
|
592
|
-
const
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
const detailedType = getDetailedIdType(componentType);
|
|
596
|
-
if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isDontFragmentComponent(detailedType.componentId)) dontFragmentData.set(componentType, data);
|
|
581
|
+
for (const componentType of other.removes) {
|
|
582
|
+
this.removes.add(componentType);
|
|
583
|
+
this.adds.delete(componentType);
|
|
597
584
|
}
|
|
598
|
-
if (dontFragmentData.size > 0) this.dontFragmentRelations.set(entityId, dontFragmentData);
|
|
599
585
|
}
|
|
600
586
|
/**
|
|
601
|
-
*
|
|
602
|
-
* @param entityId The entity to get data for
|
|
603
|
-
* @returns Map of component type to component data (includes both regular and dontFragment components)
|
|
587
|
+
* Apply the changeset to existing components and return the final state
|
|
604
588
|
*/
|
|
605
|
-
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
for (const componentType of this.componentTypes) {
|
|
610
|
-
const data = this.getComponentData(componentType)[index];
|
|
611
|
-
entityData.set(componentType, data === MISSING_COMPONENT ? void 0 : data);
|
|
612
|
-
}
|
|
613
|
-
const dontFragmentData = this.dontFragmentRelations.get(entityId);
|
|
614
|
-
if (dontFragmentData) for (const [componentType, data] of dontFragmentData) entityData.set(componentType, data);
|
|
615
|
-
return entityData;
|
|
589
|
+
applyTo(existingComponents) {
|
|
590
|
+
for (const componentType of this.removes) existingComponents.delete(componentType);
|
|
591
|
+
for (const [componentType, component$1] of this.adds) existingComponents.set(componentType, component$1);
|
|
592
|
+
return existingComponents;
|
|
616
593
|
}
|
|
617
594
|
/**
|
|
618
|
-
*
|
|
619
|
-
* @
|
|
595
|
+
* Get the final component types after applying the changeset
|
|
596
|
+
* @param existingComponentTypes - The current component types on the entity
|
|
597
|
+
* @returns The final component types or undefined if no changes
|
|
620
598
|
*/
|
|
621
|
-
|
|
622
|
-
const
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
components.set(componentType, data === MISSING_COMPONENT ? void 0 : data);
|
|
599
|
+
getFinalComponentTypes(existingComponentTypes) {
|
|
600
|
+
const finalComponentTypes = new Set(existingComponentTypes);
|
|
601
|
+
let changed = false;
|
|
602
|
+
for (const componentType of this.removes) {
|
|
603
|
+
if (!finalComponentTypes.has(componentType)) {
|
|
604
|
+
this.removes.delete(componentType);
|
|
605
|
+
continue;
|
|
629
606
|
}
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
result.push({
|
|
633
|
-
entity,
|
|
634
|
-
components
|
|
635
|
-
});
|
|
607
|
+
changed = true;
|
|
608
|
+
finalComponentTypes.delete(componentType);
|
|
636
609
|
}
|
|
637
|
-
|
|
610
|
+
for (const componentType of this.adds.keys()) {
|
|
611
|
+
if (finalComponentTypes.has(componentType)) continue;
|
|
612
|
+
changed = true;
|
|
613
|
+
finalComponentTypes.add(componentType);
|
|
614
|
+
}
|
|
615
|
+
return changed ? Array.from(finalComponentTypes) : void 0;
|
|
638
616
|
}
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
//#endregion
|
|
620
|
+
//#region src/commands/command-buffer.ts
|
|
621
|
+
/**
|
|
622
|
+
* Command buffer for deferred structural changes
|
|
623
|
+
*/
|
|
624
|
+
var CommandBuffer = class {
|
|
625
|
+
commands = [];
|
|
626
|
+
executeEntityCommands;
|
|
639
627
|
/**
|
|
640
|
-
*
|
|
641
|
-
* @param entityId The entity to remove
|
|
642
|
-
* @returns The component data of the removed entity (includes both regular and dontFragment components)
|
|
628
|
+
* Create a command buffer with an executor function
|
|
643
629
|
*/
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
for (const [componentType, data] of dontFragmentData) removedData.set(componentType, data);
|
|
655
|
-
this.dontFragmentRelations.delete(entityId);
|
|
656
|
-
}
|
|
657
|
-
this.entityToIndex.delete(entityId);
|
|
658
|
-
const lastIndex = this.entities.length - 1;
|
|
659
|
-
if (index !== lastIndex) {
|
|
660
|
-
const lastEntity = this.entities[lastIndex];
|
|
661
|
-
this.entities[index] = lastEntity;
|
|
662
|
-
this.entityToIndex.set(lastEntity, index);
|
|
663
|
-
for (const componentType of this.componentTypes) {
|
|
664
|
-
const dataArray = this.getComponentData(componentType);
|
|
665
|
-
dataArray[index] = dataArray[lastIndex];
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
this.entities.pop();
|
|
669
|
-
for (const componentType of this.componentTypes) this.getComponentData(componentType).pop();
|
|
670
|
-
return removedData;
|
|
630
|
+
constructor(executeEntityCommands) {
|
|
631
|
+
this.executeEntityCommands = executeEntityCommands;
|
|
632
|
+
}
|
|
633
|
+
set(entityId, componentType, component$1) {
|
|
634
|
+
this.commands.push({
|
|
635
|
+
type: "set",
|
|
636
|
+
entityId,
|
|
637
|
+
componentType,
|
|
638
|
+
component: component$1
|
|
639
|
+
});
|
|
671
640
|
}
|
|
672
641
|
/**
|
|
673
|
-
*
|
|
674
|
-
* @param entityId The entity to check
|
|
642
|
+
* Remove a component from an entity (deferred)
|
|
675
643
|
*/
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
if (isWildcardRelationId(componentType)) {
|
|
683
|
-
const componentId = getComponentIdFromRelationId(componentType);
|
|
684
|
-
const relations = [];
|
|
685
|
-
for (const relType of this.componentTypes) {
|
|
686
|
-
const relDetailed = getDetailedIdType(relType);
|
|
687
|
-
if ((relDetailed.type === "entity-relation" || relDetailed.type === "component-relation") && relDetailed.componentId === componentId) {
|
|
688
|
-
const dataArray = this.getComponentData(relType);
|
|
689
|
-
if (dataArray && dataArray[index] !== void 0) {
|
|
690
|
-
const data = dataArray[index];
|
|
691
|
-
relations.push([relDetailed.targetId, data === MISSING_COMPONENT ? void 0 : data]);
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
const dontFragmentData = this.dontFragmentRelations.get(entityId);
|
|
696
|
-
if (dontFragmentData) for (const [relType, data] of dontFragmentData) {
|
|
697
|
-
const relDetailed = getDetailedIdType(relType);
|
|
698
|
-
if ((relDetailed.type === "entity-relation" || relDetailed.type === "component-relation") && relDetailed.componentId === componentId) relations.push([relDetailed.targetId, data]);
|
|
699
|
-
}
|
|
700
|
-
return relations;
|
|
701
|
-
} else {
|
|
702
|
-
if (this.componentTypes.includes(componentType)) {
|
|
703
|
-
const data = this.getComponentData(componentType)[index];
|
|
704
|
-
if (data === MISSING_COMPONENT) throw new Error(`Component type ${componentType} not found for entity ${entityId}`);
|
|
705
|
-
return data;
|
|
706
|
-
}
|
|
707
|
-
const dontFragmentData = this.dontFragmentRelations.get(entityId);
|
|
708
|
-
if (dontFragmentData && dontFragmentData.has(componentType)) return dontFragmentData.get(componentType);
|
|
709
|
-
throw new Error(`Component type ${componentType} not found for entity ${entityId}`);
|
|
710
|
-
}
|
|
644
|
+
remove(entityId, componentType) {
|
|
645
|
+
this.commands.push({
|
|
646
|
+
type: "delete",
|
|
647
|
+
entityId,
|
|
648
|
+
componentType
|
|
649
|
+
});
|
|
711
650
|
}
|
|
712
651
|
/**
|
|
713
|
-
*
|
|
714
|
-
* @param entityId The entity
|
|
715
|
-
* @param componentType The component type
|
|
716
|
-
* @returns { value: T } if component exists, undefined otherwise
|
|
652
|
+
* Destroy an entity (deferred)
|
|
717
653
|
*/
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
if (data === MISSING_COMPONENT) return;
|
|
724
|
-
return { value: data };
|
|
725
|
-
}
|
|
726
|
-
const dontFragmentData = this.dontFragmentRelations.get(entityId);
|
|
727
|
-
if (dontFragmentData && dontFragmentData.has(componentType)) return { value: dontFragmentData.get(componentType) };
|
|
654
|
+
delete(entityId) {
|
|
655
|
+
this.commands.push({
|
|
656
|
+
type: "destroy",
|
|
657
|
+
entityId
|
|
658
|
+
});
|
|
728
659
|
}
|
|
729
660
|
/**
|
|
730
|
-
*
|
|
731
|
-
* @param entityId The entity
|
|
732
|
-
* @param componentType The component type
|
|
733
|
-
* @param data The component data
|
|
661
|
+
* Execute all commands and clear the buffer
|
|
734
662
|
*/
|
|
735
|
-
|
|
736
|
-
const
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
dontFragmentData = /* @__PURE__ */ new Map();
|
|
748
|
-
this.dontFragmentRelations.set(entityId, dontFragmentData);
|
|
663
|
+
execute() {
|
|
664
|
+
const MAX_ITERATIONS = 100;
|
|
665
|
+
let iterations = 0;
|
|
666
|
+
while (this.commands.length > 0) {
|
|
667
|
+
if (iterations >= MAX_ITERATIONS) throw new Error("Command execution exceeded maximum iterations, possible infinite loop");
|
|
668
|
+
iterations++;
|
|
669
|
+
const currentCommands = [...this.commands];
|
|
670
|
+
this.commands = [];
|
|
671
|
+
const entityCommands = /* @__PURE__ */ new Map();
|
|
672
|
+
for (const cmd of currentCommands) {
|
|
673
|
+
if (!entityCommands.has(cmd.entityId)) entityCommands.set(cmd.entityId, []);
|
|
674
|
+
entityCommands.get(cmd.entityId).push(cmd);
|
|
749
675
|
}
|
|
750
|
-
|
|
751
|
-
return;
|
|
676
|
+
for (const [entityId, commands] of entityCommands) this.executeEntityCommands(entityId, commands);
|
|
752
677
|
}
|
|
753
|
-
throw new Error(`Component type ${componentType} is not in this archetype`);
|
|
754
678
|
}
|
|
755
679
|
/**
|
|
756
|
-
* Get
|
|
680
|
+
* Get current commands (for testing)
|
|
757
681
|
*/
|
|
758
|
-
|
|
759
|
-
return this.
|
|
682
|
+
getCommands() {
|
|
683
|
+
return [...this.commands];
|
|
760
684
|
}
|
|
761
685
|
/**
|
|
762
|
-
*
|
|
763
|
-
*/
|
|
764
|
-
getEntityToIndexMap() {
|
|
765
|
-
return this.entityToIndex;
|
|
766
|
-
}
|
|
767
|
-
/**
|
|
768
|
-
* Get component data for all entities of a specific component type
|
|
769
|
-
* @param componentType The component type
|
|
770
|
-
*/
|
|
771
|
-
getComponentData(componentType) {
|
|
772
|
-
const data = this.componentData.get(componentType);
|
|
773
|
-
if (!data) throw new Error(`Component type ${componentType} is not in this archetype`);
|
|
774
|
-
return data;
|
|
775
|
-
}
|
|
776
|
-
/**
|
|
777
|
-
* Get optional component data for all entities of a specific component type
|
|
778
|
-
* @param componentType The component type
|
|
779
|
-
* @returns An array of component data or undefined if not present
|
|
780
|
-
*/
|
|
781
|
-
getOptionalComponentData(componentType) {
|
|
782
|
-
return this.componentData.get(componentType);
|
|
783
|
-
}
|
|
784
|
-
/**
|
|
785
|
-
* Helper: compute or return cached data sources for provided componentTypes
|
|
786
|
-
*/
|
|
787
|
-
getCachedComponentDataSources(componentTypes) {
|
|
788
|
-
const cacheKey = this.buildCacheKey(componentTypes);
|
|
789
|
-
return getOrComputeCache(this.componentDataSourcesCache, cacheKey, () => componentTypes.map((compType) => this.getComponentDataSource(compType)));
|
|
790
|
-
}
|
|
791
|
-
/**
|
|
792
|
-
* Build cache key for component types
|
|
793
|
-
*/
|
|
794
|
-
buildCacheKey(componentTypes) {
|
|
795
|
-
return componentTypes.map((id) => isOptionalEntityId(id) ? `opt(${id.optional})` : `${id}`).join(",");
|
|
796
|
-
}
|
|
797
|
-
/**
|
|
798
|
-
* Get data source for a single component type
|
|
686
|
+
* Clear all commands
|
|
799
687
|
*/
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
const actualType = optional ? compType.optional : compType;
|
|
803
|
-
const detailedType = getDetailedIdType(actualType);
|
|
804
|
-
if (detailedType.type === "wildcard-relation") return this.getWildcardRelationDataSource(detailedType.componentId, optional);
|
|
805
|
-
else return optional ? this.getOptionalComponentData(actualType) : this.getComponentData(actualType);
|
|
688
|
+
clear() {
|
|
689
|
+
this.commands = [];
|
|
806
690
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
//#endregion
|
|
694
|
+
//#region src/query/filter.ts
|
|
695
|
+
/**
|
|
696
|
+
* Serialize a QueryFilter into a deterministic string suitable for cache keys.
|
|
697
|
+
* Currently only serializes `negativeComponentTypes`.
|
|
698
|
+
*/
|
|
699
|
+
function serializeQueryFilter(filter = {}) {
|
|
700
|
+
const negative = (filter.negativeComponentTypes || []).slice().sort((a, b) => a - b);
|
|
701
|
+
if (negative.length === 0) return "";
|
|
702
|
+
return `neg:${negative.join(",")}`;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Check if an archetype matches the given component types
|
|
706
|
+
*/
|
|
707
|
+
function matchesComponentTypes(archetype, componentTypes) {
|
|
708
|
+
return componentTypes.every((type) => {
|
|
709
|
+
const detailedType = getDetailedIdType(type);
|
|
710
|
+
if (detailedType.type === "wildcard-relation") return archetype.componentTypes.some((archetypeType) => {
|
|
711
|
+
if (!isRelationId(archetypeType)) return false;
|
|
712
|
+
return getComponentIdFromRelationId(archetypeType) === detailedType.componentId;
|
|
814
713
|
});
|
|
815
|
-
return
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
714
|
+
else return archetype.componentTypes.includes(type);
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Check if an archetype matches the filter conditions (only filtering logic)
|
|
719
|
+
*/
|
|
720
|
+
function matchesFilter(archetype, filter) {
|
|
721
|
+
return (filter.negativeComponentTypes || []).every((type) => {
|
|
722
|
+
const detailedType = getDetailedIdType(type);
|
|
723
|
+
if (detailedType.type === "wildcard-relation") return !archetype.componentTypes.some((archetypeType) => {
|
|
724
|
+
if (!isRelationId(archetypeType)) return false;
|
|
725
|
+
return getComponentIdFromRelationId(archetypeType) === detailedType.componentId;
|
|
824
726
|
});
|
|
727
|
+
else return !archetype.componentTypes.includes(type);
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
//#endregion
|
|
732
|
+
//#region src/query/query.ts
|
|
733
|
+
/**
|
|
734
|
+
* Query class for efficient entity queries with cached archetypes
|
|
735
|
+
*/
|
|
736
|
+
var Query = class {
|
|
737
|
+
world;
|
|
738
|
+
componentTypes;
|
|
739
|
+
filter;
|
|
740
|
+
cachedArchetypes = [];
|
|
741
|
+
isDisposed = false;
|
|
742
|
+
/** Cached wildcard component types for faster entity filtering */
|
|
743
|
+
wildcardTypes;
|
|
744
|
+
constructor(world, componentTypes, filter = {}) {
|
|
745
|
+
this.world = world;
|
|
746
|
+
this.componentTypes = [...componentTypes].sort((a, b) => a - b);
|
|
747
|
+
this.filter = filter;
|
|
748
|
+
this.wildcardTypes = this.componentTypes.filter((ct) => getDetailedIdType(ct).type === "wildcard-relation");
|
|
749
|
+
this.updateCache();
|
|
750
|
+
world._registerQuery(this);
|
|
825
751
|
}
|
|
826
752
|
/**
|
|
827
|
-
*
|
|
753
|
+
* Check if query is disposed and throw error if so
|
|
828
754
|
*/
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
const actualType = optional ? compType.optional : compType;
|
|
832
|
-
if (getIdType(actualType) === "wildcard-relation") return this.buildWildcardRelationValue(actualType, dataSource, entityIndex, entityId, optional);
|
|
833
|
-
else return this.buildRegularComponentValue(dataSource, entityIndex, optional);
|
|
755
|
+
ensureNotDisposed() {
|
|
756
|
+
if (this.isDisposed) throw new Error("Query has been disposed");
|
|
834
757
|
}
|
|
835
758
|
/**
|
|
836
|
-
*
|
|
759
|
+
* Get all entities matching the query
|
|
837
760
|
*/
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
const
|
|
843
|
-
|
|
844
|
-
relations.push([targetId, data === MISSING_COMPONENT ? void 0 : data]);
|
|
845
|
-
}
|
|
846
|
-
const targetComponentId = getComponentIdFromRelationId(wildcardRelationType);
|
|
847
|
-
const dontFragmentData = this.dontFragmentRelations.get(entityId);
|
|
848
|
-
if (dontFragmentData) for (const [relType, data] of dontFragmentData) {
|
|
849
|
-
const relDetailed = getDetailedIdType(relType);
|
|
850
|
-
if ((relDetailed.type === "entity-relation" || relDetailed.type === "component-relation") && relDetailed.componentId === targetComponentId) relations.push([relDetailed.targetId, data]);
|
|
851
|
-
}
|
|
852
|
-
if (relations.length === 0) {
|
|
853
|
-
if (!optional) {
|
|
854
|
-
const componentId = getComponentIdFromRelationId(wildcardRelationType);
|
|
855
|
-
throw new Error(`No matching relations found for mandatory wildcard relation component ${componentId} on entity ${entityId}`);
|
|
856
|
-
}
|
|
857
|
-
return;
|
|
761
|
+
getEntities() {
|
|
762
|
+
this.ensureNotDisposed();
|
|
763
|
+
if (this.wildcardTypes.length === 0) {
|
|
764
|
+
const result$1 = [];
|
|
765
|
+
for (const archetype of this.cachedArchetypes) result$1.push(...archetype.getEntities());
|
|
766
|
+
return result$1;
|
|
858
767
|
}
|
|
859
|
-
|
|
768
|
+
const result = [];
|
|
769
|
+
for (const archetype of this.cachedArchetypes) for (const entity of archetype.getEntities()) if (this.entityHasAllWildcards(archetype, entity)) result.push(entity);
|
|
770
|
+
return result;
|
|
860
771
|
}
|
|
861
772
|
/**
|
|
862
|
-
*
|
|
773
|
+
* Check if entity has all required wildcard relations
|
|
863
774
|
*/
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
775
|
+
entityHasAllWildcards(archetype, entity) {
|
|
776
|
+
for (const wildcardType of this.wildcardTypes) {
|
|
777
|
+
const relations = archetype.get(entity, wildcardType);
|
|
778
|
+
if (!relations || relations.length === 0) return false;
|
|
868
779
|
}
|
|
869
|
-
|
|
870
|
-
const result = data === MISSING_COMPONENT ? void 0 : data;
|
|
871
|
-
return optional ? { value: result } : result;
|
|
780
|
+
return true;
|
|
872
781
|
}
|
|
873
782
|
/**
|
|
874
|
-
* Get entities with their component data
|
|
875
|
-
* Optimized for bulk component access with pre-computed indices
|
|
783
|
+
* Get entities with their component data
|
|
876
784
|
* @param componentTypes Array of component types to retrieve
|
|
877
785
|
* @returns Array of objects with entity and component data
|
|
878
786
|
*/
|
|
879
787
|
getEntitiesWithComponents(componentTypes) {
|
|
788
|
+
this.ensureNotDisposed();
|
|
880
789
|
const result = [];
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
components
|
|
885
|
-
});
|
|
886
|
-
});
|
|
887
|
-
return result;
|
|
888
|
-
}
|
|
889
|
-
/**
|
|
890
|
-
* Iterate over entities with their component data for specified component types
|
|
891
|
-
* implemented as a generator returning each entity/components pair lazily
|
|
892
|
-
* @param componentTypes Array of component types to retrieve
|
|
893
|
-
*/
|
|
894
|
-
*iterateWithComponents(componentTypes) {
|
|
895
|
-
const componentDataSources = this.getCachedComponentDataSources(componentTypes);
|
|
896
|
-
for (let entityIndex = 0; entityIndex < this.entities.length; entityIndex++) {
|
|
897
|
-
const entity = this.entities[entityIndex];
|
|
898
|
-
yield [entity, ...this.buildComponentsForIndex(componentTypes, componentDataSources, entityIndex, entity)];
|
|
790
|
+
for (const archetype of this.cachedArchetypes) {
|
|
791
|
+
const entitiesWithData = archetype.getEntitiesWithComponents(componentTypes);
|
|
792
|
+
result.push(...entitiesWithData);
|
|
899
793
|
}
|
|
794
|
+
return result;
|
|
900
795
|
}
|
|
901
796
|
/**
|
|
902
|
-
* Iterate over entities with their component data
|
|
903
|
-
* Optimized for bulk component access
|
|
797
|
+
* Iterate over entities with their component data
|
|
904
798
|
* @param componentTypes Array of component types to retrieve
|
|
905
799
|
* @param callback Function called for each entity with its components
|
|
906
800
|
*/
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
for (
|
|
910
|
-
const entity = this.entities[entityIndex];
|
|
911
|
-
callback(entity, ...this.buildComponentsForIndex(componentTypes, componentDataSources, entityIndex, entity));
|
|
912
|
-
}
|
|
801
|
+
forEach(componentTypes, callback) {
|
|
802
|
+
this.ensureNotDisposed();
|
|
803
|
+
for (const archetype of this.cachedArchetypes) archetype.forEachWithComponents(componentTypes, callback);
|
|
913
804
|
}
|
|
914
805
|
/**
|
|
915
|
-
* Iterate over
|
|
916
|
-
* @param
|
|
806
|
+
* Iterate over entities with their component data (generator)
|
|
807
|
+
* @param componentTypes Array of component types to retrieve
|
|
917
808
|
*/
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
for (const componentType of this.componentTypes) {
|
|
922
|
-
const data = this.getComponentData(componentType)[i];
|
|
923
|
-
components.set(componentType, data === MISSING_COMPONENT ? void 0 : data);
|
|
924
|
-
}
|
|
925
|
-
callback(this.entities[i], components);
|
|
926
|
-
}
|
|
809
|
+
*iterate(componentTypes) {
|
|
810
|
+
this.ensureNotDisposed();
|
|
811
|
+
for (const archetype of this.cachedArchetypes) yield* archetype.iterateWithComponents(componentTypes);
|
|
927
812
|
}
|
|
928
813
|
/**
|
|
929
|
-
*
|
|
930
|
-
*
|
|
931
|
-
* @
|
|
932
|
-
* @returns true if any entity has a matching relation
|
|
814
|
+
* Get component data arrays for all matching entities
|
|
815
|
+
* @param componentType The component type to retrieve
|
|
816
|
+
* @returns Array of component data for all matching entities
|
|
933
817
|
*/
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
for (const entityId of this.entities) {
|
|
940
|
-
const entityDontFragmentRelations = this.dontFragmentRelations.get(entityId);
|
|
941
|
-
if (entityDontFragmentRelations) for (const relationType of entityDontFragmentRelations.keys()) {
|
|
942
|
-
const detailedType = getDetailedIdType(relationType);
|
|
943
|
-
if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && detailedType.componentId === componentId) return true;
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
return false;
|
|
818
|
+
getComponentData(componentType) {
|
|
819
|
+
this.ensureNotDisposed();
|
|
820
|
+
const result = [];
|
|
821
|
+
for (const archetype of this.cachedArchetypes) result.push(...archetype.getComponentData(componentType));
|
|
822
|
+
return result;
|
|
947
823
|
}
|
|
948
|
-
};
|
|
949
|
-
|
|
950
|
-
//#endregion
|
|
951
|
-
//#region src/changeset.ts
|
|
952
|
-
/**
|
|
953
|
-
* @internal Represents a set of component changes to be applied to an entity
|
|
954
|
-
*/
|
|
955
|
-
var ComponentChangeset = class {
|
|
956
|
-
adds = /* @__PURE__ */ new Map();
|
|
957
|
-
removes = /* @__PURE__ */ new Set();
|
|
958
824
|
/**
|
|
959
|
-
*
|
|
825
|
+
* Update the cached archetypes
|
|
826
|
+
* Called when new archetypes are created
|
|
960
827
|
*/
|
|
961
|
-
|
|
962
|
-
this.
|
|
963
|
-
this.
|
|
828
|
+
updateCache() {
|
|
829
|
+
if (this.isDisposed) return;
|
|
830
|
+
this.cachedArchetypes = this.world.getMatchingArchetypes(this.componentTypes).filter((archetype) => matchesFilter(archetype, this.filter));
|
|
964
831
|
}
|
|
965
832
|
/**
|
|
966
|
-
*
|
|
833
|
+
* Check if a new archetype matches this query and add to cache if it does
|
|
967
834
|
*/
|
|
968
|
-
|
|
969
|
-
this.
|
|
970
|
-
this.
|
|
835
|
+
checkNewArchetype(archetype) {
|
|
836
|
+
if (this.isDisposed) return;
|
|
837
|
+
if (matchesComponentTypes(archetype, this.componentTypes) && matchesFilter(archetype, this.filter) && !this.cachedArchetypes.includes(archetype)) this.cachedArchetypes.push(archetype);
|
|
971
838
|
}
|
|
972
839
|
/**
|
|
973
|
-
*
|
|
840
|
+
* Remove an archetype from the cached archetypes
|
|
974
841
|
*/
|
|
975
|
-
|
|
976
|
-
|
|
842
|
+
removeArchetype(archetype) {
|
|
843
|
+
if (this.isDisposed) return;
|
|
844
|
+
const index = this.cachedArchetypes.indexOf(archetype);
|
|
845
|
+
if (index !== -1) this.cachedArchetypes.splice(index, 1);
|
|
977
846
|
}
|
|
978
847
|
/**
|
|
979
|
-
*
|
|
848
|
+
* Request disposal of this query.
|
|
849
|
+
* This will decrement the world's reference count for the query.
|
|
850
|
+
* The query will only be fully disposed when the ref count reaches zero.
|
|
980
851
|
*/
|
|
981
|
-
|
|
982
|
-
this.
|
|
983
|
-
this.removes.clear();
|
|
852
|
+
dispose() {
|
|
853
|
+
this.world.releaseQuery(this);
|
|
984
854
|
}
|
|
985
855
|
/**
|
|
986
|
-
*
|
|
856
|
+
* Internal full dispose called by World when refCount reaches zero.
|
|
987
857
|
*/
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
this.
|
|
991
|
-
this.
|
|
992
|
-
|
|
993
|
-
for (const componentType of other.removes) {
|
|
994
|
-
this.removes.add(componentType);
|
|
995
|
-
this.adds.delete(componentType);
|
|
858
|
+
_disposeInternal() {
|
|
859
|
+
if (!this.isDisposed) {
|
|
860
|
+
this.world._unregisterQuery(this);
|
|
861
|
+
this.cachedArchetypes = [];
|
|
862
|
+
this.isDisposed = true;
|
|
996
863
|
}
|
|
997
864
|
}
|
|
998
865
|
/**
|
|
999
|
-
*
|
|
866
|
+
* Symbol.dispose implementation for automatic resource management
|
|
1000
867
|
*/
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
for (const [componentType, component$1] of this.adds) existingComponents.set(componentType, component$1);
|
|
1004
|
-
return existingComponents;
|
|
868
|
+
[Symbol.dispose]() {
|
|
869
|
+
this.dispose();
|
|
1005
870
|
}
|
|
1006
871
|
/**
|
|
1007
|
-
*
|
|
1008
|
-
* @param existingComponentTypes - The current component types on the entity
|
|
1009
|
-
* @returns The final component types or undefined if no changes
|
|
872
|
+
* Check if the query has been disposed
|
|
1010
873
|
*/
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
let changed = false;
|
|
1014
|
-
for (const componentType of this.removes) {
|
|
1015
|
-
if (!finalComponentTypes.has(componentType)) {
|
|
1016
|
-
this.removes.delete(componentType);
|
|
1017
|
-
continue;
|
|
1018
|
-
}
|
|
1019
|
-
changed = true;
|
|
1020
|
-
finalComponentTypes.delete(componentType);
|
|
1021
|
-
}
|
|
1022
|
-
for (const componentType of this.adds.keys()) {
|
|
1023
|
-
if (finalComponentTypes.has(componentType)) continue;
|
|
1024
|
-
changed = true;
|
|
1025
|
-
finalComponentTypes.add(componentType);
|
|
1026
|
-
}
|
|
1027
|
-
return changed ? Array.from(finalComponentTypes) : void 0;
|
|
874
|
+
get disposed() {
|
|
875
|
+
return this.isDisposed;
|
|
1028
876
|
}
|
|
1029
877
|
};
|
|
1030
878
|
|
|
1031
879
|
//#endregion
|
|
1032
|
-
//#region src/
|
|
880
|
+
//#region src/utils/utils.ts
|
|
1033
881
|
/**
|
|
1034
|
-
*
|
|
882
|
+
* Utility functions for ECS library
|
|
1035
883
|
*/
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
entityId,
|
|
1049
|
-
componentType,
|
|
1050
|
-
component: component$1
|
|
1051
|
-
});
|
|
1052
|
-
}
|
|
1053
|
-
/**
|
|
1054
|
-
* Remove a component from an entity (deferred)
|
|
1055
|
-
*/
|
|
1056
|
-
remove(entityId, componentType) {
|
|
1057
|
-
this.commands.push({
|
|
1058
|
-
type: "delete",
|
|
1059
|
-
entityId,
|
|
1060
|
-
componentType
|
|
1061
|
-
});
|
|
1062
|
-
}
|
|
1063
|
-
/**
|
|
1064
|
-
* Destroy an entity (deferred)
|
|
1065
|
-
*/
|
|
1066
|
-
delete(entityId) {
|
|
1067
|
-
this.commands.push({
|
|
1068
|
-
type: "destroy",
|
|
1069
|
-
entityId
|
|
1070
|
-
});
|
|
1071
|
-
}
|
|
1072
|
-
/**
|
|
1073
|
-
* Execute all commands and clear the buffer
|
|
1074
|
-
*/
|
|
1075
|
-
execute() {
|
|
1076
|
-
const MAX_ITERATIONS = 100;
|
|
1077
|
-
let iterations = 0;
|
|
1078
|
-
while (this.commands.length > 0) {
|
|
1079
|
-
if (iterations >= MAX_ITERATIONS) throw new Error("Command execution exceeded maximum iterations, possible infinite loop");
|
|
1080
|
-
iterations++;
|
|
1081
|
-
const currentCommands = [...this.commands];
|
|
1082
|
-
this.commands = [];
|
|
1083
|
-
const entityCommands = /* @__PURE__ */ new Map();
|
|
1084
|
-
for (const cmd of currentCommands) {
|
|
1085
|
-
if (!entityCommands.has(cmd.entityId)) entityCommands.set(cmd.entityId, []);
|
|
1086
|
-
entityCommands.get(cmd.entityId).push(cmd);
|
|
1087
|
-
}
|
|
1088
|
-
for (const [entityId, commands] of entityCommands) this.executeEntityCommands(entityId, commands);
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
/**
|
|
1092
|
-
* Get current commands (for testing)
|
|
1093
|
-
*/
|
|
1094
|
-
getCommands() {
|
|
1095
|
-
return [...this.commands];
|
|
884
|
+
/**
|
|
885
|
+
* Get a value from cache or compute and cache it if not present
|
|
886
|
+
* @param cache The cache map
|
|
887
|
+
* @param key The cache key
|
|
888
|
+
* @param compute Function to compute the value if not cached
|
|
889
|
+
* @returns The cached or computed value
|
|
890
|
+
*/
|
|
891
|
+
function getOrComputeCache(cache, key, compute) {
|
|
892
|
+
let value = cache.get(key);
|
|
893
|
+
if (value === void 0) {
|
|
894
|
+
value = compute();
|
|
895
|
+
cache.set(key, value);
|
|
1096
896
|
}
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
897
|
+
return value;
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Get a value from cache or create and cache it if not present, allowing side effects during creation
|
|
901
|
+
* @param cache The cache map
|
|
902
|
+
* @param key The cache key
|
|
903
|
+
* @param create Function to create the value if not cached (can have side effects)
|
|
904
|
+
* @returns The cached or created value
|
|
905
|
+
*/
|
|
906
|
+
function getOrCreateWithSideEffect(cache, key, create) {
|
|
907
|
+
let value = cache.get(key);
|
|
908
|
+
if (value === void 0) {
|
|
909
|
+
value = create();
|
|
910
|
+
cache.set(key, value);
|
|
1102
911
|
}
|
|
1103
|
-
|
|
912
|
+
return value;
|
|
913
|
+
}
|
|
1104
914
|
|
|
1105
915
|
//#endregion
|
|
1106
|
-
//#region src/
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
get valueCount() {
|
|
1111
|
-
return this._valueCount;
|
|
1112
|
-
}
|
|
1113
|
-
get keyCount() {
|
|
1114
|
-
return this.map.size;
|
|
1115
|
-
}
|
|
1116
|
-
hasKey(key) {
|
|
1117
|
-
return this.map.has(key);
|
|
1118
|
-
}
|
|
1119
|
-
has(key, value) {
|
|
1120
|
-
const set = this.map.get(key);
|
|
1121
|
-
if (!set) return false;
|
|
1122
|
-
if (arguments.length === 1) return true;
|
|
1123
|
-
return set.has(value);
|
|
1124
|
-
}
|
|
1125
|
-
add(key, value) {
|
|
1126
|
-
let set = this.map.get(key);
|
|
1127
|
-
if (!set) {
|
|
1128
|
-
set = /* @__PURE__ */ new Set();
|
|
1129
|
-
this.map.set(key, set);
|
|
1130
|
-
}
|
|
1131
|
-
if (!set.has(value)) {
|
|
1132
|
-
set.add(value);
|
|
1133
|
-
this._valueCount++;
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
remove(key, value) {
|
|
1137
|
-
const set = this.map.get(key);
|
|
1138
|
-
if (!set) return false;
|
|
1139
|
-
if (!set.has(value)) return false;
|
|
1140
|
-
set.delete(value);
|
|
1141
|
-
this._valueCount--;
|
|
1142
|
-
if (set.size === 0) this.map.delete(key);
|
|
1143
|
-
return true;
|
|
1144
|
-
}
|
|
1145
|
-
deleteKey(key) {
|
|
1146
|
-
const set = this.map.get(key);
|
|
1147
|
-
if (!set) return false;
|
|
1148
|
-
this._valueCount -= set.size;
|
|
1149
|
-
this.map.delete(key);
|
|
1150
|
-
return true;
|
|
1151
|
-
}
|
|
1152
|
-
get(key) {
|
|
1153
|
-
const set = this.map.get(key);
|
|
1154
|
-
return set ? new Set(set) : /* @__PURE__ */ new Set();
|
|
1155
|
-
}
|
|
1156
|
-
*keys() {
|
|
1157
|
-
yield* this.map.keys();
|
|
1158
|
-
}
|
|
1159
|
-
*values() {
|
|
1160
|
-
for (const set of this.map.values()) for (const v of set) yield v;
|
|
1161
|
-
}
|
|
1162
|
-
[Symbol.iterator]() {
|
|
1163
|
-
return this.entries();
|
|
1164
|
-
}
|
|
1165
|
-
*entries() {
|
|
1166
|
-
for (const [k, set] of this.map.entries()) for (const v of set) yield [k, v];
|
|
1167
|
-
}
|
|
1168
|
-
clear() {
|
|
1169
|
-
this.map.clear();
|
|
1170
|
-
this._valueCount = 0;
|
|
1171
|
-
}
|
|
1172
|
-
};
|
|
916
|
+
//#region src/core/types.ts
|
|
917
|
+
function isOptionalEntityId(type) {
|
|
918
|
+
return typeof type === "object" && type !== null && "optional" in type;
|
|
919
|
+
}
|
|
1173
920
|
|
|
1174
921
|
//#endregion
|
|
1175
|
-
//#region src/
|
|
922
|
+
//#region src/core/archetype-helpers.ts
|
|
1176
923
|
/**
|
|
1177
|
-
*
|
|
1178
|
-
* Currently only serializes `negativeComponentTypes`.
|
|
924
|
+
* Check if a detailed type represents a relation (entity or component)
|
|
1179
925
|
*/
|
|
1180
|
-
function
|
|
1181
|
-
|
|
1182
|
-
if (negative.length === 0) return "";
|
|
1183
|
-
return `neg:${negative.join(",")}`;
|
|
926
|
+
function isRelationType(detailedType) {
|
|
927
|
+
return detailedType.type === "entity-relation" || detailedType.type === "component-relation";
|
|
1184
928
|
}
|
|
1185
929
|
/**
|
|
1186
|
-
* Check if
|
|
930
|
+
* Check if a component type matches a given component ID for relations
|
|
1187
931
|
*/
|
|
1188
|
-
function
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
if (detailedType.type === "wildcard-relation") return archetype.componentTypes.some((archetypeType) => {
|
|
1192
|
-
if (!isRelationId(archetypeType)) return false;
|
|
1193
|
-
return getComponentIdFromRelationId(archetypeType) === detailedType.componentId;
|
|
1194
|
-
});
|
|
1195
|
-
else return archetype.componentTypes.includes(type);
|
|
1196
|
-
});
|
|
932
|
+
function matchesRelationComponentId(componentType, componentId) {
|
|
933
|
+
const detailedType = getDetailedIdType(componentType);
|
|
934
|
+
return isRelationType(detailedType) && detailedType.componentId === componentId;
|
|
1197
935
|
}
|
|
1198
936
|
/**
|
|
1199
|
-
*
|
|
937
|
+
* Find all relations in dontFragment data that match a component ID
|
|
1200
938
|
*/
|
|
1201
|
-
function
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
939
|
+
function findMatchingDontFragmentRelations(dontFragmentData, componentId) {
|
|
940
|
+
const relations = [];
|
|
941
|
+
if (!dontFragmentData) return relations;
|
|
942
|
+
for (const [relType, data] of dontFragmentData) {
|
|
943
|
+
const relDetailed = getDetailedIdType(relType);
|
|
944
|
+
if (isRelationType(relDetailed) && relDetailed.componentId === componentId) relations.push([relDetailed.targetId, data]);
|
|
945
|
+
}
|
|
946
|
+
return relations;
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Build cache key for component types
|
|
950
|
+
*/
|
|
951
|
+
function buildCacheKey(componentTypes) {
|
|
952
|
+
return componentTypes.map((id) => isOptionalEntityId(id) ? `opt(${id.optional})` : `${id}`).join(",");
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* Get data source for wildcard relations from component types
|
|
956
|
+
*/
|
|
957
|
+
function getWildcardRelationDataSource(componentTypes, componentId, optional) {
|
|
958
|
+
const matchingRelations = componentTypes.filter((ct) => matchesRelationComponentId(ct, componentId));
|
|
959
|
+
return optional ? matchingRelations.length > 0 ? matchingRelations : void 0 : matchingRelations;
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Build wildcard relation value from matching relations
|
|
963
|
+
*/
|
|
964
|
+
function buildWildcardRelationValue(wildcardRelationType, matchingRelations, getDataAtIndex, dontFragmentData, entityId, optional) {
|
|
965
|
+
const relations = [];
|
|
966
|
+
const targetComponentId = getComponentIdFromRelationId(wildcardRelationType);
|
|
967
|
+
for (const relType of matchingRelations || []) {
|
|
968
|
+
const data = getDataAtIndex(relType);
|
|
969
|
+
const targetId = getTargetIdFromRelationId(relType);
|
|
970
|
+
relations.push([targetId, data === MISSING_COMPONENT ? void 0 : data]);
|
|
971
|
+
}
|
|
972
|
+
if (targetComponentId !== void 0) relations.push(...findMatchingDontFragmentRelations(dontFragmentData, targetComponentId));
|
|
973
|
+
if (relations.length === 0) {
|
|
974
|
+
if (!optional) {
|
|
975
|
+
const componentId = getComponentIdFromRelationId(wildcardRelationType);
|
|
976
|
+
throw new Error(`No matching relations found for mandatory wildcard relation component ${componentId} on entity ${entityId}`);
|
|
977
|
+
}
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
return optional ? { value: relations } : relations;
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Build regular component value from data source
|
|
984
|
+
*/
|
|
985
|
+
function buildRegularComponentValue(dataSource, entityIndex, optional) {
|
|
986
|
+
if (dataSource === void 0) {
|
|
987
|
+
if (optional) return void 0;
|
|
988
|
+
throw new Error(`Component data not found for mandatory component type`);
|
|
989
|
+
}
|
|
990
|
+
const data = dataSource[entityIndex];
|
|
991
|
+
const result = data === MISSING_COMPONENT ? void 0 : data;
|
|
992
|
+
return optional ? { value: result } : result;
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Build a single component value based on its type
|
|
996
|
+
*/
|
|
997
|
+
function buildSingleComponent(compType, dataSource, entityIndex, entityId, getComponentData, dontFragmentRelations) {
|
|
998
|
+
const optional = isOptionalEntityId(compType);
|
|
999
|
+
const actualType = optional ? compType.optional : compType;
|
|
1000
|
+
if (getIdType(actualType) === "wildcard-relation") return buildWildcardRelationValue(actualType, dataSource, (relType) => getComponentData(relType)[entityIndex], dontFragmentRelations.get(entityId), entityId, optional);
|
|
1001
|
+
else return buildRegularComponentValue(dataSource, entityIndex, optional);
|
|
1210
1002
|
}
|
|
1211
1003
|
|
|
1212
1004
|
//#endregion
|
|
1213
|
-
//#region src/
|
|
1005
|
+
//#region src/core/archetype.ts
|
|
1214
1006
|
/**
|
|
1215
|
-
*
|
|
1007
|
+
* Special value to represent missing component data
|
|
1216
1008
|
*/
|
|
1217
|
-
|
|
1218
|
-
|
|
1009
|
+
const MISSING_COMPONENT = Symbol("missing component");
|
|
1010
|
+
/**
|
|
1011
|
+
* Archetype class for ECS architecture
|
|
1012
|
+
* Represents a group of entities that share the same set of components
|
|
1013
|
+
* Optimized for fast iteration and component access
|
|
1014
|
+
*/
|
|
1015
|
+
var Archetype = class {
|
|
1016
|
+
/**
|
|
1017
|
+
* The component types that define this archetype
|
|
1018
|
+
*/
|
|
1219
1019
|
componentTypes;
|
|
1220
|
-
filter;
|
|
1221
|
-
cachedArchetypes = [];
|
|
1222
|
-
isDisposed = false;
|
|
1223
|
-
constructor(world, componentTypes, filter = {}) {
|
|
1224
|
-
this.world = world;
|
|
1225
|
-
this.componentTypes = [...componentTypes].sort((a, b) => a - b);
|
|
1226
|
-
this.filter = filter;
|
|
1227
|
-
this.updateCache();
|
|
1228
|
-
world._registerQuery(this);
|
|
1229
|
-
}
|
|
1230
1020
|
/**
|
|
1231
|
-
*
|
|
1021
|
+
* List of entities in this archetype
|
|
1232
1022
|
*/
|
|
1233
|
-
|
|
1234
|
-
if (this.isDisposed) throw new Error("Query has been disposed");
|
|
1235
|
-
}
|
|
1023
|
+
entities = [];
|
|
1236
1024
|
/**
|
|
1237
|
-
*
|
|
1025
|
+
* Component data storage - maps component type to array of component data
|
|
1026
|
+
* Each array index corresponds to the entity index in the entities array
|
|
1238
1027
|
*/
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1028
|
+
componentData = /* @__PURE__ */ new Map();
|
|
1029
|
+
/**
|
|
1030
|
+
* Reverse mapping from entity to its index in this archetype
|
|
1031
|
+
*/
|
|
1032
|
+
entityToIndex = /* @__PURE__ */ new Map();
|
|
1033
|
+
/**
|
|
1034
|
+
* Reference to dontFragment relations storage from World
|
|
1035
|
+
* This allows entities with different relation targets to share the same archetype
|
|
1036
|
+
* Stored in World to avoid migration overhead when entities change archetypes
|
|
1037
|
+
*/
|
|
1038
|
+
dontFragmentRelations;
|
|
1039
|
+
/**
|
|
1040
|
+
* Cache for pre-computed component data sources to avoid repeated calculations
|
|
1041
|
+
*/
|
|
1042
|
+
componentDataSourcesCache = /* @__PURE__ */ new Map();
|
|
1043
|
+
constructor(componentTypes, dontFragmentRelations) {
|
|
1044
|
+
this.componentTypes = [...componentTypes].sort((a, b) => a - b);
|
|
1045
|
+
this.dontFragmentRelations = dontFragmentRelations;
|
|
1046
|
+
for (const componentType of this.componentTypes) this.componentData.set(componentType, []);
|
|
1047
|
+
}
|
|
1048
|
+
get size() {
|
|
1049
|
+
return this.entities.length;
|
|
1050
|
+
}
|
|
1051
|
+
matches(componentTypes) {
|
|
1052
|
+
if (this.componentTypes.length !== componentTypes.length) return false;
|
|
1053
|
+
const sortedTypes = [...componentTypes].sort((a, b) => a - b);
|
|
1054
|
+
return this.componentTypes.every((type, index) => type === sortedTypes[index]);
|
|
1055
|
+
}
|
|
1056
|
+
addEntity(entityId, componentData) {
|
|
1057
|
+
if (this.entityToIndex.has(entityId)) throw new Error(`Entity ${entityId} is already in this archetype`);
|
|
1058
|
+
const index = this.entities.length;
|
|
1059
|
+
this.entities.push(entityId);
|
|
1060
|
+
this.entityToIndex.set(entityId, index);
|
|
1061
|
+
for (const componentType of this.componentTypes) {
|
|
1062
|
+
const data = componentData.get(componentType);
|
|
1063
|
+
this.getComponentData(componentType).push(!componentData.has(componentType) ? MISSING_COMPONENT : data);
|
|
1064
|
+
}
|
|
1065
|
+
this.addDontFragmentRelations(entityId, componentData);
|
|
1066
|
+
}
|
|
1067
|
+
addDontFragmentRelations(entityId, componentData) {
|
|
1068
|
+
const dontFragmentData = /* @__PURE__ */ new Map();
|
|
1069
|
+
for (const [componentType, data] of componentData) {
|
|
1070
|
+
if (this.componentTypes.includes(componentType)) continue;
|
|
1071
|
+
const detailedType = getDetailedIdType(componentType);
|
|
1072
|
+
if (isRelationType(detailedType) && isDontFragmentComponent(detailedType.componentId)) dontFragmentData.set(componentType, data);
|
|
1073
|
+
}
|
|
1074
|
+
if (dontFragmentData.size > 0) this.dontFragmentRelations.set(entityId, dontFragmentData);
|
|
1075
|
+
}
|
|
1076
|
+
getEntity(entityId) {
|
|
1077
|
+
const index = this.entityToIndex.get(entityId);
|
|
1078
|
+
if (index === void 0) return void 0;
|
|
1079
|
+
const entityData = /* @__PURE__ */ new Map();
|
|
1080
|
+
for (const componentType of this.componentTypes) {
|
|
1081
|
+
const data = this.getComponentData(componentType)[index];
|
|
1082
|
+
entityData.set(componentType, data === MISSING_COMPONENT ? void 0 : data);
|
|
1083
|
+
}
|
|
1084
|
+
const dontFragmentData = this.dontFragmentRelations.get(entityId);
|
|
1085
|
+
if (dontFragmentData) for (const [componentType, data] of dontFragmentData) entityData.set(componentType, data);
|
|
1086
|
+
return entityData;
|
|
1087
|
+
}
|
|
1088
|
+
dump() {
|
|
1089
|
+
return this.entities.map((entity, i) => {
|
|
1090
|
+
const components = /* @__PURE__ */ new Map();
|
|
1091
|
+
for (const componentType of this.componentTypes) {
|
|
1092
|
+
const data = this.getComponentData(componentType)[i];
|
|
1093
|
+
components.set(componentType, data === MISSING_COMPONENT ? void 0 : data);
|
|
1094
|
+
}
|
|
1095
|
+
const dontFragmentData = this.dontFragmentRelations.get(entity);
|
|
1096
|
+
if (dontFragmentData) for (const [componentType, data] of dontFragmentData) components.set(componentType, data);
|
|
1097
|
+
return {
|
|
1098
|
+
entity,
|
|
1099
|
+
components
|
|
1100
|
+
};
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
removeEntity(entityId) {
|
|
1104
|
+
const index = this.entityToIndex.get(entityId);
|
|
1105
|
+
if (index === void 0) return void 0;
|
|
1106
|
+
const removedData = /* @__PURE__ */ new Map();
|
|
1107
|
+
for (const componentType of this.componentTypes) removedData.set(componentType, this.getComponentData(componentType)[index]);
|
|
1108
|
+
const dontFragmentData = this.dontFragmentRelations.get(entityId);
|
|
1109
|
+
if (dontFragmentData) {
|
|
1110
|
+
for (const [componentType, data] of dontFragmentData) removedData.set(componentType, data);
|
|
1111
|
+
this.dontFragmentRelations.delete(entityId);
|
|
1112
|
+
}
|
|
1113
|
+
this.entityToIndex.delete(entityId);
|
|
1114
|
+
const lastIndex = this.entities.length - 1;
|
|
1115
|
+
if (index !== lastIndex) {
|
|
1116
|
+
const lastEntity = this.entities[lastIndex];
|
|
1117
|
+
this.entities[index] = lastEntity;
|
|
1118
|
+
this.entityToIndex.set(lastEntity, index);
|
|
1119
|
+
for (const componentType of this.componentTypes) {
|
|
1120
|
+
const dataArray = this.getComponentData(componentType);
|
|
1121
|
+
dataArray[index] = dataArray[lastIndex];
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
this.entities.pop();
|
|
1125
|
+
for (const componentType of this.componentTypes) this.getComponentData(componentType).pop();
|
|
1126
|
+
return removedData;
|
|
1127
|
+
}
|
|
1128
|
+
exists(entityId) {
|
|
1129
|
+
return this.entityToIndex.has(entityId);
|
|
1130
|
+
}
|
|
1131
|
+
get(entityId, componentType) {
|
|
1132
|
+
const index = this.entityToIndex.get(entityId);
|
|
1133
|
+
if (index === void 0) throw new Error(`Entity ${entityId} is not in this archetype`);
|
|
1134
|
+
if (isWildcardRelationId(componentType)) return this.getWildcardRelations(entityId, index, componentType);
|
|
1135
|
+
return this.getRegularComponent(entityId, index, componentType);
|
|
1136
|
+
}
|
|
1137
|
+
getWildcardRelations(entityId, index, componentType) {
|
|
1138
|
+
const componentId = getComponentIdFromRelationId(componentType);
|
|
1139
|
+
const relations = [];
|
|
1140
|
+
for (const relType of this.componentTypes) {
|
|
1141
|
+
const relDetailed = getDetailedIdType(relType);
|
|
1142
|
+
if (isRelationType(relDetailed) && relDetailed.componentId === componentId) {
|
|
1143
|
+
const dataArray = this.getComponentData(relType);
|
|
1144
|
+
if (dataArray && dataArray[index] !== void 0) {
|
|
1145
|
+
const data = dataArray[index];
|
|
1146
|
+
relations.push([relDetailed.targetId, data === MISSING_COMPONENT ? void 0 : data]);
|
|
1251
1147
|
}
|
|
1252
1148
|
}
|
|
1253
|
-
if (hasAllRelations) result.push(entity);
|
|
1254
1149
|
}
|
|
1255
|
-
|
|
1256
|
-
return
|
|
1150
|
+
if (componentId !== void 0) relations.push(...findMatchingDontFragmentRelations(this.dontFragmentRelations.get(entityId), componentId));
|
|
1151
|
+
return relations;
|
|
1257
1152
|
}
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
getEntitiesWithComponents(componentTypes) {
|
|
1264
|
-
this.ensureNotDisposed();
|
|
1265
|
-
const result = [];
|
|
1266
|
-
for (const archetype of this.cachedArchetypes) {
|
|
1267
|
-
const entitiesWithData = archetype.getEntitiesWithComponents(componentTypes);
|
|
1268
|
-
result.push(...entitiesWithData);
|
|
1153
|
+
getRegularComponent(entityId, index, componentType) {
|
|
1154
|
+
if (this.componentTypes.includes(componentType)) {
|
|
1155
|
+
const data = this.getComponentData(componentType)[index];
|
|
1156
|
+
if (data === MISSING_COMPONENT) throw new Error(`Component type ${componentType} not found for entity ${entityId}`);
|
|
1157
|
+
return data;
|
|
1269
1158
|
}
|
|
1270
|
-
|
|
1159
|
+
const dontFragmentData = this.dontFragmentRelations.get(entityId);
|
|
1160
|
+
if (dontFragmentData?.has(componentType)) return dontFragmentData.get(componentType);
|
|
1161
|
+
throw new Error(`Component type ${componentType} not found for entity ${entityId}`);
|
|
1271
1162
|
}
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1163
|
+
getOptional(entityId, componentType) {
|
|
1164
|
+
const index = this.entityToIndex.get(entityId);
|
|
1165
|
+
if (index === void 0) throw new Error(`Entity ${entityId} is not in this archetype`);
|
|
1166
|
+
if (this.componentTypes.includes(componentType)) {
|
|
1167
|
+
const data = this.getComponentData(componentType)[index];
|
|
1168
|
+
if (data === MISSING_COMPONENT) return void 0;
|
|
1169
|
+
return { value: data };
|
|
1170
|
+
}
|
|
1171
|
+
const dontFragmentData = this.dontFragmentRelations.get(entityId);
|
|
1172
|
+
if (dontFragmentData?.has(componentType)) return { value: dontFragmentData.get(componentType) };
|
|
1173
|
+
}
|
|
1174
|
+
set(entityId, componentType, data) {
|
|
1175
|
+
const index = this.entityToIndex.get(entityId);
|
|
1176
|
+
if (index === void 0) throw new Error(`Entity ${entityId} is not in this archetype`);
|
|
1177
|
+
if (this.componentData.has(componentType)) {
|
|
1178
|
+
this.getComponentData(componentType)[index] = data;
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
const detailedType = getDetailedIdType(componentType);
|
|
1182
|
+
if (isRelationType(detailedType) && isDontFragmentComponent(detailedType.componentId)) {
|
|
1183
|
+
let dontFragmentData = this.dontFragmentRelations.get(entityId);
|
|
1184
|
+
if (!dontFragmentData) {
|
|
1185
|
+
dontFragmentData = /* @__PURE__ */ new Map();
|
|
1186
|
+
this.dontFragmentRelations.set(entityId, dontFragmentData);
|
|
1187
|
+
}
|
|
1188
|
+
dontFragmentData.set(componentType, data);
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
throw new Error(`Component type ${componentType} is not in this archetype`);
|
|
1280
1192
|
}
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
this.ensureNotDisposed();
|
|
1287
|
-
for (const archetype of this.cachedArchetypes) yield* archetype.iterateWithComponents(componentTypes);
|
|
1193
|
+
getEntities() {
|
|
1194
|
+
return this.entities;
|
|
1195
|
+
}
|
|
1196
|
+
getEntityToIndexMap() {
|
|
1197
|
+
return this.entityToIndex;
|
|
1288
1198
|
}
|
|
1289
|
-
/**
|
|
1290
|
-
* Get component data arrays for all matching entities
|
|
1291
|
-
* @param componentType The component type to retrieve
|
|
1292
|
-
* @returns Array of component data for all matching entities
|
|
1293
|
-
*/
|
|
1294
1199
|
getComponentData(componentType) {
|
|
1295
|
-
this.
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
return result;
|
|
1200
|
+
const data = this.componentData.get(componentType);
|
|
1201
|
+
if (!data) throw new Error(`Component type ${componentType} is not in this archetype`);
|
|
1202
|
+
return data;
|
|
1299
1203
|
}
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
* Called when new archetypes are created
|
|
1303
|
-
*/
|
|
1304
|
-
updateCache() {
|
|
1305
|
-
if (this.isDisposed) return;
|
|
1306
|
-
this.cachedArchetypes = this.world.getMatchingArchetypes(this.componentTypes).filter((archetype) => matchesFilter(archetype, this.filter));
|
|
1204
|
+
getOptionalComponentData(componentType) {
|
|
1205
|
+
return this.componentData.get(componentType);
|
|
1307
1206
|
}
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
checkNewArchetype(archetype) {
|
|
1312
|
-
if (this.isDisposed) return;
|
|
1313
|
-
if (matchesComponentTypes(archetype, this.componentTypes) && matchesFilter(archetype, this.filter) && !this.cachedArchetypes.includes(archetype)) this.cachedArchetypes.push(archetype);
|
|
1207
|
+
getCachedComponentDataSources(componentTypes) {
|
|
1208
|
+
const cacheKey = buildCacheKey(componentTypes);
|
|
1209
|
+
return getOrComputeCache(this.componentDataSourcesCache, cacheKey, () => componentTypes.map((compType) => this.getComponentDataSource(compType)));
|
|
1314
1210
|
}
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1211
|
+
getComponentDataSource(compType) {
|
|
1212
|
+
const optional = isOptionalEntityId(compType);
|
|
1213
|
+
const actualType = optional ? compType.optional : compType;
|
|
1214
|
+
if (getIdType(actualType) === "wildcard-relation") {
|
|
1215
|
+
const componentId = getComponentIdFromRelationId(actualType);
|
|
1216
|
+
return getWildcardRelationDataSource(this.componentTypes, componentId, optional);
|
|
1217
|
+
}
|
|
1218
|
+
return optional ? this.getOptionalComponentData(actualType) : this.getComponentData(actualType);
|
|
1322
1219
|
}
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
*/
|
|
1326
|
-
/**
|
|
1327
|
-
* Request disposal of this query.
|
|
1328
|
-
* This will decrement the world's reference count for the query.
|
|
1329
|
-
* The query will only be fully disposed when the ref count reaches zero.
|
|
1330
|
-
*/
|
|
1331
|
-
dispose() {
|
|
1332
|
-
this.world.releaseQuery(this);
|
|
1220
|
+
buildComponentsForIndex(componentTypes, componentDataSources, entityIndex, entityId) {
|
|
1221
|
+
return componentDataSources.map((dataSource, i) => buildSingleComponent(componentTypes[i], dataSource, entityIndex, entityId, (type) => this.getComponentData(type), this.dontFragmentRelations));
|
|
1333
1222
|
}
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1223
|
+
getEntitiesWithComponents(componentTypes) {
|
|
1224
|
+
const result = [];
|
|
1225
|
+
this.forEachWithComponents(componentTypes, (entity, ...components) => {
|
|
1226
|
+
result.push({
|
|
1227
|
+
entity,
|
|
1228
|
+
components
|
|
1229
|
+
});
|
|
1230
|
+
});
|
|
1231
|
+
return result;
|
|
1232
|
+
}
|
|
1233
|
+
*iterateWithComponents(componentTypes) {
|
|
1234
|
+
const componentDataSources = this.getCachedComponentDataSources(componentTypes);
|
|
1235
|
+
for (let entityIndex = 0; entityIndex < this.entities.length; entityIndex++) {
|
|
1236
|
+
const entity = this.entities[entityIndex];
|
|
1237
|
+
yield [entity, ...this.buildComponentsForIndex(componentTypes, componentDataSources, entityIndex, entity)];
|
|
1342
1238
|
}
|
|
1343
1239
|
}
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1240
|
+
forEachWithComponents(componentTypes, callback) {
|
|
1241
|
+
const componentDataSources = this.getCachedComponentDataSources(componentTypes);
|
|
1242
|
+
for (let entityIndex = 0; entityIndex < this.entities.length; entityIndex++) {
|
|
1243
|
+
const entity = this.entities[entityIndex];
|
|
1244
|
+
callback(entity, ...this.buildComponentsForIndex(componentTypes, componentDataSources, entityIndex, entity));
|
|
1245
|
+
}
|
|
1349
1246
|
}
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1247
|
+
forEach(callback) {
|
|
1248
|
+
for (let i = 0; i < this.entities.length; i++) {
|
|
1249
|
+
const components = /* @__PURE__ */ new Map();
|
|
1250
|
+
for (const componentType of this.componentTypes) {
|
|
1251
|
+
const data = this.getComponentData(componentType)[i];
|
|
1252
|
+
components.set(componentType, data === MISSING_COMPONENT ? void 0 : data);
|
|
1253
|
+
}
|
|
1254
|
+
callback(this.entities[i], components);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
hasRelationWithComponentId(componentId) {
|
|
1258
|
+
for (const componentType of this.componentTypes) {
|
|
1259
|
+
const detailedType = getDetailedIdType(componentType);
|
|
1260
|
+
if (isRelationType(detailedType) && detailedType.componentId === componentId) return true;
|
|
1261
|
+
}
|
|
1262
|
+
for (const entityId of this.entities) {
|
|
1263
|
+
const entityDontFragmentRelations = this.dontFragmentRelations.get(entityId);
|
|
1264
|
+
if (entityDontFragmentRelations) for (const relationType of entityDontFragmentRelations.keys()) {
|
|
1265
|
+
const detailedType = getDetailedIdType(relationType);
|
|
1266
|
+
if (isRelationType(detailedType) && detailedType.componentId === componentId) return true;
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
return false;
|
|
1355
1270
|
}
|
|
1356
1271
|
};
|
|
1357
1272
|
|
|
1358
1273
|
//#endregion
|
|
1359
|
-
//#region src/
|
|
1274
|
+
//#region src/core/serialization.ts
|
|
1360
1275
|
/**
|
|
1361
1276
|
* Encode an internal EntityId into a SerializedEntityId for snapshots
|
|
1362
1277
|
*/
|
|
@@ -1432,77 +1347,302 @@ function decodeSerializedId(sid) {
|
|
|
1432
1347
|
}
|
|
1433
1348
|
throw new Error(`Invalid ID in snapshot: ${JSON.stringify(sid)}`);
|
|
1434
1349
|
}
|
|
1350
|
+
|
|
1351
|
+
//#endregion
|
|
1352
|
+
//#region src/core/world-commands.ts
|
|
1353
|
+
function processCommands(entityId, currentArchetype, commands, changeset, handleExclusiveRelation) {
|
|
1354
|
+
for (const command of commands) if (command.type === "set" && command.componentType) processSetCommand(entityId, currentArchetype, command.componentType, command.component, changeset, handleExclusiveRelation);
|
|
1355
|
+
else if (command.type === "delete" && command.componentType) processDeleteCommand(entityId, currentArchetype, command.componentType, changeset);
|
|
1356
|
+
}
|
|
1357
|
+
function processSetCommand(entityId, currentArchetype, componentType, component$1, changeset, handleExclusiveRelation) {
|
|
1358
|
+
const componentId = getComponentIdFromRelationId(componentType);
|
|
1359
|
+
if (componentId !== void 0) {
|
|
1360
|
+
handleExclusiveRelation(entityId, currentArchetype, componentId);
|
|
1361
|
+
if (isDontFragmentComponent(componentId)) {
|
|
1362
|
+
const wildcardMarker = relation(componentId, "*");
|
|
1363
|
+
if (!currentArchetype.componentTypes.includes(wildcardMarker)) changeset.set(wildcardMarker, void 0);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
changeset.set(componentType, component$1);
|
|
1367
|
+
}
|
|
1368
|
+
function processDeleteCommand(entityId, currentArchetype, componentType, changeset) {
|
|
1369
|
+
const componentId = getComponentIdFromRelationId(componentType);
|
|
1370
|
+
if (isWildcardRelationId(componentType) && componentId !== void 0) removeWildcardRelations(entityId, currentArchetype, componentId, changeset);
|
|
1371
|
+
else {
|
|
1372
|
+
changeset.delete(componentType);
|
|
1373
|
+
maybeRemoveWildcardMarker(entityId, currentArchetype, componentType, componentId, changeset);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
function removeMatchingRelations(entityId, archetype, baseComponentId, changeset) {
|
|
1377
|
+
for (const componentType of archetype.componentTypes) if (getComponentIdFromRelationId(componentType) === baseComponentId) changeset.delete(componentType);
|
|
1378
|
+
const entityData = archetype.getEntity(entityId);
|
|
1379
|
+
if (entityData) for (const [componentType] of entityData) {
|
|
1380
|
+
if (archetype.componentTypes.includes(componentType)) continue;
|
|
1381
|
+
if (getComponentIdFromRelationId(componentType) === baseComponentId) changeset.delete(componentType);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
function removeWildcardRelations(entityId, currentArchetype, baseComponentId, changeset) {
|
|
1385
|
+
removeMatchingRelations(entityId, currentArchetype, baseComponentId, changeset);
|
|
1386
|
+
if (isDontFragmentComponent(baseComponentId)) changeset.delete(relation(baseComponentId, "*"));
|
|
1387
|
+
}
|
|
1388
|
+
function maybeRemoveWildcardMarker(entityId, archetype, removedComponentType, componentId, changeset) {
|
|
1389
|
+
if (componentId === void 0 || !isDontFragmentComponent(componentId)) return;
|
|
1390
|
+
const wildcardMarker = relation(componentId, "*");
|
|
1391
|
+
const entityData = archetype.getEntity(entityId);
|
|
1392
|
+
if (!entityData) {
|
|
1393
|
+
changeset.delete(wildcardMarker);
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
for (const [otherComponentType] of entityData) {
|
|
1397
|
+
if (otherComponentType === removedComponentType) continue;
|
|
1398
|
+
if (otherComponentType === wildcardMarker) continue;
|
|
1399
|
+
if (changeset.removes.has(otherComponentType)) continue;
|
|
1400
|
+
if (getComponentIdFromRelationId(otherComponentType) === componentId) return;
|
|
1401
|
+
}
|
|
1402
|
+
changeset.delete(wildcardMarker);
|
|
1403
|
+
}
|
|
1404
|
+
function applyChangeset(ctx, entityId, currentArchetype, changeset, entityToArchetype) {
|
|
1405
|
+
const currentEntityData = currentArchetype.getEntity(entityId);
|
|
1406
|
+
const allCurrentComponentTypes = currentEntityData ? Array.from(currentEntityData.keys()) : currentArchetype.componentTypes;
|
|
1407
|
+
const finalComponentTypes = changeset.getFinalComponentTypes(allCurrentComponentTypes);
|
|
1408
|
+
const removedComponents = /* @__PURE__ */ new Map();
|
|
1409
|
+
if (finalComponentTypes) if (!areComponentTypesEqual(filterRegularComponentTypes(allCurrentComponentTypes), filterRegularComponentTypes(finalComponentTypes))) moveEntityToNewArchetype(ctx, entityId, currentArchetype, finalComponentTypes, changeset, removedComponents, entityToArchetype);
|
|
1410
|
+
else updateEntityInSameArchetype(ctx, entityId, currentArchetype, changeset, removedComponents);
|
|
1411
|
+
else updateEntityInSameArchetype(ctx, entityId, currentArchetype, changeset, removedComponents);
|
|
1412
|
+
return removedComponents;
|
|
1413
|
+
}
|
|
1414
|
+
function moveEntityToNewArchetype(ctx, entityId, currentArchetype, finalComponentTypes, changeset, removedComponents, entityToArchetype) {
|
|
1415
|
+
const newArchetype = ctx.ensureArchetype(finalComponentTypes);
|
|
1416
|
+
const currentComponents = currentArchetype.removeEntity(entityId);
|
|
1417
|
+
for (const componentType of changeset.removes) removedComponents.set(componentType, currentComponents.get(componentType));
|
|
1418
|
+
newArchetype.addEntity(entityId, changeset.applyTo(currentComponents));
|
|
1419
|
+
entityToArchetype.set(entityId, newArchetype);
|
|
1420
|
+
}
|
|
1421
|
+
function updateEntityInSameArchetype(ctx, entityId, currentArchetype, changeset, removedComponents) {
|
|
1422
|
+
applyDontFragmentChanges(ctx.dontFragmentRelations, entityId, changeset, removedComponents);
|
|
1423
|
+
for (const [componentType, component$1] of changeset.adds) {
|
|
1424
|
+
if (isDontFragmentRelation(componentType)) continue;
|
|
1425
|
+
currentArchetype.set(entityId, componentType, component$1);
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
function applyDontFragmentChanges(dontFragmentRelations, entityId, changeset, removedComponents) {
|
|
1429
|
+
let entityRelations = dontFragmentRelations.get(entityId);
|
|
1430
|
+
for (const componentType of changeset.removes) if (isDontFragmentRelation(componentType)) {
|
|
1431
|
+
if (entityRelations) {
|
|
1432
|
+
const removedValue = entityRelations.get(componentType);
|
|
1433
|
+
if (removedValue !== void 0 || entityRelations.has(componentType)) {
|
|
1434
|
+
removedComponents.set(componentType, removedValue);
|
|
1435
|
+
entityRelations.delete(componentType);
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
for (const [componentType, component$1] of changeset.adds) if (isDontFragmentRelation(componentType)) {
|
|
1440
|
+
if (!entityRelations) {
|
|
1441
|
+
entityRelations = /* @__PURE__ */ new Map();
|
|
1442
|
+
dontFragmentRelations.set(entityId, entityRelations);
|
|
1443
|
+
}
|
|
1444
|
+
entityRelations.set(componentType, component$1);
|
|
1445
|
+
}
|
|
1446
|
+
if (entityRelations && entityRelations.size === 0) dontFragmentRelations.delete(entityId);
|
|
1447
|
+
}
|
|
1448
|
+
function filterRegularComponentTypes(componentTypes) {
|
|
1449
|
+
const regularTypes = [];
|
|
1450
|
+
for (const componentType of componentTypes) {
|
|
1451
|
+
if (isDontFragmentWildcard(componentType)) {
|
|
1452
|
+
regularTypes.push(componentType);
|
|
1453
|
+
continue;
|
|
1454
|
+
}
|
|
1455
|
+
if (isDontFragmentRelation(componentType)) continue;
|
|
1456
|
+
regularTypes.push(componentType);
|
|
1457
|
+
}
|
|
1458
|
+
return regularTypes;
|
|
1459
|
+
}
|
|
1460
|
+
function areComponentTypesEqual(types1, types2) {
|
|
1461
|
+
if (types1.length !== types2.length) return false;
|
|
1462
|
+
const sorted1 = [...types1].sort((a, b) => a - b);
|
|
1463
|
+
const sorted2 = [...types2].sort((a, b) => a - b);
|
|
1464
|
+
return sorted1.every((v, i) => v === sorted2[i]);
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
//#endregion
|
|
1468
|
+
//#region src/core/world-hooks.ts
|
|
1469
|
+
function triggerLifecycleHooks(ctx, entityId, addedComponents, removedComponents) {
|
|
1470
|
+
invokeHooksForComponents(ctx.hooks, entityId, addedComponents, "on_set");
|
|
1471
|
+
invokeHooksForComponents(ctx.hooks, entityId, removedComponents, "on_remove");
|
|
1472
|
+
triggerMultiComponentHooks(ctx, entityId, addedComponents, removedComponents);
|
|
1473
|
+
}
|
|
1474
|
+
function invokeHooksForComponents(hooks, entityId, components, hookType) {
|
|
1475
|
+
for (const [componentType, component$1] of components) {
|
|
1476
|
+
const directHooks = hooks.get(componentType);
|
|
1477
|
+
if (directHooks) for (const hook of directHooks) hook[hookType]?.(entityId, componentType, component$1);
|
|
1478
|
+
const componentId = getComponentIdFromRelationId(componentType);
|
|
1479
|
+
if (componentId !== void 0) {
|
|
1480
|
+
const wildcardHooks = hooks.get(relation(componentId, "*"));
|
|
1481
|
+
if (wildcardHooks) for (const hook of wildcardHooks) hook[hookType]?.(entityId, componentType, component$1);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
function triggerMultiComponentHooks(ctx, entityId, addedComponents, removedComponents) {
|
|
1486
|
+
for (const { componentTypes, requiredComponents, hook } of ctx.multiHooks) {
|
|
1487
|
+
const anyRequiredAdded = requiredComponents.some((c) => addedComponents.has(c));
|
|
1488
|
+
const anyRequiredRemoved = requiredComponents.some((c) => removedComponents.has(c));
|
|
1489
|
+
if (anyRequiredAdded && hook.on_set && entityHasAllComponents(ctx, entityId, requiredComponents)) hook.on_set(entityId, componentTypes, collectMultiHookComponents(ctx, entityId, componentTypes));
|
|
1490
|
+
if (anyRequiredRemoved && hook.on_remove && entityHadAllComponentsBefore(ctx, entityId, requiredComponents, removedComponents)) hook.on_remove(entityId, componentTypes, collectMultiHookComponentsWithRemoved(ctx, entityId, componentTypes, removedComponents));
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
function entityHasAllComponents(ctx, entityId, requiredComponents) {
|
|
1494
|
+
return requiredComponents.every((c) => ctx.has(entityId, c));
|
|
1495
|
+
}
|
|
1496
|
+
function entityHadAllComponentsBefore(ctx, entityId, requiredComponents, removedComponents) {
|
|
1497
|
+
return requiredComponents.every((c) => removedComponents.has(c) || ctx.has(entityId, c));
|
|
1498
|
+
}
|
|
1499
|
+
function collectMultiHookComponents(ctx, entityId, componentTypes) {
|
|
1500
|
+
return componentTypes.map((ct) => isOptionalEntityId(ct) ? ctx.getOptional(entityId, ct.optional) : ctx.get(entityId, ct));
|
|
1501
|
+
}
|
|
1502
|
+
function collectMultiHookComponentsWithRemoved(ctx, entityId, componentTypes, removedComponents) {
|
|
1503
|
+
return componentTypes.map((ct) => {
|
|
1504
|
+
if (isOptionalEntityId(ct)) {
|
|
1505
|
+
const optionalId = ct.optional;
|
|
1506
|
+
return removedComponents.has(optionalId) ? { value: removedComponents.get(optionalId) } : ctx.getOptional(entityId, optionalId);
|
|
1507
|
+
}
|
|
1508
|
+
const compId = ct;
|
|
1509
|
+
return removedComponents.has(compId) ? removedComponents.get(compId) : ctx.get(entityId, compId);
|
|
1510
|
+
});
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
//#endregion
|
|
1514
|
+
//#region src/utils/multi-map.ts
|
|
1515
|
+
var MultiMap = class {
|
|
1516
|
+
map = /* @__PURE__ */ new Map();
|
|
1517
|
+
_valueCount = 0;
|
|
1518
|
+
get valueCount() {
|
|
1519
|
+
return this._valueCount;
|
|
1520
|
+
}
|
|
1521
|
+
get keyCount() {
|
|
1522
|
+
return this.map.size;
|
|
1523
|
+
}
|
|
1524
|
+
hasKey(key) {
|
|
1525
|
+
return this.map.has(key);
|
|
1526
|
+
}
|
|
1527
|
+
has(key, value) {
|
|
1528
|
+
const set = this.map.get(key);
|
|
1529
|
+
if (!set) return false;
|
|
1530
|
+
if (arguments.length === 1) return true;
|
|
1531
|
+
return set.has(value);
|
|
1532
|
+
}
|
|
1533
|
+
add(key, value) {
|
|
1534
|
+
let set = this.map.get(key);
|
|
1535
|
+
if (!set) {
|
|
1536
|
+
set = /* @__PURE__ */ new Set();
|
|
1537
|
+
this.map.set(key, set);
|
|
1538
|
+
}
|
|
1539
|
+
if (!set.has(value)) {
|
|
1540
|
+
set.add(value);
|
|
1541
|
+
this._valueCount++;
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
remove(key, value) {
|
|
1545
|
+
const set = this.map.get(key);
|
|
1546
|
+
if (!set) return false;
|
|
1547
|
+
if (!set.has(value)) return false;
|
|
1548
|
+
set.delete(value);
|
|
1549
|
+
this._valueCount--;
|
|
1550
|
+
if (set.size === 0) this.map.delete(key);
|
|
1551
|
+
return true;
|
|
1552
|
+
}
|
|
1553
|
+
deleteKey(key) {
|
|
1554
|
+
const set = this.map.get(key);
|
|
1555
|
+
if (!set) return false;
|
|
1556
|
+
this._valueCount -= set.size;
|
|
1557
|
+
this.map.delete(key);
|
|
1558
|
+
return true;
|
|
1559
|
+
}
|
|
1560
|
+
get(key) {
|
|
1561
|
+
const set = this.map.get(key);
|
|
1562
|
+
return set ? new Set(set) : /* @__PURE__ */ new Set();
|
|
1563
|
+
}
|
|
1564
|
+
*keys() {
|
|
1565
|
+
yield* this.map.keys();
|
|
1566
|
+
}
|
|
1567
|
+
*values() {
|
|
1568
|
+
for (const set of this.map.values()) for (const v of set) yield v;
|
|
1569
|
+
}
|
|
1570
|
+
[Symbol.iterator]() {
|
|
1571
|
+
return this.entries();
|
|
1572
|
+
}
|
|
1573
|
+
*entries() {
|
|
1574
|
+
for (const [k, set] of this.map.entries()) for (const v of set) yield [k, v];
|
|
1575
|
+
}
|
|
1576
|
+
clear() {
|
|
1577
|
+
this.map.clear();
|
|
1578
|
+
this._valueCount = 0;
|
|
1579
|
+
}
|
|
1580
|
+
};
|
|
1581
|
+
|
|
1582
|
+
//#endregion
|
|
1583
|
+
//#region src/core/world-references.ts
|
|
1584
|
+
function trackEntityReference(entityReferences, sourceEntityId, componentType, targetEntityId) {
|
|
1585
|
+
if (!entityReferences.has(targetEntityId)) entityReferences.set(targetEntityId, new MultiMap());
|
|
1586
|
+
entityReferences.get(targetEntityId).add(sourceEntityId, componentType);
|
|
1587
|
+
}
|
|
1588
|
+
function untrackEntityReference(entityReferences, sourceEntityId, componentType, targetEntityId) {
|
|
1589
|
+
const references = entityReferences.get(targetEntityId);
|
|
1590
|
+
if (references) {
|
|
1591
|
+
references.remove(sourceEntityId, componentType);
|
|
1592
|
+
if (references.keyCount === 0) entityReferences.delete(targetEntityId);
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
function getEntityReferences(entityReferences, targetEntityId) {
|
|
1596
|
+
return entityReferences.get(targetEntityId) ?? new MultiMap();
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
//#endregion
|
|
1600
|
+
//#region src/core/world.ts
|
|
1435
1601
|
/**
|
|
1436
1602
|
* World class for ECS architecture
|
|
1437
1603
|
* Manages entities and components
|
|
1438
1604
|
*/
|
|
1439
1605
|
var World = class {
|
|
1440
|
-
/** Manages allocation and deallocation of entity IDs */
|
|
1441
1606
|
entityIdManager = new EntityIdManager();
|
|
1442
|
-
/** Array of all archetypes in the world */
|
|
1443
1607
|
archetypes = [];
|
|
1444
|
-
/** Maps archetype signatures (component type signatures) to archetype instances */
|
|
1445
1608
|
archetypeBySignature = /* @__PURE__ */ new Map();
|
|
1446
|
-
/** Maps entity IDs to their current archetype */
|
|
1447
1609
|
entityToArchetype = /* @__PURE__ */ new Map();
|
|
1448
|
-
/** Maps component types to arrays of archetypes that contain them */
|
|
1449
1610
|
archetypesByComponent = /* @__PURE__ */ new Map();
|
|
1450
|
-
/** Tracks which entities reference each entity as a component type */
|
|
1451
1611
|
entityReferences = /* @__PURE__ */ new Map();
|
|
1452
|
-
/** Storage for dontFragment relations - maps entity ID to a map of relation type to component data */
|
|
1453
1612
|
dontFragmentRelations = /* @__PURE__ */ new Map();
|
|
1454
|
-
/** Array of all active queries for archetype change notifications */
|
|
1455
1613
|
queries = [];
|
|
1456
|
-
/** Cache for queries keyed by component types and filter signatures */
|
|
1457
1614
|
queryCache = /* @__PURE__ */ new Map();
|
|
1458
|
-
/** Buffers structural changes for deferred execution */
|
|
1459
1615
|
commandBuffer = new CommandBuffer((entityId, commands) => this.executeEntityCommands(entityId, commands));
|
|
1460
|
-
/** Stores lifecycle hooks for component and relation events */
|
|
1461
1616
|
hooks = /* @__PURE__ */ new Map();
|
|
1462
|
-
/** Stores multi-component lifecycle hooks */
|
|
1463
1617
|
multiHooks = /* @__PURE__ */ new Set();
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
const
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
this.
|
|
1485
|
-
|
|
1486
|
-
const detailedType = getDetailedIdType(compType);
|
|
1487
|
-
if (detailedType.type === "entity-relation") {
|
|
1488
|
-
const targetEntityId = detailedType.targetId;
|
|
1489
|
-
this.trackEntityReference(entityId, compType, targetEntityId);
|
|
1490
|
-
} else if (detailedType.type === "entity") this.trackEntityReference(entityId, compType, compType);
|
|
1491
|
-
}
|
|
1618
|
+
constructor(snapshot) {
|
|
1619
|
+
if (snapshot && typeof snapshot === "object") this.deserializeSnapshot(snapshot);
|
|
1620
|
+
}
|
|
1621
|
+
deserializeSnapshot(snapshot) {
|
|
1622
|
+
if (snapshot.entityManager) this.entityIdManager.deserializeState(snapshot.entityManager);
|
|
1623
|
+
if (Array.isArray(snapshot.entities)) for (const entry of snapshot.entities) {
|
|
1624
|
+
const entityId = decodeSerializedId(entry.id);
|
|
1625
|
+
const componentsArray = entry.components || [];
|
|
1626
|
+
const componentMap = /* @__PURE__ */ new Map();
|
|
1627
|
+
const componentTypes = [];
|
|
1628
|
+
for (const componentEntry of componentsArray) {
|
|
1629
|
+
const componentType = decodeSerializedId(componentEntry.type);
|
|
1630
|
+
componentMap.set(componentType, componentEntry.value);
|
|
1631
|
+
componentTypes.push(componentType);
|
|
1632
|
+
}
|
|
1633
|
+
const archetype = this.ensureArchetype(componentTypes);
|
|
1634
|
+
archetype.addEntity(entityId, componentMap);
|
|
1635
|
+
this.entityToArchetype.set(entityId, archetype);
|
|
1636
|
+
for (const compType of componentTypes) {
|
|
1637
|
+
const detailedType = getDetailedIdType(compType);
|
|
1638
|
+
if (detailedType.type === "entity-relation") trackEntityReference(this.entityReferences, entityId, compType, detailedType.targetId);
|
|
1639
|
+
else if (detailedType.type === "entity") trackEntityReference(this.entityReferences, entityId, compType, compType);
|
|
1492
1640
|
}
|
|
1493
1641
|
}
|
|
1494
1642
|
}
|
|
1495
|
-
/**
|
|
1496
|
-
* Generate a signature string for component types array
|
|
1497
|
-
* @returns A string signature for the component types
|
|
1498
|
-
*/
|
|
1499
1643
|
createArchetypeSignature(componentTypes) {
|
|
1500
1644
|
return componentTypes.join(",");
|
|
1501
1645
|
}
|
|
1502
|
-
/**
|
|
1503
|
-
* Create a new entity
|
|
1504
|
-
* @returns The ID of the newly created entity
|
|
1505
|
-
*/
|
|
1506
1646
|
new() {
|
|
1507
1647
|
const entityId = this.entityIdManager.allocate();
|
|
1508
1648
|
let emptyArchetype = this.ensureArchetype([]);
|
|
@@ -1510,9 +1650,6 @@ var World = class {
|
|
|
1510
1650
|
this.entityToArchetype.set(entityId, emptyArchetype);
|
|
1511
1651
|
return entityId;
|
|
1512
1652
|
}
|
|
1513
|
-
/**
|
|
1514
|
-
* Destroy an entity and remove all its components (immediate execution)
|
|
1515
|
-
*/
|
|
1516
1653
|
destroyEntityImmediate(entityId) {
|
|
1517
1654
|
const queue = [entityId];
|
|
1518
1655
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -1522,7 +1659,7 @@ var World = class {
|
|
|
1522
1659
|
visited.add(cur);
|
|
1523
1660
|
const archetype = this.entityToArchetype.get(cur);
|
|
1524
1661
|
if (!archetype) continue;
|
|
1525
|
-
const componentReferences = Array.from(this.
|
|
1662
|
+
const componentReferences = Array.from(getEntityReferences(this.entityReferences, cur));
|
|
1526
1663
|
for (const [sourceEntityId, componentType] of componentReferences) {
|
|
1527
1664
|
if (!this.entityToArchetype.get(sourceEntityId)) continue;
|
|
1528
1665
|
if (isCascadeDeleteRelation(componentType)) {
|
|
@@ -1538,9 +1675,6 @@ var World = class {
|
|
|
1538
1675
|
this.entityIdManager.deallocate(cur);
|
|
1539
1676
|
}
|
|
1540
1677
|
}
|
|
1541
|
-
/**
|
|
1542
|
-
* Check if an entity exists
|
|
1543
|
-
*/
|
|
1544
1678
|
exists(entityId) {
|
|
1545
1679
|
return this.entityToArchetype.has(entityId);
|
|
1546
1680
|
}
|
|
@@ -1551,23 +1685,14 @@ var World = class {
|
|
|
1551
1685
|
if (detailedType.type === "wildcard-relation") throw new Error(`Cannot directly add wildcard relation components: ${componentType}`);
|
|
1552
1686
|
this.commandBuffer.set(entityId, componentType, component$1);
|
|
1553
1687
|
}
|
|
1554
|
-
/**
|
|
1555
|
-
* Remove a component from an entity (deferred)
|
|
1556
|
-
*/
|
|
1557
1688
|
remove(entityId, componentType) {
|
|
1558
1689
|
if (!this.exists(entityId)) throw new Error(`Entity ${entityId} does not exist`);
|
|
1559
1690
|
if (getDetailedIdType(componentType).type === "invalid") throw new Error(`Invalid component type: ${componentType}`);
|
|
1560
1691
|
this.commandBuffer.remove(entityId, componentType);
|
|
1561
1692
|
}
|
|
1562
|
-
/**
|
|
1563
|
-
* Destroy an entity and remove all its components (deferred)
|
|
1564
|
-
*/
|
|
1565
1693
|
delete(entityId) {
|
|
1566
1694
|
this.commandBuffer.delete(entityId);
|
|
1567
1695
|
}
|
|
1568
|
-
/**
|
|
1569
|
-
* Check if an entity has a specific component
|
|
1570
|
-
*/
|
|
1571
1696
|
has(entityId, componentType) {
|
|
1572
1697
|
const archetype = this.entityToArchetype.get(entityId);
|
|
1573
1698
|
if (!archetype) return false;
|
|
@@ -1585,16 +1710,10 @@ var World = class {
|
|
|
1585
1710
|
}
|
|
1586
1711
|
return archetype.get(entityId, componentType);
|
|
1587
1712
|
}
|
|
1588
|
-
/**
|
|
1589
|
-
* Get optional component data for a specific entity and component type
|
|
1590
|
-
* @param entityId The entity
|
|
1591
|
-
* @param componentType The component type
|
|
1592
|
-
* @returns { value: T } if component exists, undefined otherwise
|
|
1593
|
-
*/
|
|
1594
1713
|
getOptional(entityId, componentType) {
|
|
1595
1714
|
const archetype = this.entityToArchetype.get(entityId);
|
|
1596
1715
|
if (!archetype) throw new Error(`Entity ${entityId} does not exist`);
|
|
1597
|
-
if (isWildcardRelationId(componentType)) return;
|
|
1716
|
+
if (isWildcardRelationId(componentType)) return void 0;
|
|
1598
1717
|
return archetype.getOptional(entityId, componentType);
|
|
1599
1718
|
}
|
|
1600
1719
|
hook(componentTypesOrSingle, hook) {
|
|
@@ -1612,7 +1731,7 @@ var World = class {
|
|
|
1612
1731
|
if (multiHook.on_init !== void 0) {
|
|
1613
1732
|
const matchingArchetypes = this.getMatchingArchetypes(requiredComponents);
|
|
1614
1733
|
for (const archetype of matchingArchetypes) for (const entityId of archetype.getEntities()) {
|
|
1615
|
-
const components = this.
|
|
1734
|
+
const components = collectMultiHookComponents(this.createHooksContext(), entityId, componentTypes);
|
|
1616
1735
|
multiHook.on_init(entityId, componentTypes, components);
|
|
1617
1736
|
}
|
|
1618
1737
|
}
|
|
@@ -1647,16 +1766,9 @@ var World = class {
|
|
|
1647
1766
|
}
|
|
1648
1767
|
}
|
|
1649
1768
|
}
|
|
1650
|
-
/**
|
|
1651
|
-
* Execute all deferred commands immediately
|
|
1652
|
-
*/
|
|
1653
1769
|
sync() {
|
|
1654
1770
|
this.commandBuffer.execute();
|
|
1655
1771
|
}
|
|
1656
|
-
/**
|
|
1657
|
-
* Create a cached query for efficient entity lookups
|
|
1658
|
-
* @returns A Query object for the specified component types and filter
|
|
1659
|
-
*/
|
|
1660
1772
|
createQuery(componentTypes, filter = {}) {
|
|
1661
1773
|
const sortedTypes = [...componentTypes].sort((a, b) => a - b);
|
|
1662
1774
|
const filterKey = serializeQueryFilter(filter);
|
|
@@ -1673,19 +1785,9 @@ var World = class {
|
|
|
1673
1785
|
});
|
|
1674
1786
|
return query;
|
|
1675
1787
|
}
|
|
1676
|
-
/**
|
|
1677
|
-
* Create an EntityBuilder for convenient entity creation.
|
|
1678
|
-
* @returns EntityBuilder
|
|
1679
|
-
*/
|
|
1680
1788
|
spawn() {
|
|
1681
1789
|
return new EntityBuilder(this);
|
|
1682
1790
|
}
|
|
1683
|
-
/**
|
|
1684
|
-
* Spawn multiple entities using an EntityBuilder configuration callback
|
|
1685
|
-
* @param count number of entities
|
|
1686
|
-
* @param configure builder configuration callback
|
|
1687
|
-
* @returns Created entity IDs
|
|
1688
|
-
*/
|
|
1689
1791
|
spawnMany(count, configure) {
|
|
1690
1792
|
const entities = [];
|
|
1691
1793
|
for (let i = 0; i < count; i++) {
|
|
@@ -1694,23 +1796,13 @@ var World = class {
|
|
|
1694
1796
|
}
|
|
1695
1797
|
return entities;
|
|
1696
1798
|
}
|
|
1697
|
-
/**
|
|
1698
|
-
* @internal Register a query for archetype update notifications
|
|
1699
|
-
*/
|
|
1700
1799
|
_registerQuery(query) {
|
|
1701
1800
|
this.queries.push(query);
|
|
1702
1801
|
}
|
|
1703
|
-
/**
|
|
1704
|
-
* @internal Unregister a query
|
|
1705
|
-
*/
|
|
1706
1802
|
_unregisterQuery(query) {
|
|
1707
1803
|
const index = this.queries.indexOf(query);
|
|
1708
1804
|
if (index !== -1) this.queries.splice(index, 1);
|
|
1709
1805
|
}
|
|
1710
|
-
/**
|
|
1711
|
-
* Release a query reference obtained from createQuery.
|
|
1712
|
-
* Decrements the refCount and fully disposes the query when it reaches zero.
|
|
1713
|
-
*/
|
|
1714
1806
|
releaseQuery(query) {
|
|
1715
1807
|
for (const [k, v] of this.queryCache.entries()) if (v.query === query) {
|
|
1716
1808
|
v.refCount--;
|
|
@@ -1722,9 +1814,6 @@ var World = class {
|
|
|
1722
1814
|
return;
|
|
1723
1815
|
}
|
|
1724
1816
|
}
|
|
1725
|
-
/**
|
|
1726
|
-
* @internal Get archetypes that match specific component types (for internal use by queries)
|
|
1727
|
-
*/
|
|
1728
1817
|
getMatchingArchetypes(componentTypes) {
|
|
1729
1818
|
if (componentTypes.length === 0) return [...this.archetypes];
|
|
1730
1819
|
const regularComponents = [];
|
|
@@ -1736,42 +1825,24 @@ var World = class {
|
|
|
1736
1825
|
relationId: componentType
|
|
1737
1826
|
});
|
|
1738
1827
|
} else regularComponents.push(componentType);
|
|
1739
|
-
let matchingArchetypes =
|
|
1740
|
-
|
|
1741
|
-
const
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
matchingArchetypes = this.archetypesByComponent.get(componentType) || [];
|
|
1745
|
-
} else {
|
|
1746
|
-
const archetypeLists = sortedRegularTypes.map((type) => this.archetypesByComponent.get(type) || []);
|
|
1747
|
-
const firstList = archetypeLists[0] || [];
|
|
1748
|
-
const intersection = /* @__PURE__ */ new Set();
|
|
1749
|
-
for (const archetype of firstList) {
|
|
1750
|
-
let hasAllComponents = true;
|
|
1751
|
-
for (let listIndex = 1; listIndex < archetypeLists.length; listIndex++) if (!archetypeLists[listIndex].includes(archetype)) {
|
|
1752
|
-
hasAllComponents = false;
|
|
1753
|
-
break;
|
|
1754
|
-
}
|
|
1755
|
-
if (hasAllComponents) intersection.add(archetype);
|
|
1756
|
-
}
|
|
1757
|
-
matchingArchetypes = Array.from(intersection);
|
|
1758
|
-
}
|
|
1759
|
-
} else matchingArchetypes = [...this.archetypes];
|
|
1760
|
-
for (const wildcard of wildcardRelations) if (isDontFragmentComponent(wildcard.componentId)) {
|
|
1761
|
-
const archetypesWithMarker = this.archetypesByComponent.get(wildcard.relationId) || [];
|
|
1762
|
-
if (matchingArchetypes.length === 0) matchingArchetypes = archetypesWithMarker;
|
|
1763
|
-
else matchingArchetypes = matchingArchetypes.filter((archetype) => archetypesWithMarker.includes(archetype));
|
|
1764
|
-
} else matchingArchetypes = matchingArchetypes.filter((archetype) => archetype.hasRelationWithComponentId(wildcard.componentId));
|
|
1828
|
+
let matchingArchetypes = this.getArchetypesWithComponents(regularComponents);
|
|
1829
|
+
for (const { componentId, relationId } of wildcardRelations) {
|
|
1830
|
+
const archetypesWithMarker = this.archetypesByComponent.get(relationId) || [];
|
|
1831
|
+
matchingArchetypes = matchingArchetypes.length === 0 ? archetypesWithMarker : matchingArchetypes.filter((a) => archetypesWithMarker.includes(a) || a.hasRelationWithComponentId(componentId));
|
|
1832
|
+
}
|
|
1765
1833
|
return matchingArchetypes;
|
|
1766
1834
|
}
|
|
1835
|
+
getArchetypesWithComponents(componentTypes) {
|
|
1836
|
+
if (componentTypes.length === 0) return [...this.archetypes];
|
|
1837
|
+
if (componentTypes.length === 1) return this.archetypesByComponent.get(componentTypes[0]) || [];
|
|
1838
|
+
const archetypeLists = componentTypes.map((type) => this.archetypesByComponent.get(type) || []);
|
|
1839
|
+
return archetypeLists[0].filter((archetype) => archetypeLists.slice(1).every((list) => list.includes(archetype)));
|
|
1840
|
+
}
|
|
1767
1841
|
query(componentTypes, includeComponents) {
|
|
1768
1842
|
const matchingArchetypes = this.getMatchingArchetypes(componentTypes);
|
|
1769
1843
|
if (includeComponents) {
|
|
1770
1844
|
const result = [];
|
|
1771
|
-
for (const archetype of matchingArchetypes)
|
|
1772
|
-
const entitiesWithData = archetype.getEntitiesWithComponents(componentTypes);
|
|
1773
|
-
result.push(...entitiesWithData);
|
|
1774
|
-
}
|
|
1845
|
+
for (const archetype of matchingArchetypes) result.push(...archetype.getEntitiesWithComponents(componentTypes));
|
|
1775
1846
|
return result;
|
|
1776
1847
|
} else {
|
|
1777
1848
|
const result = [];
|
|
@@ -1779,10 +1850,6 @@ var World = class {
|
|
|
1779
1850
|
return result;
|
|
1780
1851
|
}
|
|
1781
1852
|
}
|
|
1782
|
-
/**
|
|
1783
|
-
* @internal Execute commands for a single entity (for internal use by CommandBuffer)
|
|
1784
|
-
* @returns ComponentChangeset describing the changes made
|
|
1785
|
-
*/
|
|
1786
1853
|
executeEntityCommands(entityId, commands) {
|
|
1787
1854
|
const changeset = new ComponentChangeset();
|
|
1788
1855
|
if (commands.some((cmd) => cmd.type === "destroy")) {
|
|
@@ -1791,327 +1858,79 @@ var World = class {
|
|
|
1791
1858
|
}
|
|
1792
1859
|
const currentArchetype = this.entityToArchetype.get(entityId);
|
|
1793
1860
|
if (!currentArchetype) return changeset;
|
|
1794
|
-
|
|
1795
|
-
|
|
1861
|
+
processCommands(entityId, currentArchetype, commands, changeset, (eid, arch, compId) => {
|
|
1862
|
+
if (isExclusiveComponent(compId)) removeMatchingRelations(eid, arch, compId, changeset);
|
|
1863
|
+
});
|
|
1864
|
+
const removedComponents = applyChangeset({
|
|
1865
|
+
dontFragmentRelations: this.dontFragmentRelations,
|
|
1866
|
+
ensureArchetype: (ct) => this.ensureArchetype(ct)
|
|
1867
|
+
}, entityId, currentArchetype, changeset, this.entityToArchetype);
|
|
1796
1868
|
this.updateEntityReferences(entityId, changeset);
|
|
1797
|
-
this.
|
|
1869
|
+
triggerLifecycleHooks(this.createHooksContext(), entityId, changeset.adds, removedComponents);
|
|
1798
1870
|
return changeset;
|
|
1799
1871
|
}
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
* Process a set command, handling exclusive relations
|
|
1809
|
-
*/
|
|
1810
|
-
processSetCommand(entityId, currentArchetype, componentType, component$1, changeset) {
|
|
1811
|
-
const componentId = getComponentIdFromRelationId(componentType);
|
|
1812
|
-
if (componentId !== void 0) {
|
|
1813
|
-
if (componentId !== void 0 && isExclusiveComponent(componentId)) this.removeExclusiveRelations(entityId, currentArchetype, componentId, changeset);
|
|
1814
|
-
if (componentId !== void 0 && isDontFragmentComponent(componentId)) {
|
|
1815
|
-
const wildcardMarker = relation(componentId, "*");
|
|
1816
|
-
if (!currentArchetype.componentTypes.includes(wildcardMarker)) changeset.set(wildcardMarker, void 0);
|
|
1817
|
-
}
|
|
1818
|
-
}
|
|
1819
|
-
changeset.set(componentType, component$1);
|
|
1820
|
-
}
|
|
1821
|
-
/**
|
|
1822
|
-
* Remove all relations with the same base component (for exclusive relations)
|
|
1823
|
-
*/
|
|
1824
|
-
removeExclusiveRelations(entityId, currentArchetype, baseComponentId, changeset) {
|
|
1825
|
-
for (const componentType of currentArchetype.componentTypes) if (this.isRelationWithComponent(componentType, baseComponentId)) changeset.delete(componentType);
|
|
1826
|
-
const entityData = currentArchetype.getEntity(entityId);
|
|
1827
|
-
if (entityData) for (const [componentType] of entityData) {
|
|
1828
|
-
if (currentArchetype.componentTypes.includes(componentType)) continue;
|
|
1829
|
-
if (this.isRelationWithComponent(componentType, baseComponentId)) changeset.delete(componentType);
|
|
1830
|
-
}
|
|
1831
|
-
}
|
|
1832
|
-
isRelationWithComponent(componentType, baseComponentId) {
|
|
1833
|
-
return getComponentIdFromRelationId(componentType) === baseComponentId;
|
|
1834
|
-
}
|
|
1835
|
-
/**
|
|
1836
|
-
* Process a delete command, handling wildcard relations
|
|
1837
|
-
*/
|
|
1838
|
-
processDeleteCommand(entityId, currentArchetype, componentType, changeset) {
|
|
1839
|
-
const componentId = getComponentIdFromRelationId(componentType);
|
|
1840
|
-
if (isWildcardRelationId(componentType) && componentId !== void 0) this.removeWildcardRelations(entityId, currentArchetype, componentId, changeset);
|
|
1841
|
-
else {
|
|
1842
|
-
changeset.delete(componentType);
|
|
1843
|
-
if (componentId !== void 0 && isDontFragmentComponent(componentId)) {
|
|
1844
|
-
const wildcardMarker = relation(componentId, "*");
|
|
1845
|
-
const entityData = currentArchetype.getEntity(entityId);
|
|
1846
|
-
let hasOtherRelations = false;
|
|
1847
|
-
if (entityData) for (const [otherComponentType] of entityData) {
|
|
1848
|
-
if (otherComponentType === componentType) continue;
|
|
1849
|
-
if (otherComponentType === wildcardMarker) continue;
|
|
1850
|
-
if (changeset.removes.has(otherComponentType)) continue;
|
|
1851
|
-
if (getComponentIdFromRelationId(otherComponentType) === componentId) {
|
|
1852
|
-
hasOtherRelations = true;
|
|
1853
|
-
break;
|
|
1854
|
-
}
|
|
1855
|
-
}
|
|
1856
|
-
if (!hasOtherRelations) changeset.delete(wildcardMarker);
|
|
1857
|
-
}
|
|
1858
|
-
}
|
|
1859
|
-
}
|
|
1860
|
-
/**
|
|
1861
|
-
* Remove all relations matching a wildcard component ID
|
|
1862
|
-
*/
|
|
1863
|
-
removeWildcardRelations(entityId, currentArchetype, baseComponentId, changeset) {
|
|
1864
|
-
for (const componentType of currentArchetype.componentTypes) if (this.isRelationWithComponent(componentType, baseComponentId)) changeset.delete(componentType);
|
|
1865
|
-
const entityData = currentArchetype.getEntity(entityId);
|
|
1866
|
-
if (entityData) for (const [componentType] of entityData) {
|
|
1867
|
-
if (currentArchetype.componentTypes.includes(componentType)) continue;
|
|
1868
|
-
if (this.isRelationWithComponent(componentType, baseComponentId)) changeset.delete(componentType);
|
|
1869
|
-
}
|
|
1870
|
-
if (isDontFragmentComponent(baseComponentId)) {
|
|
1871
|
-
const wildcardMarker = relation(baseComponentId, "*");
|
|
1872
|
-
changeset.delete(wildcardMarker);
|
|
1873
|
-
}
|
|
1872
|
+
createHooksContext() {
|
|
1873
|
+
return {
|
|
1874
|
+
hooks: this.hooks,
|
|
1875
|
+
multiHooks: this.multiHooks,
|
|
1876
|
+
has: (eid, ct) => this.has(eid, ct),
|
|
1877
|
+
get: (eid, ct) => this.get(eid, ct),
|
|
1878
|
+
getOptional: (eid, ct) => this.getOptional(eid, ct)
|
|
1879
|
+
};
|
|
1874
1880
|
}
|
|
1875
|
-
/**
|
|
1876
|
-
* Remove a single component from an entity immediately, handling dontFragment relations correctly.
|
|
1877
|
-
* Used by destroyEntityImmediate for non-cascade relation cleanup.
|
|
1878
|
-
*/
|
|
1879
1881
|
removeComponentImmediate(entityId, componentType, targetEntityId) {
|
|
1880
1882
|
const sourceArchetype = this.entityToArchetype.get(entityId);
|
|
1881
1883
|
if (!sourceArchetype) return;
|
|
1882
1884
|
const changeset = new ComponentChangeset();
|
|
1883
|
-
const componentId = getComponentIdFromRelationId(componentType);
|
|
1884
1885
|
changeset.delete(componentType);
|
|
1885
|
-
|
|
1886
|
-
const wildcardMarker = relation(componentId, "*");
|
|
1887
|
-
const entityData = sourceArchetype.getEntity(entityId);
|
|
1888
|
-
let hasOtherRelations = false;
|
|
1889
|
-
if (entityData) for (const [otherComponentType] of entityData) {
|
|
1890
|
-
if (otherComponentType === componentType) continue;
|
|
1891
|
-
if (otherComponentType === wildcardMarker) continue;
|
|
1892
|
-
if (getComponentIdFromRelationId(otherComponentType) === componentId) {
|
|
1893
|
-
hasOtherRelations = true;
|
|
1894
|
-
break;
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
|
-
if (!hasOtherRelations) changeset.delete(wildcardMarker);
|
|
1898
|
-
}
|
|
1886
|
+
maybeRemoveWildcardMarker(entityId, sourceArchetype, componentType, getComponentIdFromRelationId(componentType), changeset);
|
|
1899
1887
|
const removedComponent = sourceArchetype.get(entityId, componentType);
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
* @returns Map of removed components with their data
|
|
1907
|
-
*/
|
|
1908
|
-
applyChangeset(entityId, currentArchetype, changeset) {
|
|
1909
|
-
const currentEntityData = currentArchetype.getEntity(entityId);
|
|
1910
|
-
const allCurrentComponentTypes = currentEntityData ? Array.from(currentEntityData.keys()) : currentArchetype.componentTypes;
|
|
1911
|
-
const finalComponentTypes = changeset.getFinalComponentTypes(allCurrentComponentTypes);
|
|
1912
|
-
const removedComponents = /* @__PURE__ */ new Map();
|
|
1913
|
-
if (finalComponentTypes) {
|
|
1914
|
-
const currentRegularTypes = this.filterRegularComponentTypes(allCurrentComponentTypes);
|
|
1915
|
-
const finalRegularTypes = this.filterRegularComponentTypes(finalComponentTypes);
|
|
1916
|
-
if (!this.areComponentTypesEqual(currentRegularTypes, finalRegularTypes)) this.moveEntityToNewArchetype(entityId, currentArchetype, finalComponentTypes, changeset, removedComponents);
|
|
1917
|
-
else this.updateEntityInSameArchetype(entityId, currentArchetype, changeset, removedComponents);
|
|
1918
|
-
} else this.updateEntityInSameArchetype(entityId, currentArchetype, changeset, removedComponents);
|
|
1919
|
-
return removedComponents;
|
|
1920
|
-
}
|
|
1921
|
-
/**
|
|
1922
|
-
* Move entity to a new archetype with updated components
|
|
1923
|
-
*/
|
|
1924
|
-
moveEntityToNewArchetype(entityId, currentArchetype, finalComponentTypes, changeset, removedComponents) {
|
|
1925
|
-
const newArchetype = this.ensureArchetype(finalComponentTypes);
|
|
1926
|
-
const currentComponents = currentArchetype.removeEntity(entityId);
|
|
1927
|
-
for (const componentType of changeset.removes) removedComponents.set(componentType, currentComponents.get(componentType));
|
|
1928
|
-
newArchetype.addEntity(entityId, changeset.applyTo(currentComponents));
|
|
1929
|
-
this.entityToArchetype.set(entityId, newArchetype);
|
|
1930
|
-
}
|
|
1931
|
-
/**
|
|
1932
|
-
* Update entity in same archetype (no archetype change needed)
|
|
1933
|
-
*/
|
|
1934
|
-
updateEntityInSameArchetype(entityId, currentArchetype, changeset, removedComponents) {
|
|
1935
|
-
this.applyDontFragmentChanges(entityId, changeset, removedComponents);
|
|
1936
|
-
for (const [componentType, component$1] of changeset.adds) {
|
|
1937
|
-
if (isDontFragmentRelation(componentType)) continue;
|
|
1938
|
-
currentArchetype.set(entityId, componentType, component$1);
|
|
1939
|
-
}
|
|
1940
|
-
}
|
|
1941
|
-
/**
|
|
1942
|
-
* Apply dontFragment relation changes directly to World's storage
|
|
1943
|
-
* This is much more efficient than the removeEntity + addEntity approach
|
|
1944
|
-
*/
|
|
1945
|
-
applyDontFragmentChanges(entityId, changeset, removedComponents) {
|
|
1946
|
-
let entityRelations = this.dontFragmentRelations.get(entityId);
|
|
1947
|
-
for (const componentType of changeset.removes) if (isDontFragmentRelation(componentType)) {
|
|
1948
|
-
if (entityRelations) {
|
|
1949
|
-
const removedValue = entityRelations.get(componentType);
|
|
1950
|
-
if (removedValue !== void 0 || entityRelations.has(componentType)) {
|
|
1951
|
-
removedComponents.set(componentType, removedValue);
|
|
1952
|
-
entityRelations.delete(componentType);
|
|
1953
|
-
}
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
for (const [componentType, component$1] of changeset.adds) if (isDontFragmentRelation(componentType)) {
|
|
1957
|
-
if (!entityRelations) {
|
|
1958
|
-
entityRelations = /* @__PURE__ */ new Map();
|
|
1959
|
-
this.dontFragmentRelations.set(entityId, entityRelations);
|
|
1960
|
-
}
|
|
1961
|
-
entityRelations.set(componentType, component$1);
|
|
1962
|
-
}
|
|
1963
|
-
if (entityRelations && entityRelations.size === 0) this.dontFragmentRelations.delete(entityId);
|
|
1888
|
+
applyChangeset({
|
|
1889
|
+
dontFragmentRelations: this.dontFragmentRelations,
|
|
1890
|
+
ensureArchetype: (ct) => this.ensureArchetype(ct)
|
|
1891
|
+
}, entityId, sourceArchetype, changeset, this.entityToArchetype);
|
|
1892
|
+
untrackEntityReference(this.entityReferences, entityId, componentType, targetEntityId);
|
|
1893
|
+
triggerLifecycleHooks(this.createHooksContext(), entityId, /* @__PURE__ */ new Map(), new Map([[componentType, removedComponent]]));
|
|
1964
1894
|
}
|
|
1965
|
-
/**
|
|
1966
|
-
* Update entity reference tracking based on changeset
|
|
1967
|
-
*/
|
|
1968
1895
|
updateEntityReferences(entityId, changeset) {
|
|
1969
1896
|
for (const componentType of changeset.removes) if (isEntityRelation(componentType)) {
|
|
1970
1897
|
const targetId = getTargetIdFromRelationId(componentType);
|
|
1971
|
-
this.
|
|
1972
|
-
} else if (componentType >= 1024) this.
|
|
1898
|
+
untrackEntityReference(this.entityReferences, entityId, componentType, targetId);
|
|
1899
|
+
} else if (componentType >= 1024) untrackEntityReference(this.entityReferences, entityId, componentType, componentType);
|
|
1973
1900
|
for (const [componentType] of changeset.adds) if (isEntityRelation(componentType)) {
|
|
1974
1901
|
const targetId = getTargetIdFromRelationId(componentType);
|
|
1975
|
-
this.
|
|
1976
|
-
} else if (componentType >= 1024) this.
|
|
1902
|
+
trackEntityReference(this.entityReferences, entityId, componentType, targetId);
|
|
1903
|
+
} else if (componentType >= 1024) trackEntityReference(this.entityReferences, entityId, componentType, componentType);
|
|
1977
1904
|
}
|
|
1978
|
-
/**
|
|
1979
|
-
* Get or create an archetype for the given component types
|
|
1980
|
-
* Filters out dontFragment relations from the archetype signature
|
|
1981
|
-
* @returns The archetype for the given component types (excluding dontFragment relations)
|
|
1982
|
-
*/
|
|
1983
1905
|
ensureArchetype(componentTypes) {
|
|
1984
|
-
const sortedTypes =
|
|
1906
|
+
const sortedTypes = filterRegularComponentTypes(componentTypes).sort((a, b) => a - b);
|
|
1985
1907
|
const hashKey = this.createArchetypeSignature(sortedTypes);
|
|
1986
1908
|
return getOrCreateWithSideEffect(this.archetypeBySignature, hashKey, () => this.createNewArchetype(sortedTypes));
|
|
1987
1909
|
}
|
|
1988
|
-
/**
|
|
1989
|
-
* Compare two arrays of component types for equality (order-independent)
|
|
1990
|
-
*/
|
|
1991
|
-
areComponentTypesEqual(types1, types2) {
|
|
1992
|
-
if (types1.length !== types2.length) return false;
|
|
1993
|
-
const sorted1 = [...types1].sort((a, b) => a - b);
|
|
1994
|
-
const sorted2 = [...types2].sort((a, b) => a - b);
|
|
1995
|
-
return sorted1.every((v, i) => v === sorted2[i]);
|
|
1996
|
-
}
|
|
1997
|
-
/**
|
|
1998
|
-
* Filter out dontFragment relations from component types, but keep wildcard markers
|
|
1999
|
-
*/
|
|
2000
|
-
filterRegularComponentTypes(componentTypes) {
|
|
2001
|
-
const regularTypes = [];
|
|
2002
|
-
for (const componentType of componentTypes) {
|
|
2003
|
-
if (isDontFragmentWildcard(componentType)) {
|
|
2004
|
-
regularTypes.push(componentType);
|
|
2005
|
-
continue;
|
|
2006
|
-
}
|
|
2007
|
-
if (isDontFragmentRelation(componentType)) continue;
|
|
2008
|
-
regularTypes.push(componentType);
|
|
2009
|
-
}
|
|
2010
|
-
return regularTypes;
|
|
2011
|
-
}
|
|
2012
|
-
/**
|
|
2013
|
-
* Create a new archetype and register it with all tracking structures
|
|
2014
|
-
*/
|
|
2015
1910
|
createNewArchetype(componentTypes) {
|
|
2016
1911
|
const newArchetype = new Archetype(componentTypes, this.dontFragmentRelations);
|
|
2017
1912
|
this.archetypes.push(newArchetype);
|
|
2018
|
-
this.registerArchetypeInComponentIndex(newArchetype, componentTypes);
|
|
2019
|
-
this.notifyQueriesOfNewArchetype(newArchetype);
|
|
2020
|
-
return newArchetype;
|
|
2021
|
-
}
|
|
2022
|
-
/**
|
|
2023
|
-
* Register archetype in the component-to-archetype index
|
|
2024
|
-
*/
|
|
2025
|
-
registerArchetypeInComponentIndex(archetype, componentTypes) {
|
|
2026
1913
|
for (const componentType of componentTypes) {
|
|
2027
1914
|
const archetypes = this.archetypesByComponent.get(componentType) || [];
|
|
2028
|
-
archetypes.push(
|
|
1915
|
+
archetypes.push(newArchetype);
|
|
2029
1916
|
this.archetypesByComponent.set(componentType, archetypes);
|
|
2030
1917
|
}
|
|
1918
|
+
for (const query of this.queries) query.checkNewArchetype(newArchetype);
|
|
1919
|
+
return newArchetype;
|
|
2031
1920
|
}
|
|
2032
|
-
/**
|
|
2033
|
-
* Notify all queries to check the new archetype
|
|
2034
|
-
*/
|
|
2035
|
-
notifyQueriesOfNewArchetype(archetype) {
|
|
2036
|
-
for (const query of this.queries) query.checkNewArchetype(archetype);
|
|
2037
|
-
}
|
|
2038
|
-
/**
|
|
2039
|
-
* Add a component reference to the reverse index when an entity is used as a component type
|
|
2040
|
-
* @param sourceEntityId The entity that has the component
|
|
2041
|
-
* @param componentType The component type (which may be an entity ID used as component type)
|
|
2042
|
-
* @param targetEntityId The entity being used as component type
|
|
2043
|
-
*/
|
|
2044
|
-
trackEntityReference(sourceEntityId, componentType, targetEntityId) {
|
|
2045
|
-
if (!this.entityReferences.has(targetEntityId)) this.entityReferences.set(targetEntityId, new MultiMap());
|
|
2046
|
-
this.entityReferences.get(targetEntityId).add(sourceEntityId, componentType);
|
|
2047
|
-
}
|
|
2048
|
-
/**
|
|
2049
|
-
* Remove a component reference from the reverse index
|
|
2050
|
-
* @param sourceEntityId The entity that has the component
|
|
2051
|
-
* @param componentType The component type
|
|
2052
|
-
* @param targetEntityId The entity being used as component type
|
|
2053
|
-
*/
|
|
2054
|
-
untrackEntityReference(sourceEntityId, componentType, targetEntityId) {
|
|
2055
|
-
const references = this.entityReferences.get(targetEntityId);
|
|
2056
|
-
if (references) {
|
|
2057
|
-
references.remove(sourceEntityId, componentType);
|
|
2058
|
-
if (references.keyCount === 0) this.entityReferences.delete(targetEntityId);
|
|
2059
|
-
}
|
|
2060
|
-
}
|
|
2061
|
-
/**
|
|
2062
|
-
* Get all component references where a target entity is used as a component type
|
|
2063
|
-
* @param targetEntityId The target entity
|
|
2064
|
-
* @returns A MultiMap of sourceEntityId to componentTypes that reference the target entity
|
|
2065
|
-
*/
|
|
2066
|
-
getEntityReferences(targetEntityId) {
|
|
2067
|
-
return this.entityReferences.get(targetEntityId) ?? new MultiMap();
|
|
2068
|
-
}
|
|
2069
|
-
/**
|
|
2070
|
-
* Check if an archetype's signature references a specific entity
|
|
2071
|
-
* (via entity-relation targeting the entity, or using entity as component type)
|
|
2072
|
-
*/
|
|
2073
1921
|
archetypeReferencesEntity(archetype, entityId) {
|
|
2074
|
-
|
|
2075
|
-
if (componentType === entityId) return true;
|
|
2076
|
-
if (isEntityRelation(componentType)) {
|
|
2077
|
-
if (getTargetIdFromRelationId(componentType) === entityId) return true;
|
|
2078
|
-
}
|
|
2079
|
-
}
|
|
2080
|
-
return false;
|
|
1922
|
+
return archetype.componentTypes.some((ct) => ct === entityId || isEntityRelation(ct) && getTargetIdFromRelationId(ct) === entityId);
|
|
2081
1923
|
}
|
|
2082
|
-
/**
|
|
2083
|
-
* Cleanup empty archetypes that reference a specific deleted entity
|
|
2084
|
-
* Only removes archetypes whose component types reference the entity
|
|
2085
|
-
*/
|
|
2086
1924
|
cleanupArchetypesReferencingEntity(entityId) {
|
|
2087
1925
|
for (let i = this.archetypes.length - 1; i >= 0; i--) {
|
|
2088
1926
|
const archetype = this.archetypes[i];
|
|
2089
|
-
if (archetype.getEntities().length === 0 && this.archetypeReferencesEntity(archetype, entityId))
|
|
2090
|
-
this.removeArchetypeFromList(archetype);
|
|
2091
|
-
this.removeArchetypeFromSignatureMap(archetype);
|
|
2092
|
-
this.removeArchetypeFromComponentIndex(archetype);
|
|
2093
|
-
this.removeArchetypeFromQueries(archetype);
|
|
2094
|
-
}
|
|
1927
|
+
if (archetype.getEntities().length === 0 && this.archetypeReferencesEntity(archetype, entityId)) this.removeArchetype(archetype);
|
|
2095
1928
|
}
|
|
2096
1929
|
}
|
|
2097
|
-
|
|
2098
|
-
* Remove archetype from the main archetypes list
|
|
2099
|
-
*/
|
|
2100
|
-
removeArchetypeFromList(archetype) {
|
|
1930
|
+
removeArchetype(archetype) {
|
|
2101
1931
|
const index = this.archetypes.indexOf(archetype);
|
|
2102
1932
|
if (index !== -1) this.archetypes.splice(index, 1);
|
|
2103
|
-
|
|
2104
|
-
/**
|
|
2105
|
-
* Remove archetype from the signature-to-archetype map
|
|
2106
|
-
*/
|
|
2107
|
-
removeArchetypeFromSignatureMap(archetype) {
|
|
2108
|
-
const hashKey = this.createArchetypeSignature(archetype.componentTypes);
|
|
2109
|
-
this.archetypeBySignature.delete(hashKey);
|
|
2110
|
-
}
|
|
2111
|
-
/**
|
|
2112
|
-
* Remove archetype from the component-to-archetypes index
|
|
2113
|
-
*/
|
|
2114
|
-
removeArchetypeFromComponentIndex(archetype) {
|
|
1933
|
+
this.archetypeBySignature.delete(this.createArchetypeSignature(archetype.componentTypes));
|
|
2115
1934
|
for (const componentType of archetype.componentTypes) {
|
|
2116
1935
|
const archetypes = this.archetypesByComponent.get(componentType);
|
|
2117
1936
|
if (archetypes) {
|
|
@@ -2122,119 +1941,8 @@ var World = class {
|
|
|
2122
1941
|
}
|
|
2123
1942
|
}
|
|
2124
1943
|
}
|
|
2125
|
-
}
|
|
2126
|
-
/**
|
|
2127
|
-
* Remove archetype from all queries
|
|
2128
|
-
*/
|
|
2129
|
-
removeArchetypeFromQueries(archetype) {
|
|
2130
1944
|
for (const query of this.queries) query.removeArchetype(archetype);
|
|
2131
1945
|
}
|
|
2132
|
-
/**
|
|
2133
|
-
* Execute component lifecycle hooks for added and removed components
|
|
2134
|
-
*/
|
|
2135
|
-
triggerLifecycleHooks(entityId, addedComponents, removedComponents) {
|
|
2136
|
-
for (const [componentType, component$1] of addedComponents) {
|
|
2137
|
-
const directHooks = this.hooks.get(componentType);
|
|
2138
|
-
if (directHooks) for (const lifecycleHook of directHooks) lifecycleHook.on_set?.(entityId, componentType, component$1);
|
|
2139
|
-
const componentId = getComponentIdFromRelationId(componentType);
|
|
2140
|
-
if (componentId !== void 0) {
|
|
2141
|
-
const wildcardRelationId = relation(componentId, "*");
|
|
2142
|
-
const wildcardHooks = this.hooks.get(wildcardRelationId);
|
|
2143
|
-
if (wildcardHooks) for (const lifecycleHook of wildcardHooks) lifecycleHook.on_set?.(entityId, componentType, component$1);
|
|
2144
|
-
}
|
|
2145
|
-
}
|
|
2146
|
-
for (const [componentType, component$1] of removedComponents) {
|
|
2147
|
-
const directHooks = this.hooks.get(componentType);
|
|
2148
|
-
if (directHooks) for (const lifecycleHook of directHooks) lifecycleHook.on_remove?.(entityId, componentType, component$1);
|
|
2149
|
-
const componentId = getComponentIdFromRelationId(componentType);
|
|
2150
|
-
if (componentId !== void 0) {
|
|
2151
|
-
const wildcardRelationId = relation(componentId, "*");
|
|
2152
|
-
const wildcardHooks = this.hooks.get(wildcardRelationId);
|
|
2153
|
-
if (wildcardHooks) for (const hook of wildcardHooks) hook.on_remove?.(entityId, componentType, component$1);
|
|
2154
|
-
}
|
|
2155
|
-
}
|
|
2156
|
-
this.triggerMultiComponentHooks(entityId, addedComponents, removedComponents);
|
|
2157
|
-
}
|
|
2158
|
-
/**
|
|
2159
|
-
* Trigger multi-component hooks based on changes
|
|
2160
|
-
*/
|
|
2161
|
-
triggerMultiComponentHooks(entityId, addedComponents, removedComponents) {
|
|
2162
|
-
for (const entry of this.multiHooks) {
|
|
2163
|
-
const { componentTypes, requiredComponents, hook } = entry;
|
|
2164
|
-
let anyRequiredAdded = false;
|
|
2165
|
-
for (const reqComp of requiredComponents) if (addedComponents.has(reqComp)) {
|
|
2166
|
-
anyRequiredAdded = true;
|
|
2167
|
-
break;
|
|
2168
|
-
}
|
|
2169
|
-
let anyRequiredRemoved = false;
|
|
2170
|
-
for (const reqComp of requiredComponents) if (removedComponents.has(reqComp)) {
|
|
2171
|
-
anyRequiredRemoved = true;
|
|
2172
|
-
break;
|
|
2173
|
-
}
|
|
2174
|
-
if (anyRequiredAdded && hook.on_set) {
|
|
2175
|
-
if (this.entityHasAllComponents(entityId, requiredComponents)) {
|
|
2176
|
-
const components = this.collectMultiHookComponents(entityId, componentTypes);
|
|
2177
|
-
hook.on_set(entityId, componentTypes, components);
|
|
2178
|
-
}
|
|
2179
|
-
}
|
|
2180
|
-
if (anyRequiredRemoved && hook.on_remove) {
|
|
2181
|
-
if (this.entityHadAllComponentsBefore(entityId, requiredComponents, removedComponents)) {
|
|
2182
|
-
const components = this.collectMultiHookComponentsWithRemoved(entityId, componentTypes, removedComponents);
|
|
2183
|
-
hook.on_remove(entityId, componentTypes, components);
|
|
2184
|
-
}
|
|
2185
|
-
}
|
|
2186
|
-
}
|
|
2187
|
-
}
|
|
2188
|
-
/**
|
|
2189
|
-
* Check if entity currently has all required components
|
|
2190
|
-
*/
|
|
2191
|
-
entityHasAllComponents(entityId, requiredComponents) {
|
|
2192
|
-
for (const reqComp of requiredComponents) if (!this.has(entityId, reqComp)) return false;
|
|
2193
|
-
return true;
|
|
2194
|
-
}
|
|
2195
|
-
/**
|
|
2196
|
-
* Check if entity had all required components before removal
|
|
2197
|
-
*/
|
|
2198
|
-
entityHadAllComponentsBefore(entityId, requiredComponents, removedComponents) {
|
|
2199
|
-
for (const reqComp of requiredComponents) {
|
|
2200
|
-
const wasRemoved = removedComponents.has(reqComp);
|
|
2201
|
-
const stillHas = this.has(entityId, reqComp);
|
|
2202
|
-
if (!wasRemoved && !stillHas) return false;
|
|
2203
|
-
}
|
|
2204
|
-
return true;
|
|
2205
|
-
}
|
|
2206
|
-
/**
|
|
2207
|
-
* Collect component values for multi-component hook (current state)
|
|
2208
|
-
*/
|
|
2209
|
-
collectMultiHookComponents(entityId, componentTypes) {
|
|
2210
|
-
const result = [];
|
|
2211
|
-
for (const ct of componentTypes) if (isOptionalEntityId(ct)) {
|
|
2212
|
-
const optionalId = ct.optional;
|
|
2213
|
-
result.push(this.getOptional(entityId, optionalId));
|
|
2214
|
-
} else result.push(this.get(entityId, ct));
|
|
2215
|
-
return result;
|
|
2216
|
-
}
|
|
2217
|
-
/**
|
|
2218
|
-
* Collect component values for multi-component hook with removed components (snapshot before removal)
|
|
2219
|
-
*/
|
|
2220
|
-
collectMultiHookComponentsWithRemoved(entityId, componentTypes, removedComponents) {
|
|
2221
|
-
const result = [];
|
|
2222
|
-
for (const ct of componentTypes) if (isOptionalEntityId(ct)) {
|
|
2223
|
-
const optionalId = ct.optional;
|
|
2224
|
-
if (removedComponents.has(optionalId)) result.push({ value: removedComponents.get(optionalId) });
|
|
2225
|
-
else result.push(this.getOptional(entityId, optionalId));
|
|
2226
|
-
} else {
|
|
2227
|
-
const compId = ct;
|
|
2228
|
-
if (removedComponents.has(compId)) result.push(removedComponents.get(compId));
|
|
2229
|
-
else result.push(this.get(entityId, compId));
|
|
2230
|
-
}
|
|
2231
|
-
return result;
|
|
2232
|
-
}
|
|
2233
|
-
/**
|
|
2234
|
-
* Convert the world into a plain snapshot object.
|
|
2235
|
-
* This returns an in-memory structure and does not perform JSON stringification.
|
|
2236
|
-
* Component values are stored as-is (they may be non-JSON-serializable).
|
|
2237
|
-
*/
|
|
2238
1946
|
serialize() {
|
|
2239
1947
|
const entities = [];
|
|
2240
1948
|
for (const archetype of this.archetypes) {
|
|
@@ -2254,63 +1962,7 @@ var World = class {
|
|
|
2254
1962
|
};
|
|
2255
1963
|
}
|
|
2256
1964
|
};
|
|
2257
|
-
var EntityBuilder = class {
|
|
2258
|
-
world;
|
|
2259
|
-
components = [];
|
|
2260
|
-
constructor(world) {
|
|
2261
|
-
this.world = world;
|
|
2262
|
-
}
|
|
2263
|
-
with(componentId, value) {
|
|
2264
|
-
this.components.push({
|
|
2265
|
-
type: "component",
|
|
2266
|
-
id: componentId,
|
|
2267
|
-
value
|
|
2268
|
-
});
|
|
2269
|
-
return this;
|
|
2270
|
-
}
|
|
2271
|
-
withTag(componentId) {
|
|
2272
|
-
this.components.push({
|
|
2273
|
-
type: "component",
|
|
2274
|
-
id: componentId,
|
|
2275
|
-
value: void 0
|
|
2276
|
-
});
|
|
2277
|
-
return this;
|
|
2278
|
-
}
|
|
2279
|
-
withRelation(componentId, targetEntity, value) {
|
|
2280
|
-
this.components.push({
|
|
2281
|
-
type: "relation",
|
|
2282
|
-
componentId,
|
|
2283
|
-
targetId: targetEntity,
|
|
2284
|
-
value
|
|
2285
|
-
});
|
|
2286
|
-
return this;
|
|
2287
|
-
}
|
|
2288
|
-
withRelationTag(componentId, targetEntity) {
|
|
2289
|
-
this.components.push({
|
|
2290
|
-
type: "relation",
|
|
2291
|
-
componentId,
|
|
2292
|
-
targetId: targetEntity,
|
|
2293
|
-
value: void 0
|
|
2294
|
-
});
|
|
2295
|
-
return this;
|
|
2296
|
-
}
|
|
2297
|
-
/**
|
|
2298
|
-
* Create an entity and enqueue components to be applied. This method
|
|
2299
|
-
* does NOT call `world.sync()` automatically; callers must invoke
|
|
2300
|
-
* `world.sync()` to apply deferred commands.
|
|
2301
|
-
* (Previously auto-synced; now a breaking change — buildDeferred() removed.)
|
|
2302
|
-
*/
|
|
2303
|
-
build() {
|
|
2304
|
-
const entity = this.world.new();
|
|
2305
|
-
for (const def of this.components) if (def.type === "component") this.world.set(entity, def.id, def.value);
|
|
2306
|
-
else {
|
|
2307
|
-
const relationId = relation(def.componentId, def.targetId);
|
|
2308
|
-
this.world.set(entity, relationId, def.value);
|
|
2309
|
-
}
|
|
2310
|
-
return entity;
|
|
2311
|
-
}
|
|
2312
|
-
};
|
|
2313
1965
|
|
|
2314
1966
|
//#endregion
|
|
2315
|
-
export {
|
|
1967
|
+
export { getComponentIdByName as a, isWildcardRelationId as c, isEntityId as d, isRelationId as f, component as i, relation as l, Query as n, getComponentNameById as o, EntityBuilder as r, decodeRelationId as s, World as t, isComponentId as u };
|
|
2316
1968
|
//# sourceMappingURL=world.mjs.map
|