Mercurial > parpg-core
diff src/parpg/bGrease/world.py @ 92:0f659c7675f6
Changed "import bGrease" to "import parpg.bGrease".
author | KarstenBock@gmx.net |
---|---|
date | Wed, 21 Sep 2011 16:10:14 +0200 |
parents | 96af64cf3b81 |
children |
line wrap: on
line diff
--- a/src/parpg/bGrease/world.py Wed Sep 21 15:43:12 2011 +0200 +++ b/src/parpg/bGrease/world.py Wed Sep 21 16:10:14 2011 +0200 @@ -1,302 +1,301 @@ -############################################################################# -# -# Copyright (c) 2010 by Casey Duncan and contributors -# All Rights Reserved. -# -# This software is subject to the provisions of the MIT License -# A copy of the license should accompany this distribution. -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# -############################################################################# -"""Worlds are environments described by a configuration of components, systems and -renderers. These parts describe the data, behavioral and presentation aspects -of the world respectively. - -The world environment is the context within which entities exist. A typical -application consists of one or more worlds containing entities that evolve -over time and react to internal and external interaction. - -See :ref:`an example of world configuration in the tutorial <tut-world-example>`. -""" - -__version__ = '$Id$' - -import itertools -from bGrease import mode -from bGrease.component import ComponentError -from bGrease.entity import Entity, ComponentEntitySet - - -class BaseWorld(object): - """A coordinated collection of components, systems and entities - - A world is also a mode that may be pushed onto a - :class:`grease.mode.Manager` - """ - - components = None - """:class:`ComponentParts` object containing all world components. - :class:`grease.component.Component` objects define and contain all entity data - """ - - systems = None - """:class:`Parts` object containing all world systems. - :class:`grease.System` objects define world and entity behavior - """ - - renderers = None - """:class:`Parts` object containing all world renderers. - :class:`grease.Renderer` objects define world presentation - """ - - entities = None - """Set of all entities that exist in the world""" - - def __init__(self): - self.components = ComponentParts(self) - self.systems = Parts(self) - self.renderers = Parts(self) - self.new_entity_id = itertools.count().next - self.new_entity_id() # skip id 0 - self.entities = WorldEntitySet(self) - self._full_extent = EntityExtent(self, self.entities) - self._extents = {} - self.configure() - - def configure(self): - """Hook to configure the world after construction. This method - is called immediately after the world is initialized. Override - in a subclass to configure the world's components, systems, - and renderers. - - The default implementation does nothing. - """ - - def __getitem__(self, entity_class): - """Return an :class:`EntityExtent` for the given entity class. This extent - can be used to access the set of entities of that class in the world - or to query these entities via their components. - - Examples:: - - world[MyEntity] - world[...] - - :param entity_class: The entity class for the extent. - - May also be a tuple of entity classes, in which case - the extent returned contains union of all entities of the classes - in the world. - - May also be the special value ellipsis (``...``), which - returns an extent containing all entities in the world. This allows - you to conveniently query all entities using ``world[...]``. - """ - if isinstance(entity_class, tuple): - entities = set() - for cls in entity_class: - if cls in self._extents: - entities |= self._extents[cls].entities - return EntityExtent(self, entities) - elif entity_class is Ellipsis: - return self._full_extent - try: - return self._extents[entity_class] - except KeyError: - extent = self._extents[entity_class] = EntityExtent(self, set()) - return extent - - def draw_renderers(self): - """Draw all renderers""" - for renderer in self.renderers: - renderer.draw() - -class WorldEntitySet(set): - """Entity set for a :class:`World`""" - - def __init__(self, world): - self.world = world - - def add(self, entity): - """Add the entity to the set and all necessary class sets - Return the unique entity id for the entity, creating one - as needed. - """ - super(WorldEntitySet, self).add(entity) - for cls in entity.__class__.__mro__: - if issubclass(cls, Entity): - self.world[cls].entities.add(entity) - - def remove(self, entity): - """Remove the entity from the set and, world components, - and all necessary class sets - """ - super(WorldEntitySet, self).remove(entity) - for component in self.world.components: - try: - del component[entity] - except KeyError: - pass - for cls in entity.__class__.__mro__: - if issubclass(cls, Entity): - self.world[cls].entities.discard(entity) - - def discard(self, entity): - """Remove the entity from the set if it exists, if not, - do nothing - """ - try: - self.remove(entity) - except KeyError: - pass - - -class EntityExtent(object): - """Encapsulates a set of entities queriable by component. Extents - are accessed by using an entity class as a key on the :class:`World`:: - - extent = world[MyEntity] - """ - - entities = None - """The full set of entities in the extent""" - - def __init__(self, world, entities): - self.__world = world - self.entities = entities - - def __getattr__(self, name): - """Return a queriable :class:`ComponentEntitySet` for the named component - - Example:: - - world[MyEntity].movement.velocity > (0, 0) - - Returns a set of entities where the value of the :attr:`velocity` field - of the :attr:`movement` component is greater than ``(0, 0)``. - """ - component = getattr(self.__world.components, name) - return ComponentEntitySet(component, self.entities & component.entities) - - -class Parts(object): - """Maps world parts to attributes. The parts are kept in the - order they are set. Parts may also be inserted out of order. - - Used for: - - - :attr:`World.systems` - - :attr:`World.renderers` - """ - - _world = None - _parts = None - _reserved_names = ('entities', 'entity_id', 'world') - - def __init__(self, world): - self._world = world - self._parts = [] - - def _validate_name(self, name): - if (name in self._reserved_names or name.startswith('_') - or hasattr(self.__class__, name)): - raise ComponentError('illegal part name: %s' % name) - return name - - def __setattr__(self, name, part): - if not hasattr(self.__class__, name): - self._validate_name(name) - if not hasattr(self, name): - self._parts.append(part) - else: - old_part = getattr(self, name) - self._parts[self._parts.index(old_part)] = part - super(Parts, self).__setattr__(name, part) - if hasattr(part, 'set_world'): - part.set_world(self._world) - elif name.startswith("_"): - super(Parts, self).__setattr__(name, part) - else: - raise AttributeError("%s attribute is read only" % name) - - def __delattr__(self, name): - self._validate_name(name) - part = getattr(self, name) - self._parts.remove(part) - super(Parts, self).__delattr__(name) - - def insert(self, name, part, before=None, index=None): - """Add a part with a particular name at a particular index. - If a part by that name already exists, it is replaced. - - :arg name: The name of the part. - :type name: str - - :arg part: The component, system, or renderer part to insert - - :arg before: A part object or name. If specified, the part is - inserted before the specified part in order. - - :arg index: If specified, the part is inserted in the position - specified. You cannot specify both before and index. - :type index: int - """ - assert before is not None or index is not None, ( - "Must specify a value for 'before' or 'index'") - assert before is None or index is None, ( - "Cannot specify both 'before' and 'index' arguments when inserting") - self._validate_name(name) - if before is not None: - if isinstance(before, str): - before = getattr(self, before) - index = self._parts.index(before) - if hasattr(self, name): - old_part = getattr(self, name) - self._parts.remove(old_part) - self._parts.insert(index, part) - super(Parts, self).__setattr__(name, part) - if hasattr(part, 'set_world'): - part.set_world(self._world) - - def __iter__(self): - """Iterate the parts in order""" - return iter(tuple(self._parts)) - - def __len__(self): - return len(self._parts) - - -class ComponentParts(Parts): - """Maps world components to attributes. The components are kept in the - order they are set. Components may also be inserted out of order. - - Used for: :attr:`World.components` - """ - - def join(self, *component_names): - """Join and iterate entity data from multiple components together. - - For each entity in all of the components named, yield a tuple containing - the entity data from each component specified. - - This is useful in systems that pull data from multiple components. - - Typical Usage:: - - for position, movement in world.components.join("position", "movement"): - # Do something with each entity's position and movement data - """ - if component_names: - components = [getattr(self, self._validate_name(name)) - for name in component_names] - if len(components) > 1: - entities = components[0].entities & components[1].entities - for comp in components[2:]: - entities &= comp.entities - else: - entities = components[0].entities - for entity in entities: - yield tuple(comp[entity] for comp in components) - +############################################################################# +# +# Copyright (c) 2010 by Casey Duncan and contributors +# All Rights Reserved. +# +# This software is subject to the provisions of the MIT License +# A copy of the license should accompany this distribution. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# +############################################################################# +"""Worlds are environments described by a configuration of components, systems and +renderers. These parts describe the data, behavioral and presentation aspects +of the world respectively. + +The world environment is the context within which entities exist. A typical +application consists of one or more worlds containing entities that evolve +over time and react to internal and external interaction. + +See :ref:`an example of world configuration in the tutorial <tut-world-example>`. +""" + +__version__ = '$Id$' + +import itertools +from parpg.bGrease import mode +from parpg.bGrease.component import ComponentError +from parpg.bGrease.entity import Entity, ComponentEntitySet + + +class BaseWorld(object): + """A coordinated collection of components, systems and entities + + A world is also a mode that may be pushed onto a + :class:`grease.mode.Manager` + """ + + components = None + """:class:`ComponentParts` object containing all world components. + :class:`grease.component.Component` objects define and contain all entity data + """ + + systems = None + """:class:`Parts` object containing all world systems. + :class:`grease.System` objects define world and entity behavior + """ + + renderers = None + """:class:`Parts` object containing all world renderers. + :class:`grease.Renderer` objects define world presentation + """ + + entities = None + """Set of all entities that exist in the world""" + + def __init__(self): + self.components = ComponentParts(self) + self.systems = Parts(self) + self.renderers = Parts(self) + self.new_entity_id = itertools.count().next + self.new_entity_id() # skip id 0 + self.entities = WorldEntitySet(self) + self._full_extent = EntityExtent(self, self.entities) + self._extents = {} + self.configure() + + def configure(self): + """Hook to configure the world after construction. This method + is called immediately after the world is initialized. Override + in a subclass to configure the world's components, systems, + and renderers. + + The default implementation does nothing. + """ + + def __getitem__(self, entity_class): + """Return an :class:`EntityExtent` for the given entity class. This extent + can be used to access the set of entities of that class in the world + or to query these entities via their components. + + Examples:: + + world[MyEntity] + world[...] + + :param entity_class: The entity class for the extent. + + May also be a tuple of entity classes, in which case + the extent returned contains union of all entities of the classes + in the world. + + May also be the special value ellipsis (``...``), which + returns an extent containing all entities in the world. This allows + you to conveniently query all entities using ``world[...]``. + """ + if isinstance(entity_class, tuple): + entities = set() + for cls in entity_class: + if cls in self._extents: + entities |= self._extents[cls].entities + return EntityExtent(self, entities) + elif entity_class is Ellipsis: + return self._full_extent + try: + return self._extents[entity_class] + except KeyError: + extent = self._extents[entity_class] = EntityExtent(self, set()) + return extent + + def draw_renderers(self): + """Draw all renderers""" + for renderer in self.renderers: + renderer.draw() + +class WorldEntitySet(set): + """Entity set for a :class:`World`""" + + def __init__(self, world): + self.world = world + + def add(self, entity): + """Add the entity to the set and all necessary class sets + Return the unique entity id for the entity, creating one + as needed. + """ + super(WorldEntitySet, self).add(entity) + for cls in entity.__class__.__mro__: + if issubclass(cls, Entity): + self.world[cls].entities.add(entity) + + def remove(self, entity): + """Remove the entity from the set and, world components, + and all necessary class sets + """ + super(WorldEntitySet, self).remove(entity) + for component in self.world.components: + try: + del component[entity] + except KeyError: + pass + for cls in entity.__class__.__mro__: + if issubclass(cls, Entity): + self.world[cls].entities.discard(entity) + + def discard(self, entity): + """Remove the entity from the set if it exists, if not, + do nothing + """ + try: + self.remove(entity) + except KeyError: + pass + + +class EntityExtent(object): + """Encapsulates a set of entities queriable by component. Extents + are accessed by using an entity class as a key on the :class:`World`:: + + extent = world[MyEntity] + """ + + entities = None + """The full set of entities in the extent""" + + def __init__(self, world, entities): + self.__world = world + self.entities = entities + + def __getattr__(self, name): + """Return a queriable :class:`ComponentEntitySet` for the named component + + Example:: + + world[MyEntity].movement.velocity > (0, 0) + + Returns a set of entities where the value of the :attr:`velocity` field + of the :attr:`movement` component is greater than ``(0, 0)``. + """ + component = getattr(self.__world.components, name) + return ComponentEntitySet(component, self.entities & component.entities) + + +class Parts(object): + """Maps world parts to attributes. The parts are kept in the + order they are set. Parts may also be inserted out of order. + + Used for: + + - :attr:`World.systems` + - :attr:`World.renderers` + """ + + _world = None + _parts = None + _reserved_names = ('entities', 'entity_id', 'world') + + def __init__(self, world): + self._world = world + self._parts = [] + + def _validate_name(self, name): + if (name in self._reserved_names or name.startswith('_') + or hasattr(self.__class__, name)): + raise ComponentError('illegal part name: %s' % name) + return name + + def __setattr__(self, name, part): + if not hasattr(self.__class__, name): + self._validate_name(name) + if not hasattr(self, name): + self._parts.append(part) + else: + old_part = getattr(self, name) + self._parts[self._parts.index(old_part)] = part + super(Parts, self).__setattr__(name, part) + if hasattr(part, 'set_world'): + part.set_world(self._world) + elif name.startswith("_"): + super(Parts, self).__setattr__(name, part) + else: + raise AttributeError("%s attribute is read only" % name) + + def __delattr__(self, name): + self._validate_name(name) + part = getattr(self, name) + self._parts.remove(part) + super(Parts, self).__delattr__(name) + + def insert(self, name, part, before=None, index=None): + """Add a part with a particular name at a particular index. + If a part by that name already exists, it is replaced. + + :arg name: The name of the part. + :type name: str + + :arg part: The component, system, or renderer part to insert + + :arg before: A part object or name. If specified, the part is + inserted before the specified part in order. + + :arg index: If specified, the part is inserted in the position + specified. You cannot specify both before and index. + :type index: int + """ + assert before is not None or index is not None, ( + "Must specify a value for 'before' or 'index'") + assert before is None or index is None, ( + "Cannot specify both 'before' and 'index' arguments when inserting") + self._validate_name(name) + if before is not None: + if isinstance(before, str): + before = getattr(self, before) + index = self._parts.index(before) + if hasattr(self, name): + old_part = getattr(self, name) + self._parts.remove(old_part) + self._parts.insert(index, part) + super(Parts, self).__setattr__(name, part) + if hasattr(part, 'set_world'): + part.set_world(self._world) + + def __iter__(self): + """Iterate the parts in order""" + return iter(tuple(self._parts)) + + def __len__(self): + return len(self._parts) + + +class ComponentParts(Parts): + """Maps world components to attributes. The components are kept in the + order they are set. Components may also be inserted out of order. + + Used for: :attr:`World.components` + """ + + def join(self, *component_names): + """Join and iterate entity data from multiple components together. + + For each entity in all of the components named, yield a tuple containing + the entity data from each component specified. + + This is useful in systems that pull data from multiple components. + + Typical Usage:: + + for position, movement in world.components.join("position", "movement"): + # Do something with each entity's position and movement data + """ + if component_names: + components = [getattr(self, self._validate_name(name)) + for name in component_names] + if len(components) > 1: + entities = components[0].entities & components[1].entities + for comp in components[2:]: + entities &= comp.entities + else: + entities = components[0].entities + for entity in entities: + yield tuple(comp[entity] for comp in components)