Skip to content

Commit 6229dc1

Browse files
committed
feat(world): add support for retrieving wildcard relation components
- Implemented functionality to retrieve wildcard relation components in the World and Archetype classes. - Added tests to verify the correct behavior of wildcard relations when adding and retrieving components. - Enhanced the getComponent method to handle wildcard relation types, returning all matching relations for a given entity.
1 parent e49a91f commit 6229dc1

File tree

4 files changed

+114
-8
lines changed

4 files changed

+114
-8
lines changed

src/archetype.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,41 @@ describe("Archetype", () => {
6666
expect(retrieved2).toEqual(newPosition);
6767
});
6868

69+
it("should get wildcard relation components", () => {
70+
// Create relation component types
71+
const target1 = createEntityId(1027);
72+
const target2 = createEntityId(1028);
73+
const relation1 = createRelationId(positionComponent, target1);
74+
const relation2 = createRelationId(positionComponent, target2);
75+
const wildcardPositionRelation = createRelationId(positionComponent, "*");
76+
77+
const entity = createEntityId(1024);
78+
79+
// Archetype with multiple relations
80+
const archetype = new Archetype([relation1, relation2]);
81+
82+
// Add entity with relations to target1 and target2
83+
archetype.addEntity(
84+
entity,
85+
new Map([
86+
[relation1, { distance: 10 }],
87+
[relation2, { distance: 20 }],
88+
])
89+
);
90+
91+
// Get wildcard relations
92+
const relations = archetype.getComponent(entity, wildcardPositionRelation);
93+
expect(relations).toEqual([
94+
[target2, { distance: 20 }],
95+
[target1, { distance: 10 }],
96+
]);
97+
98+
// Test with entity not in archetype
99+
const nonExistentEntity = createEntityId(9999);
100+
const result = archetype.getComponent(nonExistentEntity, wildcardPositionRelation);
101+
expect(result).toEqual([]);
102+
});
103+
69104
it("should iterate over entities", () => {
70105
const archetype = new Archetype([positionComponent]);
71106
const entity1 = createEntityId(1024);

src/archetype.ts

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { EntityId } from "./entity";
1+
import type { EntityId, WildcardRelationId } from "./entity";
22
import { getIdType, decodeRelationId } from "./entity";
33
import type { ComponentTuple } from "./types";
44
import { getOrComputeCache } from "./utils";
@@ -132,17 +132,48 @@ export class Archetype {
132132
return this.entityToIndex.has(entityId);
133133
}
134134

135+
/**
136+
* Get component data for a specific entity and wildcard relation type
137+
* Returns an array of all matching relation instances
138+
* @param entityId The entity
139+
* @param componentType The wildcard relation type
140+
*/
141+
getComponent<T>(entityId: EntityId, componentType: WildcardRelationId<T>): [EntityId<unknown>, any][] | undefined;
135142
/**
136143
* Get component data for a specific entity and component type
137144
* @param entityId The entity
138145
* @param componentType The component type
139146
*/
140-
getComponent<T>(entityId: EntityId, componentType: EntityId<T>): T | undefined {
147+
getComponent<T>(entityId: EntityId, componentType: EntityId<T>): T | undefined;
148+
getComponent<T>(entityId: EntityId, componentType: EntityId<T> | WildcardRelationId<T>): T | [EntityId<unknown>, any][] | undefined {
141149
const index = this.entityToIndex.get(entityId);
142150
if (index === undefined) {
143-
return undefined;
151+
if (getIdType(componentType) === "wildcard-relation") {
152+
return [];
153+
} else {
154+
return undefined;
155+
}
156+
}
157+
158+
if (getIdType(componentType) === "wildcard-relation") {
159+
const decoded = decodeRelationId(componentType);
160+
const componentId = decoded.componentId;
161+
const relations: [EntityId<unknown>, any][] = [];
162+
163+
for (const relType of this.componentTypes) {
164+
const relDecoded = decodeRelationId(relType);
165+
if (relDecoded.componentId === componentId && (getIdType(relType) === "entity-relation" || getIdType(relType) === "component-relation")) {
166+
const dataArray = this.componentData.get(relType);
167+
if (dataArray && dataArray[index] !== undefined) {
168+
relations.push([relDecoded.targetId, dataArray[index]]);
169+
}
170+
}
171+
}
172+
173+
return relations;
174+
} else {
175+
return this.componentData.get(componentType)?.[index];
144176
}
145-
return this.componentData.get(componentType)?.[index];
146177
}
147178

148179
/**

src/world.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,34 @@ describe("World", () => {
114114
expect(world.hasComponent(entity, relationId2)).toBe(false);
115115
});
116116

117+
it("should get wildcard relation components", () => {
118+
const world = new World();
119+
const entity = world.createEntity();
120+
const position: Position = { x: 10, y: 20 };
121+
const targetEntity1 = world.createEntity();
122+
const targetEntity2 = world.createEntity();
123+
const relationId1 = createRelationId(positionComponent, targetEntity1);
124+
const relationId2 = createRelationId(positionComponent, targetEntity2);
125+
126+
// Add multiple relation components with the same base component
127+
world.addComponent(entity, relationId1, position);
128+
world.addComponent(entity, relationId2, { x: 20, y: 30 });
129+
world.flushCommands();
130+
131+
// Get wildcard relations
132+
const wildcardRelation = createRelationId(positionComponent, "*");
133+
const relations = world.getComponent(entity, wildcardRelation);
134+
expect(relations).toEqual([
135+
[targetEntity2, { x: 20, y: 30 }],
136+
[targetEntity1, { x: 10, y: 20 }],
137+
]);
138+
139+
// Test with entity not having components
140+
const otherEntity = world.createEntity();
141+
const result = world.getComponent(otherEntity, wildcardRelation);
142+
expect(result).toEqual([]);
143+
});
144+
117145
it("should handle multiple components", () => {
118146
const world = new World();
119147
const entity = world.createEntity();

src/world.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Archetype } from "./archetype";
22
import { CommandBuffer, type Command } from "./command-buffer";
3-
import type { EntityId } from "./entity";
4-
import { EntityIdManager, getDetailedIdType } from "./entity";
3+
import type { EntityId, WildcardRelationId } from "./entity";
4+
import { EntityIdManager, getDetailedIdType, getIdType } from "./entity";
55
import { Query } from "./query";
66
import type { QueryFilter } from "./query-filter";
77
import type { ComponentTuple, LifecycleHook } from "./types";
@@ -180,12 +180,24 @@ export class World<ExtraParams extends any[] = [deltaTime: number]> {
180180
return archetype ? archetype.componentTypes.includes(componentType) : false;
181181
}
182182

183+
/**
184+
* Get wildcard relations from an entity
185+
*/
186+
getComponent<T>(entityId: EntityId, componentType: WildcardRelationId<T>): [EntityId<unknown>, any][] | undefined;
183187
/**
184188
* Get a component from an entity
185189
*/
186-
getComponent<T>(entityId: EntityId, componentType: EntityId<T>): T | undefined {
190+
getComponent<T>(entityId: EntityId, componentType: EntityId<T>): T | undefined;
191+
getComponent<T>(entityId: EntityId, componentType: EntityId<T> | WildcardRelationId<T>): T | [EntityId<unknown>, any][] | undefined {
187192
const archetype = this.entityToArchetype.get(entityId);
188-
return archetype ? archetype.getComponent(entityId, componentType) : undefined;
193+
if (!archetype) {
194+
if (getIdType(componentType) === "wildcard-relation") {
195+
return [];
196+
} else {
197+
return undefined;
198+
}
199+
}
200+
return archetype.getComponent(entityId, componentType);
189201
}
190202

191203
/**

0 commit comments

Comments
 (0)