Mercurial > parpg-source
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 |