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