@codehz/ecs 0.3.8 → 0.3.10
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/index.d.mts +168 -151
- package/index.mjs +472 -179
- package/index.mjs.map +1 -1
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
//#region src/entity.ts
|
|
2
|
-
/**
|
|
3
|
-
* Constants for ID ranges
|
|
4
|
-
*/
|
|
5
|
-
const INVALID_COMPONENT_ID = 0;
|
|
6
2
|
const COMPONENT_ID_MAX = 1023;
|
|
7
3
|
const ENTITY_ID_START = 1024;
|
|
8
4
|
/**
|
|
@@ -10,23 +6,6 @@ const ENTITY_ID_START = 1024;
|
|
|
10
6
|
*/
|
|
11
7
|
const RELATION_SHIFT = 2 ** 42;
|
|
12
8
|
const WILDCARD_TARGET_ID = 0;
|
|
13
|
-
/**
|
|
14
|
-
* Create a component ID
|
|
15
|
-
* @param id Component identifier (1-1023)
|
|
16
|
-
* @see component
|
|
17
|
-
*/
|
|
18
|
-
function createComponentId(id) {
|
|
19
|
-
if (id < 1 || id > COMPONENT_ID_MAX) throw new Error(`Component ID must be between 1 and ${COMPONENT_ID_MAX}`);
|
|
20
|
-
return id;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Create an entity ID
|
|
24
|
-
* @param id Entity identifier (starting from 1024)
|
|
25
|
-
*/
|
|
26
|
-
function createEntityId(id) {
|
|
27
|
-
if (id < ENTITY_ID_START) throw new Error(`Entity ID must be ${ENTITY_ID_START} or greater`);
|
|
28
|
-
return id;
|
|
29
|
-
}
|
|
30
9
|
function relation(componentId, targetId) {
|
|
31
10
|
if (!isComponentId(componentId)) throw new Error("First argument must be a valid component ID");
|
|
32
11
|
let actualTargetId;
|
|
@@ -142,24 +121,6 @@ function getDetailedIdType(id) {
|
|
|
142
121
|
return { type: "invalid" };
|
|
143
122
|
}
|
|
144
123
|
/**
|
|
145
|
-
* Inspect an EntityId and return a human-readable string representation
|
|
146
|
-
* @param id The EntityId to inspect
|
|
147
|
-
* @returns A friendly string representation of the ID
|
|
148
|
-
*/
|
|
149
|
-
function inspectEntityId(id) {
|
|
150
|
-
if (id === INVALID_COMPONENT_ID) return "Invalid Component ID (0)";
|
|
151
|
-
if (isComponentId(id)) return `Component ID (${id})`;
|
|
152
|
-
if (isEntityId(id)) return `Entity ID (${id})`;
|
|
153
|
-
if (isRelationId(id)) try {
|
|
154
|
-
const decoded = decodeRelationId(id);
|
|
155
|
-
if (!isComponentId(decoded.componentId) || decoded.type !== "wildcard" && !isEntityId(decoded.targetId) && !isComponentId(decoded.targetId)) return `Invalid Relation ID (${id})`;
|
|
156
|
-
return `Relation ID: ${`Component ID (${decoded.componentId})`} -> ${decoded.type === "entity" ? `Entity ID (${decoded.targetId})` : decoded.type === "component" ? `Component ID (${decoded.targetId})` : "Wildcard (*)"}`;
|
|
157
|
-
} catch (error) {
|
|
158
|
-
return `Invalid Relation ID (${id})`;
|
|
159
|
-
}
|
|
160
|
-
return `Unknown ID (${id})`;
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
124
|
* Entity ID Manager for automatic allocation and freelist recycling
|
|
164
125
|
*/
|
|
165
126
|
var EntityIdManager = class {
|
|
@@ -254,20 +215,36 @@ var ComponentIdAllocator = class {
|
|
|
254
215
|
const globalComponentIdAllocator = new ComponentIdAllocator();
|
|
255
216
|
const ComponentNames = /* @__PURE__ */ new Map();
|
|
256
217
|
const ComponentIdForNames = /* @__PURE__ */ new Map();
|
|
218
|
+
const ComponentOptions = /* @__PURE__ */ new Map();
|
|
257
219
|
/**
|
|
258
220
|
* Allocate a new component ID from the global allocator.
|
|
259
|
-
*
|
|
260
|
-
* The name is only for serialization/debugging and does not affect base functionality.
|
|
261
|
-
* @param name Optional name for the component
|
|
221
|
+
* @param nameOrOptions Optional name for the component (for serialization/debugging) or options object
|
|
262
222
|
* @returns The allocated component ID
|
|
223
|
+
* @example
|
|
224
|
+
* // Just a name
|
|
225
|
+
* const Position = component<Position>("Position");
|
|
226
|
+
*
|
|
227
|
+
* // With options
|
|
228
|
+
* const ChildOf = component({ exclusive: true, cascadeDelete: true });
|
|
229
|
+
*
|
|
230
|
+
* // With name and options
|
|
231
|
+
* const ChildOf = component({ name: "ChildOf", exclusive: true });
|
|
263
232
|
*/
|
|
264
|
-
function component(
|
|
233
|
+
function component(nameOrOptions) {
|
|
265
234
|
const id = globalComponentIdAllocator.allocate();
|
|
235
|
+
let name;
|
|
236
|
+
let options;
|
|
237
|
+
if (typeof nameOrOptions === "string") name = nameOrOptions;
|
|
238
|
+
else if (typeof nameOrOptions === "object" && nameOrOptions !== null) {
|
|
239
|
+
options = nameOrOptions;
|
|
240
|
+
name = options.name;
|
|
241
|
+
}
|
|
266
242
|
if (name) {
|
|
267
243
|
if (ComponentIdForNames.has(name)) throw new Error(`Component name "${name}" is already registered`);
|
|
268
244
|
ComponentNames.set(id, name);
|
|
269
245
|
ComponentIdForNames.set(name, id);
|
|
270
246
|
}
|
|
247
|
+
if (options) ComponentOptions.set(id, options);
|
|
271
248
|
return id;
|
|
272
249
|
}
|
|
273
250
|
/**
|
|
@@ -285,6 +262,30 @@ function getComponentIdByName(name) {
|
|
|
285
262
|
function getComponentNameById(id) {
|
|
286
263
|
return ComponentNames.get(id);
|
|
287
264
|
}
|
|
265
|
+
/**
|
|
266
|
+
* Check if a component is marked as exclusive
|
|
267
|
+
* @param id The component ID
|
|
268
|
+
* @returns true if the component is exclusive, false otherwise
|
|
269
|
+
*/
|
|
270
|
+
function isExclusiveComponent(id) {
|
|
271
|
+
return ComponentOptions.get(id)?.exclusive ?? false;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Check if a component is marked as cascade delete
|
|
275
|
+
* @param id The component ID
|
|
276
|
+
* @returns true if the component is cascade delete, false otherwise
|
|
277
|
+
*/
|
|
278
|
+
function isCascadeDeleteComponent(id) {
|
|
279
|
+
return ComponentOptions.get(id)?.cascadeDelete ?? false;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Check if a component is marked as dontFragment
|
|
283
|
+
* @param id The component ID
|
|
284
|
+
* @returns true if the component is dontFragment, false otherwise
|
|
285
|
+
*/
|
|
286
|
+
function isDontFragmentComponent(id) {
|
|
287
|
+
return ComponentOptions.get(id)?.dontFragment ?? false;
|
|
288
|
+
}
|
|
288
289
|
|
|
289
290
|
//#endregion
|
|
290
291
|
//#region src/types.ts
|
|
@@ -358,6 +359,12 @@ var Archetype = class {
|
|
|
358
359
|
*/
|
|
359
360
|
entityToIndex = /* @__PURE__ */ new Map();
|
|
360
361
|
/**
|
|
362
|
+
* Reference to dontFragment relations storage from World
|
|
363
|
+
* This allows entities with different relation targets to share the same archetype
|
|
364
|
+
* Stored in World to avoid migration overhead when entities change archetypes
|
|
365
|
+
*/
|
|
366
|
+
dontFragmentRelations;
|
|
367
|
+
/**
|
|
361
368
|
* Cache for pre-computed component data sources to avoid repeated calculations
|
|
362
369
|
* For regular components: data array
|
|
363
370
|
* For wildcards: matching relation types array
|
|
@@ -366,9 +373,11 @@ var Archetype = class {
|
|
|
366
373
|
/**
|
|
367
374
|
* Create a new archetype with the specified component types
|
|
368
375
|
* @param componentTypes The component types that define this archetype
|
|
376
|
+
* @param dontFragmentRelations Reference to the World's dontFragmentRelations storage
|
|
369
377
|
*/
|
|
370
|
-
constructor(componentTypes) {
|
|
378
|
+
constructor(componentTypes, dontFragmentRelations) {
|
|
371
379
|
this.componentTypes = [...componentTypes].sort((a, b) => a - b);
|
|
380
|
+
this.dontFragmentRelations = dontFragmentRelations;
|
|
372
381
|
for (const componentType of this.componentTypes) this.componentData.set(componentType, []);
|
|
373
382
|
}
|
|
374
383
|
/**
|
|
@@ -389,7 +398,7 @@ var Archetype = class {
|
|
|
389
398
|
/**
|
|
390
399
|
* Add an entity to this archetype with initial component data
|
|
391
400
|
* @param entityId The entity to add
|
|
392
|
-
* @param componentData Map of component type to component data
|
|
401
|
+
* @param componentData Map of component type to component data (includes both regular and dontFragment components)
|
|
393
402
|
*/
|
|
394
403
|
addEntity(entityId, componentData) {
|
|
395
404
|
if (this.entityToIndex.has(entityId)) throw new Error(`Entity ${entityId} is already in this archetype`);
|
|
@@ -400,11 +409,18 @@ var Archetype = class {
|
|
|
400
409
|
const data = componentData.get(componentType);
|
|
401
410
|
this.getComponentData(componentType).push(data === void 0 ? MISSING_COMPONENT : data);
|
|
402
411
|
}
|
|
412
|
+
const dontFragmentData = /* @__PURE__ */ new Map();
|
|
413
|
+
for (const [componentType, data] of componentData) {
|
|
414
|
+
if (this.componentTypes.includes(componentType)) continue;
|
|
415
|
+
const detailedType = getDetailedIdType(componentType);
|
|
416
|
+
if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isDontFragmentComponent(detailedType.componentId)) dontFragmentData.set(componentType, data);
|
|
417
|
+
}
|
|
418
|
+
if (dontFragmentData.size > 0) this.dontFragmentRelations.set(entityId, dontFragmentData);
|
|
403
419
|
}
|
|
404
420
|
/**
|
|
405
421
|
* Get all component data for a specific entity
|
|
406
422
|
* @param entityId The entity to get data for
|
|
407
|
-
* @returns Map of component type to component data
|
|
423
|
+
* @returns Map of component type to component data (includes both regular and dontFragment components)
|
|
408
424
|
*/
|
|
409
425
|
getEntity(entityId) {
|
|
410
426
|
const index = this.entityToIndex.get(entityId);
|
|
@@ -414,11 +430,13 @@ var Archetype = class {
|
|
|
414
430
|
const data = this.getComponentData(componentType)[index];
|
|
415
431
|
entityData.set(componentType, data === MISSING_COMPONENT ? void 0 : data);
|
|
416
432
|
}
|
|
433
|
+
const dontFragmentData = this.dontFragmentRelations.get(entityId);
|
|
434
|
+
if (dontFragmentData) for (const [componentType, data] of dontFragmentData) entityData.set(componentType, data);
|
|
417
435
|
return entityData;
|
|
418
436
|
}
|
|
419
437
|
/**
|
|
420
438
|
* Dump all entities and their component data in this archetype
|
|
421
|
-
* @returns Array of objects with entity and component data
|
|
439
|
+
* @returns Array of objects with entity and component data (includes both regular and dontFragment components)
|
|
422
440
|
*/
|
|
423
441
|
dump() {
|
|
424
442
|
const result = [];
|
|
@@ -429,6 +447,8 @@ var Archetype = class {
|
|
|
429
447
|
const data = this.getComponentData(componentType)[i];
|
|
430
448
|
components.set(componentType, data === MISSING_COMPONENT ? void 0 : data);
|
|
431
449
|
}
|
|
450
|
+
const dontFragmentData = this.dontFragmentRelations.get(entity);
|
|
451
|
+
if (dontFragmentData) for (const [componentType, data] of dontFragmentData) components.set(componentType, data);
|
|
432
452
|
result.push({
|
|
433
453
|
entity,
|
|
434
454
|
components
|
|
@@ -439,7 +459,7 @@ var Archetype = class {
|
|
|
439
459
|
/**
|
|
440
460
|
* Remove an entity from this archetype
|
|
441
461
|
* @param entityId The entity to remove
|
|
442
|
-
* @returns The component data of the removed entity
|
|
462
|
+
* @returns The component data of the removed entity (includes both regular and dontFragment components)
|
|
443
463
|
*/
|
|
444
464
|
removeEntity(entityId) {
|
|
445
465
|
const index = this.entityToIndex.get(entityId);
|
|
@@ -449,6 +469,11 @@ var Archetype = class {
|
|
|
449
469
|
const dataArray = this.getComponentData(componentType);
|
|
450
470
|
removedData.set(componentType, dataArray[index]);
|
|
451
471
|
}
|
|
472
|
+
const dontFragmentData = this.dontFragmentRelations.get(entityId);
|
|
473
|
+
if (dontFragmentData) {
|
|
474
|
+
for (const [componentType, data] of dontFragmentData) removedData.set(componentType, data);
|
|
475
|
+
this.dontFragmentRelations.delete(entityId);
|
|
476
|
+
}
|
|
452
477
|
this.entityToIndex.delete(entityId);
|
|
453
478
|
const lastIndex = this.entities.length - 1;
|
|
454
479
|
if (index !== lastIndex) {
|
|
@@ -487,10 +512,20 @@ var Archetype = class {
|
|
|
487
512
|
}
|
|
488
513
|
}
|
|
489
514
|
}
|
|
515
|
+
const dontFragmentData = this.dontFragmentRelations.get(entityId);
|
|
516
|
+
if (dontFragmentData) for (const [relType, data] of dontFragmentData) {
|
|
517
|
+
const relDetailed = getDetailedIdType(relType);
|
|
518
|
+
if ((relDetailed.type === "entity-relation" || relDetailed.type === "component-relation") && relDetailed.componentId === componentId) relations.push([relDetailed.targetId, data]);
|
|
519
|
+
}
|
|
490
520
|
return relations;
|
|
491
521
|
} else {
|
|
492
|
-
|
|
493
|
-
|
|
522
|
+
if (this.componentTypes.includes(componentType)) {
|
|
523
|
+
const data = this.getComponentData(componentType)[index];
|
|
524
|
+
return data === MISSING_COMPONENT ? void 0 : data;
|
|
525
|
+
}
|
|
526
|
+
const dontFragmentData = this.dontFragmentRelations.get(entityId);
|
|
527
|
+
if (dontFragmentData && dontFragmentData.has(componentType)) return dontFragmentData.get(componentType);
|
|
528
|
+
throw new Error(`Component type ${componentType} not found for entity ${entityId}`);
|
|
494
529
|
}
|
|
495
530
|
}
|
|
496
531
|
/**
|
|
@@ -500,11 +535,24 @@ var Archetype = class {
|
|
|
500
535
|
* @param data The component data
|
|
501
536
|
*/
|
|
502
537
|
set(entityId, componentType, data) {
|
|
503
|
-
if (!this.componentData.has(componentType)) throw new Error(`Component type ${componentType} is not in this archetype`);
|
|
504
538
|
const index = this.entityToIndex.get(entityId);
|
|
505
539
|
if (index === void 0) throw new Error(`Entity ${entityId} is not in this archetype`);
|
|
506
|
-
|
|
507
|
-
|
|
540
|
+
if (this.componentData.has(componentType)) {
|
|
541
|
+
const dataArray = this.getComponentData(componentType);
|
|
542
|
+
dataArray[index] = data;
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
const detailedType = getDetailedIdType(componentType);
|
|
546
|
+
if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isDontFragmentComponent(detailedType.componentId)) {
|
|
547
|
+
let dontFragmentData = this.dontFragmentRelations.get(entityId);
|
|
548
|
+
if (!dontFragmentData) {
|
|
549
|
+
dontFragmentData = /* @__PURE__ */ new Map();
|
|
550
|
+
this.dontFragmentRelations.set(entityId, dontFragmentData);
|
|
551
|
+
}
|
|
552
|
+
dontFragmentData.set(componentType, data);
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
throw new Error(`Component type ${componentType} is not in this archetype`);
|
|
508
556
|
}
|
|
509
557
|
/**
|
|
510
558
|
* Get all entities in this archetype
|
|
@@ -539,59 +587,92 @@ var Archetype = class {
|
|
|
539
587
|
* Helper: compute or return cached data sources for provided componentTypes
|
|
540
588
|
*/
|
|
541
589
|
getCachedComponentDataSources(componentTypes) {
|
|
542
|
-
const cacheKey =
|
|
543
|
-
return getOrComputeCache(this.componentDataSourcesCache, cacheKey, () =>
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
590
|
+
const cacheKey = this.buildCacheKey(componentTypes);
|
|
591
|
+
return getOrComputeCache(this.componentDataSourcesCache, cacheKey, () => componentTypes.map((compType) => this.getComponentDataSource(compType)));
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Build cache key for component types
|
|
595
|
+
*/
|
|
596
|
+
buildCacheKey(componentTypes) {
|
|
597
|
+
return componentTypes.map((id) => isOptionalEntityId(id) ? `opt(${id.optional})` : `${id}`).join(",");
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Get data source for a single component type
|
|
601
|
+
*/
|
|
602
|
+
getComponentDataSource(compType) {
|
|
603
|
+
const optional = isOptionalEntityId(compType);
|
|
604
|
+
const actualType = optional ? compType.optional : compType;
|
|
605
|
+
const detailedType = getDetailedIdType(actualType);
|
|
606
|
+
if (detailedType.type === "wildcard-relation") return this.getWildcardRelationDataSource(detailedType.componentId, optional);
|
|
607
|
+
else return optional ? this.getOptionalComponentData(actualType) : this.getComponentData(actualType);
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Get data source for wildcard relations
|
|
611
|
+
*/
|
|
612
|
+
getWildcardRelationDataSource(componentId, optional) {
|
|
613
|
+
const matchingRelations = this.componentTypes.filter((ct) => {
|
|
614
|
+
const detailedCt = getDetailedIdType(ct);
|
|
615
|
+
return (detailedCt.type === "entity-relation" || detailedCt.type === "component-relation") && detailedCt.componentId === componentId;
|
|
561
616
|
});
|
|
617
|
+
return optional ? matchingRelations.length > 0 ? matchingRelations : void 0 : matchingRelations;
|
|
562
618
|
}
|
|
563
619
|
/**
|
|
564
620
|
* Helper: build component tuples for a specific entity index using precomputed data sources
|
|
565
621
|
*/
|
|
566
|
-
buildComponentsForIndex(componentTypes, componentDataSources, entityIndex) {
|
|
622
|
+
buildComponentsForIndex(componentTypes, componentDataSources, entityIndex, entityId) {
|
|
567
623
|
return componentDataSources.map((dataSource, i) => {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
if (isOptionalEntityId(compType)) {
|
|
571
|
-
compType = compType.optional;
|
|
572
|
-
optional = true;
|
|
573
|
-
}
|
|
574
|
-
if (getIdType(compType) === "wildcard-relation") {
|
|
575
|
-
if (dataSource === void 0) if (optional) return;
|
|
576
|
-
else throw new Error(`No matching relations found for mandatory wildcard relation component type`);
|
|
577
|
-
const matchingRelations = dataSource;
|
|
578
|
-
const relations = [];
|
|
579
|
-
for (const relType of matchingRelations) {
|
|
580
|
-
const data = this.getComponentData(relType)[entityIndex];
|
|
581
|
-
const decodedRel = decodeRelationId(relType);
|
|
582
|
-
relations.push([decodedRel.targetId, data === MISSING_COMPONENT ? void 0 : data]);
|
|
583
|
-
}
|
|
584
|
-
return optional ? { value: relations } : relations;
|
|
585
|
-
} else {
|
|
586
|
-
if (dataSource === void 0) if (optional) return;
|
|
587
|
-
else throw new Error(`No matching relations found for mandatory wildcard relation component type`);
|
|
588
|
-
const data = dataSource[entityIndex];
|
|
589
|
-
const result = data === MISSING_COMPONENT ? void 0 : data;
|
|
590
|
-
return optional ? { value: result } : result;
|
|
591
|
-
}
|
|
624
|
+
const compType = componentTypes[i];
|
|
625
|
+
return this.buildSingleComponent(compType, dataSource, entityIndex, entityId);
|
|
592
626
|
});
|
|
593
627
|
}
|
|
594
628
|
/**
|
|
629
|
+
* Build a single component value from its data source
|
|
630
|
+
*/
|
|
631
|
+
buildSingleComponent(compType, dataSource, entityIndex, entityId) {
|
|
632
|
+
const optional = isOptionalEntityId(compType);
|
|
633
|
+
const actualType = optional ? compType.optional : compType;
|
|
634
|
+
if (getIdType(actualType) === "wildcard-relation") return this.buildWildcardRelationValue(actualType, dataSource, entityIndex, entityId, optional);
|
|
635
|
+
else return this.buildRegularComponentValue(dataSource, entityIndex, optional);
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Build wildcard relation value from matching relations
|
|
639
|
+
*/
|
|
640
|
+
buildWildcardRelationValue(wildcardRelationType, dataSource, entityIndex, entityId, optional) {
|
|
641
|
+
const matchingRelations = dataSource || [];
|
|
642
|
+
const relations = [];
|
|
643
|
+
for (const relType of matchingRelations) {
|
|
644
|
+
const data = this.getComponentData(relType)[entityIndex];
|
|
645
|
+
const decodedRel = decodeRelationId(relType);
|
|
646
|
+
relations.push([decodedRel.targetId, data === MISSING_COMPONENT ? void 0 : data]);
|
|
647
|
+
}
|
|
648
|
+
const targetComponentId = decodeRelationId(wildcardRelationType).componentId;
|
|
649
|
+
const dontFragmentData = this.dontFragmentRelations.get(entityId);
|
|
650
|
+
if (dontFragmentData) for (const [relType, data] of dontFragmentData) {
|
|
651
|
+
const relDetailed = getDetailedIdType(relType);
|
|
652
|
+
if ((relDetailed.type === "entity-relation" || relDetailed.type === "component-relation") && relDetailed.componentId === targetComponentId) relations.push([relDetailed.targetId, data]);
|
|
653
|
+
}
|
|
654
|
+
if (relations.length === 0) {
|
|
655
|
+
if (!optional) {
|
|
656
|
+
const wildcardDecoded = decodeRelationId(wildcardRelationType);
|
|
657
|
+
throw new Error(`No matching relations found for mandatory wildcard relation component ${wildcardDecoded.componentId} on entity ${entityId}`);
|
|
658
|
+
}
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
return optional ? { value: relations } : relations;
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Build regular component value from data source
|
|
665
|
+
*/
|
|
666
|
+
buildRegularComponentValue(dataSource, entityIndex, optional) {
|
|
667
|
+
if (dataSource === void 0) {
|
|
668
|
+
if (optional) return;
|
|
669
|
+
throw new Error(`Component data not found for mandatory component type`);
|
|
670
|
+
}
|
|
671
|
+
const data = dataSource[entityIndex];
|
|
672
|
+
const result = data === MISSING_COMPONENT ? void 0 : data;
|
|
673
|
+
return optional ? { value: result } : result;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
595
676
|
* Get entities with their component data for specified component types
|
|
596
677
|
* Optimized for bulk component access with pre-computed indices
|
|
597
678
|
* @param componentTypes Array of component types to retrieve
|
|
@@ -614,7 +695,10 @@ var Archetype = class {
|
|
|
614
695
|
*/
|
|
615
696
|
*iterateWithComponents(componentTypes) {
|
|
616
697
|
const componentDataSources = this.getCachedComponentDataSources(componentTypes);
|
|
617
|
-
for (let entityIndex = 0; entityIndex < this.entities.length; entityIndex++)
|
|
698
|
+
for (let entityIndex = 0; entityIndex < this.entities.length; entityIndex++) {
|
|
699
|
+
const entity = this.entities[entityIndex];
|
|
700
|
+
yield [entity, ...this.buildComponentsForIndex(componentTypes, componentDataSources, entityIndex, entity)];
|
|
701
|
+
}
|
|
618
702
|
}
|
|
619
703
|
/**
|
|
620
704
|
* Iterate over entities with their component data for specified component types
|
|
@@ -626,7 +710,7 @@ var Archetype = class {
|
|
|
626
710
|
const componentDataSources = this.getCachedComponentDataSources(componentTypes);
|
|
627
711
|
for (let entityIndex = 0; entityIndex < this.entities.length; entityIndex++) {
|
|
628
712
|
const entity = this.entities[entityIndex];
|
|
629
|
-
callback(entity, ...this.buildComponentsForIndex(componentTypes, componentDataSources, entityIndex));
|
|
713
|
+
callback(entity, ...this.buildComponentsForIndex(componentTypes, componentDataSources, entityIndex, entity));
|
|
630
714
|
}
|
|
631
715
|
}
|
|
632
716
|
/**
|
|
@@ -643,6 +727,26 @@ var Archetype = class {
|
|
|
643
727
|
callback(this.entities[i], components);
|
|
644
728
|
}
|
|
645
729
|
}
|
|
730
|
+
/**
|
|
731
|
+
* Check if any entity in this archetype has a relation matching the given component ID
|
|
732
|
+
* This includes both regular relations in componentTypes and dontFragment relations
|
|
733
|
+
* @param componentId The component ID to match
|
|
734
|
+
* @returns true if any entity has a matching relation
|
|
735
|
+
*/
|
|
736
|
+
hasRelationWithComponentId(componentId) {
|
|
737
|
+
for (const componentType of this.componentTypes) {
|
|
738
|
+
const detailedType = getDetailedIdType(componentType);
|
|
739
|
+
if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && detailedType.componentId === componentId) return true;
|
|
740
|
+
}
|
|
741
|
+
for (const entityId of this.entities) {
|
|
742
|
+
const entityDontFragmentRelations = this.dontFragmentRelations.get(entityId);
|
|
743
|
+
if (entityDontFragmentRelations) for (const relationType of entityDontFragmentRelations.keys()) {
|
|
744
|
+
const detailedType = getDetailedIdType(relationType);
|
|
745
|
+
if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && detailedType.componentId === componentId) return true;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
646
750
|
};
|
|
647
751
|
|
|
648
752
|
//#endregion
|
|
@@ -926,12 +1030,31 @@ var Query = class {
|
|
|
926
1030
|
world._registerQuery(this);
|
|
927
1031
|
}
|
|
928
1032
|
/**
|
|
1033
|
+
* Check if query is disposed and throw error if so
|
|
1034
|
+
*/
|
|
1035
|
+
ensureNotDisposed() {
|
|
1036
|
+
if (this.isDisposed) throw new Error("Query has been disposed");
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
929
1039
|
* Get all entities matching the query
|
|
930
1040
|
*/
|
|
931
1041
|
getEntities() {
|
|
932
|
-
|
|
1042
|
+
this.ensureNotDisposed();
|
|
933
1043
|
const result = [];
|
|
934
|
-
|
|
1044
|
+
if (this.componentTypes.some((ct) => {
|
|
1045
|
+
return getDetailedIdType(ct).type === "wildcard-relation";
|
|
1046
|
+
})) for (const archetype of this.cachedArchetypes) for (const entity of archetype.getEntities()) {
|
|
1047
|
+
let hasAllRelations = true;
|
|
1048
|
+
for (const componentType of this.componentTypes) if (getDetailedIdType(componentType).type === "wildcard-relation") {
|
|
1049
|
+
const relations = archetype.get(entity, componentType);
|
|
1050
|
+
if (!relations || relations.length === 0) {
|
|
1051
|
+
hasAllRelations = false;
|
|
1052
|
+
break;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
if (hasAllRelations) result.push(entity);
|
|
1056
|
+
}
|
|
1057
|
+
else for (const archetype of this.cachedArchetypes) result.push(...archetype.getEntities());
|
|
935
1058
|
return result;
|
|
936
1059
|
}
|
|
937
1060
|
/**
|
|
@@ -940,7 +1063,7 @@ var Query = class {
|
|
|
940
1063
|
* @returns Array of objects with entity and component data
|
|
941
1064
|
*/
|
|
942
1065
|
getEntitiesWithComponents(componentTypes) {
|
|
943
|
-
|
|
1066
|
+
this.ensureNotDisposed();
|
|
944
1067
|
const result = [];
|
|
945
1068
|
for (const archetype of this.cachedArchetypes) {
|
|
946
1069
|
const entitiesWithData = archetype.getEntitiesWithComponents(componentTypes);
|
|
@@ -954,7 +1077,7 @@ var Query = class {
|
|
|
954
1077
|
* @param callback Function called for each entity with its components
|
|
955
1078
|
*/
|
|
956
1079
|
forEach(componentTypes, callback) {
|
|
957
|
-
|
|
1080
|
+
this.ensureNotDisposed();
|
|
958
1081
|
for (const archetype of this.cachedArchetypes) archetype.forEachWithComponents(componentTypes, callback);
|
|
959
1082
|
}
|
|
960
1083
|
/**
|
|
@@ -962,7 +1085,7 @@ var Query = class {
|
|
|
962
1085
|
* @param componentTypes Array of component types to retrieve
|
|
963
1086
|
*/
|
|
964
1087
|
*iterate(componentTypes) {
|
|
965
|
-
|
|
1088
|
+
this.ensureNotDisposed();
|
|
966
1089
|
for (const archetype of this.cachedArchetypes) yield* archetype.iterateWithComponents(componentTypes);
|
|
967
1090
|
}
|
|
968
1091
|
/**
|
|
@@ -971,7 +1094,7 @@ var Query = class {
|
|
|
971
1094
|
* @returns Array of component data for all matching entities
|
|
972
1095
|
*/
|
|
973
1096
|
getComponentData(componentType) {
|
|
974
|
-
|
|
1097
|
+
this.ensureNotDisposed();
|
|
975
1098
|
const result = [];
|
|
976
1099
|
for (const archetype of this.cachedArchetypes) result.push(...archetype.getComponentData(componentType));
|
|
977
1100
|
return result;
|
|
@@ -1119,6 +1242,8 @@ var World = class {
|
|
|
1119
1242
|
archetypesByComponent = /* @__PURE__ */ new Map();
|
|
1120
1243
|
/** Tracks which entities reference each entity as a component type */
|
|
1121
1244
|
entityReferences = /* @__PURE__ */ new Map();
|
|
1245
|
+
/** Storage for dontFragment relations - maps entity ID to a map of relation type to component data */
|
|
1246
|
+
dontFragmentRelations = /* @__PURE__ */ new Map();
|
|
1122
1247
|
/** Array of all active queries for archetype change notifications */
|
|
1123
1248
|
queries = [];
|
|
1124
1249
|
/** Cache for queries keyed by component types and filter signatures */
|
|
@@ -1129,10 +1254,6 @@ var World = class {
|
|
|
1129
1254
|
commandBuffer = new CommandBuffer((entityId, commands) => this.executeEntityCommands(entityId, commands));
|
|
1130
1255
|
/** Stores lifecycle hooks for component and relation events */
|
|
1131
1256
|
hooks = /* @__PURE__ */ new Map();
|
|
1132
|
-
/** Set of component IDs marked as exclusive relations */
|
|
1133
|
-
exclusiveComponents = /* @__PURE__ */ new Set();
|
|
1134
|
-
/** Set of component IDs that will cascade delete when the relation target is deleted */
|
|
1135
|
-
cascadeDeleteComponents = /* @__PURE__ */ new Set();
|
|
1136
1257
|
/**
|
|
1137
1258
|
* Create a new World.
|
|
1138
1259
|
* If an optional snapshot object is provided (previously produced by `world.serialize()`),
|
|
@@ -1214,7 +1335,7 @@ var World = class {
|
|
|
1214
1335
|
const sourceArchetype = this.entityToArchetype.get(sourceEntityId);
|
|
1215
1336
|
if (!sourceArchetype) continue;
|
|
1216
1337
|
const detailedType = getDetailedIdType(componentType);
|
|
1217
|
-
if (detailedType.type === "entity-relation" &&
|
|
1338
|
+
if (detailedType.type === "entity-relation" && isCascadeDeleteComponent(detailedType.componentId)) {
|
|
1218
1339
|
if (!visited.has(sourceEntityId)) queue.push(sourceEntityId);
|
|
1219
1340
|
continue;
|
|
1220
1341
|
}
|
|
@@ -1271,13 +1392,20 @@ var World = class {
|
|
|
1271
1392
|
*/
|
|
1272
1393
|
has(entityId, componentType) {
|
|
1273
1394
|
const archetype = this.entityToArchetype.get(entityId);
|
|
1274
|
-
|
|
1395
|
+
if (!archetype) return false;
|
|
1396
|
+
if (archetype.componentTypes.includes(componentType)) return true;
|
|
1397
|
+
const detailedType = getDetailedIdType(componentType);
|
|
1398
|
+
if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isDontFragmentComponent(detailedType.componentId)) return this.dontFragmentRelations.get(entityId)?.has(componentType) ?? false;
|
|
1399
|
+
return false;
|
|
1275
1400
|
}
|
|
1276
1401
|
get(entityId, componentType) {
|
|
1277
1402
|
const archetype = this.entityToArchetype.get(entityId);
|
|
1278
1403
|
if (!archetype) throw new Error(`Entity ${entityId} does not exist`);
|
|
1279
|
-
|
|
1280
|
-
|
|
1404
|
+
const detailedType = getDetailedIdType(componentType);
|
|
1405
|
+
if (detailedType.type !== "wildcard-relation") {
|
|
1406
|
+
const inArchetype = archetype.componentTypes.includes(componentType);
|
|
1407
|
+
const isDontFragment = (detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isDontFragmentComponent(detailedType.componentId);
|
|
1408
|
+
if (!(inArchetype || isDontFragment && this.dontFragmentRelations.get(entityId)?.has(componentType))) throw new Error(`Entity ${entityId} does not have component ${componentType}. Use has() to check component existence before calling get().`);
|
|
1281
1409
|
}
|
|
1282
1410
|
return archetype.get(entityId, componentType);
|
|
1283
1411
|
}
|
|
@@ -1315,19 +1443,19 @@ var World = class {
|
|
|
1315
1443
|
}
|
|
1316
1444
|
/**
|
|
1317
1445
|
* Mark a component as exclusive relation
|
|
1318
|
-
*
|
|
1446
|
+
* @deprecated This method has been removed. Use component options instead: component({ exclusive: true })
|
|
1447
|
+
* @throws Always throws an error directing to the new API
|
|
1319
1448
|
*/
|
|
1320
1449
|
setExclusive(componentId) {
|
|
1321
|
-
|
|
1450
|
+
throw new Error("setExclusive has been removed. Use component options instead: component({ exclusive: true })");
|
|
1322
1451
|
}
|
|
1323
1452
|
/**
|
|
1324
1453
|
* Mark a component as cascade-delete relation
|
|
1325
|
-
*
|
|
1326
|
-
*
|
|
1327
|
-
* Only applicable to entity-relation components
|
|
1454
|
+
* @deprecated This method has been removed. Use component options instead: component({ cascadeDelete: true })
|
|
1455
|
+
* @throws Always throws an error directing to the new API
|
|
1328
1456
|
*/
|
|
1329
1457
|
setCascadeDelete(componentId) {
|
|
1330
|
-
|
|
1458
|
+
throw new Error("setCascadeDelete has been removed. Use component options instead: component({ cascadeDelete: true })");
|
|
1331
1459
|
}
|
|
1332
1460
|
/**
|
|
1333
1461
|
* Update the world (run all systems in dependency order)
|
|
@@ -1429,10 +1557,11 @@ var World = class {
|
|
|
1429
1557
|
matchingArchetypes = Array.from(intersection);
|
|
1430
1558
|
}
|
|
1431
1559
|
} else matchingArchetypes = [...this.archetypes];
|
|
1432
|
-
for (const wildcard of wildcardRelations)
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1560
|
+
for (const wildcard of wildcardRelations) if (isDontFragmentComponent(wildcard.componentId)) {
|
|
1561
|
+
const archetypesWithMarker = this.archetypesByComponent.get(wildcard.relationId) || [];
|
|
1562
|
+
if (matchingArchetypes.length === 0) matchingArchetypes = archetypesWithMarker;
|
|
1563
|
+
else matchingArchetypes = matchingArchetypes.filter((archetype) => archetypesWithMarker.includes(archetype));
|
|
1564
|
+
} else matchingArchetypes = matchingArchetypes.filter((archetype) => archetype.hasRelationWithComponentId(wildcard.componentId));
|
|
1436
1565
|
return matchingArchetypes;
|
|
1437
1566
|
}
|
|
1438
1567
|
query(componentTypes, includeComponents) {
|
|
@@ -1462,76 +1591,216 @@ var World = class {
|
|
|
1462
1591
|
}
|
|
1463
1592
|
const currentArchetype = this.entityToArchetype.get(entityId);
|
|
1464
1593
|
if (!currentArchetype) return changeset;
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1594
|
+
this.processCommands(entityId, currentArchetype, commands, changeset);
|
|
1595
|
+
const removedComponents = this.applyChangeset(entityId, currentArchetype, changeset);
|
|
1596
|
+
this.updateEntityReferences(entityId, changeset);
|
|
1597
|
+
this.triggerLifecycleHooks(entityId, changeset.adds, removedComponents);
|
|
1598
|
+
return changeset;
|
|
1599
|
+
}
|
|
1600
|
+
/**
|
|
1601
|
+
* Process commands and populate the changeset
|
|
1602
|
+
*/
|
|
1603
|
+
processCommands(entityId, currentArchetype, commands, changeset) {
|
|
1604
|
+
for (const command of commands) if (command.type === "set" && command.componentType) this.processSetCommand(entityId, currentArchetype, command.componentType, command.component, changeset);
|
|
1605
|
+
else if (command.type === "delete" && command.componentType) this.processDeleteCommand(entityId, currentArchetype, command.componentType, changeset);
|
|
1606
|
+
}
|
|
1607
|
+
/**
|
|
1608
|
+
* Process a set command, handling exclusive relations
|
|
1609
|
+
*/
|
|
1610
|
+
processSetCommand(entityId, currentArchetype, componentType, component$1, changeset) {
|
|
1611
|
+
const detailedType = getDetailedIdType(componentType);
|
|
1612
|
+
if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isExclusiveComponent(detailedType.componentId)) this.removeExclusiveRelations(entityId, currentArchetype, detailedType.componentId, changeset);
|
|
1613
|
+
if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isDontFragmentComponent(detailedType.componentId)) {
|
|
1614
|
+
const wildcardMarker = relation(detailedType.componentId, "*");
|
|
1615
|
+
if (!currentArchetype.componentTypes.includes(wildcardMarker)) changeset.set(wildcardMarker, void 0);
|
|
1616
|
+
}
|
|
1617
|
+
changeset.set(componentType, component$1);
|
|
1618
|
+
}
|
|
1619
|
+
/**
|
|
1620
|
+
* Remove all relations with the same base component (for exclusive relations)
|
|
1621
|
+
*/
|
|
1622
|
+
removeExclusiveRelations(entityId, currentArchetype, baseComponentId, changeset) {
|
|
1623
|
+
for (const componentType of currentArchetype.componentTypes) if (this.isRelationWithComponent(componentType, baseComponentId)) changeset.delete(componentType);
|
|
1624
|
+
const entityData = currentArchetype.getEntity(entityId);
|
|
1625
|
+
if (entityData) for (const [componentType] of entityData) {
|
|
1626
|
+
if (currentArchetype.componentTypes.includes(componentType)) continue;
|
|
1627
|
+
if (this.isRelationWithComponent(componentType, baseComponentId)) changeset.delete(componentType);
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
/**
|
|
1631
|
+
* Check if a component type is a relation with the given base component
|
|
1632
|
+
*/
|
|
1633
|
+
isRelationWithComponent(componentType, baseComponentId) {
|
|
1634
|
+
const detailedType = getDetailedIdType(componentType);
|
|
1635
|
+
return (detailedType.type === "entity-relation" || detailedType.type === "component-relation") && detailedType.componentId === baseComponentId;
|
|
1636
|
+
}
|
|
1637
|
+
/**
|
|
1638
|
+
* Process a delete command, handling wildcard relations
|
|
1639
|
+
*/
|
|
1640
|
+
processDeleteCommand(entityId, currentArchetype, componentType, changeset) {
|
|
1641
|
+
const detailedType = getDetailedIdType(componentType);
|
|
1642
|
+
if (detailedType.type === "wildcard-relation") this.removeWildcardRelations(entityId, currentArchetype, detailedType.componentId, changeset);
|
|
1643
|
+
else {
|
|
1644
|
+
changeset.delete(componentType);
|
|
1645
|
+
if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isDontFragmentComponent(detailedType.componentId)) {
|
|
1646
|
+
const wildcardMarker = relation(detailedType.componentId, "*");
|
|
1647
|
+
const entityData = currentArchetype.getEntity(entityId);
|
|
1648
|
+
let hasOtherRelations = false;
|
|
1649
|
+
if (entityData) for (const [otherComponentType] of entityData) {
|
|
1650
|
+
if (otherComponentType === componentType) continue;
|
|
1651
|
+
if (changeset.removes.has(otherComponentType)) continue;
|
|
1652
|
+
const otherDetailedType = getDetailedIdType(otherComponentType);
|
|
1653
|
+
if ((otherDetailedType.type === "entity-relation" || otherDetailedType.type === "component-relation") && otherDetailedType.componentId === detailedType.componentId) {
|
|
1654
|
+
hasOtherRelations = true;
|
|
1655
|
+
break;
|
|
1472
1656
|
}
|
|
1473
|
-
changeset.set(command.componentType, command.component);
|
|
1474
|
-
}
|
|
1475
|
-
break;
|
|
1476
|
-
case "delete":
|
|
1477
|
-
if (command.componentType) {
|
|
1478
|
-
const detailedType = getDetailedIdType(command.componentType);
|
|
1479
|
-
if (detailedType.type === "wildcard-relation") {
|
|
1480
|
-
const baseComponentId = detailedType.componentId;
|
|
1481
|
-
for (const componentType of currentArchetype.componentTypes) {
|
|
1482
|
-
const componentDetailedType = getDetailedIdType(componentType);
|
|
1483
|
-
if (componentDetailedType.type === "entity-relation" || componentDetailedType.type === "component-relation") {
|
|
1484
|
-
if (componentDetailedType.componentId === baseComponentId) changeset.delete(componentType);
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
} else changeset.delete(command.componentType);
|
|
1488
1657
|
}
|
|
1489
|
-
|
|
1658
|
+
if (!hasOtherRelations) changeset.delete(wildcardMarker);
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
/**
|
|
1663
|
+
* Remove all relations matching a wildcard component ID
|
|
1664
|
+
*/
|
|
1665
|
+
removeWildcardRelations(entityId, currentArchetype, baseComponentId, changeset) {
|
|
1666
|
+
for (const componentType of currentArchetype.componentTypes) if (this.isRelationWithComponent(componentType, baseComponentId)) changeset.delete(componentType);
|
|
1667
|
+
const entityData = currentArchetype.getEntity(entityId);
|
|
1668
|
+
if (entityData) for (const [componentType] of entityData) {
|
|
1669
|
+
if (currentArchetype.componentTypes.includes(componentType)) continue;
|
|
1670
|
+
if (this.isRelationWithComponent(componentType, baseComponentId)) changeset.delete(componentType);
|
|
1671
|
+
}
|
|
1672
|
+
if (isDontFragmentComponent(baseComponentId)) {
|
|
1673
|
+
const wildcardMarker = relation(baseComponentId, "*");
|
|
1674
|
+
changeset.delete(wildcardMarker);
|
|
1490
1675
|
}
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1676
|
+
}
|
|
1677
|
+
/**
|
|
1678
|
+
* Apply changeset to entity, moving to new archetype if needed
|
|
1679
|
+
* @returns Map of removed components with their data
|
|
1680
|
+
*/
|
|
1681
|
+
applyChangeset(entityId, currentArchetype, changeset) {
|
|
1682
|
+
const currentEntityData = currentArchetype.getEntity(entityId);
|
|
1683
|
+
const allCurrentComponentTypes = currentEntityData ? Array.from(currentEntityData.keys()) : currentArchetype.componentTypes;
|
|
1684
|
+
const finalComponentTypes = changeset.getFinalComponentTypes(allCurrentComponentTypes);
|
|
1685
|
+
const removedComponents = /* @__PURE__ */ new Map();
|
|
1686
|
+
if (finalComponentTypes) this.moveEntityToNewArchetype(entityId, currentArchetype, finalComponentTypes, changeset, removedComponents);
|
|
1687
|
+
else this.updateEntityInSameArchetype(entityId, currentArchetype, changeset, removedComponents);
|
|
1688
|
+
return removedComponents;
|
|
1689
|
+
}
|
|
1690
|
+
/**
|
|
1691
|
+
* Move entity to a new archetype with updated components
|
|
1692
|
+
*/
|
|
1693
|
+
moveEntityToNewArchetype(entityId, currentArchetype, finalComponentTypes, changeset, removedComponents) {
|
|
1694
|
+
const newArchetype = this.ensureArchetype(finalComponentTypes);
|
|
1695
|
+
const currentComponents = currentArchetype.removeEntity(entityId);
|
|
1696
|
+
for (const componentType of changeset.removes) removedComponents.set(componentType, currentComponents.get(componentType));
|
|
1697
|
+
newArchetype.addEntity(entityId, changeset.applyTo(currentComponents));
|
|
1698
|
+
this.entityToArchetype.set(entityId, newArchetype);
|
|
1699
|
+
if (currentArchetype.getEntities().length === 0) this.cleanupEmptyArchetype(currentArchetype);
|
|
1700
|
+
}
|
|
1701
|
+
/**
|
|
1702
|
+
* Update entity in same archetype (no archetype change needed)
|
|
1703
|
+
*/
|
|
1704
|
+
updateEntityInSameArchetype(entityId, currentArchetype, changeset, removedComponents) {
|
|
1705
|
+
const currentComponents = currentArchetype.getEntity(entityId);
|
|
1706
|
+
const hasDontFragmentChanges = this.hasDontFragmentChanges(changeset);
|
|
1707
|
+
if (hasDontFragmentChanges) for (const componentType of changeset.removes) {
|
|
1708
|
+
const detailedType = getDetailedIdType(componentType);
|
|
1709
|
+
if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isDontFragmentComponent(detailedType.componentId)) removedComponents.set(componentType, currentComponents.get(componentType));
|
|
1710
|
+
}
|
|
1711
|
+
if (hasDontFragmentChanges) this.readdEntityWithUpdatedComponents(entityId, currentArchetype, currentComponents, changeset);
|
|
1712
|
+
else for (const [componentType, component$1] of changeset.adds) currentArchetype.set(entityId, componentType, component$1);
|
|
1713
|
+
}
|
|
1714
|
+
/**
|
|
1715
|
+
* Check if changeset contains dontFragment relation changes
|
|
1716
|
+
*/
|
|
1717
|
+
hasDontFragmentChanges(changeset) {
|
|
1500
1718
|
for (const componentType of changeset.removes) {
|
|
1501
1719
|
const detailedType = getDetailedIdType(componentType);
|
|
1502
|
-
if (detailedType.type === "entity-relation")
|
|
1503
|
-
const targetEntityId = detailedType.targetId;
|
|
1504
|
-
this.untrackEntityReference(entityId, componentType, targetEntityId);
|
|
1505
|
-
} else if (detailedType.type === "entity") this.untrackEntityReference(entityId, componentType, componentType);
|
|
1720
|
+
if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isDontFragmentComponent(detailedType.componentId)) return true;
|
|
1506
1721
|
}
|
|
1507
|
-
for (const [componentType
|
|
1722
|
+
for (const [componentType] of changeset.adds) {
|
|
1508
1723
|
const detailedType = getDetailedIdType(componentType);
|
|
1509
|
-
if (detailedType.type === "entity-relation")
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1724
|
+
if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isDontFragmentComponent(detailedType.componentId)) return true;
|
|
1725
|
+
}
|
|
1726
|
+
return false;
|
|
1727
|
+
}
|
|
1728
|
+
/**
|
|
1729
|
+
* Remove and re-add entity with updated components (for dontFragment changes)
|
|
1730
|
+
*/
|
|
1731
|
+
readdEntityWithUpdatedComponents(entityId, archetype, currentComponents, changeset) {
|
|
1732
|
+
const newComponents = /* @__PURE__ */ new Map();
|
|
1733
|
+
for (const [ct, value] of currentComponents) if (!changeset.removes.has(ct)) newComponents.set(ct, value);
|
|
1734
|
+
for (const [ct, value] of changeset.adds) newComponents.set(ct, value);
|
|
1735
|
+
archetype.removeEntity(entityId);
|
|
1736
|
+
archetype.addEntity(entityId, newComponents);
|
|
1737
|
+
}
|
|
1738
|
+
/**
|
|
1739
|
+
* Update entity reference tracking based on changeset
|
|
1740
|
+
*/
|
|
1741
|
+
updateEntityReferences(entityId, changeset) {
|
|
1742
|
+
for (const componentType of changeset.removes) {
|
|
1743
|
+
const detailedType = getDetailedIdType(componentType);
|
|
1744
|
+
if (detailedType.type === "entity-relation") this.untrackEntityReference(entityId, componentType, detailedType.targetId);
|
|
1745
|
+
else if (detailedType.type === "entity") this.untrackEntityReference(entityId, componentType, componentType);
|
|
1746
|
+
}
|
|
1747
|
+
for (const [componentType] of changeset.adds) {
|
|
1748
|
+
const detailedType = getDetailedIdType(componentType);
|
|
1749
|
+
if (detailedType.type === "entity-relation") this.trackEntityReference(entityId, componentType, detailedType.targetId);
|
|
1750
|
+
else if (detailedType.type === "entity") this.trackEntityReference(entityId, componentType, componentType);
|
|
1513
1751
|
}
|
|
1514
|
-
this.triggerLifecycleHooks(entityId, changeset.adds, removedCompoents);
|
|
1515
|
-
return changeset;
|
|
1516
1752
|
}
|
|
1517
1753
|
/**
|
|
1518
1754
|
* Get or create an archetype for the given component types
|
|
1519
|
-
*
|
|
1755
|
+
* Filters out dontFragment relations from the archetype signature
|
|
1756
|
+
* @returns The archetype for the given component types (excluding dontFragment relations)
|
|
1520
1757
|
*/
|
|
1521
1758
|
ensureArchetype(componentTypes) {
|
|
1522
|
-
const sortedTypes =
|
|
1759
|
+
const sortedTypes = this.filterRegularComponentTypes(componentTypes).sort((a, b) => a - b);
|
|
1523
1760
|
const hashKey = this.createArchetypeSignature(sortedTypes);
|
|
1524
|
-
return getOrCreateWithSideEffect(this.archetypeBySignature, hashKey, () =>
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1761
|
+
return getOrCreateWithSideEffect(this.archetypeBySignature, hashKey, () => this.createNewArchetype(sortedTypes));
|
|
1762
|
+
}
|
|
1763
|
+
/**
|
|
1764
|
+
* Filter out dontFragment relations from component types, but keep wildcard markers
|
|
1765
|
+
*/
|
|
1766
|
+
filterRegularComponentTypes(componentTypes) {
|
|
1767
|
+
const regularTypes = [];
|
|
1768
|
+
for (const componentType of componentTypes) {
|
|
1769
|
+
const detailedType = getDetailedIdType(componentType);
|
|
1770
|
+
if (detailedType.type === "wildcard-relation" && isDontFragmentComponent(detailedType.componentId)) {
|
|
1771
|
+
regularTypes.push(componentType);
|
|
1772
|
+
continue;
|
|
1531
1773
|
}
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
}
|
|
1774
|
+
if ((detailedType.type === "entity-relation" || detailedType.type === "component-relation") && isDontFragmentComponent(detailedType.componentId)) continue;
|
|
1775
|
+
regularTypes.push(componentType);
|
|
1776
|
+
}
|
|
1777
|
+
return regularTypes;
|
|
1778
|
+
}
|
|
1779
|
+
/**
|
|
1780
|
+
* Create a new archetype and register it with all tracking structures
|
|
1781
|
+
*/
|
|
1782
|
+
createNewArchetype(componentTypes) {
|
|
1783
|
+
const newArchetype = new Archetype(componentTypes, this.dontFragmentRelations);
|
|
1784
|
+
this.archetypes.push(newArchetype);
|
|
1785
|
+
this.registerArchetypeInComponentIndex(newArchetype, componentTypes);
|
|
1786
|
+
this.notifyQueriesOfNewArchetype(newArchetype);
|
|
1787
|
+
return newArchetype;
|
|
1788
|
+
}
|
|
1789
|
+
/**
|
|
1790
|
+
* Register archetype in the component-to-archetype index
|
|
1791
|
+
*/
|
|
1792
|
+
registerArchetypeInComponentIndex(archetype, componentTypes) {
|
|
1793
|
+
for (const componentType of componentTypes) {
|
|
1794
|
+
const archetypes = this.archetypesByComponent.get(componentType) || [];
|
|
1795
|
+
archetypes.push(archetype);
|
|
1796
|
+
this.archetypesByComponent.set(componentType, archetypes);
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
/**
|
|
1800
|
+
* Notify all queries to check the new archetype
|
|
1801
|
+
*/
|
|
1802
|
+
notifyQueriesOfNewArchetype(archetype) {
|
|
1803
|
+
for (const query of this.queries) query.checkNewArchetype(archetype);
|
|
1535
1804
|
}
|
|
1536
1805
|
/**
|
|
1537
1806
|
* Add a component reference to the reverse index when an entity is used as a component type
|
|
@@ -1569,10 +1838,29 @@ var World = class {
|
|
|
1569
1838
|
*/
|
|
1570
1839
|
cleanupEmptyArchetype(archetype) {
|
|
1571
1840
|
if (archetype.getEntities().length > 0) return;
|
|
1841
|
+
this.removeArchetypeFromList(archetype);
|
|
1842
|
+
this.removeArchetypeFromSignatureMap(archetype);
|
|
1843
|
+
this.removeArchetypeFromComponentIndex(archetype);
|
|
1844
|
+
this.removeArchetypeFromQueries(archetype);
|
|
1845
|
+
}
|
|
1846
|
+
/**
|
|
1847
|
+
* Remove archetype from the main archetypes list
|
|
1848
|
+
*/
|
|
1849
|
+
removeArchetypeFromList(archetype) {
|
|
1572
1850
|
const index = this.archetypes.indexOf(archetype);
|
|
1573
1851
|
if (index !== -1) this.archetypes.splice(index, 1);
|
|
1852
|
+
}
|
|
1853
|
+
/**
|
|
1854
|
+
* Remove archetype from the signature-to-archetype map
|
|
1855
|
+
*/
|
|
1856
|
+
removeArchetypeFromSignatureMap(archetype) {
|
|
1574
1857
|
const hashKey = this.createArchetypeSignature(archetype.componentTypes);
|
|
1575
1858
|
this.archetypeBySignature.delete(hashKey);
|
|
1859
|
+
}
|
|
1860
|
+
/**
|
|
1861
|
+
* Remove archetype from the component-to-archetypes index
|
|
1862
|
+
*/
|
|
1863
|
+
removeArchetypeFromComponentIndex(archetype) {
|
|
1576
1864
|
for (const componentType of archetype.componentTypes) {
|
|
1577
1865
|
const archetypes = this.archetypesByComponent.get(componentType);
|
|
1578
1866
|
if (archetypes) {
|
|
@@ -1583,6 +1871,11 @@ var World = class {
|
|
|
1583
1871
|
}
|
|
1584
1872
|
}
|
|
1585
1873
|
}
|
|
1874
|
+
}
|
|
1875
|
+
/**
|
|
1876
|
+
* Remove archetype from all queries
|
|
1877
|
+
*/
|
|
1878
|
+
removeArchetypeFromQueries(archetype) {
|
|
1586
1879
|
for (const query of this.queries) query.removeArchetype(archetype);
|
|
1587
1880
|
}
|
|
1588
1881
|
/**
|
|
@@ -1660,5 +1953,5 @@ var World = class {
|
|
|
1660
1953
|
};
|
|
1661
1954
|
|
|
1662
1955
|
//#endregion
|
|
1663
|
-
export {
|
|
1956
|
+
export { Query, World, component, decodeRelationId, getComponentIdByName, getComponentNameById, isComponentId, isEntityId, isRelationId, isWildcardRelationId, relation };
|
|
1664
1957
|
//# sourceMappingURL=index.mjs.map
|