@codehz/ecs 0.8.0 → 0.8.1
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/dist/builder.d.mts +28 -9
- package/dist/world.mjs +247 -89
- package/dist/world.mjs.map +1 -1
- package/package.json +2 -1
- package/skills/ecs/SKILL.md +333 -0
- package/src/__tests__/core/archetype.test.ts +4 -2
- package/src/__tests__/perf/dontfragment-wildcard.perf.test.ts +107 -0
- package/src/__tests__/query/filter.test.ts +3 -2
- package/src/archetype/archetype.ts +64 -60
- package/src/archetype/helpers.ts +13 -6
- package/src/archetype/store.ts +222 -15
- package/src/world/commands.ts +22 -34
- package/src/world/references.ts +59 -0
- package/src/world/world.ts +9 -2
package/src/world/references.ts
CHANGED
|
@@ -1,8 +1,41 @@
|
|
|
1
1
|
import type { EntityId } from "../entity";
|
|
2
2
|
import { MultiMap } from "../utils/multi-map";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Reverse reference index: maps each target entity to the set of (source entity, component) pairs
|
|
6
|
+
* that currently hold a reference to it.
|
|
7
|
+
*
|
|
8
|
+
* Used internally to support efficient entity deletion, including:
|
|
9
|
+
* - Fast-path deletion for unreferenced entities
|
|
10
|
+
* - Cascading deletes for relations marked with `cascadeDelete`
|
|
11
|
+
* - Automatic cleanup of entity-valued components and entity-relations when their target is destroyed
|
|
12
|
+
*
|
|
13
|
+
* Structure:
|
|
14
|
+
* targetEntityId -> MultiMap<sourceEntityId, componentOrRelationId>
|
|
15
|
+
*
|
|
16
|
+
* - For plain entity-valued components (component value is an EntityId):
|
|
17
|
+
* componentOrRelationId === the component type (which is also the entity id being pointed to)
|
|
18
|
+
* - For entity-relations (`relation(Comp, target)`):
|
|
19
|
+
* componentOrRelationId is the encoded (negative) relation ID
|
|
20
|
+
*
|
|
21
|
+
* This index is maintained in sync with structural changes via `updateEntityReferences` in World.
|
|
22
|
+
*
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
4
25
|
export type EntityReferencesMap = Map<EntityId, MultiMap<EntityId, EntityId>>;
|
|
5
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Record that `sourceEntityId` holds a reference to `targetEntityId` via the given component/relation.
|
|
29
|
+
*
|
|
30
|
+
* Called when an entity-valued component or an entity-relation is added to an entity.
|
|
31
|
+
*
|
|
32
|
+
* @param entityReferences - The shared reverse index map
|
|
33
|
+
* @param sourceEntityId - The entity that contains the reference
|
|
34
|
+
* @param componentType - The component type or encoded relation ID used for the reference
|
|
35
|
+
* @param targetEntityId - The entity being referenced
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
6
39
|
export function trackEntityReference(
|
|
7
40
|
entityReferences: EntityReferencesMap,
|
|
8
41
|
sourceEntityId: EntityId,
|
|
@@ -15,6 +48,19 @@ export function trackEntityReference(
|
|
|
15
48
|
entityReferences.get(targetEntityId)!.add(sourceEntityId, componentType);
|
|
16
49
|
}
|
|
17
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Remove the record that `sourceEntityId` references `targetEntityId` via the given component/relation.
|
|
53
|
+
*
|
|
54
|
+
* Called when an entity-valued component or entity-relation is removed (or during deletion).
|
|
55
|
+
* Automatically prunes empty target entries from the map.
|
|
56
|
+
*
|
|
57
|
+
* @param entityReferences - The shared reverse index map
|
|
58
|
+
* @param sourceEntityId - The entity that no longer holds the reference
|
|
59
|
+
* @param componentType - The component type or encoded relation ID that was used
|
|
60
|
+
* @param targetEntityId - The previously referenced entity
|
|
61
|
+
*
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
18
64
|
export function untrackEntityReference(
|
|
19
65
|
entityReferences: EntityReferencesMap,
|
|
20
66
|
sourceEntityId: EntityId,
|
|
@@ -30,6 +76,19 @@ export function untrackEntityReference(
|
|
|
30
76
|
}
|
|
31
77
|
}
|
|
32
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Iterate over all (sourceEntityId, componentOrRelationId) pairs that currently reference the given target.
|
|
81
|
+
*
|
|
82
|
+
* Returns an empty iterable when the target has no incoming references.
|
|
83
|
+
* The returned iterable yields `[source, componentType]` pairs suitable for cleanup decisions
|
|
84
|
+
* (e.g. whether to cascade-delete the source or just remove the specific component/relation).
|
|
85
|
+
*
|
|
86
|
+
* @param entityReferences - The shared reverse index map
|
|
87
|
+
* @param targetEntityId - The entity whose referrers we want to inspect
|
|
88
|
+
* @returns Iterable of [sourceEntityId, componentOrRelationId]
|
|
89
|
+
*
|
|
90
|
+
* @internal
|
|
91
|
+
*/
|
|
33
92
|
export function getEntityReferences(
|
|
34
93
|
entityReferences: EntityReferencesMap,
|
|
35
94
|
targetEntityId: EntityId,
|
package/src/world/world.ts
CHANGED
|
@@ -444,7 +444,10 @@ export class World {
|
|
|
444
444
|
if (archetype.componentTypeSet.has(componentType)) return true;
|
|
445
445
|
|
|
446
446
|
if (isDontFragmentRelation(componentType)) {
|
|
447
|
-
|
|
447
|
+
// Use getValue; presence check via getAllForEntity only if value can legitimately be undefined
|
|
448
|
+
const val = this.dontFragmentStore.getValue(entityId, componentType);
|
|
449
|
+
if (val !== undefined) return true;
|
|
450
|
+
return this.dontFragmentStore.getAllForEntity(entityId).some(([t]) => t === componentType);
|
|
448
451
|
}
|
|
449
452
|
|
|
450
453
|
return false;
|
|
@@ -493,7 +496,11 @@ export class World {
|
|
|
493
496
|
if (componentType >= 0 || componentType % RELATION_SHIFT !== 0) {
|
|
494
497
|
const inArchetype = archetype.componentTypeSet.has(componentType);
|
|
495
498
|
const hasDontFragment = isDontFragmentRelation(componentType);
|
|
496
|
-
const hasComponent =
|
|
499
|
+
const hasComponent =
|
|
500
|
+
inArchetype ||
|
|
501
|
+
(hasDontFragment &&
|
|
502
|
+
(this.dontFragmentStore.getValue(entityId, componentType) !== undefined ||
|
|
503
|
+
this.dontFragmentStore.getAllForEntity(entityId).some(([t]) => t === componentType)));
|
|
497
504
|
|
|
498
505
|
if (!hasComponent) {
|
|
499
506
|
throw new Error(
|