tcod-ecs 5.5.2__tar.gz → 5.6.0__tar.gz
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.
- {tcod_ecs-5.5.2 → tcod_ecs-5.6.0}/PKG-INFO +2 -3
- {tcod_ecs-5.5.2 → tcod_ecs-5.6.0}/pyproject.toml +1 -5
- {tcod_ecs-5.5.2 → tcod_ecs-5.6.0}/tcod/ecs/_version.py +3 -3
- {tcod_ecs-5.5.2 → tcod_ecs-5.6.0}/tcod/ecs/entity.py +41 -145
- {tcod_ecs-5.5.2 → tcod_ecs-5.6.0}/tcod/ecs/query.py +43 -97
- tcod_ecs-5.6.0/tcod/ecs/registry.py +480 -0
- tcod_ecs-5.6.0/tcod/ecs/traits.py +106 -0
- {tcod_ecs-5.5.2 → tcod_ecs-5.6.0}/tcod/ecs/typing.py +4 -0
- tcod_ecs-5.5.2/tcod/ecs/_converter.py +0 -46
- tcod_ecs-5.5.2/tcod/ecs/registry.py +0 -323
- {tcod_ecs-5.5.2 → tcod_ecs-5.6.0}/LICENSE +0 -0
- {tcod_ecs-5.5.2 → tcod_ecs-5.6.0}/README.md +0 -0
- {tcod_ecs-5.5.2 → tcod_ecs-5.6.0}/tcod/ecs/__init__.py +0 -0
- {tcod_ecs-5.5.2 → tcod_ecs-5.6.0}/tcod/ecs/callbacks.py +0 -0
- {tcod_ecs-5.5.2 → tcod_ecs-5.6.0}/tcod/ecs/constants.py +0 -0
- {tcod_ecs-5.5.2 → tcod_ecs-5.6.0}/tcod/ecs/py.typed +0 -0
- {tcod_ecs-5.5.2 → tcod_ecs-5.6.0}/tcod/ecs/world.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tcod-ecs
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.6.0
|
|
4
4
|
Summary: A type-hinted Entity Component System based on Python dictionaries and sets.
|
|
5
5
|
Author-email: Kyle Benesch <4b796c65+github@gmail.com>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -14,8 +14,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
14
14
|
Classifier: Typing :: Typed
|
|
15
15
|
License-File: LICENSE
|
|
16
16
|
Requires-Dist: attrs >=23.1.0
|
|
17
|
-
Requires-Dist:
|
|
18
|
-
Requires-Dist: typing-extensions >=4.14.0
|
|
17
|
+
Requires-Dist: typing-extensions >=4.16.0
|
|
19
18
|
Requires-Dist: attrs >=22.2.0 ; extra == "test"
|
|
20
19
|
Requires-Dist: pytest >=7.2.0 ; extra == "test"
|
|
21
20
|
Requires-Dist: pytest-cov >=4.0.0 ; extra == "test"
|
|
@@ -18,11 +18,7 @@ classifiers = [
|
|
|
18
18
|
]
|
|
19
19
|
dynamic = ["version", "description"]
|
|
20
20
|
requires-python = ">=3.10"
|
|
21
|
-
dependencies = [
|
|
22
|
-
"attrs >=23.1.0",
|
|
23
|
-
"cattrs >=23.1.2",
|
|
24
|
-
"typing-extensions >=4.14.0",
|
|
25
|
-
]
|
|
21
|
+
dependencies = ["attrs >=23.1.0", "typing-extensions >=4.16.0"]
|
|
26
22
|
|
|
27
23
|
[tool.setuptools_scm]
|
|
28
24
|
write_to = "tcod/ecs/_version.py"
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '5.
|
|
22
|
-
__version_tuple__ = version_tuple = (5,
|
|
21
|
+
__version__ = version = '5.6.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (5, 6, 0)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'ga15c18076'
|
|
@@ -9,7 +9,6 @@ from typing import (
|
|
|
9
9
|
Final,
|
|
10
10
|
Generic,
|
|
11
11
|
TypeVar,
|
|
12
|
-
cast,
|
|
13
12
|
overload,
|
|
14
13
|
)
|
|
15
14
|
from weakref import WeakKeyDictionary, WeakValueDictionary
|
|
@@ -18,8 +17,8 @@ import attrs
|
|
|
18
17
|
from typing_extensions import Self, Sentinel, TypeForm, deprecated
|
|
19
18
|
|
|
20
19
|
import tcod.ecs.callbacks
|
|
21
|
-
import tcod.ecs.query
|
|
22
20
|
from tcod.ecs.constants import IsA
|
|
21
|
+
from tcod.ecs.traits import Component, Tag
|
|
23
22
|
from tcod.ecs.typing import ComponentKey
|
|
24
23
|
|
|
25
24
|
if TYPE_CHECKING:
|
|
@@ -362,7 +361,7 @@ def _traverse_entities(start: Entity, traverse_parents: tuple[object, ...]) -> I
|
|
|
362
361
|
traverse_parents = traverse_parents[::-1]
|
|
363
362
|
visited = {start}
|
|
364
363
|
stack = [start]
|
|
365
|
-
_relation_tags_by_entity = start.registry.
|
|
364
|
+
_relation_tags_by_entity = start.registry._any_by_key_by_entity_by_category["RelationTag"]
|
|
366
365
|
while stack:
|
|
367
366
|
entity = stack.pop()
|
|
368
367
|
yield entity
|
|
@@ -418,7 +417,7 @@ class EntityComponents(MutableMapping[TypeForm[Any] | tuple[object, TypeForm[Any
|
|
|
418
417
|
def __getitem__(self, key: ComponentKey[T]) -> T:
|
|
419
418
|
"""Return a component belonging to this entity, or an indirect parent."""
|
|
420
419
|
assert self.__assert_key(key)
|
|
421
|
-
_components_by_entity = self.entity.registry.
|
|
420
|
+
_components_by_entity = self.entity.registry._any_by_key_by_entity_by_category["Component"]
|
|
422
421
|
for entity in _traverse_entities(self.entity, self.traverse):
|
|
423
422
|
components = _components_by_entity.get(entity)
|
|
424
423
|
if components is None:
|
|
@@ -433,13 +432,9 @@ class EntityComponents(MutableMapping[TypeForm[Any] | tuple[object, TypeForm[Any
|
|
|
433
432
|
"""Assign a component directly to an entity."""
|
|
434
433
|
assert self.__assert_key(key)
|
|
435
434
|
|
|
436
|
-
old_value = self.entity.registry.
|
|
435
|
+
old_value = self.entity.registry._any_by_key_by_entity_by_category["Component"].get(self.entity, {}).get(key)
|
|
437
436
|
|
|
438
|
-
|
|
439
|
-
tcod.ecs.query._touch_component(self.entity.registry, key) # Component added
|
|
440
|
-
|
|
441
|
-
self.entity.registry._components_by_entity[self.entity][key] = value
|
|
442
|
-
self.entity.registry._components_by_type[key][self.entity] = value
|
|
437
|
+
self.entity.registry._set_data(self.entity, Component.from_key(key), "Component", key, value)
|
|
443
438
|
|
|
444
439
|
tcod.ecs.callbacks._on_component_changed(key, self.entity, old_value, value)
|
|
445
440
|
|
|
@@ -447,25 +442,16 @@ class EntityComponents(MutableMapping[TypeForm[Any] | tuple[object, TypeForm[Any
|
|
|
447
442
|
"""Delete a directly held component from an entity."""
|
|
448
443
|
assert self.__assert_key(key)
|
|
449
444
|
|
|
450
|
-
|
|
451
|
-
if components is None:
|
|
452
|
-
raise KeyError(key)
|
|
453
|
-
old_value = components.get(key)
|
|
454
|
-
|
|
455
|
-
del components[key]
|
|
456
|
-
if not components:
|
|
457
|
-
del self.entity.registry._components_by_entity[self.entity]
|
|
445
|
+
old_value = self.entity.registry._any_by_key_by_entity_by_category["Component"].get(self.entity, {}).get(key)
|
|
458
446
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
del self.entity.registry._components_by_type[key]
|
|
447
|
+
if not self.entity.registry._discard_data(self.entity, Component.from_key(key), "Component", key):
|
|
448
|
+
raise KeyError(key)
|
|
462
449
|
|
|
463
|
-
tcod.ecs.query._touch_component(self.entity.registry, key) # Component removed
|
|
464
450
|
tcod.ecs.callbacks._on_component_changed(key, self.entity, old_value, None)
|
|
465
451
|
|
|
466
452
|
def keys(self) -> AbstractSet[ComponentKey[object]]: # type: ignore[override]
|
|
467
453
|
"""Return the components held by this entity, including inherited components."""
|
|
468
|
-
_components_by_entity = self.entity.registry.
|
|
454
|
+
_components_by_entity = self.entity.registry._any_by_key_by_entity_by_category["Component"]
|
|
469
455
|
if not self.traverse:
|
|
470
456
|
return _components_by_entity.get(self.entity, {}).keys()
|
|
471
457
|
set_: set[ComponentKey[object]] = set()
|
|
@@ -475,7 +461,7 @@ class EntityComponents(MutableMapping[TypeForm[Any] | tuple[object, TypeForm[Any
|
|
|
475
461
|
|
|
476
462
|
def __contains__(self, key: object) -> bool:
|
|
477
463
|
"""Return True if this entity has the provided component key."""
|
|
478
|
-
_components_by_entity = self.entity.registry.
|
|
464
|
+
_components_by_entity = self.entity.registry._any_by_key_by_entity_by_category["Component"]
|
|
479
465
|
return any(
|
|
480
466
|
key in _components_by_entity.get(entity, ()) for entity in _traverse_entities(self.entity, self.traverse)
|
|
481
467
|
)
|
|
@@ -583,12 +569,12 @@ class EntityComponents(MutableMapping[TypeForm[Any] | tuple[object, TypeForm[Any
|
|
|
583
569
|
...
|
|
584
570
|
KeyError: <class 'str'>
|
|
585
571
|
"""
|
|
586
|
-
|
|
587
|
-
if __key not in
|
|
572
|
+
data_by_key = self.entity.registry._any_by_key_by_entity_by_category["Component"].get(self.entity, {})
|
|
573
|
+
if __key not in data_by_key:
|
|
588
574
|
if default is _raise:
|
|
589
575
|
raise KeyError(__key)
|
|
590
576
|
return default
|
|
591
|
-
value: T | _T1 =
|
|
577
|
+
value: T | _T1 = data_by_key[__key]
|
|
592
578
|
del self[__key]
|
|
593
579
|
return value
|
|
594
580
|
|
|
@@ -598,9 +584,6 @@ class EntityComponents(MutableMapping[TypeForm[Any] | tuple[object, TypeForm[Any
|
|
|
598
584
|
self(traverse=()).clear()
|
|
599
585
|
return
|
|
600
586
|
super().clear()
|
|
601
|
-
# MutableMapping.clear() only calls __delitem__ for existing components,
|
|
602
|
-
# so it leaves an already empty registry row behind.
|
|
603
|
-
self.entity.registry._components_by_entity.pop(self.entity, None)
|
|
604
587
|
|
|
605
588
|
|
|
606
589
|
@attrs.define(eq=False, frozen=True, weakref_slot=False)
|
|
@@ -627,49 +610,30 @@ class EntityTags(MutableSet[Any]):
|
|
|
627
610
|
|
|
628
611
|
def add(self, tag: object) -> None:
|
|
629
612
|
"""Add a tag to the entity."""
|
|
630
|
-
|
|
631
|
-
return # Already has tag
|
|
632
|
-
tcod.ecs.query._touch_tag(self.entity.registry, tag) # Tag added
|
|
633
|
-
|
|
634
|
-
self.entity.registry._tags_by_entity[self.entity].add(tag)
|
|
635
|
-
self.entity.registry._tags_by_key[tag].add(self.entity)
|
|
613
|
+
self.entity.registry._set_data(self.entity, Tag(tag), "Tag", tag, True) # noqa: FBT003
|
|
636
614
|
|
|
637
615
|
def discard(self, tag: object) -> None:
|
|
638
616
|
"""Discard a tag directly held by an entity."""
|
|
639
|
-
|
|
640
|
-
return # Already doesn't have tag
|
|
641
|
-
tcod.ecs.query._touch_tag(self.entity.registry, tag) # Tag removed
|
|
642
|
-
|
|
643
|
-
self.entity.registry._tags_by_entity[self.entity].discard(tag)
|
|
644
|
-
if not self.entity.registry._tags_by_entity[self.entity]:
|
|
645
|
-
del self.entity.registry._tags_by_entity[self.entity]
|
|
646
|
-
|
|
647
|
-
self.entity.registry._tags_by_key[tag].discard(self.entity)
|
|
648
|
-
if not self.entity.registry._tags_by_key[tag]:
|
|
649
|
-
del self.entity.registry._tags_by_key[tag]
|
|
617
|
+
self.entity.registry._discard_data(self.entity, Tag(tag), "Tag", tag)
|
|
650
618
|
|
|
651
619
|
def remove(self, tag: object) -> None:
|
|
652
620
|
"""Remove a tag directly held by an entity."""
|
|
653
|
-
|
|
654
|
-
if tags is None or tag not in tags:
|
|
621
|
+
if not self.entity.registry._discard_data(self.entity, Tag(tag), "Tag", tag):
|
|
655
622
|
raise KeyError(tag)
|
|
656
|
-
self.discard(tag)
|
|
657
623
|
|
|
658
624
|
def clear(self) -> None:
|
|
659
625
|
"""Remove all tags directly in this entity."""
|
|
660
|
-
|
|
661
|
-
while tags := _tags_by_entity.get(self.entity):
|
|
626
|
+
while tags := self.entity.registry._any_by_key_by_entity_by_category["Tag"].get(self.entity):
|
|
662
627
|
self.discard(next(iter(tags)))
|
|
663
|
-
_tags_by_entity.pop(self.entity, None)
|
|
664
628
|
|
|
665
629
|
def __contains__(self, x: object) -> bool:
|
|
666
630
|
"""Return True if this entity has the given tag."""
|
|
667
|
-
_tags_by_entity = self.entity.registry.
|
|
631
|
+
_tags_by_entity = self.entity.registry._any_by_key_by_entity_by_category["Tag"]
|
|
668
632
|
return any(x in _tags_by_entity.get(entity, ()) for entity in _traverse_entities(self.entity, self.traverse))
|
|
669
633
|
|
|
670
634
|
def _as_set(self) -> set[object]:
|
|
671
635
|
"""Return all tags inherited by traversal rules into a single set with no duplicates."""
|
|
672
|
-
_tags_by_entity = self.entity.registry.
|
|
636
|
+
_tags_by_entity = self.entity.registry._any_by_key_by_entity_by_category["Tag"]
|
|
673
637
|
return set().union(
|
|
674
638
|
*(_tags_by_entity.get(entity, ()) for entity in _traverse_entities(self.entity, self.traverse))
|
|
675
639
|
)
|
|
@@ -701,42 +665,6 @@ class EntityTags(MutableSet[Any]):
|
|
|
701
665
|
return self
|
|
702
666
|
|
|
703
667
|
|
|
704
|
-
def _relations_lookup_add(registry: Registry, origin: Entity, tag: object, target: Entity) -> None:
|
|
705
|
-
"""Add a relation tag/component to the lookup table and handle side effects."""
|
|
706
|
-
registry._relations_lookup[(tag, target)].add(origin)
|
|
707
|
-
registry._relations_lookup[(tag, ...)].add(origin)
|
|
708
|
-
registry._relations_lookup[(origin, tag, None)].add(target)
|
|
709
|
-
registry._relations_lookup[(..., tag, None)].add(target)
|
|
710
|
-
tcod.ecs.query._touch_relations(registry, ((tag, target), (tag, ...), (origin, tag, None), (..., tag, None)))
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
def _relations_lookup_discard(registry: Registry, origin: Entity, tag: object, target: Entity) -> None:
|
|
714
|
-
"""Discard a relation tag/component from the lookup table and handle side effects."""
|
|
715
|
-
tags = registry._relation_tags_by_entity.get(origin)
|
|
716
|
-
if tags is not None and target in tags.get(tag, ()):
|
|
717
|
-
return # A tag relation still owns this index entry.
|
|
718
|
-
components = registry._relation_components_by_entity.get(origin)
|
|
719
|
-
if components is not None and target in components.get(cast("ComponentKey[object]", tag), ()):
|
|
720
|
-
return # A component relation still owns this index entry.
|
|
721
|
-
registry._relations_lookup[(tag, target)].discard(origin)
|
|
722
|
-
if not registry._relations_lookup[(tag, target)]:
|
|
723
|
-
del registry._relations_lookup[(tag, target)]
|
|
724
|
-
|
|
725
|
-
registry._relations_lookup[(..., tag, None)].discard(target)
|
|
726
|
-
if not registry._relations_lookup[(..., tag, None)]:
|
|
727
|
-
del registry._relations_lookup[(..., tag, None)]
|
|
728
|
-
|
|
729
|
-
registry._relations_lookup[(origin, tag, None)].discard(target)
|
|
730
|
-
if not registry._relations_lookup[(origin, tag, None)]:
|
|
731
|
-
del registry._relations_lookup[(origin, tag, None)]
|
|
732
|
-
|
|
733
|
-
registry._relations_lookup[(tag, ...)].discard(origin)
|
|
734
|
-
if not registry._relations_lookup[(tag, ...)]:
|
|
735
|
-
del registry._relations_lookup[(tag, ...)]
|
|
736
|
-
|
|
737
|
-
tcod.ecs.query._touch_relations(registry, ((tag, target), (tag, ...), (origin, tag, None), (..., tag, None)))
|
|
738
|
-
|
|
739
|
-
|
|
740
668
|
@attrs.define(eq=False, frozen=True, weakref_slot=False)
|
|
741
669
|
class EntityRelationsMapping(MutableSet[Entity]):
|
|
742
670
|
"""A proxy attribute to access entity relation targets like a set.
|
|
@@ -761,39 +689,23 @@ class EntityRelationsMapping(MutableSet[Entity]):
|
|
|
761
689
|
|
|
762
690
|
def add(self, target: Entity) -> None:
|
|
763
691
|
"""Add a relation target to this tag."""
|
|
764
|
-
registry
|
|
765
|
-
registry._relation_tags_by_entity[self.entity][self.key].add(target)
|
|
766
|
-
|
|
767
|
-
_relations_lookup_add(registry, self.entity, self.key, target)
|
|
692
|
+
self.entity.registry._set_relation_data(self.entity, Tag(self.key), "RelationTag", self.key, target, value=True)
|
|
768
693
|
|
|
769
694
|
def discard(self, target: Entity) -> None:
|
|
770
695
|
"""Discard a directly held relation target from this tag."""
|
|
771
|
-
registry
|
|
772
|
-
|
|
773
|
-
registry._relation_tags_by_entity[self.entity][self.key].discard(target)
|
|
774
|
-
if not registry._relation_tags_by_entity[self.entity][self.key]:
|
|
775
|
-
del registry._relation_tags_by_entity[self.entity][self.key]
|
|
776
|
-
if not registry._relation_tags_by_entity[self.entity]:
|
|
777
|
-
del registry._relation_tags_by_entity[self.entity]
|
|
778
|
-
|
|
779
|
-
_relations_lookup_discard(registry, self.entity, self.key, target)
|
|
696
|
+
self.entity.registry._discard_relation_data(self.entity, Tag(self.key), "RelationTag", self.key, target)
|
|
780
697
|
|
|
781
698
|
def remove(self, target: Entity) -> None:
|
|
782
699
|
"""Remove a directly held relation target from this tag.
|
|
783
700
|
|
|
784
|
-
This will raise KeyError
|
|
701
|
+
This will raise KeyError if only an indirect relation target exists.
|
|
785
702
|
"""
|
|
786
|
-
|
|
787
|
-
if relations is None:
|
|
788
|
-
raise KeyError(target)
|
|
789
|
-
targets = relations.get(self.key)
|
|
790
|
-
if targets is None or target not in targets:
|
|
703
|
+
if not self.entity.registry._discard_relation_data(self.entity, Tag(self.key), "RelationTag", self.key, target):
|
|
791
704
|
raise KeyError(target)
|
|
792
|
-
self.discard(target)
|
|
793
705
|
|
|
794
706
|
def __contains__(self, target: Entity) -> bool: # type: ignore[override]
|
|
795
707
|
"""Return True if this relation contains the given value."""
|
|
796
|
-
_relation_tags_by_entity = self.entity.registry.
|
|
708
|
+
_relation_tags_by_entity = self.entity.registry._any_by_key_by_entity_by_category["RelationTag"]
|
|
797
709
|
for entity in _traverse_entities(self.entity, self.traverse):
|
|
798
710
|
by_entity = _relation_tags_by_entity.get(entity)
|
|
799
711
|
if by_entity is None:
|
|
@@ -804,7 +716,7 @@ class EntityRelationsMapping(MutableSet[Entity]):
|
|
|
804
716
|
|
|
805
717
|
def _as_set(self) -> set[Entity]:
|
|
806
718
|
"""Return the combined targets of this mapping via traversal with duplicates removed."""
|
|
807
|
-
_relation_tags_by_entity = self.entity.registry.
|
|
719
|
+
_relation_tags_by_entity = self.entity.registry._any_by_key_by_entity_by_category["RelationTag"]
|
|
808
720
|
results: set[Entity] = set()
|
|
809
721
|
for entity in _traverse_entities(self.entity, self.traverse):
|
|
810
722
|
by_entity = _relation_tags_by_entity.get(entity)
|
|
@@ -823,7 +735,7 @@ class EntityRelationsMapping(MutableSet[Entity]):
|
|
|
823
735
|
|
|
824
736
|
def clear(self) -> None:
|
|
825
737
|
"""Discard all targets for this tag relation."""
|
|
826
|
-
by_entity = self.entity.registry.
|
|
738
|
+
by_entity = self.entity.registry._any_by_key_by_entity_by_category["RelationTag"].get(self.entity)
|
|
827
739
|
if by_entity is None:
|
|
828
740
|
return
|
|
829
741
|
for key in list(by_entity.get(self.key, ())):
|
|
@@ -869,7 +781,7 @@ class EntityRelations(MutableMapping[object, EntityRelationsMapping]):
|
|
|
869
781
|
|
|
870
782
|
def __iter__(self) -> Iterator[Any]:
|
|
871
783
|
"""Iterate over the unique relation tags of this entity."""
|
|
872
|
-
_relation_tags_by_entity = self.entity.registry.
|
|
784
|
+
_relation_tags_by_entity = self.entity.registry._any_by_key_by_entity_by_category["RelationTag"]
|
|
873
785
|
empty_dict: dict[object, set[Entity]] = {}
|
|
874
786
|
yield from set().union(
|
|
875
787
|
*(
|
|
@@ -884,7 +796,7 @@ class EntityRelations(MutableMapping[object, EntityRelationsMapping]):
|
|
|
884
796
|
|
|
885
797
|
def clear(self) -> None:
|
|
886
798
|
"""Discard all tag relations from an entity."""
|
|
887
|
-
for key in list(self.entity.registry.
|
|
799
|
+
for key in list(self.entity.registry._any_by_key_by_entity_by_category["RelationTag"].get(self.entity, ())):
|
|
888
800
|
del self[key]
|
|
889
801
|
|
|
890
802
|
|
|
@@ -911,7 +823,7 @@ class EntityRelationsExclusive(MutableMapping[object, Entity]):
|
|
|
911
823
|
If the relation has no target then raises KeyError.
|
|
912
824
|
If the relation is not exclusive then raises ValueError.
|
|
913
825
|
"""
|
|
914
|
-
_relation_tags_by_entity = self.entity.registry.
|
|
826
|
+
_relation_tags_by_entity = self.entity.registry._any_by_key_by_entity_by_category["RelationTag"]
|
|
915
827
|
for entity in _traverse_entities(self.entity, self.traverse):
|
|
916
828
|
by_entity = _relation_tags_by_entity.get(entity)
|
|
917
829
|
if by_entity is None:
|
|
@@ -977,7 +889,7 @@ class EntityComponentRelationMapping(MutableMapping[Entity, T], Generic[T]):
|
|
|
977
889
|
|
|
978
890
|
def __getitem__(self, target: Entity) -> T:
|
|
979
891
|
"""Return the component related to a target entity."""
|
|
980
|
-
_relation_components_by_entity = self.entity.registry.
|
|
892
|
+
_relation_components_by_entity = self.entity.registry._any_by_key_by_entity_by_category["RelationComponent"]
|
|
981
893
|
for entity in _traverse_entities(self.entity, self.traverse):
|
|
982
894
|
by_entity = _relation_components_by_entity.get(entity)
|
|
983
895
|
if by_entity is None:
|
|
@@ -992,38 +904,20 @@ class EntityComponentRelationMapping(MutableMapping[Entity, T], Generic[T]):
|
|
|
992
904
|
|
|
993
905
|
def __setitem__(self, target: Entity, component: T) -> None:
|
|
994
906
|
"""Assign a component to the target entity."""
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
if old_value is None: # Relation added
|
|
999
|
-
tcod.ecs.query._touch_relations(
|
|
1000
|
-
registry, ((self.key, target), (self.key, ...), (self.entity, self.key, None), (..., self.key, None))
|
|
1001
|
-
)
|
|
1002
|
-
|
|
1003
|
-
registry._relation_components_by_entity[self.entity][self.key][target] = component
|
|
1004
|
-
|
|
1005
|
-
_relations_lookup_add(registry, self.entity, self.key, target)
|
|
907
|
+
self.entity.registry._set_relation_data(
|
|
908
|
+
self.entity, Component.from_key(self.key), "RelationComponent", self.key, target, component
|
|
909
|
+
)
|
|
1006
910
|
|
|
1007
911
|
def __delitem__(self, target: Entity) -> None:
|
|
1008
912
|
"""Delete a component assigned to the target entity."""
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
raise KeyError(target)
|
|
1013
|
-
by_key = by_entity.get(self.key)
|
|
1014
|
-
if by_key is None:
|
|
913
|
+
if not self.entity.registry._discard_relation_data(
|
|
914
|
+
self.entity, Component.from_key(self.key), "RelationComponent", self.key, target
|
|
915
|
+
):
|
|
1015
916
|
raise KeyError(target)
|
|
1016
|
-
del by_key[target]
|
|
1017
|
-
if not by_key:
|
|
1018
|
-
del by_entity[self.key]
|
|
1019
|
-
if not by_entity:
|
|
1020
|
-
del registry._relation_components_by_entity[self.entity]
|
|
1021
|
-
|
|
1022
|
-
_relations_lookup_discard(registry, self.entity, self.key, target)
|
|
1023
917
|
|
|
1024
918
|
def keys(self) -> AbstractSet[Entity]: # type: ignore[override]
|
|
1025
919
|
"""Return all entities with an associated component value."""
|
|
1026
|
-
_relation_components_by_entity = self.entity.registry.
|
|
920
|
+
_relation_components_by_entity = self.entity.registry._any_by_key_by_entity_by_category["RelationComponent"]
|
|
1027
921
|
result: set[Entity] = set()
|
|
1028
922
|
for entity in _traverse_entities(self.entity, self.traverse):
|
|
1029
923
|
by_entity = _relation_components_by_entity.get(entity)
|
|
@@ -1107,12 +1001,14 @@ class EntityComponentRelations(MutableMapping[ComponentKey[Any], EntityComponent
|
|
|
1107
1001
|
|
|
1108
1002
|
Does not clear relations targeting this entity.
|
|
1109
1003
|
"""
|
|
1110
|
-
for component_key in list(
|
|
1004
|
+
for component_key in list(
|
|
1005
|
+
self.entity.registry._any_by_key_by_entity_by_category["RelationComponent"].get(self.entity, ())
|
|
1006
|
+
):
|
|
1111
1007
|
self[component_key].clear()
|
|
1112
1008
|
|
|
1113
1009
|
def keys(self) -> AbstractSet[ComponentKey[object]]: # type: ignore[override]
|
|
1114
1010
|
"""Returns the components keys this entity has relations for."""
|
|
1115
|
-
_relation_components_by_entity = self.entity.registry.
|
|
1011
|
+
_relation_components_by_entity = self.entity.registry._any_by_key_by_entity_by_category["RelationComponent"]
|
|
1116
1012
|
return set().union(
|
|
1117
1013
|
*(
|
|
1118
1014
|
_relation_components_by_entity.get(entity, ())
|
|
@@ -13,6 +13,7 @@ from typing_extensions import Self, deprecated
|
|
|
13
13
|
|
|
14
14
|
import tcod.ecs.entity
|
|
15
15
|
from tcod.ecs.constants import IsA
|
|
16
|
+
from tcod.ecs.traits import Component, Relation, traits_from_query
|
|
16
17
|
|
|
17
18
|
if TYPE_CHECKING:
|
|
18
19
|
from collections.abc import Iterable, Iterator
|
|
@@ -20,7 +21,7 @@ if TYPE_CHECKING:
|
|
|
20
21
|
|
|
21
22
|
from tcod.ecs.entity import Entity
|
|
22
23
|
from tcod.ecs.registry import Registry
|
|
23
|
-
from tcod.ecs.typing import ComponentKey, _RelationQuery
|
|
24
|
+
from tcod.ecs.typing import ComponentKey, _RelationQuery, _Trait
|
|
24
25
|
|
|
25
26
|
_T1 = TypeVar("_T1")
|
|
26
27
|
_T2 = TypeVar("_T2")
|
|
@@ -35,14 +36,8 @@ class _QueryCache:
|
|
|
35
36
|
|
|
36
37
|
queries: dict[_Query, AbstractSet[Entity]] = attrs.field(factory=dict)
|
|
37
38
|
"""Table of cached queries."""
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
)
|
|
41
|
-
"""Which queries depend on which components."""
|
|
42
|
-
by_tags: defaultdict[object, WeakSet[_Query]] = attrs.field(factory=lambda: defaultdict(WeakSet))
|
|
43
|
-
"""Which queries depend on which tags."""
|
|
44
|
-
by_relations: defaultdict[_RelationQuery, WeakSet[_Query]] = attrs.field(factory=lambda: defaultdict(WeakSet))
|
|
45
|
-
"""Which queries depend on which relations."""
|
|
39
|
+
by_traits: defaultdict[_Trait, WeakSet[_Query]] = attrs.field(factory=lambda: defaultdict(WeakSet))
|
|
40
|
+
"""Which queries depend on which traits."""
|
|
46
41
|
|
|
47
42
|
dependencies: dict[_Query, set[tuple[Registry, _Query]]] = attrs.field(factory=lambda: defaultdict(set))
|
|
48
43
|
"""Tracks which queries depend on the queries of the current registry.
|
|
@@ -50,40 +45,18 @@ class _QueryCache:
|
|
|
50
45
|
`dependencies[dependency] = {dependant}`
|
|
51
46
|
"""
|
|
52
47
|
|
|
48
|
+
def _drop_cached_query(self, query: _Query) -> None:
|
|
49
|
+
"""Drop a cached query and all of its dependant queries."""
|
|
50
|
+
self.queries.pop(query, None)
|
|
51
|
+
for sub_registry, sub_query in self.dependencies.pop(query, ()):
|
|
52
|
+
sub_registry._query_cache._drop_cached_query(sub_query)
|
|
53
53
|
|
|
54
|
-
def
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def _touch_component(registry: Registry, component: ComponentKey[object]) -> None:
|
|
62
|
-
"""Drop cached queries if a component change has invalidated them."""
|
|
63
|
-
cache = _get_query_cache(registry)
|
|
64
|
-
if component not in cache.by_components:
|
|
65
|
-
return
|
|
66
|
-
for touched_query in cache.by_components.pop(component, ()):
|
|
67
|
-
_drop_cached_query(cache, touched_query)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def _touch_tag(registry: Registry, tag: object) -> None:
|
|
71
|
-
"""Drop cached queries if a tag change has invalidated them."""
|
|
72
|
-
cache = _get_query_cache(registry)
|
|
73
|
-
if tag not in cache.by_tags:
|
|
74
|
-
return
|
|
75
|
-
for touched_query in cache.by_tags.pop(tag, ()):
|
|
76
|
-
_drop_cached_query(cache, touched_query)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def _touch_relations(registry: Registry, relations: Iterable[_RelationQuery]) -> None:
|
|
80
|
-
"""Drop cached queries if a relation change has invalidated them."""
|
|
81
|
-
cache = _get_query_cache(registry)
|
|
82
|
-
for relation in relations:
|
|
83
|
-
if relation not in cache.by_relations:
|
|
84
|
-
continue
|
|
85
|
-
for touched_query in cache.by_relations.pop(relation, ()):
|
|
86
|
-
_drop_cached_query(cache, touched_query)
|
|
54
|
+
def _touch_trait(self, trait: _Trait) -> None:
|
|
55
|
+
"""Drop cached queries if a trait change has invalidated them."""
|
|
56
|
+
if trait not in self.by_traits:
|
|
57
|
+
return
|
|
58
|
+
for touched_query in self.by_traits.pop(trait, ()):
|
|
59
|
+
self._drop_cached_query(touched_query)
|
|
87
60
|
|
|
88
61
|
|
|
89
62
|
def _check_suspicious_tags(tags: Iterable[object], stacklevel: int = 2) -> None:
|
|
@@ -106,29 +79,20 @@ def _fetch_relation_table(registry: Registry, relation: _RelationQuery) -> Abstr
|
|
|
106
79
|
|
|
107
80
|
For advanced cases `BoundQuery` this returns the subset of entities following the query condition.
|
|
108
81
|
"""
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
return registry._relations_lookup.get((origin, tag, target_none), frozenset())
|
|
119
|
-
|
|
120
|
-
registry = origin.registry
|
|
121
|
-
return set().union(*(registry._relations_lookup.get((entity, tag, None), ()) for entity in origin))
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def _get_query_cache(registry: Registry) -> _QueryCache:
|
|
125
|
-
"""Return the cache owned by the given registry."""
|
|
126
|
-
return registry._query_cache
|
|
82
|
+
trait: Relation[Any] = Relation._from_query(relation)
|
|
83
|
+
if not isinstance(trait.target, BoundQuery):
|
|
84
|
+
return registry._any_by_entity_by_trait.get(trait, {}).keys()
|
|
85
|
+
return set().union(
|
|
86
|
+
*(
|
|
87
|
+
registry._any_by_entity_by_trait.get(Relation(trait.trait, entity, trait.inverse), ())
|
|
88
|
+
for entity in trait.target
|
|
89
|
+
)
|
|
90
|
+
)
|
|
127
91
|
|
|
128
92
|
|
|
129
93
|
def _get_query(registry: Registry, query: _Query) -> AbstractSet[Entity]:
|
|
130
94
|
"""Return the entities for the given query and registry."""
|
|
131
|
-
cache =
|
|
95
|
+
cache = registry._query_cache
|
|
132
96
|
if cache is not None:
|
|
133
97
|
cached_entities = cache.queries.get(query)
|
|
134
98
|
if cached_entities is not None:
|
|
@@ -164,34 +128,21 @@ class _Query(Protocol):
|
|
|
164
128
|
...
|
|
165
129
|
|
|
166
130
|
def _compile(self, registry: Registry, cache: _QueryCache) -> AbstractSet[Entity]:
|
|
167
|
-
"""Compile the entities of this query, returning a set which must not be modified."""
|
|
131
|
+
"""Compile the entities of this query, returning a set which must not be modified by the caller."""
|
|
168
132
|
...
|
|
169
133
|
|
|
170
134
|
|
|
171
135
|
@attrs.define(frozen=True)
|
|
172
|
-
class
|
|
173
|
-
"""Query all entities with the given
|
|
174
|
-
|
|
175
|
-
_component: ComponentKey[object]
|
|
176
|
-
|
|
177
|
-
def _add_to_cache(self, registry: Registry, cache: _QueryCache) -> None: # noqa: ARG002
|
|
178
|
-
cache.by_components[self._component].add(self)
|
|
179
|
-
|
|
180
|
-
def _compile(self, registry: Registry, cache: _QueryCache) -> AbstractSet[Entity]: # noqa: ARG002
|
|
181
|
-
return registry._components_by_type.get(self._component, {}).keys()
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
@attrs.define(frozen=True)
|
|
185
|
-
class _QueryTag:
|
|
186
|
-
"""Query all entities with the given tag."""
|
|
136
|
+
class _QueryTrait:
|
|
137
|
+
"""Query all entities with the given trait."""
|
|
187
138
|
|
|
188
|
-
|
|
139
|
+
trait: _Trait
|
|
189
140
|
|
|
190
141
|
def _add_to_cache(self, registry: Registry, cache: _QueryCache) -> None: # noqa: ARG002
|
|
191
|
-
cache.
|
|
142
|
+
cache.by_traits[self.trait].add(self)
|
|
192
143
|
|
|
193
144
|
def _compile(self, registry: Registry, cache: _QueryCache) -> AbstractSet[Entity]: # noqa: ARG002
|
|
194
|
-
return registry.
|
|
145
|
+
return registry._any_by_entity_by_trait.get(self.trait, {}).keys()
|
|
195
146
|
|
|
196
147
|
|
|
197
148
|
@attrs.define(frozen=True)
|
|
@@ -202,19 +153,13 @@ class _QueryRelation:
|
|
|
202
153
|
|
|
203
154
|
def _add_to_cache(self, registry: Registry, cache: _QueryCache) -> None:
|
|
204
155
|
"""Add this query to the cache and mark it dependant on a registry query if the relation uses one."""
|
|
156
|
+
trait: Relation[Any] = Relation._from_query(self._relation)
|
|
205
157
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
return self._relation[-1]
|
|
212
|
-
return None
|
|
213
|
-
|
|
214
|
-
cache.by_relations[self._relation].add(self)
|
|
215
|
-
w_query = _get_registry_query()
|
|
216
|
-
if w_query is not None:
|
|
217
|
-
_get_query_cache(w_query.registry).dependencies[w_query._query].add((registry, self))
|
|
158
|
+
target_query = trait.target
|
|
159
|
+
if isinstance(target_query, BoundQuery):
|
|
160
|
+
target_query.registry._query_cache.dependencies[target_query._query].add((registry, self))
|
|
161
|
+
else:
|
|
162
|
+
cache.by_traits[trait].add(self)
|
|
218
163
|
|
|
219
164
|
def _compile(self, registry: Registry, cache: _QueryCache) -> AbstractSet[Entity]: # noqa: ARG002
|
|
220
165
|
return _fetch_relation_table(registry, self._relation)
|
|
@@ -302,10 +247,9 @@ class _QueryTraversalPropagation:
|
|
|
302
247
|
while unchecked_set and (self._max_depth is None or depth < self._max_depth):
|
|
303
248
|
depth += 1
|
|
304
249
|
new_set: set[Entity] = set()
|
|
305
|
-
empty_set: frozenset[Entity] = frozenset()
|
|
306
250
|
for traverse_key in self._traverse_keys:
|
|
307
251
|
for unchecked in unchecked_set:
|
|
308
|
-
new_set |= registry.
|
|
252
|
+
new_set |= registry._any_by_entity_by_trait.get(Relation(traverse_key, unchecked), {}).keys()
|
|
309
253
|
new_set -= cumulative_set
|
|
310
254
|
cumulative_set |= new_set
|
|
311
255
|
unchecked_set = new_set
|
|
@@ -355,8 +299,10 @@ class BoundQuery:
|
|
|
355
299
|
) -> Iterator[_Query]:
|
|
356
300
|
"""Convert parameters into queries."""
|
|
357
301
|
traverse = tuple(traverse)
|
|
358
|
-
yield from (
|
|
359
|
-
|
|
302
|
+
yield from (
|
|
303
|
+
_QueryTraversalPropagation(_QueryTrait(trait), traverse, depth)
|
|
304
|
+
for trait in traits_from_query(components=components, tags=tags)
|
|
305
|
+
)
|
|
360
306
|
yield from (_QueryTraversalPropagation(_QueryRelation(relations), traverse, depth) for relations in relations)
|
|
361
307
|
|
|
362
308
|
def all_of(
|
|
@@ -459,7 +405,7 @@ class BoundQuery:
|
|
|
459
405
|
if component_key is Entity:
|
|
460
406
|
entity_components.append(entities)
|
|
461
407
|
continue
|
|
462
|
-
registry_components = self.registry.
|
|
408
|
+
registry_components = self.registry._any_by_entity_by_trait.get(Component.from_key(component_key), {})
|
|
463
409
|
try:
|
|
464
410
|
entity_components.append([registry_components[entity] for entity in entities])
|
|
465
411
|
except KeyError: # Some matching entities inherit this component.
|