From 7bf19c20e292620e96f0dfb10f5f752fc460d3f9 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 9 Apr 2025 14:17:47 -0700 Subject: [PATCH] Add TypeForm support Converts containers to use TypeForm instead of Type. This allows for type expressions to be used as component types. --- pyproject.toml | 2 +- tcod/ecs/entity.py | 18 ++++++++---------- tcod/ecs/registry.py | 6 +++--- tcod/ecs/typing.py | 6 +++--- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d522698..af078e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ dependencies = [ "attrs >=23.1.0", "cattrs >=23.1.2", "sentinel-value >=1.0.0", - "typing-extensions >=4.9.0", + "typing-extensions >=4.13.1", ] [tool.setuptools_scm] diff --git a/tcod/ecs/entity.py b/tcod/ecs/entity.py index 6e28126..db2213d 100644 --- a/tcod/ecs/entity.py +++ b/tcod/ecs/entity.py @@ -13,7 +13,6 @@ MutableMapping, MutableSet, Tuple, - Type, TypeVar, Union, overload, @@ -22,7 +21,7 @@ import attrs from sentinel_value import sentinel -from typing_extensions import Self, deprecated +from typing_extensions import Self, TypeForm, deprecated import tcod.ecs.callbacks import tcod.ecs.query @@ -389,7 +388,7 @@ def _traverse_entities(start: Entity, traverse_parents: tuple[object, ...]) -> I @attrs.define(eq=False, frozen=True, weakref_slot=False) -class EntityComponents(MutableMapping[Union[Type[Any], Tuple[object, Type[Any]]], object]): +class EntityComponents(MutableMapping[Union[TypeForm[Any], Tuple[object, TypeForm[Any]]], Any]): """A proxy attribute to access an entities components like a dictionary. See :any:`Entity.components`. @@ -474,7 +473,7 @@ def keys(self) -> AbstractSet[ComponentKey[object]]: # type: ignore[override] *(_components_by_entity.get(entity, ()) for entity in _traverse_entities(self.entity, self.traverse)) ) - def __contains__(self, key: ComponentKey[object]) -> bool: # type: ignore[override] + def __contains__(self, key: ComponentKey[object]) -> bool: """Return True if this entity has the provided component.""" _components_by_entity = self.entity.registry._components_by_entity return any( @@ -500,7 +499,7 @@ def update_values(self, values: Iterable[object]) -> None: self.set(value) @deprecated("This method has been deprecated. Iterate over items instead.", category=FutureWarning) - def by_name_type(self, name_type: type[_T1], component_type: type[_T2]) -> Iterator[tuple[_T1, type[_T2]]]: + def by_name_type(self, name_type: TypeForm[_T1], component_type: TypeForm[_T2]) -> Iterator[tuple[_T1, type[_T2]]]: """Iterate over all of an entities component keys with a specific (name_type, component_type) combination. .. versionadded:: 3.0 @@ -508,13 +507,12 @@ def by_name_type(self, name_type: type[_T1], component_type: type[_T2]) -> Itera .. deprecated:: 3.1 This method has been deprecated. Iterate over items instead. """ - # Naive implementation until I feel like optimizing it for key in self: if not isinstance(key, tuple): continue key_name, key_component = key if key_component is component_type and isinstance(key_name, name_type): - yield key_name, key_component + yield key_name, key_component # type: ignore[unused-ignore] # Too complex for PyLance, deprecated anyways @overload def __ior__(self, value: SupportsKeysAndGetItem[ComponentKey[Any], Any]) -> Self: ... @@ -544,7 +542,7 @@ def get(self, __key: ComponentKey[T], /, default: _T1 | None = None) -> T | _T1: except KeyError: return default # type: ignore[return-value] # https://github.com/python/mypy/issues/3737 - def setdefault(self, __key: ComponentKey[T], __default: T, /) -> T: + def setdefault(self, __key: ComponentKey[T], __default: T, /) -> T: # type: ignore[override] # Does not allow None """Assign a default value if a component is missing, then returns the current value.""" try: return self[__key] @@ -1032,14 +1030,14 @@ def __getitem__(self, key: ComponentKey[T]) -> EntityComponentRelationMapping[T] """Access relations for this component key as a `{target: component}` dict-like object.""" return EntityComponentRelationMapping(self.entity, key, self.traverse) - def __setitem__(self, __key: ComponentKey[T], __values: Mapping[Entity, object], /) -> None: + def __setitem__(self, __key: ComponentKey[T], __values: Mapping[Entity, T], /) -> None: """Redefine the component relations for this entity. ..versionadded:: 4.2.0 """ if isinstance(__values, EntityComponentRelationMapping) and __values.entity is self.entity: return - mapping: EntityComponentRelationMapping[object] = self[__key] + mapping: EntityComponentRelationMapping[T] = self[__key] mapping.clear() for target, component in __values.items(): mapping[target] = component diff --git a/tcod/ecs/registry.py b/tcod/ecs/registry.py index d0a7d28..99176f5 100644 --- a/tcod/ecs/registry.py +++ b/tcod/ecs/registry.py @@ -32,10 +32,10 @@ def _defaultdict_of_dict() -> defaultdict[_T1, dict[_T2, _T3]]: def _components_by_entity_from( - by_type: defaultdict[ComponentKey[object], dict[Entity, Any]], -) -> defaultdict[Entity, dict[ComponentKey[object], Any]]: + by_type: defaultdict[ComponentKey[_T1], dict[Entity, _T1]], +) -> defaultdict[Entity, dict[ComponentKey[_T1], _T1]]: """Return the component lookup table from the components sparse-set.""" - by_entity: defaultdict[Entity, dict[ComponentKey[object], Any]] = defaultdict(dict) + by_entity: defaultdict[Entity, dict[ComponentKey[_T1], _T1]] = defaultdict(dict) for component_key, components in by_type.items(): for entity, component in components.items(): by_entity[entity][component_key] = component diff --git a/tcod/ecs/typing.py b/tcod/ecs/typing.py index 6a52595..a18095d 100644 --- a/tcod/ecs/typing.py +++ b/tcod/ecs/typing.py @@ -4,9 +4,9 @@ import sys import types -from typing import TYPE_CHECKING, Any, Tuple, Type, TypeVar, Union +from typing import TYPE_CHECKING, Any, Tuple, TypeVar, Union -from typing_extensions import TypeAlias +from typing_extensions import TypeAlias, TypeForm if TYPE_CHECKING: from tcod.ecs.entity import Entity @@ -22,7 +22,7 @@ _T = TypeVar("_T") -ComponentKey: TypeAlias = Union[Type[_T], Tuple[object, Type[_T]]] +ComponentKey: TypeAlias = Union[TypeForm[_T], Tuple[object, TypeForm[_T]]] """ComponentKey is plain `type` or tuple `(tag, type)`.""" _RelationTargetLookup: TypeAlias = Union[Entity, EllipsisType]