5
|
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 grease import mode
|
|
28 from grease.component import ComponentError
|
|
29 from grease.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
|