Skip to content

Commit a085410

Browse files
committed
Fix EntityComponents.pop
Update docs for instantiate
1 parent 6f4cd36 commit a085410

File tree

2 files changed

+63
-6
lines changed

2 files changed

+63
-6
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Fixed
11+
12+
- `EntityComponents.pop` now correctly returns defaults when the components are inherited instead of local.
13+
1014
## [5.2.1] - 2024-07-30
1115

1216
### Fixed

tcod/ecs/entity.py

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from weakref import WeakKeyDictionary, WeakValueDictionary
2323

2424
import attrs
25+
from sentinel_value import sentinel
2526
from typing_extensions import Self
2627

2728
import tcod.ecs.callbacks
@@ -41,6 +42,8 @@
4142
_T1 = TypeVar("_T1")
4243
_T2 = TypeVar("_T2")
4344

45+
_raise: Final = sentinel("_raise")
46+
4447
_entity_table: WeakKeyDictionary[Registry, WeakValueDictionary[object, Entity]] = WeakKeyDictionary()
4548
"""A weak table of registries and unique identifiers to entity objects.
4649
@@ -134,6 +137,10 @@ def instantiate(self) -> Self:
134137
This creates a new unique entity and assigns an :any:`IsA` relationship with `self` to the new entity.
135138
The :any:`IsA` relation is the only data this new entity directly holds.
136139
140+
Immutable components inherited from the parent are always copy-on-write for its child instances.
141+
142+
Keep in mind that components/tags/relations inherited from the parent are not removable from the child instance.
143+
137144
Example::
138145
139146
# 'child = entity.instantiate()' is equivalent to the following:
@@ -149,14 +156,18 @@ def instantiate(self) -> Self:
149156
>>> child.components[str] # Inherits components from parent
150157
'baz'
151158
>>> parent.components[str] = "foo"
152-
>>> child.components[str] # Changes in parent and reflected in children
159+
>>> child.components[str] # Changes in parent are reflected in children
153160
'foo'
154161
>>> child.components[str] += "bar" # In-place assignment operators will copy-on-write immutable objects
155162
>>> child.components[str]
156163
'foobar'
157164
>>> parent.components[str]
158165
'foo'
159-
>>> del child.components[str]
166+
>>> child.components.pop(str, None) # Revert the component to the inherited value
167+
'foobar'
168+
>>> child.components[str]
169+
'foo'
170+
>>> child.components.pop(str, None) # Safe to call .pop with default when the value isn't set on the child
160171
>>> child.components[str]
161172
'foo'
162173
@@ -530,16 +541,16 @@ def __ior__(
530541
return self
531542

532543
@overload
533-
def get(self, __key: ComponentKey[T]) -> T | None: ...
544+
def get(self, __key: ComponentKey[T], /) -> T | None: ...
534545
@overload
535-
def get(self, __key: ComponentKey[T], __default: _T1) -> T | _T1: ...
546+
def get(self, __key: ComponentKey[T], /, default: _T1) -> T | _T1: ...
536547

537-
def get(self, __key: ComponentKey[T], __default: _T1 | None = None) -> T | _T1:
548+
def get(self, __key: ComponentKey[T], /, default: _T1 | None = None) -> T | _T1:
538549
"""Return a component, returns None or a default value when the component is missing."""
539550
try:
540551
return self[__key]
541552
except KeyError:
542-
return __default # type: ignore[return-value] # https://github.com/python/mypy/issues/3737
553+
return default # type: ignore[return-value] # https://github.com/python/mypy/issues/3737
543554

544555
def setdefault(self, __key: ComponentKey[T], __default: T) -> T: # type: ignore[override]
545556
"""Assign a default value if a component is missing, then returns the current value."""
@@ -549,6 +560,48 @@ def setdefault(self, __key: ComponentKey[T], __default: T) -> T: # type: ignore
549560
self[__key] = __default
550561
return __default
551562

563+
@overload
564+
def pop(self, __key: ComponentKey[T], /) -> T | None: ...
565+
@overload
566+
def pop(self, __key: ComponentKey[T], /, default: _T1) -> T | _T1: ...
567+
568+
def pop(
569+
self,
570+
__key: ComponentKey[T],
571+
/,
572+
default: _T1 = _raise, # type: ignore[assignment] # https://github.com/python/mypy/issues/3737
573+
) -> T | _T1:
574+
"""Remove a component directly from this entity.
575+
576+
Returns the removed value.
577+
If the value is missing returns `default`.
578+
If `default` is unset then raises :any:`KeyError` instead.
579+
580+
Operates directly on the entity without traversal.
581+
582+
>>> parent = registry[object()]
583+
>>> parent.components[str] = "foo"
584+
>>> child = parent.instantiate()
585+
>>> child.components[str] = "bar"
586+
>>> child.components.pop(str, None)
587+
'bar'
588+
>>> child.components.pop(str, None)
589+
>>> child.components[str]
590+
'foo'
591+
>>> child.components.pop(str)
592+
Traceback (most recent call last):
593+
...
594+
KeyError: <class 'str'>
595+
"""
596+
_components = self.entity.registry._components_by_entity.get(self.entity, {})
597+
if __key not in _components:
598+
if default is _raise:
599+
raise KeyError(__key)
600+
return default
601+
value: T | _T1 = _components[__key]
602+
del self[__key]
603+
return value
604+
552605

553606
@attrs.define(eq=False, frozen=True, weakref_slot=False)
554607
class EntityTags(MutableSet[Any]):

0 commit comments

Comments
 (0)