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)
+