Skip to content

Commit d967daf

Browse files
authored
🐛 wildcard relations were removed early
1 parent b15e9ac commit d967daf

File tree

3 files changed

+53
-6
lines changed

3 files changed

+53
-6
lines changed

packages/core/src/relation/relation.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { $internal } from '../common';
2+
import { Entity } from '../entity/types';
23
import { trait } from '../trait/trait';
34
import type { Schema, Trait } from '../trait/types';
45
import type { World } from '../world/world';
@@ -75,6 +76,28 @@ export const getRelationTargets = (
7576
return targets;
7677
};
7778

79+
export const hasRelationToTarget = (
80+
world: World,
81+
entity: Entity,
82+
target: RelationTarget
83+
): boolean => {
84+
const ctx = world[$internal];
85+
const traits = ctx.entityTraits.get(entity);
86+
if (!traits) return false;
87+
88+
for (const trait of traits) {
89+
const traitCtx = trait[$internal];
90+
if (
91+
traitCtx.isPairTrait &&
92+
traitCtx.pairTarget === target &&
93+
traitCtx.relation !== Wildcard
94+
) {
95+
return true;
96+
}
97+
}
98+
return false;
99+
};
100+
78101
export const Pair = <T extends Trait>(relation: Relation<T>, target: RelationTarget): T => {
79102
if (relation === undefined) throw Error('Relation is undefined');
80103
if (target === undefined) throw Error('Relation target is undefined');

packages/core/src/trait/trait.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { $internal } from '../common';
22
import type { Entity } from '../entity/types';
33
import { getEntityId } from '../entity/utils/pack-entity';
44
import { setChanged } from '../query/modifiers/changed';
5-
import { getRelationTargets, Pair, Wildcard } from '../relation/relation';
5+
import { getRelationTargets, hasRelationToTarget, Pair, Wildcard } from '../relation/relation';
66
import { incrementWorldBitflag } from '../world/utils/increment-world-bit-flag';
77
import type { World } from '../world/world';
88
import type {
@@ -229,14 +229,13 @@ export function removeTrait(world: World, entity: Entity, ...traits: Trait[]) {
229229
// Check if entity is still a subject of any relation or not.
230230
if (world.query(Wildcard(entity)).length === 0) {
231231
ctx.relationTargetEntities.delete(entity);
232-
233-
// TODO: cleanup query by hash
234-
// removeQueryByHash(world, [Wildcard(eid)])
235232
}
236233

237-
// Remove wildcard to this target for this entity.
234+
// Remove wildcard to this target for this entity if no other relations to this target exist.
238235
const target = traitCtx.pairTarget!;
239-
removeTrait(world, entity, Pair(Wildcard, target));
236+
if (!hasRelationToTarget(world, entity, target)) {
237+
removeTrait(world, entity, Pair(Wildcard, target));
238+
}
240239

241240
// Remove wildcard relation if the entity has no other relations.
242241
const relation = traitCtx.relation!;

packages/core/tests/relation.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,4 +232,29 @@ describe('Relation', () => {
232232
expect(person.has(Likes(apple))).toBe(false);
233233
expect(person.has(Likes(banana))).toBe(false);
234234
});
235+
236+
it('should keep wildcard trait when removing one of multiple relations to the same target', () => {
237+
const Likes = relation();
238+
const Fears = relation();
239+
240+
const person = world.spawn();
241+
const dragon = world.spawn();
242+
243+
// Person both likes and fears the dragon
244+
person.add(Likes(dragon));
245+
person.add(Fears(dragon));
246+
247+
// Wildcard(dragon) query should find person
248+
expect(world.query(Wildcard(dragon))).toContain(person);
249+
250+
// Remove only the Likes relation
251+
person.remove(Likes(dragon));
252+
253+
// Person should still fear the dragon
254+
expect(person.has(Fears(dragon))).toBe(true);
255+
expect(person.has(Likes(dragon))).toBe(false);
256+
257+
// Wildcard(dragon) should still find person because Fears(dragon) remains
258+
expect(world.query(Wildcard(dragon))).toContain(person);
259+
});
235260
});

0 commit comments

Comments
 (0)