@codehz/ecs 0.5.1 → 0.5.3
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} +115 -467
- package/index.d.mts +2 -2
- package/index.mjs +1 -1
- package/package.json +1 -1
- package/testing.d.mts +3 -3
- package/testing.mjs +2 -2
- package/testing.mjs.map +1 -1
- package/world.mjs +1206 -1539
- 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
|
-
* Create a new World.
|
|
1466
|
-
* If an optional snapshot object is provided (previously produced by `world.serialize()`),
|
|
1467
|
-
* the world will be restored from that snapshot. The snapshot may contain non-JSON values.
|
|
1468
|
-
*/
|
|
1469
1618
|
constructor(snapshot) {
|
|
1470
|
-
if (snapshot && typeof snapshot === "object")
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
}
|
|
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,19 +1710,28 @@ 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) {
|
|
1720
|
+
if (typeof hook === "function") if (Array.isArray(componentTypesOrSingle)) {
|
|
1721
|
+
const callback = hook;
|
|
1722
|
+
hook = {
|
|
1723
|
+
on_init: (entityId, componentTypes, components) => callback("init", entityId, componentTypes, components),
|
|
1724
|
+
on_set: (entityId, componentTypes, components) => callback("set", entityId, componentTypes, components),
|
|
1725
|
+
on_remove: (entityId, componentTypes, components) => callback("remove", entityId, componentTypes, components)
|
|
1726
|
+
};
|
|
1727
|
+
} else {
|
|
1728
|
+
const callback = hook;
|
|
1729
|
+
hook = {
|
|
1730
|
+
on_init: (entityId, componentType, component$1) => callback("init", entityId, componentType, component$1),
|
|
1731
|
+
on_set: (entityId, componentType, component$1) => callback("set", entityId, componentType, component$1),
|
|
1732
|
+
on_remove: (entityId, componentType, component$1) => callback("remove", entityId, componentType, component$1)
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1601
1735
|
if (Array.isArray(componentTypesOrSingle)) {
|
|
1602
1736
|
const componentTypes = componentTypesOrSingle;
|
|
1603
1737
|
const requiredComponents = [];
|
|
@@ -1612,7 +1746,7 @@ var World = class {
|
|
|
1612
1746
|
if (multiHook.on_init !== void 0) {
|
|
1613
1747
|
const matchingArchetypes = this.getMatchingArchetypes(requiredComponents);
|
|
1614
1748
|
for (const archetype of matchingArchetypes) for (const entityId of archetype.getEntities()) {
|
|
1615
|
-
const components = this.
|
|
1749
|
+
const components = collectMultiHookComponents(this.createHooksContext(), entityId, componentTypes);
|
|
1616
1750
|
multiHook.on_init(entityId, componentTypes, components);
|
|
1617
1751
|
}
|
|
1618
1752
|
}
|
|
@@ -1647,16 +1781,9 @@ var World = class {
|
|
|
1647
1781
|
}
|
|
1648
1782
|
}
|
|
1649
1783
|
}
|
|
1650
|
-
/**
|
|
1651
|
-
* Execute all deferred commands immediately
|
|
1652
|
-
*/
|
|
1653
1784
|
sync() {
|
|
1654
1785
|
this.commandBuffer.execute();
|
|
1655
1786
|
}
|
|
1656
|
-
/**
|
|
1657
|
-
* Create a cached query for efficient entity lookups
|
|
1658
|
-
* @returns A Query object for the specified component types and filter
|
|
1659
|
-
*/
|
|
1660
1787
|
createQuery(componentTypes, filter = {}) {
|
|
1661
1788
|
const sortedTypes = [...componentTypes].sort((a, b) => a - b);
|
|
1662
1789
|
const filterKey = serializeQueryFilter(filter);
|
|
@@ -1673,19 +1800,9 @@ var World = class {
|
|
|
1673
1800
|
});
|
|
1674
1801
|
return query;
|
|
1675
1802
|
}
|
|
1676
|
-
/**
|
|
1677
|
-
* Create an EntityBuilder for convenient entity creation.
|
|
1678
|
-
* @returns EntityBuilder
|
|
1679
|
-
*/
|
|
1680
1803
|
spawn() {
|
|
1681
1804
|
return new EntityBuilder(this);
|
|
1682
1805
|
}
|
|
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
1806
|
spawnMany(count, configure) {
|
|
1690
1807
|
const entities = [];
|
|
1691
1808
|
for (let i = 0; i < count; i++) {
|
|
@@ -1694,23 +1811,13 @@ var World = class {
|
|
|
1694
1811
|
}
|
|
1695
1812
|
return entities;
|
|
1696
1813
|
}
|
|
1697
|
-
/**
|
|
1698
|
-
* @internal Register a query for archetype update notifications
|
|
1699
|
-
*/
|
|
1700
1814
|
_registerQuery(query) {
|
|
1701
1815
|
this.queries.push(query);
|
|
1702
1816
|
}
|
|
1703
|
-
/**
|
|
1704
|
-
* @internal Unregister a query
|
|
1705
|
-
*/
|
|
1706
1817
|
_unregisterQuery(query) {
|
|
1707
1818
|
const index = this.queries.indexOf(query);
|
|
1708
1819
|
if (index !== -1) this.queries.splice(index, 1);
|
|
1709
1820
|
}
|
|
1710
|
-
/**
|
|
1711
|
-
* Release a query reference obtained from createQuery.
|
|
1712
|
-
* Decrements the refCount and fully disposes the query when it reaches zero.
|
|
1713
|
-
*/
|
|
1714
1821
|
releaseQuery(query) {
|
|
1715
1822
|
for (const [k, v] of this.queryCache.entries()) if (v.query === query) {
|
|
1716
1823
|
v.refCount--;
|
|
@@ -1722,9 +1829,6 @@ var World = class {
|
|
|
1722
1829
|
return;
|
|
1723
1830
|
}
|
|
1724
1831
|
}
|
|
1725
|
-
/**
|
|
1726
|
-
* @internal Get archetypes that match specific component types (for internal use by queries)
|
|
1727
|
-
*/
|
|
1728
1832
|
getMatchingArchetypes(componentTypes) {
|
|
1729
1833
|
if (componentTypes.length === 0) return [...this.archetypes];
|
|
1730
1834
|
const regularComponents = [];
|
|
@@ -1736,42 +1840,24 @@ var World = class {
|
|
|
1736
1840
|
relationId: componentType
|
|
1737
1841
|
});
|
|
1738
1842
|
} 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));
|
|
1843
|
+
let matchingArchetypes = this.getArchetypesWithComponents(regularComponents);
|
|
1844
|
+
for (const { componentId, relationId } of wildcardRelations) {
|
|
1845
|
+
const archetypesWithMarker = this.archetypesByComponent.get(relationId) || [];
|
|
1846
|
+
matchingArchetypes = matchingArchetypes.length === 0 ? archetypesWithMarker : matchingArchetypes.filter((a) => archetypesWithMarker.includes(a) || a.hasRelationWithComponentId(componentId));
|
|
1847
|
+
}
|
|
1765
1848
|
return matchingArchetypes;
|
|
1766
1849
|
}
|
|
1850
|
+
getArchetypesWithComponents(componentTypes) {
|
|
1851
|
+
if (componentTypes.length === 0) return [...this.archetypes];
|
|
1852
|
+
if (componentTypes.length === 1) return this.archetypesByComponent.get(componentTypes[0]) || [];
|
|
1853
|
+
const archetypeLists = componentTypes.map((type) => this.archetypesByComponent.get(type) || []);
|
|
1854
|
+
return archetypeLists[0].filter((archetype) => archetypeLists.slice(1).every((list) => list.includes(archetype)));
|
|
1855
|
+
}
|
|
1767
1856
|
query(componentTypes, includeComponents) {
|
|
1768
1857
|
const matchingArchetypes = this.getMatchingArchetypes(componentTypes);
|
|
1769
1858
|
if (includeComponents) {
|
|
1770
1859
|
const result = [];
|
|
1771
|
-
for (const archetype of matchingArchetypes)
|
|
1772
|
-
const entitiesWithData = archetype.getEntitiesWithComponents(componentTypes);
|
|
1773
|
-
result.push(...entitiesWithData);
|
|
1774
|
-
}
|
|
1860
|
+
for (const archetype of matchingArchetypes) result.push(...archetype.getEntitiesWithComponents(componentTypes));
|
|
1775
1861
|
return result;
|
|
1776
1862
|
} else {
|
|
1777
1863
|
const result = [];
|
|
@@ -1779,10 +1865,6 @@ var World = class {
|
|
|
1779
1865
|
return result;
|
|
1780
1866
|
}
|
|
1781
1867
|
}
|
|
1782
|
-
/**
|
|
1783
|
-
* @internal Execute commands for a single entity (for internal use by CommandBuffer)
|
|
1784
|
-
* @returns ComponentChangeset describing the changes made
|
|
1785
|
-
*/
|
|
1786
1868
|
executeEntityCommands(entityId, commands) {
|
|
1787
1869
|
const changeset = new ComponentChangeset();
|
|
1788
1870
|
if (commands.some((cmd) => cmd.type === "destroy")) {
|
|
@@ -1791,327 +1873,79 @@ var World = class {
|
|
|
1791
1873
|
}
|
|
1792
1874
|
const currentArchetype = this.entityToArchetype.get(entityId);
|
|
1793
1875
|
if (!currentArchetype) return changeset;
|
|
1794
|
-
|
|
1795
|
-
|
|
1876
|
+
processCommands(entityId, currentArchetype, commands, changeset, (eid, arch, compId) => {
|
|
1877
|
+
if (isExclusiveComponent(compId)) removeMatchingRelations(eid, arch, compId, changeset);
|
|
1878
|
+
});
|
|
1879
|
+
const removedComponents = applyChangeset({
|
|
1880
|
+
dontFragmentRelations: this.dontFragmentRelations,
|
|
1881
|
+
ensureArchetype: (ct) => this.ensureArchetype(ct)
|
|
1882
|
+
}, entityId, currentArchetype, changeset, this.entityToArchetype);
|
|
1796
1883
|
this.updateEntityReferences(entityId, changeset);
|
|
1797
|
-
this.
|
|
1884
|
+
triggerLifecycleHooks(this.createHooksContext(), entityId, changeset.adds, removedComponents);
|
|
1798
1885
|
return changeset;
|
|
1799
1886
|
}
|
|
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
|
-
}
|
|
1887
|
+
createHooksContext() {
|
|
1888
|
+
return {
|
|
1889
|
+
hooks: this.hooks,
|
|
1890
|
+
multiHooks: this.multiHooks,
|
|
1891
|
+
has: (eid, ct) => this.has(eid, ct),
|
|
1892
|
+
get: (eid, ct) => this.get(eid, ct),
|
|
1893
|
+
getOptional: (eid, ct) => this.getOptional(eid, ct)
|
|
1894
|
+
};
|
|
1874
1895
|
}
|
|
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
1896
|
removeComponentImmediate(entityId, componentType, targetEntityId) {
|
|
1880
1897
|
const sourceArchetype = this.entityToArchetype.get(entityId);
|
|
1881
1898
|
if (!sourceArchetype) return;
|
|
1882
1899
|
const changeset = new ComponentChangeset();
|
|
1883
|
-
const componentId = getComponentIdFromRelationId(componentType);
|
|
1884
1900
|
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
|
-
}
|
|
1901
|
+
maybeRemoveWildcardMarker(entityId, sourceArchetype, componentType, getComponentIdFromRelationId(componentType), changeset);
|
|
1899
1902
|
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);
|
|
1903
|
+
applyChangeset({
|
|
1904
|
+
dontFragmentRelations: this.dontFragmentRelations,
|
|
1905
|
+
ensureArchetype: (ct) => this.ensureArchetype(ct)
|
|
1906
|
+
}, entityId, sourceArchetype, changeset, this.entityToArchetype);
|
|
1907
|
+
untrackEntityReference(this.entityReferences, entityId, componentType, targetEntityId);
|
|
1908
|
+
triggerLifecycleHooks(this.createHooksContext(), entityId, /* @__PURE__ */ new Map(), new Map([[componentType, removedComponent]]));
|
|
1964
1909
|
}
|
|
1965
|
-
/**
|
|
1966
|
-
* Update entity reference tracking based on changeset
|
|
1967
|
-
*/
|
|
1968
1910
|
updateEntityReferences(entityId, changeset) {
|
|
1969
1911
|
for (const componentType of changeset.removes) if (isEntityRelation(componentType)) {
|
|
1970
1912
|
const targetId = getTargetIdFromRelationId(componentType);
|
|
1971
|
-
this.
|
|
1972
|
-
} else if (componentType >= 1024) this.
|
|
1913
|
+
untrackEntityReference(this.entityReferences, entityId, componentType, targetId);
|
|
1914
|
+
} else if (componentType >= 1024) untrackEntityReference(this.entityReferences, entityId, componentType, componentType);
|
|
1973
1915
|
for (const [componentType] of changeset.adds) if (isEntityRelation(componentType)) {
|
|
1974
1916
|
const targetId = getTargetIdFromRelationId(componentType);
|
|
1975
|
-
this.
|
|
1976
|
-
} else if (componentType >= 1024) this.
|
|
1917
|
+
trackEntityReference(this.entityReferences, entityId, componentType, targetId);
|
|
1918
|
+
} else if (componentType >= 1024) trackEntityReference(this.entityReferences, entityId, componentType, componentType);
|
|
1977
1919
|
}
|
|
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
1920
|
ensureArchetype(componentTypes) {
|
|
1984
|
-
const sortedTypes =
|
|
1921
|
+
const sortedTypes = filterRegularComponentTypes(componentTypes).sort((a, b) => a - b);
|
|
1985
1922
|
const hashKey = this.createArchetypeSignature(sortedTypes);
|
|
1986
1923
|
return getOrCreateWithSideEffect(this.archetypeBySignature, hashKey, () => this.createNewArchetype(sortedTypes));
|
|
1987
1924
|
}
|
|
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
1925
|
createNewArchetype(componentTypes) {
|
|
2016
1926
|
const newArchetype = new Archetype(componentTypes, this.dontFragmentRelations);
|
|
2017
1927
|
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
1928
|
for (const componentType of componentTypes) {
|
|
2027
1929
|
const archetypes = this.archetypesByComponent.get(componentType) || [];
|
|
2028
|
-
archetypes.push(
|
|
1930
|
+
archetypes.push(newArchetype);
|
|
2029
1931
|
this.archetypesByComponent.set(componentType, archetypes);
|
|
2030
1932
|
}
|
|
1933
|
+
for (const query of this.queries) query.checkNewArchetype(newArchetype);
|
|
1934
|
+
return newArchetype;
|
|
2031
1935
|
}
|
|
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
1936
|
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;
|
|
1937
|
+
return archetype.componentTypes.some((ct) => ct === entityId || isEntityRelation(ct) && getTargetIdFromRelationId(ct) === entityId);
|
|
2081
1938
|
}
|
|
2082
|
-
/**
|
|
2083
|
-
* Cleanup empty archetypes that reference a specific deleted entity
|
|
2084
|
-
* Only removes archetypes whose component types reference the entity
|
|
2085
|
-
*/
|
|
2086
1939
|
cleanupArchetypesReferencingEntity(entityId) {
|
|
2087
1940
|
for (let i = this.archetypes.length - 1; i >= 0; i--) {
|
|
2088
1941
|
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
|
-
}
|
|
1942
|
+
if (archetype.getEntities().length === 0 && this.archetypeReferencesEntity(archetype, entityId)) this.removeArchetype(archetype);
|
|
2095
1943
|
}
|
|
2096
1944
|
}
|
|
2097
|
-
|
|
2098
|
-
* Remove archetype from the main archetypes list
|
|
2099
|
-
*/
|
|
2100
|
-
removeArchetypeFromList(archetype) {
|
|
1945
|
+
removeArchetype(archetype) {
|
|
2101
1946
|
const index = this.archetypes.indexOf(archetype);
|
|
2102
1947
|
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) {
|
|
1948
|
+
this.archetypeBySignature.delete(this.createArchetypeSignature(archetype.componentTypes));
|
|
2115
1949
|
for (const componentType of archetype.componentTypes) {
|
|
2116
1950
|
const archetypes = this.archetypesByComponent.get(componentType);
|
|
2117
1951
|
if (archetypes) {
|
|
@@ -2122,119 +1956,8 @@ var World = class {
|
|
|
2122
1956
|
}
|
|
2123
1957
|
}
|
|
2124
1958
|
}
|
|
2125
|
-
}
|
|
2126
|
-
/**
|
|
2127
|
-
* Remove archetype from all queries
|
|
2128
|
-
*/
|
|
2129
|
-
removeArchetypeFromQueries(archetype) {
|
|
2130
1959
|
for (const query of this.queries) query.removeArchetype(archetype);
|
|
2131
1960
|
}
|
|
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
1961
|
serialize() {
|
|
2239
1962
|
const entities = [];
|
|
2240
1963
|
for (const archetype of this.archetypes) {
|
|
@@ -2254,63 +1977,7 @@ var World = class {
|
|
|
2254
1977
|
};
|
|
2255
1978
|
}
|
|
2256
1979
|
};
|
|
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
1980
|
|
|
2314
1981
|
//#endregion
|
|
2315
|
-
export {
|
|
1982
|
+
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
1983
|
//# sourceMappingURL=world.mjs.map
|