API reference
Entity/Component containers for implementing composition over inheritance.
Unlike with ECS, these containers are standalone. This makes them simpler to use but they have fewer features.
- class tcod.ec.ComponentDict(components=())
Bases:
objectA dictionary of component instances, addressed with their class as the key.
This class implements the idea of a
Dict[Type[T], T]type-hint. This allows adding data and behavior to an object without needing to define which classes the object holds ahead of time.>>> import attrs >>> from tcod.ec import ComponentDict >>> @attrs.define ... class Position: # Any normal class works as a component. ... x: int = 0 ... y: int = 0 >>> entity = ComponentDict([Position()]) # Add Position during initialization. >>> entity.set(Position()) # Or with ComponentDict.set. ComponentDict([Position(x=0, y=0)]) >>> entity[Position] = Position() # Or explicitly by key. >>> entity[Position] # Access the instance with the class as the key. Position(x=0, y=0) >>> {Position} in entity # Test if an entity has a set of components. True >>> @attrs.define ... class Cursor(Position): # If you need to store a 2nd Position then a subclass can be made. ... pass >>> entity[Cursor] = Cursor() >>> entity ComponentDict([Position(x=0, y=0), Cursor(x=0, y=0)]) >>> ComponentDict([Position(1, 2), Position(3, 4)]) # The same component always overwrites the previous one. ComponentDict([Position(x=3, y=4)])
Custom functions can be added to the class variable
global_observersto trigger side-effects on component assignment. This can be used to register components to a global system, handle save migration, or other effects:>>> @attrs.define(frozen=True) ... class Position(): ... x: int = 0 ... y: int = 0 >>> def print_changes(entity: ComponentDict, kind: Type[Any], value: Any | None, old_value: Any | None) -> None: ... print(f"{kind.__name__}: {old_value} -> {value}") >>> ComponentDict.global_observers.append(print_changes) >>> entity = ComponentDict([Position()]) Position: None -> Position(x=0, y=0) >>> entity.set(Position(1, 2)) Position: Position(x=0, y=0) -> Position(x=1, y=2) ComponentDict([Position(x=1, y=2)]) >>> del entity[Position] Position: Position(x=1, y=2) -> None >>> ComponentDict.global_observers.remove(print_changes)
- Parameters
components (Iterable[object]) –
- __contains__(keys)
Return true if the types of component exist in this entity. Takes a single type or an iterable of types.
Changed in version 1.2: Now supports checking multiple types at once.
- __getitem__(key)
Return a component of type, raises KeyError if it doesn’t exist.
- Parameters
key (Type[T]) –
- Return type
T
- __getstate__()
Pickle this instance. Any subclass slots and dict attributes will also be saved.
- __missing__(key)
Called when a key is missing. Raises KeyError with the missing key.
Example:
class DefaultComponentDict(ComponentDict): __slots__ = () def __missing__(self, key: Type[T]) -> T: """Create default components for missing keys by calling `key` without parameters.""" self[key] = value = key() return value
- Parameters
key (Type[T]) –
- Return type
T
- __setitem__(key, value)
Set or replace a component.
- Parameters
key (Type[T]) –
value (T) –
- Return type
None
- __setstate__(state)
Unpickle instances from 1.0 or later, or complex subclasses from 2.0 or later.
Component classes can change between picking and unpickling, and component order should be preserved.
Side-effects are triggered. The unpickled object acts as if its components are newly assigned to it when observed.
- Parameters
state (Any | Dict[str, Any]) –
- Return type
None
- clear()
Remove all components from this container.
New in version 2.0.
- Return type
None
- get(key)
Return a component, or None if it doesn’t exist.
- global_observers: ClassVar[List[Callable[[ComponentDict, Type[Any], Optional[Any], Optional[Any]], None]]] = []
A class variable list of functions to call with component changes.
Unpickled and copied objects are observed as if their components are newly created.
These work best with frozen immutable types as components if you want to observe all value changes.
This can be used to improvise the “systems” of ECS. Observers can collect types of components in a global registry for example.
Example:
from typing import Any, Type import tcod.ec def my_observer(entity: ComponentDict, kind: Type[Any], value: Any | None, old_value: Any | None) -> None: """Print observed changes in components.""" print(f"{entity=}, {kind=}, {value=}, {old_value=}") tcod.ec.ComponentDict.global_observers.append(my_observer)
New in version 2.0.
Warning
Components in a garbage collected entity are not observed as being deleted. Use
clearwhen you are finished with an entity and want its components observed as being deleted.
- class tcod.ec.Composite(components=())
Bases:
objectA collection of multiple components organized by their inheritance tree.
Allows multiple components for the same class and allows accessing components using a shared parent class.
The order of components is preserved.
>>> import attrs >>> from tcod.ec import Composite >>> @attrs.define ... class AreaOfEffect: ... range: int >>> @attrs.define ... class Circle(AreaOfEffect): ... pass >>> @attrs.define ... class Square(AreaOfEffect): ... pass >>> spell = Composite([50, "fire", "damage", Circle(5)]) >>> spell[int] [50] >>> spell[str] ['fire', 'damage'] >>> spell[AreaOfEffect] [Circle(range=5)] >>> spell[Circle] [Circle(range=5)] >>> spell[Square] () >>> spell[object] [50, 'fire', 'damage', Circle(range=5)] >>> spell.remove('damage') >>> spell.add("effect") >>> spell Composite([50, 'fire', Circle(range=5), 'effect']) >>> spell[int] = (20,) >>> spell[int] [20] >>> spell Composite(['fire', Circle(range=5), 'effect', 20]) >>> spell[AreaOfEffect] = (Square(3),) >>> spell Composite(['fire', 'effect', 20, Square(range=3)])
New in version Unreleased.
- Parameters
components (Iterable[object]) –
- __contains__(keys)
Return true if all types or sub-types of keys exist in this entity.
Takes a single type or an iterable of types.
- __delitem__(key)
Remove all instances of key if they exist.
- __getitem__(key)
Return a sequence of all instances of key.
If no instances of key are stored then return an empty sequence.
The actual list returned is internal and should not be saved. Copy the value with
tupleorlistif you intend to store the sequence. Do not modify the sequence.
- __getstate__()
Pickle this instance. Any subclass slots and dict attributes will also be saved.
- __setitem__(key, values)
Replace all instances of key with the instances of values.
- __setstate__(state)
Unpickle instances of this object.
Any class changes in pickled components will be reflected correctly.
- clear()
Clear all components from this container.
- Return type
None
- extend(components)
Add multiple components to this container.
- tcod.ec.abstract_component(cls)
Register class cls as an abstract component and return it.
Subclasses of this cls will now use cls as the key when being accessed in
ComponentDict. This means that ComponentDict can only hold one unique instance of this subclass.Deprecated since version 2.0: This method of handling abstract components was deemed unnecessary. Instead a new component should be made to hold the base class, for example:
>>> from tcod.ec import ComponentDict >>> import attrs >>> @attrs.define ... class Base: ... pass >>> @attrs.define ... class Derived(Base): ... pass >>> @attrs.define ... class Abstract(Base): ... value: Base >>> ComponentDict([Abstract(Derived())]) ComponentDict([Abstract(value=Derived())])
Example:
>>> from tcod.ec import ComponentDict, abstract_component >>> from attrs import define >>> @abstract_component ... @define ... class Base: ... pass >>> @define ... class Derived(Base): ... pass >>> entity = ComponentDict([Derived()]) >>> entity.set(Derived()) ComponentDict([Derived()]) >>> entity[Base] = Derived() # Note there can only be one instance assigned to an abstract component class. >>> Base in entity True >>> entity[Base] # Access Base or Derived with the abstract component class. Derived() >>> entity[Base] = Base() >>> entity[Base] Base() >>> entity.set(Derived()) ComponentDict([Derived()]) >>> entity[Base] Derived()