@codehz/ecs 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -0
- package/{world.d.mts → builder.d.mts} +111 -444
- package/index.d.mts +1 -1
- package/index.mjs +1 -1
- package/package.json +1 -1
- package/testing.d.mts +2 -2
- package/testing.mjs +2 -2
- package/testing.mjs.map +1 -1
- package/world.mjs +1239 -1485
- 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
|
-
|
|
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);
|
|
1044
896
|
}
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
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);
|
|
1052
911
|
}
|
|
912
|
+
return value;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
//#endregion
|
|
916
|
+
//#region src/core/types.ts
|
|
917
|
+
function isOptionalEntityId(type) {
|
|
918
|
+
return typeof type === "object" && type !== null && "optional" in type;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
//#endregion
|
|
922
|
+
//#region src/core/archetype-helpers.ts
|
|
923
|
+
/**
|
|
924
|
+
* Check if a detailed type represents a relation (entity or component)
|
|
925
|
+
*/
|
|
926
|
+
function isRelationType(detailedType) {
|
|
927
|
+
return detailedType.type === "entity-relation" || detailedType.type === "component-relation";
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Check if a component type matches a given component ID for relations
|
|
931
|
+
*/
|
|
932
|
+
function matchesRelationComponentId(componentType, componentId) {
|
|
933
|
+
const detailedType = getDetailedIdType(componentType);
|
|
934
|
+
return isRelationType(detailedType) && detailedType.componentId === componentId;
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Find all relations in dontFragment data that match a component ID
|
|
938
|
+
*/
|
|
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);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
//#endregion
|
|
1005
|
+
//#region src/core/archetype.ts
|
|
1006
|
+
/**
|
|
1007
|
+
* Special value to represent missing component data
|
|
1008
|
+
*/
|
|
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 {
|
|
1053
1016
|
/**
|
|
1054
|
-
*
|
|
1017
|
+
* The component types that define this archetype
|
|
1055
1018
|
*/
|
|
1056
|
-
|
|
1057
|
-
this.commands.push({
|
|
1058
|
-
type: "delete",
|
|
1059
|
-
entityId,
|
|
1060
|
-
componentType
|
|
1061
|
-
});
|
|
1062
|
-
}
|
|
1019
|
+
componentTypes;
|
|
1063
1020
|
/**
|
|
1064
|
-
*
|
|
1021
|
+
* List of entities in this archetype
|
|
1065
1022
|
*/
|
|
1066
|
-
|
|
1067
|
-
this.commands.push({
|
|
1068
|
-
type: "destroy",
|
|
1069
|
-
entityId
|
|
1070
|
-
});
|
|
1071
|
-
}
|
|
1023
|
+
entities = [];
|
|
1072
1024
|
/**
|
|
1073
|
-
*
|
|
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
|
|
1074
1027
|
*/
|
|
1075
|
-
|
|
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
|
-
}
|
|
1028
|
+
componentData = /* @__PURE__ */ new Map();
|
|
1091
1029
|
/**
|
|
1092
|
-
*
|
|
1030
|
+
* Reverse mapping from entity to its index in this archetype
|
|
1093
1031
|
*/
|
|
1094
|
-
|
|
1095
|
-
return [...this.commands];
|
|
1096
|
-
}
|
|
1032
|
+
entityToIndex = /* @__PURE__ */ new Map();
|
|
1097
1033
|
/**
|
|
1098
|
-
*
|
|
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
|
|
1099
1037
|
*/
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
_valueCount = 0;
|
|
1110
|
-
get valueCount() {
|
|
1111
|
-
return this._valueCount;
|
|
1112
|
-
}
|
|
1113
|
-
get keyCount() {
|
|
1114
|
-
return this.map.size;
|
|
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, []);
|
|
1115
1047
|
}
|
|
1116
|
-
|
|
1117
|
-
return this.
|
|
1048
|
+
get size() {
|
|
1049
|
+
return this.entities.length;
|
|
1118
1050
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
return set.has(value);
|
|
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]);
|
|
1124
1055
|
}
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
this._valueCount++;
|
|
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);
|
|
1134
1064
|
}
|
|
1065
|
+
this.addDontFragmentRelations(entityId, componentData);
|
|
1135
1066
|
}
|
|
1136
|
-
|
|
1137
|
-
const
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
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;
|
|
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);
|
|
1151
1075
|
}
|
|
1152
|
-
|
|
1153
|
-
const
|
|
1154
|
-
|
|
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;
|
|
1155
1087
|
}
|
|
1156
|
-
|
|
1157
|
-
|
|
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
|
+
});
|
|
1158
1102
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
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;
|
|
1161
1127
|
}
|
|
1162
|
-
|
|
1163
|
-
return this.
|
|
1128
|
+
exists(entityId) {
|
|
1129
|
+
return this.entityToIndex.has(entityId);
|
|
1164
1130
|
}
|
|
1165
|
-
|
|
1166
|
-
|
|
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);
|
|
1167
1136
|
}
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
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]);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
if (componentId !== void 0) relations.push(...findMatchingDontFragmentRelations(this.dontFragmentRelations.get(entityId), componentId));
|
|
1151
|
+
return relations;
|
|
1171
1152
|
}
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
const negative = (filter.negativeComponentTypes || []).slice().sort((a, b) => a - b);
|
|
1182
|
-
if (negative.length === 0) return "";
|
|
1183
|
-
return `neg:${negative.join(",")}`;
|
|
1184
|
-
}
|
|
1185
|
-
/**
|
|
1186
|
-
* Check if an archetype matches the given component types
|
|
1187
|
-
*/
|
|
1188
|
-
function matchesComponentTypes(archetype, componentTypes) {
|
|
1189
|
-
return componentTypes.every((type) => {
|
|
1190
|
-
const detailedType = getDetailedIdType(type);
|
|
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
|
-
});
|
|
1197
|
-
}
|
|
1198
|
-
/**
|
|
1199
|
-
* Check if an archetype matches the filter conditions (only filtering logic)
|
|
1200
|
-
*/
|
|
1201
|
-
function matchesFilter(archetype, filter) {
|
|
1202
|
-
return (filter.negativeComponentTypes || []).every((type) => {
|
|
1203
|
-
const detailedType = getDetailedIdType(type);
|
|
1204
|
-
if (detailedType.type === "wildcard-relation") return !archetype.componentTypes.some((archetypeType) => {
|
|
1205
|
-
if (!isRelationId(archetypeType)) return false;
|
|
1206
|
-
return getComponentIdFromRelationId(archetypeType) === detailedType.componentId;
|
|
1207
|
-
});
|
|
1208
|
-
else return !archetype.componentTypes.includes(type);
|
|
1209
|
-
});
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
//#endregion
|
|
1213
|
-
//#region src/query.ts
|
|
1214
|
-
/**
|
|
1215
|
-
* Query class for efficient entity queries with cached archetypes
|
|
1216
|
-
*/
|
|
1217
|
-
var Query = class {
|
|
1218
|
-
world;
|
|
1219
|
-
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);
|
|
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;
|
|
1158
|
+
}
|
|
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}`);
|
|
1229
1162
|
}
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
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) };
|
|
1235
1173
|
}
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
let
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
hasAllRelations = false;
|
|
1250
|
-
break;
|
|
1251
|
-
}
|
|
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);
|
|
1252
1187
|
}
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
else for (const archetype of this.cachedArchetypes) result.push(...archetype.getEntities());
|
|
1256
|
-
return result;
|
|
1257
|
-
}
|
|
1258
|
-
/**
|
|
1259
|
-
* Get entities with their component data
|
|
1260
|
-
* @param componentTypes Array of component types to retrieve
|
|
1261
|
-
* @returns Array of objects with entity and component data
|
|
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);
|
|
1188
|
+
dontFragmentData.set(componentType, data);
|
|
1189
|
+
return;
|
|
1269
1190
|
}
|
|
1270
|
-
|
|
1191
|
+
throw new Error(`Component type ${componentType} is not in this archetype`);
|
|
1271
1192
|
}
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
* @param componentTypes Array of component types to retrieve
|
|
1275
|
-
* @param callback Function called for each entity with its components
|
|
1276
|
-
*/
|
|
1277
|
-
forEach(componentTypes, callback) {
|
|
1278
|
-
this.ensureNotDisposed();
|
|
1279
|
-
for (const archetype of this.cachedArchetypes) archetype.forEachWithComponents(componentTypes, callback);
|
|
1193
|
+
getEntities() {
|
|
1194
|
+
return this.entities;
|
|
1280
1195
|
}
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
* @param componentTypes Array of component types to retrieve
|
|
1284
|
-
*/
|
|
1285
|
-
*iterate(componentTypes) {
|
|
1286
|
-
this.ensureNotDisposed();
|
|
1287
|
-
for (const archetype of this.cachedArchetypes) yield* archetype.iterateWithComponents(componentTypes);
|
|
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
|
*/
|
|
@@ -1430,77 +1345,304 @@ function decodeSerializedId(sid) {
|
|
|
1430
1345
|
} else targetId = sid.target;
|
|
1431
1346
|
return relation(compId, targetId);
|
|
1432
1347
|
}
|
|
1433
|
-
throw new Error(`Invalid ID in snapshot: ${JSON.stringify(sid)}`);
|
|
1348
|
+
throw new Error(`Invalid ID in snapshot: ${JSON.stringify(sid)}`);
|
|
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);
|
|
1434
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
|
-
|
|
1463
|
-
* Create a new World.
|
|
1464
|
-
* If an optional snapshot object is provided (previously produced by `world.serialize()`),
|
|
1465
|
-
* the world will be restored from that snapshot. The snapshot may contain non-JSON values.
|
|
1466
|
-
*/
|
|
1617
|
+
multiHooks = /* @__PURE__ */ new Set();
|
|
1467
1618
|
constructor(snapshot) {
|
|
1468
|
-
if (snapshot && typeof snapshot === "object")
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
}
|
|
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);
|
|
1490
1640
|
}
|
|
1491
1641
|
}
|
|
1492
1642
|
}
|
|
1493
|
-
/**
|
|
1494
|
-
* Generate a signature string for component types array
|
|
1495
|
-
* @returns A string signature for the component types
|
|
1496
|
-
*/
|
|
1497
1643
|
createArchetypeSignature(componentTypes) {
|
|
1498
1644
|
return componentTypes.join(",");
|
|
1499
1645
|
}
|
|
1500
|
-
/**
|
|
1501
|
-
* Create a new entity
|
|
1502
|
-
* @returns The ID of the newly created entity
|
|
1503
|
-
*/
|
|
1504
1646
|
new() {
|
|
1505
1647
|
const entityId = this.entityIdManager.allocate();
|
|
1506
1648
|
let emptyArchetype = this.ensureArchetype([]);
|
|
@@ -1508,9 +1650,6 @@ var World = class {
|
|
|
1508
1650
|
this.entityToArchetype.set(entityId, emptyArchetype);
|
|
1509
1651
|
return entityId;
|
|
1510
1652
|
}
|
|
1511
|
-
/**
|
|
1512
|
-
* Destroy an entity and remove all its components (immediate execution)
|
|
1513
|
-
*/
|
|
1514
1653
|
destroyEntityImmediate(entityId) {
|
|
1515
1654
|
const queue = [entityId];
|
|
1516
1655
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -1520,7 +1659,7 @@ var World = class {
|
|
|
1520
1659
|
visited.add(cur);
|
|
1521
1660
|
const archetype = this.entityToArchetype.get(cur);
|
|
1522
1661
|
if (!archetype) continue;
|
|
1523
|
-
const componentReferences = Array.from(this.
|
|
1662
|
+
const componentReferences = Array.from(getEntityReferences(this.entityReferences, cur));
|
|
1524
1663
|
for (const [sourceEntityId, componentType] of componentReferences) {
|
|
1525
1664
|
if (!this.entityToArchetype.get(sourceEntityId)) continue;
|
|
1526
1665
|
if (isCascadeDeleteRelation(componentType)) {
|
|
@@ -1536,9 +1675,6 @@ var World = class {
|
|
|
1536
1675
|
this.entityIdManager.deallocate(cur);
|
|
1537
1676
|
}
|
|
1538
1677
|
}
|
|
1539
|
-
/**
|
|
1540
|
-
* Check if an entity exists
|
|
1541
|
-
*/
|
|
1542
1678
|
exists(entityId) {
|
|
1543
1679
|
return this.entityToArchetype.has(entityId);
|
|
1544
1680
|
}
|
|
@@ -1549,23 +1685,14 @@ var World = class {
|
|
|
1549
1685
|
if (detailedType.type === "wildcard-relation") throw new Error(`Cannot directly add wildcard relation components: ${componentType}`);
|
|
1550
1686
|
this.commandBuffer.set(entityId, componentType, component$1);
|
|
1551
1687
|
}
|
|
1552
|
-
/**
|
|
1553
|
-
* Remove a component from an entity (deferred)
|
|
1554
|
-
*/
|
|
1555
1688
|
remove(entityId, componentType) {
|
|
1556
1689
|
if (!this.exists(entityId)) throw new Error(`Entity ${entityId} does not exist`);
|
|
1557
1690
|
if (getDetailedIdType(componentType).type === "invalid") throw new Error(`Invalid component type: ${componentType}`);
|
|
1558
1691
|
this.commandBuffer.remove(entityId, componentType);
|
|
1559
1692
|
}
|
|
1560
|
-
/**
|
|
1561
|
-
* Destroy an entity and remove all its components (deferred)
|
|
1562
|
-
*/
|
|
1563
1693
|
delete(entityId) {
|
|
1564
1694
|
this.commandBuffer.delete(entityId);
|
|
1565
1695
|
}
|
|
1566
|
-
/**
|
|
1567
|
-
* Check if an entity has a specific component
|
|
1568
|
-
*/
|
|
1569
1696
|
has(entityId, componentType) {
|
|
1570
1697
|
const archetype = this.entityToArchetype.get(entityId);
|
|
1571
1698
|
if (!archetype) return false;
|
|
@@ -1583,54 +1710,65 @@ var World = class {
|
|
|
1583
1710
|
}
|
|
1584
1711
|
return archetype.get(entityId, componentType);
|
|
1585
1712
|
}
|
|
1586
|
-
/**
|
|
1587
|
-
* Get optional component data for a specific entity and component type
|
|
1588
|
-
* @param entityId The entity
|
|
1589
|
-
* @param componentType The component type
|
|
1590
|
-
* @returns { value: T } if component exists, undefined otherwise
|
|
1591
|
-
*/
|
|
1592
1713
|
getOptional(entityId, componentType) {
|
|
1593
1714
|
const archetype = this.entityToArchetype.get(entityId);
|
|
1594
1715
|
if (!archetype) throw new Error(`Entity ${entityId} does not exist`);
|
|
1595
|
-
if (isWildcardRelationId(componentType)) return;
|
|
1716
|
+
if (isWildcardRelationId(componentType)) return void 0;
|
|
1596
1717
|
return archetype.getOptional(entityId, componentType);
|
|
1597
1718
|
}
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1719
|
+
hook(componentTypesOrSingle, hook) {
|
|
1720
|
+
if (Array.isArray(componentTypesOrSingle)) {
|
|
1721
|
+
const componentTypes = componentTypesOrSingle;
|
|
1722
|
+
const requiredComponents = [];
|
|
1723
|
+
for (const ct of componentTypes) if (!isOptionalEntityId(ct)) requiredComponents.push(ct);
|
|
1724
|
+
const entry = {
|
|
1725
|
+
componentTypes,
|
|
1726
|
+
requiredComponents,
|
|
1727
|
+
hook
|
|
1728
|
+
};
|
|
1729
|
+
this.multiHooks.add(entry);
|
|
1730
|
+
const multiHook = hook;
|
|
1731
|
+
if (multiHook.on_init !== void 0) {
|
|
1732
|
+
const matchingArchetypes = this.getMatchingArchetypes(requiredComponents);
|
|
1733
|
+
for (const archetype of matchingArchetypes) for (const entityId of archetype.getEntities()) {
|
|
1734
|
+
const components = collectMultiHookComponents(this.createHooksContext(), entityId, componentTypes);
|
|
1735
|
+
multiHook.on_init(entityId, componentTypes, components);
|
|
1736
|
+
}
|
|
1611
1737
|
}
|
|
1612
|
-
}
|
|
1738
|
+
} else {
|
|
1739
|
+
const componentType = componentTypesOrSingle;
|
|
1740
|
+
if (!this.hooks.has(componentType)) this.hooks.set(componentType, /* @__PURE__ */ new Set());
|
|
1741
|
+
this.hooks.get(componentType).add(hook);
|
|
1742
|
+
const singleHook = hook;
|
|
1743
|
+
if (singleHook.on_init !== void 0) this.archetypesByComponent.get(componentType)?.forEach((archetype) => {
|
|
1744
|
+
const entities = archetype.getEntityToIndexMap();
|
|
1745
|
+
const componentData = archetype.getComponentData(componentType);
|
|
1746
|
+
for (const [entity, index] of entities) {
|
|
1747
|
+
const data = componentData[index];
|
|
1748
|
+
const value = data === MISSING_COMPONENT ? void 0 : data;
|
|
1749
|
+
singleHook.on_init?.(entity, componentType, value);
|
|
1750
|
+
}
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1613
1753
|
}
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1754
|
+
unhook(componentTypesOrSingle, hook) {
|
|
1755
|
+
if (Array.isArray(componentTypesOrSingle)) {
|
|
1756
|
+
for (const entry of this.multiHooks) if (entry.hook === hook) {
|
|
1757
|
+
this.multiHooks.delete(entry);
|
|
1758
|
+
break;
|
|
1759
|
+
}
|
|
1760
|
+
} else {
|
|
1761
|
+
const componentType = componentTypesOrSingle;
|
|
1762
|
+
const hooks = this.hooks.get(componentType);
|
|
1763
|
+
if (hooks) {
|
|
1764
|
+
hooks.delete(hook);
|
|
1765
|
+
if (hooks.size === 0) this.hooks.delete(componentType);
|
|
1766
|
+
}
|
|
1622
1767
|
}
|
|
1623
1768
|
}
|
|
1624
|
-
/**
|
|
1625
|
-
* Execute all deferred commands immediately
|
|
1626
|
-
*/
|
|
1627
1769
|
sync() {
|
|
1628
1770
|
this.commandBuffer.execute();
|
|
1629
1771
|
}
|
|
1630
|
-
/**
|
|
1631
|
-
* Create a cached query for efficient entity lookups
|
|
1632
|
-
* @returns A Query object for the specified component types and filter
|
|
1633
|
-
*/
|
|
1634
1772
|
createQuery(componentTypes, filter = {}) {
|
|
1635
1773
|
const sortedTypes = [...componentTypes].sort((a, b) => a - b);
|
|
1636
1774
|
const filterKey = serializeQueryFilter(filter);
|
|
@@ -1647,19 +1785,9 @@ var World = class {
|
|
|
1647
1785
|
});
|
|
1648
1786
|
return query;
|
|
1649
1787
|
}
|
|
1650
|
-
/**
|
|
1651
|
-
* Create an EntityBuilder for convenient entity creation.
|
|
1652
|
-
* @returns EntityBuilder
|
|
1653
|
-
*/
|
|
1654
1788
|
spawn() {
|
|
1655
1789
|
return new EntityBuilder(this);
|
|
1656
1790
|
}
|
|
1657
|
-
/**
|
|
1658
|
-
* Spawn multiple entities using an EntityBuilder configuration callback
|
|
1659
|
-
* @param count number of entities
|
|
1660
|
-
* @param configure builder configuration callback
|
|
1661
|
-
* @returns Created entity IDs
|
|
1662
|
-
*/
|
|
1663
1791
|
spawnMany(count, configure) {
|
|
1664
1792
|
const entities = [];
|
|
1665
1793
|
for (let i = 0; i < count; i++) {
|
|
@@ -1668,23 +1796,13 @@ var World = class {
|
|
|
1668
1796
|
}
|
|
1669
1797
|
return entities;
|
|
1670
1798
|
}
|
|
1671
|
-
/**
|
|
1672
|
-
* @internal Register a query for archetype update notifications
|
|
1673
|
-
*/
|
|
1674
1799
|
_registerQuery(query) {
|
|
1675
1800
|
this.queries.push(query);
|
|
1676
1801
|
}
|
|
1677
|
-
/**
|
|
1678
|
-
* @internal Unregister a query
|
|
1679
|
-
*/
|
|
1680
1802
|
_unregisterQuery(query) {
|
|
1681
1803
|
const index = this.queries.indexOf(query);
|
|
1682
1804
|
if (index !== -1) this.queries.splice(index, 1);
|
|
1683
1805
|
}
|
|
1684
|
-
/**
|
|
1685
|
-
* Release a query reference obtained from createQuery.
|
|
1686
|
-
* Decrements the refCount and fully disposes the query when it reaches zero.
|
|
1687
|
-
*/
|
|
1688
1806
|
releaseQuery(query) {
|
|
1689
1807
|
for (const [k, v] of this.queryCache.entries()) if (v.query === query) {
|
|
1690
1808
|
v.refCount--;
|
|
@@ -1696,9 +1814,6 @@ var World = class {
|
|
|
1696
1814
|
return;
|
|
1697
1815
|
}
|
|
1698
1816
|
}
|
|
1699
|
-
/**
|
|
1700
|
-
* @internal Get archetypes that match specific component types (for internal use by queries)
|
|
1701
|
-
*/
|
|
1702
1817
|
getMatchingArchetypes(componentTypes) {
|
|
1703
1818
|
if (componentTypes.length === 0) return [...this.archetypes];
|
|
1704
1819
|
const regularComponents = [];
|
|
@@ -1710,42 +1825,24 @@ var World = class {
|
|
|
1710
1825
|
relationId: componentType
|
|
1711
1826
|
});
|
|
1712
1827
|
} else regularComponents.push(componentType);
|
|
1713
|
-
let matchingArchetypes =
|
|
1714
|
-
|
|
1715
|
-
const
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
matchingArchetypes = this.archetypesByComponent.get(componentType) || [];
|
|
1719
|
-
} else {
|
|
1720
|
-
const archetypeLists = sortedRegularTypes.map((type) => this.archetypesByComponent.get(type) || []);
|
|
1721
|
-
const firstList = archetypeLists[0] || [];
|
|
1722
|
-
const intersection = /* @__PURE__ */ new Set();
|
|
1723
|
-
for (const archetype of firstList) {
|
|
1724
|
-
let hasAllComponents = true;
|
|
1725
|
-
for (let listIndex = 1; listIndex < archetypeLists.length; listIndex++) if (!archetypeLists[listIndex].includes(archetype)) {
|
|
1726
|
-
hasAllComponents = false;
|
|
1727
|
-
break;
|
|
1728
|
-
}
|
|
1729
|
-
if (hasAllComponents) intersection.add(archetype);
|
|
1730
|
-
}
|
|
1731
|
-
matchingArchetypes = Array.from(intersection);
|
|
1732
|
-
}
|
|
1733
|
-
} else matchingArchetypes = [...this.archetypes];
|
|
1734
|
-
for (const wildcard of wildcardRelations) if (isDontFragmentComponent(wildcard.componentId)) {
|
|
1735
|
-
const archetypesWithMarker = this.archetypesByComponent.get(wildcard.relationId) || [];
|
|
1736
|
-
if (matchingArchetypes.length === 0) matchingArchetypes = archetypesWithMarker;
|
|
1737
|
-
else matchingArchetypes = matchingArchetypes.filter((archetype) => archetypesWithMarker.includes(archetype));
|
|
1738
|
-
} else matchingArchetypes = matchingArchetypes.filter((archetype) => archetype.hasRelationWithComponentId(wildcard.componentId));
|
|
1828
|
+
let matchingArchetypes = this.getArchetypesWithComponents(regularComponents);
|
|
1829
|
+
for (const { componentId, relationId } of wildcardRelations) {
|
|
1830
|
+
const archetypesWithMarker = this.archetypesByComponent.get(relationId) || [];
|
|
1831
|
+
matchingArchetypes = matchingArchetypes.length === 0 ? archetypesWithMarker : matchingArchetypes.filter((a) => archetypesWithMarker.includes(a) || a.hasRelationWithComponentId(componentId));
|
|
1832
|
+
}
|
|
1739
1833
|
return matchingArchetypes;
|
|
1740
1834
|
}
|
|
1835
|
+
getArchetypesWithComponents(componentTypes) {
|
|
1836
|
+
if (componentTypes.length === 0) return [...this.archetypes];
|
|
1837
|
+
if (componentTypes.length === 1) return this.archetypesByComponent.get(componentTypes[0]) || [];
|
|
1838
|
+
const archetypeLists = componentTypes.map((type) => this.archetypesByComponent.get(type) || []);
|
|
1839
|
+
return archetypeLists[0].filter((archetype) => archetypeLists.slice(1).every((list) => list.includes(archetype)));
|
|
1840
|
+
}
|
|
1741
1841
|
query(componentTypes, includeComponents) {
|
|
1742
1842
|
const matchingArchetypes = this.getMatchingArchetypes(componentTypes);
|
|
1743
1843
|
if (includeComponents) {
|
|
1744
1844
|
const result = [];
|
|
1745
|
-
for (const archetype of matchingArchetypes)
|
|
1746
|
-
const entitiesWithData = archetype.getEntitiesWithComponents(componentTypes);
|
|
1747
|
-
result.push(...entitiesWithData);
|
|
1748
|
-
}
|
|
1845
|
+
for (const archetype of matchingArchetypes) result.push(...archetype.getEntitiesWithComponents(componentTypes));
|
|
1749
1846
|
return result;
|
|
1750
1847
|
} else {
|
|
1751
1848
|
const result = [];
|
|
@@ -1753,10 +1850,6 @@ var World = class {
|
|
|
1753
1850
|
return result;
|
|
1754
1851
|
}
|
|
1755
1852
|
}
|
|
1756
|
-
/**
|
|
1757
|
-
* @internal Execute commands for a single entity (for internal use by CommandBuffer)
|
|
1758
|
-
* @returns ComponentChangeset describing the changes made
|
|
1759
|
-
*/
|
|
1760
1853
|
executeEntityCommands(entityId, commands) {
|
|
1761
1854
|
const changeset = new ComponentChangeset();
|
|
1762
1855
|
if (commands.some((cmd) => cmd.type === "destroy")) {
|
|
@@ -1765,327 +1858,79 @@ var World = class {
|
|
|
1765
1858
|
}
|
|
1766
1859
|
const currentArchetype = this.entityToArchetype.get(entityId);
|
|
1767
1860
|
if (!currentArchetype) return changeset;
|
|
1768
|
-
|
|
1769
|
-
|
|
1861
|
+
processCommands(entityId, currentArchetype, commands, changeset, (eid, arch, compId) => {
|
|
1862
|
+
if (isExclusiveComponent(compId)) removeMatchingRelations(eid, arch, compId, changeset);
|
|
1863
|
+
});
|
|
1864
|
+
const removedComponents = applyChangeset({
|
|
1865
|
+
dontFragmentRelations: this.dontFragmentRelations,
|
|
1866
|
+
ensureArchetype: (ct) => this.ensureArchetype(ct)
|
|
1867
|
+
}, entityId, currentArchetype, changeset, this.entityToArchetype);
|
|
1770
1868
|
this.updateEntityReferences(entityId, changeset);
|
|
1771
|
-
this.
|
|
1869
|
+
triggerLifecycleHooks(this.createHooksContext(), entityId, changeset.adds, removedComponents);
|
|
1772
1870
|
return changeset;
|
|
1773
1871
|
}
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
* Process a set command, handling exclusive relations
|
|
1783
|
-
*/
|
|
1784
|
-
processSetCommand(entityId, currentArchetype, componentType, component$1, changeset) {
|
|
1785
|
-
const componentId = getComponentIdFromRelationId(componentType);
|
|
1786
|
-
if (componentId !== void 0) {
|
|
1787
|
-
if (componentId !== void 0 && isExclusiveComponent(componentId)) this.removeExclusiveRelations(entityId, currentArchetype, componentId, changeset);
|
|
1788
|
-
if (componentId !== void 0 && isDontFragmentComponent(componentId)) {
|
|
1789
|
-
const wildcardMarker = relation(componentId, "*");
|
|
1790
|
-
if (!currentArchetype.componentTypes.includes(wildcardMarker)) changeset.set(wildcardMarker, void 0);
|
|
1791
|
-
}
|
|
1792
|
-
}
|
|
1793
|
-
changeset.set(componentType, component$1);
|
|
1794
|
-
}
|
|
1795
|
-
/**
|
|
1796
|
-
* Remove all relations with the same base component (for exclusive relations)
|
|
1797
|
-
*/
|
|
1798
|
-
removeExclusiveRelations(entityId, currentArchetype, baseComponentId, changeset) {
|
|
1799
|
-
for (const componentType of currentArchetype.componentTypes) if (this.isRelationWithComponent(componentType, baseComponentId)) changeset.delete(componentType);
|
|
1800
|
-
const entityData = currentArchetype.getEntity(entityId);
|
|
1801
|
-
if (entityData) for (const [componentType] of entityData) {
|
|
1802
|
-
if (currentArchetype.componentTypes.includes(componentType)) continue;
|
|
1803
|
-
if (this.isRelationWithComponent(componentType, baseComponentId)) changeset.delete(componentType);
|
|
1804
|
-
}
|
|
1805
|
-
}
|
|
1806
|
-
isRelationWithComponent(componentType, baseComponentId) {
|
|
1807
|
-
return getComponentIdFromRelationId(componentType) === baseComponentId;
|
|
1808
|
-
}
|
|
1809
|
-
/**
|
|
1810
|
-
* Process a delete command, handling wildcard relations
|
|
1811
|
-
*/
|
|
1812
|
-
processDeleteCommand(entityId, currentArchetype, componentType, changeset) {
|
|
1813
|
-
const componentId = getComponentIdFromRelationId(componentType);
|
|
1814
|
-
if (isWildcardRelationId(componentType) && componentId !== void 0) this.removeWildcardRelations(entityId, currentArchetype, componentId, changeset);
|
|
1815
|
-
else {
|
|
1816
|
-
changeset.delete(componentType);
|
|
1817
|
-
if (componentId !== void 0 && isDontFragmentComponent(componentId)) {
|
|
1818
|
-
const wildcardMarker = relation(componentId, "*");
|
|
1819
|
-
const entityData = currentArchetype.getEntity(entityId);
|
|
1820
|
-
let hasOtherRelations = false;
|
|
1821
|
-
if (entityData) for (const [otherComponentType] of entityData) {
|
|
1822
|
-
if (otherComponentType === componentType) continue;
|
|
1823
|
-
if (otherComponentType === wildcardMarker) continue;
|
|
1824
|
-
if (changeset.removes.has(otherComponentType)) continue;
|
|
1825
|
-
if (getComponentIdFromRelationId(otherComponentType) === componentId) {
|
|
1826
|
-
hasOtherRelations = true;
|
|
1827
|
-
break;
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1830
|
-
if (!hasOtherRelations) changeset.delete(wildcardMarker);
|
|
1831
|
-
}
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1834
|
-
/**
|
|
1835
|
-
* Remove all relations matching a wildcard component ID
|
|
1836
|
-
*/
|
|
1837
|
-
removeWildcardRelations(entityId, currentArchetype, baseComponentId, changeset) {
|
|
1838
|
-
for (const componentType of currentArchetype.componentTypes) if (this.isRelationWithComponent(componentType, baseComponentId)) changeset.delete(componentType);
|
|
1839
|
-
const entityData = currentArchetype.getEntity(entityId);
|
|
1840
|
-
if (entityData) for (const [componentType] of entityData) {
|
|
1841
|
-
if (currentArchetype.componentTypes.includes(componentType)) continue;
|
|
1842
|
-
if (this.isRelationWithComponent(componentType, baseComponentId)) changeset.delete(componentType);
|
|
1843
|
-
}
|
|
1844
|
-
if (isDontFragmentComponent(baseComponentId)) {
|
|
1845
|
-
const wildcardMarker = relation(baseComponentId, "*");
|
|
1846
|
-
changeset.delete(wildcardMarker);
|
|
1847
|
-
}
|
|
1872
|
+
createHooksContext() {
|
|
1873
|
+
return {
|
|
1874
|
+
hooks: this.hooks,
|
|
1875
|
+
multiHooks: this.multiHooks,
|
|
1876
|
+
has: (eid, ct) => this.has(eid, ct),
|
|
1877
|
+
get: (eid, ct) => this.get(eid, ct),
|
|
1878
|
+
getOptional: (eid, ct) => this.getOptional(eid, ct)
|
|
1879
|
+
};
|
|
1848
1880
|
}
|
|
1849
|
-
/**
|
|
1850
|
-
* Remove a single component from an entity immediately, handling dontFragment relations correctly.
|
|
1851
|
-
* Used by destroyEntityImmediate for non-cascade relation cleanup.
|
|
1852
|
-
*/
|
|
1853
1881
|
removeComponentImmediate(entityId, componentType, targetEntityId) {
|
|
1854
1882
|
const sourceArchetype = this.entityToArchetype.get(entityId);
|
|
1855
1883
|
if (!sourceArchetype) return;
|
|
1856
1884
|
const changeset = new ComponentChangeset();
|
|
1857
|
-
const componentId = getComponentIdFromRelationId(componentType);
|
|
1858
1885
|
changeset.delete(componentType);
|
|
1859
|
-
|
|
1860
|
-
const wildcardMarker = relation(componentId, "*");
|
|
1861
|
-
const entityData = sourceArchetype.getEntity(entityId);
|
|
1862
|
-
let hasOtherRelations = false;
|
|
1863
|
-
if (entityData) for (const [otherComponentType] of entityData) {
|
|
1864
|
-
if (otherComponentType === componentType) continue;
|
|
1865
|
-
if (otherComponentType === wildcardMarker) continue;
|
|
1866
|
-
if (getComponentIdFromRelationId(otherComponentType) === componentId) {
|
|
1867
|
-
hasOtherRelations = true;
|
|
1868
|
-
break;
|
|
1869
|
-
}
|
|
1870
|
-
}
|
|
1871
|
-
if (!hasOtherRelations) changeset.delete(wildcardMarker);
|
|
1872
|
-
}
|
|
1886
|
+
maybeRemoveWildcardMarker(entityId, sourceArchetype, componentType, getComponentIdFromRelationId(componentType), changeset);
|
|
1873
1887
|
const removedComponent = sourceArchetype.get(entityId, componentType);
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
* @returns Map of removed components with their data
|
|
1881
|
-
*/
|
|
1882
|
-
applyChangeset(entityId, currentArchetype, changeset) {
|
|
1883
|
-
const currentEntityData = currentArchetype.getEntity(entityId);
|
|
1884
|
-
const allCurrentComponentTypes = currentEntityData ? Array.from(currentEntityData.keys()) : currentArchetype.componentTypes;
|
|
1885
|
-
const finalComponentTypes = changeset.getFinalComponentTypes(allCurrentComponentTypes);
|
|
1886
|
-
const removedComponents = /* @__PURE__ */ new Map();
|
|
1887
|
-
if (finalComponentTypes) {
|
|
1888
|
-
const currentRegularTypes = this.filterRegularComponentTypes(allCurrentComponentTypes);
|
|
1889
|
-
const finalRegularTypes = this.filterRegularComponentTypes(finalComponentTypes);
|
|
1890
|
-
if (!this.areComponentTypesEqual(currentRegularTypes, finalRegularTypes)) this.moveEntityToNewArchetype(entityId, currentArchetype, finalComponentTypes, changeset, removedComponents);
|
|
1891
|
-
else this.updateEntityInSameArchetype(entityId, currentArchetype, changeset, removedComponents);
|
|
1892
|
-
} else this.updateEntityInSameArchetype(entityId, currentArchetype, changeset, removedComponents);
|
|
1893
|
-
return removedComponents;
|
|
1894
|
-
}
|
|
1895
|
-
/**
|
|
1896
|
-
* Move entity to a new archetype with updated components
|
|
1897
|
-
*/
|
|
1898
|
-
moveEntityToNewArchetype(entityId, currentArchetype, finalComponentTypes, changeset, removedComponents) {
|
|
1899
|
-
const newArchetype = this.ensureArchetype(finalComponentTypes);
|
|
1900
|
-
const currentComponents = currentArchetype.removeEntity(entityId);
|
|
1901
|
-
for (const componentType of changeset.removes) removedComponents.set(componentType, currentComponents.get(componentType));
|
|
1902
|
-
newArchetype.addEntity(entityId, changeset.applyTo(currentComponents));
|
|
1903
|
-
this.entityToArchetype.set(entityId, newArchetype);
|
|
1904
|
-
}
|
|
1905
|
-
/**
|
|
1906
|
-
* Update entity in same archetype (no archetype change needed)
|
|
1907
|
-
*/
|
|
1908
|
-
updateEntityInSameArchetype(entityId, currentArchetype, changeset, removedComponents) {
|
|
1909
|
-
this.applyDontFragmentChanges(entityId, changeset, removedComponents);
|
|
1910
|
-
for (const [componentType, component$1] of changeset.adds) {
|
|
1911
|
-
if (isDontFragmentRelation(componentType)) continue;
|
|
1912
|
-
currentArchetype.set(entityId, componentType, component$1);
|
|
1913
|
-
}
|
|
1914
|
-
}
|
|
1915
|
-
/**
|
|
1916
|
-
* Apply dontFragment relation changes directly to World's storage
|
|
1917
|
-
* This is much more efficient than the removeEntity + addEntity approach
|
|
1918
|
-
*/
|
|
1919
|
-
applyDontFragmentChanges(entityId, changeset, removedComponents) {
|
|
1920
|
-
let entityRelations = this.dontFragmentRelations.get(entityId);
|
|
1921
|
-
for (const componentType of changeset.removes) if (isDontFragmentRelation(componentType)) {
|
|
1922
|
-
if (entityRelations) {
|
|
1923
|
-
const removedValue = entityRelations.get(componentType);
|
|
1924
|
-
if (removedValue !== void 0 || entityRelations.has(componentType)) {
|
|
1925
|
-
removedComponents.set(componentType, removedValue);
|
|
1926
|
-
entityRelations.delete(componentType);
|
|
1927
|
-
}
|
|
1928
|
-
}
|
|
1929
|
-
}
|
|
1930
|
-
for (const [componentType, component$1] of changeset.adds) if (isDontFragmentRelation(componentType)) {
|
|
1931
|
-
if (!entityRelations) {
|
|
1932
|
-
entityRelations = /* @__PURE__ */ new Map();
|
|
1933
|
-
this.dontFragmentRelations.set(entityId, entityRelations);
|
|
1934
|
-
}
|
|
1935
|
-
entityRelations.set(componentType, component$1);
|
|
1936
|
-
}
|
|
1937
|
-
if (entityRelations && entityRelations.size === 0) this.dontFragmentRelations.delete(entityId);
|
|
1888
|
+
applyChangeset({
|
|
1889
|
+
dontFragmentRelations: this.dontFragmentRelations,
|
|
1890
|
+
ensureArchetype: (ct) => this.ensureArchetype(ct)
|
|
1891
|
+
}, entityId, sourceArchetype, changeset, this.entityToArchetype);
|
|
1892
|
+
untrackEntityReference(this.entityReferences, entityId, componentType, targetEntityId);
|
|
1893
|
+
triggerLifecycleHooks(this.createHooksContext(), entityId, /* @__PURE__ */ new Map(), new Map([[componentType, removedComponent]]));
|
|
1938
1894
|
}
|
|
1939
|
-
/**
|
|
1940
|
-
* Update entity reference tracking based on changeset
|
|
1941
|
-
*/
|
|
1942
1895
|
updateEntityReferences(entityId, changeset) {
|
|
1943
1896
|
for (const componentType of changeset.removes) if (isEntityRelation(componentType)) {
|
|
1944
1897
|
const targetId = getTargetIdFromRelationId(componentType);
|
|
1945
|
-
this.
|
|
1946
|
-
} else if (componentType >= 1024) this.
|
|
1898
|
+
untrackEntityReference(this.entityReferences, entityId, componentType, targetId);
|
|
1899
|
+
} else if (componentType >= 1024) untrackEntityReference(this.entityReferences, entityId, componentType, componentType);
|
|
1947
1900
|
for (const [componentType] of changeset.adds) if (isEntityRelation(componentType)) {
|
|
1948
1901
|
const targetId = getTargetIdFromRelationId(componentType);
|
|
1949
|
-
this.
|
|
1950
|
-
} else if (componentType >= 1024) this.
|
|
1902
|
+
trackEntityReference(this.entityReferences, entityId, componentType, targetId);
|
|
1903
|
+
} else if (componentType >= 1024) trackEntityReference(this.entityReferences, entityId, componentType, componentType);
|
|
1951
1904
|
}
|
|
1952
|
-
/**
|
|
1953
|
-
* Get or create an archetype for the given component types
|
|
1954
|
-
* Filters out dontFragment relations from the archetype signature
|
|
1955
|
-
* @returns The archetype for the given component types (excluding dontFragment relations)
|
|
1956
|
-
*/
|
|
1957
1905
|
ensureArchetype(componentTypes) {
|
|
1958
|
-
const sortedTypes =
|
|
1906
|
+
const sortedTypes = filterRegularComponentTypes(componentTypes).sort((a, b) => a - b);
|
|
1959
1907
|
const hashKey = this.createArchetypeSignature(sortedTypes);
|
|
1960
1908
|
return getOrCreateWithSideEffect(this.archetypeBySignature, hashKey, () => this.createNewArchetype(sortedTypes));
|
|
1961
1909
|
}
|
|
1962
|
-
/**
|
|
1963
|
-
* Compare two arrays of component types for equality (order-independent)
|
|
1964
|
-
*/
|
|
1965
|
-
areComponentTypesEqual(types1, types2) {
|
|
1966
|
-
if (types1.length !== types2.length) return false;
|
|
1967
|
-
const sorted1 = [...types1].sort((a, b) => a - b);
|
|
1968
|
-
const sorted2 = [...types2].sort((a, b) => a - b);
|
|
1969
|
-
return sorted1.every((v, i) => v === sorted2[i]);
|
|
1970
|
-
}
|
|
1971
|
-
/**
|
|
1972
|
-
* Filter out dontFragment relations from component types, but keep wildcard markers
|
|
1973
|
-
*/
|
|
1974
|
-
filterRegularComponentTypes(componentTypes) {
|
|
1975
|
-
const regularTypes = [];
|
|
1976
|
-
for (const componentType of componentTypes) {
|
|
1977
|
-
if (isDontFragmentWildcard(componentType)) {
|
|
1978
|
-
regularTypes.push(componentType);
|
|
1979
|
-
continue;
|
|
1980
|
-
}
|
|
1981
|
-
if (isDontFragmentRelation(componentType)) continue;
|
|
1982
|
-
regularTypes.push(componentType);
|
|
1983
|
-
}
|
|
1984
|
-
return regularTypes;
|
|
1985
|
-
}
|
|
1986
|
-
/**
|
|
1987
|
-
* Create a new archetype and register it with all tracking structures
|
|
1988
|
-
*/
|
|
1989
1910
|
createNewArchetype(componentTypes) {
|
|
1990
1911
|
const newArchetype = new Archetype(componentTypes, this.dontFragmentRelations);
|
|
1991
1912
|
this.archetypes.push(newArchetype);
|
|
1992
|
-
this.registerArchetypeInComponentIndex(newArchetype, componentTypes);
|
|
1993
|
-
this.notifyQueriesOfNewArchetype(newArchetype);
|
|
1994
|
-
return newArchetype;
|
|
1995
|
-
}
|
|
1996
|
-
/**
|
|
1997
|
-
* Register archetype in the component-to-archetype index
|
|
1998
|
-
*/
|
|
1999
|
-
registerArchetypeInComponentIndex(archetype, componentTypes) {
|
|
2000
1913
|
for (const componentType of componentTypes) {
|
|
2001
1914
|
const archetypes = this.archetypesByComponent.get(componentType) || [];
|
|
2002
|
-
archetypes.push(
|
|
1915
|
+
archetypes.push(newArchetype);
|
|
2003
1916
|
this.archetypesByComponent.set(componentType, archetypes);
|
|
2004
1917
|
}
|
|
1918
|
+
for (const query of this.queries) query.checkNewArchetype(newArchetype);
|
|
1919
|
+
return newArchetype;
|
|
2005
1920
|
}
|
|
2006
|
-
/**
|
|
2007
|
-
* Notify all queries to check the new archetype
|
|
2008
|
-
*/
|
|
2009
|
-
notifyQueriesOfNewArchetype(archetype) {
|
|
2010
|
-
for (const query of this.queries) query.checkNewArchetype(archetype);
|
|
2011
|
-
}
|
|
2012
|
-
/**
|
|
2013
|
-
* Add a component reference to the reverse index when an entity is used as a component type
|
|
2014
|
-
* @param sourceEntityId The entity that has the component
|
|
2015
|
-
* @param componentType The component type (which may be an entity ID used as component type)
|
|
2016
|
-
* @param targetEntityId The entity being used as component type
|
|
2017
|
-
*/
|
|
2018
|
-
trackEntityReference(sourceEntityId, componentType, targetEntityId) {
|
|
2019
|
-
if (!this.entityReferences.has(targetEntityId)) this.entityReferences.set(targetEntityId, new MultiMap());
|
|
2020
|
-
this.entityReferences.get(targetEntityId).add(sourceEntityId, componentType);
|
|
2021
|
-
}
|
|
2022
|
-
/**
|
|
2023
|
-
* Remove a component reference from the reverse index
|
|
2024
|
-
* @param sourceEntityId The entity that has the component
|
|
2025
|
-
* @param componentType The component type
|
|
2026
|
-
* @param targetEntityId The entity being used as component type
|
|
2027
|
-
*/
|
|
2028
|
-
untrackEntityReference(sourceEntityId, componentType, targetEntityId) {
|
|
2029
|
-
const references = this.entityReferences.get(targetEntityId);
|
|
2030
|
-
if (references) {
|
|
2031
|
-
references.remove(sourceEntityId, componentType);
|
|
2032
|
-
if (references.keyCount === 0) this.entityReferences.delete(targetEntityId);
|
|
2033
|
-
}
|
|
2034
|
-
}
|
|
2035
|
-
/**
|
|
2036
|
-
* Get all component references where a target entity is used as a component type
|
|
2037
|
-
* @param targetEntityId The target entity
|
|
2038
|
-
* @returns A MultiMap of sourceEntityId to componentTypes that reference the target entity
|
|
2039
|
-
*/
|
|
2040
|
-
getEntityReferences(targetEntityId) {
|
|
2041
|
-
return this.entityReferences.get(targetEntityId) ?? new MultiMap();
|
|
2042
|
-
}
|
|
2043
|
-
/**
|
|
2044
|
-
* Check if an archetype's signature references a specific entity
|
|
2045
|
-
* (via entity-relation targeting the entity, or using entity as component type)
|
|
2046
|
-
*/
|
|
2047
1921
|
archetypeReferencesEntity(archetype, entityId) {
|
|
2048
|
-
|
|
2049
|
-
if (componentType === entityId) return true;
|
|
2050
|
-
if (isEntityRelation(componentType)) {
|
|
2051
|
-
if (getTargetIdFromRelationId(componentType) === entityId) return true;
|
|
2052
|
-
}
|
|
2053
|
-
}
|
|
2054
|
-
return false;
|
|
1922
|
+
return archetype.componentTypes.some((ct) => ct === entityId || isEntityRelation(ct) && getTargetIdFromRelationId(ct) === entityId);
|
|
2055
1923
|
}
|
|
2056
|
-
/**
|
|
2057
|
-
* Cleanup empty archetypes that reference a specific deleted entity
|
|
2058
|
-
* Only removes archetypes whose component types reference the entity
|
|
2059
|
-
*/
|
|
2060
1924
|
cleanupArchetypesReferencingEntity(entityId) {
|
|
2061
1925
|
for (let i = this.archetypes.length - 1; i >= 0; i--) {
|
|
2062
1926
|
const archetype = this.archetypes[i];
|
|
2063
|
-
if (archetype.getEntities().length === 0 && this.archetypeReferencesEntity(archetype, entityId))
|
|
2064
|
-
this.removeArchetypeFromList(archetype);
|
|
2065
|
-
this.removeArchetypeFromSignatureMap(archetype);
|
|
2066
|
-
this.removeArchetypeFromComponentIndex(archetype);
|
|
2067
|
-
this.removeArchetypeFromQueries(archetype);
|
|
2068
|
-
}
|
|
1927
|
+
if (archetype.getEntities().length === 0 && this.archetypeReferencesEntity(archetype, entityId)) this.removeArchetype(archetype);
|
|
2069
1928
|
}
|
|
2070
1929
|
}
|
|
2071
|
-
|
|
2072
|
-
* Remove archetype from the main archetypes list
|
|
2073
|
-
*/
|
|
2074
|
-
removeArchetypeFromList(archetype) {
|
|
1930
|
+
removeArchetype(archetype) {
|
|
2075
1931
|
const index = this.archetypes.indexOf(archetype);
|
|
2076
1932
|
if (index !== -1) this.archetypes.splice(index, 1);
|
|
2077
|
-
|
|
2078
|
-
/**
|
|
2079
|
-
* Remove archetype from the signature-to-archetype map
|
|
2080
|
-
*/
|
|
2081
|
-
removeArchetypeFromSignatureMap(archetype) {
|
|
2082
|
-
const hashKey = this.createArchetypeSignature(archetype.componentTypes);
|
|
2083
|
-
this.archetypeBySignature.delete(hashKey);
|
|
2084
|
-
}
|
|
2085
|
-
/**
|
|
2086
|
-
* Remove archetype from the component-to-archetypes index
|
|
2087
|
-
*/
|
|
2088
|
-
removeArchetypeFromComponentIndex(archetype) {
|
|
1933
|
+
this.archetypeBySignature.delete(this.createArchetypeSignature(archetype.componentTypes));
|
|
2089
1934
|
for (const componentType of archetype.componentTypes) {
|
|
2090
1935
|
const archetypes = this.archetypesByComponent.get(componentType);
|
|
2091
1936
|
if (archetypes) {
|
|
@@ -2096,43 +1941,8 @@ var World = class {
|
|
|
2096
1941
|
}
|
|
2097
1942
|
}
|
|
2098
1943
|
}
|
|
2099
|
-
}
|
|
2100
|
-
/**
|
|
2101
|
-
* Remove archetype from all queries
|
|
2102
|
-
*/
|
|
2103
|
-
removeArchetypeFromQueries(archetype) {
|
|
2104
1944
|
for (const query of this.queries) query.removeArchetype(archetype);
|
|
2105
1945
|
}
|
|
2106
|
-
/**
|
|
2107
|
-
* Execute component lifecycle hooks for added and removed components
|
|
2108
|
-
*/
|
|
2109
|
-
triggerLifecycleHooks(entityId, addedComponents, removedComponents) {
|
|
2110
|
-
for (const [componentType, component$1] of addedComponents) {
|
|
2111
|
-
const directHooks = this.hooks.get(componentType);
|
|
2112
|
-
if (directHooks) for (const lifecycleHook of directHooks) lifecycleHook.on_set?.(entityId, componentType, component$1);
|
|
2113
|
-
const componentId = getComponentIdFromRelationId(componentType);
|
|
2114
|
-
if (componentId !== void 0) {
|
|
2115
|
-
const wildcardRelationId = relation(componentId, "*");
|
|
2116
|
-
const wildcardHooks = this.hooks.get(wildcardRelationId);
|
|
2117
|
-
if (wildcardHooks) for (const lifecycleHook of wildcardHooks) lifecycleHook.on_set?.(entityId, componentType, component$1);
|
|
2118
|
-
}
|
|
2119
|
-
}
|
|
2120
|
-
for (const [componentType, component$1] of removedComponents) {
|
|
2121
|
-
const directHooks = this.hooks.get(componentType);
|
|
2122
|
-
if (directHooks) for (const lifecycleHook of directHooks) lifecycleHook.on_remove?.(entityId, componentType, component$1);
|
|
2123
|
-
const componentId = getComponentIdFromRelationId(componentType);
|
|
2124
|
-
if (componentId !== void 0) {
|
|
2125
|
-
const wildcardRelationId = relation(componentId, "*");
|
|
2126
|
-
const wildcardHooks = this.hooks.get(wildcardRelationId);
|
|
2127
|
-
if (wildcardHooks) for (const hook of wildcardHooks) hook.on_remove?.(entityId, componentType, component$1);
|
|
2128
|
-
}
|
|
2129
|
-
}
|
|
2130
|
-
}
|
|
2131
|
-
/**
|
|
2132
|
-
* Convert the world into a plain snapshot object.
|
|
2133
|
-
* This returns an in-memory structure and does not perform JSON stringification.
|
|
2134
|
-
* Component values are stored as-is (they may be non-JSON-serializable).
|
|
2135
|
-
*/
|
|
2136
1946
|
serialize() {
|
|
2137
1947
|
const entities = [];
|
|
2138
1948
|
for (const archetype of this.archetypes) {
|
|
@@ -2152,63 +1962,7 @@ var World = class {
|
|
|
2152
1962
|
};
|
|
2153
1963
|
}
|
|
2154
1964
|
};
|
|
2155
|
-
var EntityBuilder = class {
|
|
2156
|
-
world;
|
|
2157
|
-
components = [];
|
|
2158
|
-
constructor(world) {
|
|
2159
|
-
this.world = world;
|
|
2160
|
-
}
|
|
2161
|
-
with(componentId, value) {
|
|
2162
|
-
this.components.push({
|
|
2163
|
-
type: "component",
|
|
2164
|
-
id: componentId,
|
|
2165
|
-
value
|
|
2166
|
-
});
|
|
2167
|
-
return this;
|
|
2168
|
-
}
|
|
2169
|
-
withTag(componentId) {
|
|
2170
|
-
this.components.push({
|
|
2171
|
-
type: "component",
|
|
2172
|
-
id: componentId,
|
|
2173
|
-
value: void 0
|
|
2174
|
-
});
|
|
2175
|
-
return this;
|
|
2176
|
-
}
|
|
2177
|
-
withRelation(componentId, targetEntity, value) {
|
|
2178
|
-
this.components.push({
|
|
2179
|
-
type: "relation",
|
|
2180
|
-
componentId,
|
|
2181
|
-
targetId: targetEntity,
|
|
2182
|
-
value
|
|
2183
|
-
});
|
|
2184
|
-
return this;
|
|
2185
|
-
}
|
|
2186
|
-
withRelationTag(componentId, targetEntity) {
|
|
2187
|
-
this.components.push({
|
|
2188
|
-
type: "relation",
|
|
2189
|
-
componentId,
|
|
2190
|
-
targetId: targetEntity,
|
|
2191
|
-
value: void 0
|
|
2192
|
-
});
|
|
2193
|
-
return this;
|
|
2194
|
-
}
|
|
2195
|
-
/**
|
|
2196
|
-
* Create an entity and enqueue components to be applied. This method
|
|
2197
|
-
* does NOT call `world.sync()` automatically; callers must invoke
|
|
2198
|
-
* `world.sync()` to apply deferred commands.
|
|
2199
|
-
* (Previously auto-synced; now a breaking change — buildDeferred() removed.)
|
|
2200
|
-
*/
|
|
2201
|
-
build() {
|
|
2202
|
-
const entity = this.world.new();
|
|
2203
|
-
for (const def of this.components) if (def.type === "component") this.world.set(entity, def.id, def.value);
|
|
2204
|
-
else {
|
|
2205
|
-
const relationId = relation(def.componentId, def.targetId);
|
|
2206
|
-
this.world.set(entity, relationId, def.value);
|
|
2207
|
-
}
|
|
2208
|
-
return entity;
|
|
2209
|
-
}
|
|
2210
|
-
};
|
|
2211
1965
|
|
|
2212
1966
|
//#endregion
|
|
2213
|
-
export {
|
|
1967
|
+
export { getComponentIdByName as a, isWildcardRelationId as c, isEntityId as d, isRelationId as f, component as i, relation as l, Query as n, getComponentNameById as o, EntityBuilder as r, decodeRelationId as s, World as t, isComponentId as u };
|
|
2214
1968
|
//# sourceMappingURL=world.mjs.map
|