Skip to content

Commit 3d5f52c

Browse files
committed
Skip side-effects on equal assignments
Fix relation components were not invalidated on changes.
1 parent b561d76 commit 3d5f52c

File tree

4 files changed

+25
-2
lines changed

4 files changed

+25
-2
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
- `tcod.ecs.typing.ComponentKey` is now stable.
1111
- Can now register a callback to be called on component changes.
1212

13+
### Fixed
14+
- Fixed stale caches for relation components.
15+
1316
## [4.2.1] - 2023-07-28
1417
### Fixed
1518
- Unpickled worlds had reversed relations from what were saved.

tcod/ecs/entity.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,8 @@ def __setitem__(self, key: ComponentKey[T], value: T) -> None:
334334

335335
if old_value is None:
336336
tcod.ecs.query._touch_component(self.entity.world, key) # Component added
337+
elif old_value == value:
338+
return
337339

338340
self.entity.world._components_by_entity[self.entity][key] = value
339341
self.entity.world._components_by_type[key][self.entity] = value
@@ -682,8 +684,15 @@ def __getitem__(self, target: Entity) -> T:
682684
def __setitem__(self, target: Entity, component: T) -> None:
683685
"""Assign a component to the target entity."""
684686
world = self.entity.world
685-
if target in world._relation_components_by_entity[self.entity][self.key] is not None:
686-
del self[target]
687+
688+
old_value = world._relation_components_by_entity[self.entity][self.key].get(target)
689+
if old_value is None: # Relation added
690+
tcod.ecs.query._touch_relations(
691+
world, ((self.key, target), (self.key, ...), (self.entity, self.key, None), (..., self.key, None))
692+
)
693+
elif old_value == component:
694+
return
695+
687696
world._relation_components_by_entity[self.entity][self.key][target] = component
688697

689698
world._relations_lookup[(self.key, target)] = {self.entity}
@@ -716,6 +725,10 @@ def __delitem__(self, target: Entity) -> None:
716725
if not world._relations_lookup[(..., self.key, None)]:
717726
del world._relations_lookup[(..., self.key, None)]
718727

728+
tcod.ecs.query._touch_relations(
729+
world, ((self.key, target), (self.key, ...), (self.entity, self.key, None), (..., self.key, None))
730+
)
731+
719732
def __iter__(self) -> Iterator[Entity]:
720733
"""Iterate over the targets with assigned components."""
721734
by_entity = self.entity.world._relation_components_by_entity.get(self.entity)

tests/test_ecs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,10 @@ def test_tag_query() -> None:
239239
world = tcod.ecs.World()
240240
assert not set(world.Q.all_of(tags=["A"]))
241241
world["A"].tags.add("A")
242+
world["A"].tags.add("A") # Cover redundant add
242243
assert set(world.Q.all_of(tags=["A"])) == {world["A"]}
243244
world["A"].tags.remove("A")
245+
world["A"].tags.discard("A") # Cover redundant discard
244246
assert not set(world.Q.all_of(tags=["A"]))
245247

246248

tests/test_relations.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,14 @@ def test_relation_components() -> None:
7474

7575
entity_a.relation_components[int] = {world[1]: 1, world[2]: 2}
7676
entity_a.relation_components[int] = entity_a.relation_components[int]
77+
entity_a.relation_components[int][world[1]] = 1
78+
79+
assert set(world.Q.all_of(relations=[(int, world[1])])) == {entity_a}
7780

7881
entity_a.relation_components.clear()
7982

83+
assert not set(world.Q.all_of(relations=[(int, world[1])]))
84+
8085

8186
def test_conditional_relations() -> None:
8287
world = tcod.ecs.World()

0 commit comments

Comments
 (0)