Source code for ecs.managers
"""Entity and System Managers."""
from ecs.exceptions import (
NonexistentComponentTypeForEntity, DuplicateSystemTypeError)
from ecs.models import Entity
[docs]class EntityManager(object):
"""Provide database-like access to components based on an entity key."""
def __init__(self):
self._database = {}
self._next_guid = 0
@property
[docs] def database(self):
"""Get this manager's database. Direct modification is not
permitted.
:return: the database
:rtype: :class:`dict`
"""
return self._database
[docs] def create_entity(self):
"""Return a new entity instance with the current lowest GUID value.
Does not store a reference to it, and does not make any entries in the
database referencing it.
:return: the new entity
:rtype: :class:`ecs.models.Entity`
"""
entity = Entity(self._next_guid)
self._next_guid += 1
return entity
[docs] def add_component(self, entity_id, component_instance):
"""Add a component to the database and associates it with the given
``entity_id``. ``entity_id`` can be an :class:`ecs.models.Entity`
object or a plain :class:`int`.
:param entity_id: GUID of the entity
:type entity_id: :class:`int` or :class:`ecs.models.Entity`
:param component_instance: component to add to the entity
:type component_instance: :class:`ecs.models.Component`
"""
component_type = type(component_instance)
if component_type not in self._database:
self._database[component_type] = {}
self._database[component_type][entity_id] = component_instance
[docs] def remove_component(self, entity_id, component_type):
"""Remove the component of ``component_type`` associated with
``entity_id`` from the database. Doesn't do any kind of data-teardown.
It is up to the system calling this code to do that. In the future,
a callback system may be used to implement type-specific destructors.
:param entity_id: GUID of the entity
:type entity_id: :class:`int`
:param component_type: component to remove from the entity
:type component_type: :class:`ecs.models.Component`
"""
try:
del self._database[component_type][entity_id]
if self._database[component_type] == {}:
del self._database[component_type]
except KeyError:
pass
[docs] def pairs_for_type(self, component_type):
"""Return a list of ``(entity_id, component_instance)`` tuples for all
entities in the database possessing a component of ``component_type``.
Return an empty list if there are no components of this type in the
database. Can use in a loop like this, where ``Renderable`` is a
component type:
.. code-block:: python
for entity, renderable_component in \
entity_manager.pairs_for_type(Renderable):
pass # do something
:param component_type: a type of created component
:type component_type: :class:`type`
:return: list of ``(entity_id, component_instance)`` tuples
:rtype: :class:`tuple` of (:class:`int`, :class:`ecs.models.Component`)
"""
try:
return self._database[component_type].items()
except KeyError:
return []
[docs] def component_for_entity(self, entity_id, component_type):
"""Return the instance of ``component_type`` for the ``entity_id``
from the database.
:param entity_id: entity GUID
:type entity_id: :class:`int`
:param component_type: a type of created component
:type component_type: :class:`type`
:return: list of ``(entity_id, component_instance)`` tuples
:rtype: :class:`tuple` of (:class:`int`, :class:`ecs.models.Component`)
:raises: :exc:`NonexistentComponentTypeForEntity` when \
``component_type`` does not exist on ``entity_instance``
"""
try:
return self._database[component_type][entity_id]
except KeyError:
raise NonexistentComponentTypeForEntity(
entity_id, component_type)
[docs] def remove_entity(self, entity_id):
"""Remove all components from the database that are associated with
``entity_id``, with the side-effect that the entity is also no longer
in the database.
:param entity_id: entity GUID
:type entity_id: :class:`int`
"""
# Don't use iterkeys(), otherwise we will get a RuntimeError about
# mutating the length of the dictionary at runtime.
for comp_type in self._database.keys():
try:
del self._database[comp_type][entity_id]
if self._database[comp_type] == {}:
del self._database[comp_type]
except KeyError:
pass
[docs]class SystemManager(object):
"""A container and manager for :class:`ecs.models.System` objects."""
def __init__(self):
self._systems = []
self._system_types = {}
# Allow getting the list of systems but not directly setting it.
@property
[docs] def systems(self):
"""Get this manager's list of systems.
:return: system list
:rtype: :class:`list` of :class:`ecs.models.System`
"""
return self._systems
[docs] def add_system(self, system_instance):
"""Add a :class:`ecs.models.System` instance to the manager.
:param system_instance: instance of a system
:type system_instance: :class:`ecs.models.System`
"""
system_type = type(system_instance)
if system_type in self._system_types:
raise DuplicateSystemTypeError(system_type)
self._system_types[system_type] = system_instance
self._systems.append(system_instance)
[docs] def remove_system(self, system_type):
"""Tell the manager to no longer run the system of this type.
:param system_type: type of system to remove
:type system_type: :class:`type`
"""
self._systems.remove(self._system_types[system_type])
del self._system_types[system_type]
[docs] def update(self, dt):
"""Run all systems in order, for this frame.
:param dt: delta time, or elapsed time for this frame
:type dt: :class:`float`
"""
# Iterating over a list of systems instead of values in a dictionary is
# noticeably faster. We maintain a list in addition to a dictionary
# specifically for this purpose.
for system in self._systems:
system.update(dt)