comparison bGrease/world.py @ 41:ff3e395abf91

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 grease/world.py@bc88f7d5ca8b
children e856b604b650
comparison
equal deleted inserted replaced
40:2e3ab06a2f47 41:ff3e395abf91
1 #############################################################################
2 #
3 # Copyright (c) 2010 by Casey Duncan and contributors
4 # All Rights Reserved.
5 #
6 # This software is subject to the provisions of the MIT License
7 # A copy of the license should accompany this distribution.
8 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
9 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
10 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
11 #
12 #############################################################################
13 """Worlds are environments described by a configuration of components, systems and
14 renderers. These parts describe the data, behavioral and presentation aspects
15 of the world respectively.
16
17 The world environment is the context within which entities exist. A typical
18 application consists of one or more worlds containing entities that evolve
19 over time and react to internal and external interaction.
20
21 See :ref:`an example of world configuration in the tutorial <tut-world-example>`.
22 """
23
24 __version__ = '$Id$'
25
26 import itertools
27 from bGrease import mode
28 from bGrease.component import ComponentError
29 from bGrease.entity import Entity, ComponentEntitySet
30
31
32 class BaseWorld(object):
33 """A coordinated collection of components, systems and entities
34
35 A world is also a mode that may be pushed onto a
36 :class:`grease.mode.Manager`
37 """
38
39 components = None
40 """:class:`ComponentParts` object containing all world components.
41 :class:`grease.component.Component` objects define and contain all entity data
42 """
43
44 systems = None
45 """:class:`Parts` object containing all world systems.
46 :class:`grease.System` objects define world and entity behavior
47 """
48
49 renderers = None
50 """:class:`Parts` object containing all world renderers.
51 :class:`grease.Renderer` objects define world presentation
52 """
53
54 entities = None
55 """Set of all entities that exist in the world"""
56
57 def __init__(self):
58 self.components = ComponentParts(self)
59 self.systems = Parts(self)
60 self.renderers = Parts(self)
61 self.new_entity_id = itertools.count().next
62 self.new_entity_id() # skip id 0
63 self.entities = WorldEntitySet(self)
64 self._full_extent = EntityExtent(self, self.entities)
65 self._extents = {}
66 self.configure()
67
68 def configure(self):
69 """Hook to configure the world after construction. This method
70 is called immediately after the world is initialized. Override
71 in a subclass to configure the world's components, systems,
72 and renderers.
73
74 The default implementation does nothing.
75 """
76
77 def __getitem__(self, entity_class):
78 """Return an :class:`EntityExtent` for the given entity class. This extent
79 can be used to access the set of entities of that class in the world
80 or to query these entities via their components.
81
82 Examples::
83
84 world[MyEntity]
85 world[...]
86
87 :param entity_class: The entity class for the extent.
88
89 May also be a tuple of entity classes, in which case
90 the extent returned contains union of all entities of the classes
91 in the world.
92
93 May also be the special value ellipsis (``...``), which
94 returns an extent containing all entities in the world. This allows
95 you to conveniently query all entities using ``world[...]``.
96 """
97 if isinstance(entity_class, tuple):
98 entities = set()
99 for cls in entity_class:
100 if cls in self._extents:
101 entities |= self._extents[cls].entities
102 return EntityExtent(self, entities)
103 elif entity_class is Ellipsis:
104 return self._full_extent
105 try:
106 return self._extents[entity_class]
107 except KeyError:
108 extent = self._extents[entity_class] = EntityExtent(self, set())
109 return extent
110
111 def draw_renderers(self):
112 """Draw all renderers"""
113 for renderer in self.renderers:
114 renderer.draw()
115
116 class WorldEntitySet(set):
117 """Entity set for a :class:`World`"""
118
119 def __init__(self, world):
120 self.world = world
121
122 def add(self, entity):
123 """Add the entity to the set and all necessary class sets
124 Return the unique entity id for the entity, creating one
125 as needed.
126 """
127 super(WorldEntitySet, self).add(entity)
128 for cls in entity.__class__.__mro__:
129 if issubclass(cls, Entity):
130 self.world[cls].entities.add(entity)
131
132 def remove(self, entity):
133 """Remove the entity from the set and, world components,
134 and all necessary class sets
135 """
136 super(WorldEntitySet, self).remove(entity)
137 for component in self.world.components:
138 try:
139 del component[entity]
140 except KeyError:
141 pass
142 for cls in entity.__class__.__mro__:
143 if issubclass(cls, Entity):
144 self.world[cls].entities.discard(entity)
145
146 def discard(self, entity):
147 """Remove the entity from the set if it exists, if not,
148 do nothing
149 """
150 try:
151 self.remove(entity)
152 except KeyError:
153 pass
154
155
156 class EntityExtent(object):
157 """Encapsulates a set of entities queriable by component. Extents
158 are accessed by using an entity class as a key on the :class:`World`::
159
160 extent = world[MyEntity]
161 """
162
163 entities = None
164 """The full set of entities in the extent"""
165
166 def __init__(self, world, entities):
167 self.__world = world
168 self.entities = entities
169
170 def __getattr__(self, name):
171 """Return a queriable :class:`ComponentEntitySet` for the named component
172
173 Example::
174
175 world[MyEntity].movement.velocity > (0, 0)
176
177 Returns a set of entities where the value of the :attr:`velocity` field
178 of the :attr:`movement` component is greater than ``(0, 0)``.
179 """
180 component = getattr(self.__world.components, name)
181 return ComponentEntitySet(component, self.entities & component.entities)
182
183
184 class Parts(object):
185 """Maps world parts to attributes. The parts are kept in the
186 order they are set. Parts may also be inserted out of order.
187
188 Used for:
189
190 - :attr:`World.systems`
191 - :attr:`World.renderers`
192 """
193
194 _world = None
195 _parts = None
196 _reserved_names = ('entities', 'entity_id', 'world')
197
198 def __init__(self, world):
199 self._world = world
200 self._parts = []
201
202 def _validate_name(self, name):
203 if (name in self._reserved_names or name.startswith('_')
204 or hasattr(self.__class__, name)):
205 raise ComponentError('illegal part name: %s' % name)
206 return name
207
208 def __setattr__(self, name, part):
209 if not hasattr(self.__class__, name):
210 self._validate_name(name)
211 if not hasattr(self, name):
212 self._parts.append(part)
213 else:
214 old_part = getattr(self, name)
215 self._parts[self._parts.index(old_part)] = part
216 super(Parts, self).__setattr__(name, part)
217 if hasattr(part, 'set_world'):
218 part.set_world(self._world)
219 elif name.startswith("_"):
220 super(Parts, self).__setattr__(name, part)
221 else:
222 raise AttributeError("%s attribute is read only" % name)
223
224 def __delattr__(self, name):
225 self._validate_name(name)
226 part = getattr(self, name)
227 self._parts.remove(part)
228 super(Parts, self).__delattr__(name)
229
230 def insert(self, name, part, before=None, index=None):
231 """Add a part with a particular name at a particular index.
232 If a part by that name already exists, it is replaced.
233
234 :arg name: The name of the part.
235 :type name: str
236
237 :arg part: The component, system, or renderer part to insert
238
239 :arg before: A part object or name. If specified, the part is
240 inserted before the specified part in order.
241
242 :arg index: If specified, the part is inserted in the position
243 specified. You cannot specify both before and index.
244 :type index: int
245 """
246 assert before is not None or index is not None, (
247 "Must specify a value for 'before' or 'index'")
248 assert before is None or index is None, (
249 "Cannot specify both 'before' and 'index' arguments when inserting")
250 self._validate_name(name)
251 if before is not None:
252 if isinstance(before, str):
253 before = getattr(self, before)
254 index = self._parts.index(before)
255 if hasattr(self, name):
256 old_part = getattr(self, name)
257 self._parts.remove(old_part)
258 self._parts.insert(index, part)
259 super(Parts, self).__setattr__(name, part)
260 if hasattr(part, 'set_world'):
261 part.set_world(self._world)
262
263 def __iter__(self):
264 """Iterate the parts in order"""
265 return iter(tuple(self._parts))
266
267 def __len__(self):
268 return len(self._parts)
269
270
271 class ComponentParts(Parts):
272 """Maps world components to attributes. The components are kept in the
273 order they are set. Components may also be inserted out of order.
274
275 Used for: :attr:`World.components`
276 """
277
278 def join(self, *component_names):
279 """Join and iterate entity data from multiple components together.
280
281 For each entity in all of the components named, yield a tuple containing
282 the entity data from each component specified.
283
284 This is useful in systems that pull data from multiple components.
285
286 Typical Usage::
287
288 for position, movement in world.components.join("position", "movement"):
289 # Do something with each entity's position and movement data
290 """
291 if component_names:
292 components = [getattr(self, self._validate_name(name))
293 for name in component_names]
294 if len(components) > 1:
295 entities = components[0].entities & components[1].entities
296 for comp in components[2:]:
297 entities &= comp.entities
298 else:
299 entities = components[0].entities
300 for entity in entities:
301 yield tuple(comp[entity] for comp in components)
302