Mercurial > parpg-core
diff src/parpg/bGrease/entity.py @ 66:96af64cf3b81
Renamed grease to bGrease (Basic Grease) to get rid of conflicts with an already installed grease.
author | KarstenBock@gmx.net |
---|---|
date | Mon, 05 Sep 2011 15:00:34 +0200 |
parents | src/parpg/grease/entity.py@09b581087d68 |
children | 9b5498e3bda0 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/parpg/bGrease/entity.py Mon Sep 05 15:00:34 2011 +0200 @@ -0,0 +1,212 @@ +############################################################################# +# +# 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. +# +############################################################################# +"""Grease entities are useful as actionable, interactive +game elements that are often visible to the player. + +You might use entities to represent: + +- Characters +- Bullets +- Particles +- Pick-ups +- Space Ships +- Weapons +- Trees +- Planets +- Explosions + +See :ref:`an example entity class in the tutorial <tut-entity-example>`. +""" + +__version__ = '$Id$' + +__all__ = ('Entity', 'EntityComponentAccessor', 'ComponentEntitySet') + + +class EntityMeta(type): + """The entity metaclass enforces fixed slots of `entity_id` and `world` + for all subclasses. This prevents accidental use of other entity instance + attributes, which may not be saved. + + Class attributes are not affected by this restriction, but subclasses + should be careful not to cause name collisions with world components, + which are exposed as entity attributes. Using a naming convention for + class attributes, such as UPPER_CASE_WITH_UNDERSCORES is recommended to + avoid name clashes. + + Note as a result of this, entity subclasses are not allowed to define + `__slots__`, and doing so will cause a `TypeError` to be raised. + """ + + def __new__(cls, name, bases, clsdict): + if '__slots__' in clsdict: + raise TypeError('__slots__ may not be defined in Entity subclasses') + clsdict['__slots__'] = ('world', 'entity_id') + return type.__new__(cls, name, bases, clsdict) + + +class Entity(object): + """Base class for grease entities. + + Entity objects themselves are merely identifiers within a :class:`grease.world.World`. + They also provide a facade for convenient entity-wise access of component + data. However, they do not contain any data themselves other than an + entity id. + + Entities must be instantiated in the context of a world. To instantiate an + entity, you must pass the world as the first argument to the constructor. + Subclasses that implement the :meth:`__init__()` method, must accept the world + as their first argument (after ``self``). Other constructor arguments can be + specified arbitarily by the subclass. + """ + __metaclass__ = EntityMeta + + def __new__(cls, world, *args, **kw): + """Create a new entity and add it to the world""" + entity = object.__new__(cls) + entity.world = world + entity.entity_id = world.new_entity_id() + world.entities.add(entity) + return entity + + def __getattr__(self, name): + """Return an :class:`EntityComponentAccessor` for this entity + for the component named. + + Example:: + + my_entity.movement + """ + component = getattr(self.world.components, name) + return EntityComponentAccessor(component, self) + + def __setattr__(self, name, value): + """Set the entity data in the named component for this entity. + This sets the values of the component fields to the values of + the matching attributes of the value provided. This value must + have attributes for each of the component fields. + + This allows you to easily copy component data from one entity + to another. + + Example:: + + my_entity.position = other_entity.position + """ + if name in self.__class__.__slots__: + super(Entity, self).__setattr__(name, value) + else: + component = getattr(self.world.components, name) + component.set(self, value) + + def __delattr__(self, name): + """Remove this entity and its data from the component. + + Example:: + + del my_entity.renderable + """ + component = getattr(self.world.components, name) + del component[self] + + def __hash__(self): + return self.entity_id + + def __eq__(self, other): + return self.world is other.world and self.entity_id == other.entity_id + + def __repr__(self): + return "<%s id: %s of %s %x>" % ( + self.__class__.__name__, self.entity_id, + self.world.__class__.__name__, id(self.world)) + + def delete(self): + """Delete the entity from its world. This removes all of its + component data. If then entity has already been deleted, + this call does nothing. + """ + self.world.entities.discard(self) + + @property + def exists(self): + """True if the entity still exists in the world""" + return self in self.world.entities + + +class EntityComponentAccessor(object): + """A facade for accessing specific component data for a single entity. + The implementation is lazy and does not actually access the component + data until needed. If an attribute is set for a component that the + entity is not yet a member of, it is automatically added to the + component first. + + :param component: The :class:`grease.Component` being accessed + :param entity: The :class:`Entity` being accessed + """ + + # beware, name mangling ahead. We want to avoid clashing with any + # user-configured component field names + __data = None + + def __init__(self, component, entity): + clsname = self.__class__.__name__ + self.__dict__['_%s__component' % clsname] = component + self.__dict__['_%s__entity' % clsname] = entity + + def __nonzero__(self): + """The accessor is True if the entity is in the component, + False if not, for convenient membership tests + """ + return self.__entity in self.__component + + def __getattr__(self, name): + """Return the data for the specified field of the entity's component""" + if self.__data is None: + try: + data = self.__component[self.__entity] + except KeyError: + raise AttributeError(name) + clsname = self.__class__.__name__ + self.__dict__['_%s__data' % clsname] = data + return getattr(self.__data, name) + + def __setattr__(self, name, value): + """Set the data for the specified field of the entity's component""" + if self.__data is None: + clsname = self.__class__.__name__ + if self.__entity in self.__component: + self.__dict__['_%s__data' % clsname] = self.__component[self.__entity] + else: + self.__dict__['_%s__data' % clsname] = self.__component.set(self.__entity) + setattr(self.__data, name, value) + + +class ComponentEntitySet(set): + """Set of entities in a component, can be queried by component fields""" + + _component = None + + def __init__(self, component, entities=()): + self.__dict__['_component'] = component + super(ComponentEntitySet, self).__init__(entities) + + def __getattr__(self, name): + if self._component is not None and name in self._component.fields: + return self._component.fields[name].accessor(self) + raise AttributeError(name) + + def __setattr__(self, name, value): + if self._component is not None and name in self._component.fields: + self._component.fields[name].accessor(self).__set__(value) + raise AttributeError(name) +