Source

py-sdl2 / doc / modules / sdl2ext_ebs.rst

Full commit
Marcus von Appen 28f1e1e 

Marcus von Appen 9e4aac0 
Marcus von Appen 28f1e1e 

Marcus von Appen bc59846 
Marcus von Appen 9e4aac0 


Marcus von Appen bc59846 
Marcus von Appen 9e4aac0 









Marcus von Appen bc59846 
Marcus von Appen 9e4aac0 
























































































Marcus von Appen 8f3bb84 
Marcus von Appen 9e4aac0 








































Marcus von Appen bc59846 


Marcus von Appen 9e4aac0 




Marcus von Appen bc59846 

Marcus von Appen 9e4aac0 



Marcus von Appen 28f1e1e 


Marcus von Appen 9e4aac0 
Marcus von Appen 28f1e1e 
Marcus von Appen 9e4aac0 







Marcus von Appen bc59846 
Marcus von Appen 9e4aac0 









































































Marcus von Appen bc59846 
Marcus von Appen 9e4aac0 
























Marcus von Appen 8f3bb84 
Marcus von Appen 9e4aac0 
Marcus von Appen 8f3bb84 
Marcus von Appen 9e4aac0 




Marcus von Appen bc59846 
Marcus von Appen 9e4aac0 
Marcus von Appen bc59846 



Marcus von Appen 9e4aac0 
Marcus von Appen bc59846 


Marcus von Appen 9e4aac0 
Marcus von Appen bc59846 
Marcus von Appen 9e4aac0 
Marcus von Appen bc59846 


Marcus von Appen 9e4aac0 
Marcus von Appen bc59846 






Marcus von Appen 9e4aac0 













































Marcus von Appen 8f3bb84 
Marcus von Appen 9e4aac0 


Marcus von Appen 8f3bb84 
Marcus von Appen 9e4aac0 




Marcus von Appen 8f3bb84 
Marcus von Appen 9e4aac0 





Marcus von Appen 8f3bb84 
Marcus von Appen 9e4aac0 



Marcus von Appen 8f3bb84 
Marcus von Appen 9e4aac0 

Marcus von Appen 8f3bb84 
Marcus von Appen 9e4aac0 

Marcus von Appen 8f3bb84 
Marcus von Appen 9e4aac0 






























  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
.. currentmodule:: sdl2.ext
.. _ref-ebs:

Working with component-based entities
=====================================
:mod:`sdl2.ext` supports a component oriented programming pattern to separate
object instances, carried data and processing logic within applications
or games. It uses an entity based approach, in which object instances are
unique identifiers, while their data is managed within components, which
are stored separately. For each individual component type a processing
system will take care of all necessary updates on running the application.

Component-based patterns
------------------------
Component-based means that - instead of a traditional OOP approach - object
information are split up into separate data bags for reusability and that those
data bags are separated from any application logic.

Behavioural design
^^^^^^^^^^^^^^^^^^
Imagine a car class in traditional OOP, which might look like ::

   class Car:
       def __init__(self):
           self.color = "red"
           self.position = 0, 0
           self.velocity = 0, 0
           self.sprite = get_some_car_image()
           ...
       def drive(self, timedelta):
           self.position[0] = self.velocity[0] * timedelta
           self.position[1] = self.velocity[1] * timedelta
           ...
       def stop(self):
           self.velocity = 0, 0
           ...
       def render(self, screen):
           screen.display(self.sprite)

   mycar = new Car()
   mycar.color = "green"
   mycar.velocity = 10, 0

The car features information stored in attributes (``color``, ``position``,
...) and behaviour (application logic, ``drive()``, ``stop()`` ...).

A component-based approach aims to split and reduce the car to a set of
information and external systems providing the application logic. ::

   class Car:
       def __init__(self):
           self.color = "red"
           self.position = 0, 0
           self.velocity = 0, 0
           self.sprite = get_some_car_image()

   class CarMovement:
       def drive(self, car, timedelta):
           car.position[0] = car.velocity[0] * timedelta
           car.position[1] = car.velocity[1] * timedelta
           ...
       def stop(self):
           car.velocity = 0, 0

   class CarRenderer:
       def render(self, car, screen):
           screen.display(car.sprite)

At this point of time, there is no notable difference between both approaches,
except that the latter one adds additional overhead.

The benefit comes in, when you

* use subclassing in your OOP design
* want to change behavioural patterns on a global scale or based on states
* want to refactor code logic in central locations
* want to cascade application behaviours

The initial ``Car`` class from above defines, how it should be displayed
on the screen. If you now want to add a feature for rescaling the screen
size after the user activates the magnifier mode, you need to refactor
the ``Car`` and all other classes that render things on the screen, have
to consider all subclasses that override the method and so on.
Refactoring the ``CarRenderer`` code by adding a check for the magnifier
mode sounds quite simple in contrast to that, not?

The same applies to the movement logic - inverting the movement logic
requires you to refactor all your classes instead of a single piece of
application code.

Information design
^^^^^^^^^^^^^^^^^^
Subclassing with traditional OOP for behavioural changes also might
bloat your classes with unnecessary information, causing the memory
footprint for your application to rise without any need. Let's assume
you have a ``Truck`` class that inherits from ``Car``. Let's further
assume that all trucks in your application look the same. Why should any
of those carry a ``sprite`` or ``color`` attribute? You would need to
refactor your ``Car`` class to get rid of those superfluous information,
adding another level of subclassing. If at a later point of time you
decide to give your trucks different colors, you need to refactor
everything again.

Wouldn't it be easier to deal with colors, if they are available on the
truck and leave them out, if they are not? We initially stated that the
component-based approach aims to separate data (information) from code
logic.  That said, if the truck has a color, we can handle it easily, if
it has not, we will do as usual.

Also, checking for the color of an object (regardless, if it is a truck,
car, aeroplane or death star) allows us to apply the same or similar
behaviour for every object. If the information is available, we will
process it, if it is not, we will not do anything.

All in all
^^^^^^^^^^
Once we split up the previously OOP-style classes into pure data containers and
some separate processing code for the behaviour, we are talking about components
and (processing) systems. A component is a data container, ideally grouping
related information on a granular level, so that it is easy to (re)use.
When you combine different components to build your in-application objects and
instantiate those, we are talking about entities.

.. image:: images/ebs.png

*Component*
   provides information (data bag)

*Entity*
   In-application instance that consists of *component* items

*System*
   Application logic for working with *Entity* items and their
   *component* data

*World*
   The environment that contains the different *System* instances and
   all *Entity* items with their *component* data

Within a strict COP design, the application logic (ideally) only knows about
data to process. It does not know anything about entities or complex classes
and only operates on the data.

.. image:: images/copprocessing.png

To keep things simple, modular and easy to maintain and change, you usually
create small processing systems, which perform the necessary operations on the
data they shall handle. That said, a ``MovementSystem`` for our car entity would
only operate on the position and velocity component of the car entity. It does
not know anything about the the car's sprite or sounds that the car makes,
since *this is nothing it has to deal with*.

To display the car on the screen, a ``RenderingSystem`` might pick up the
sprite component of the car, maybe along with the position information (so it
knows, where to place the sprite) and render it on the screen.

If you want the car to play sounds, you would add an audio playback system,
that can perform the task. Afterwards you can add the necessary audio
information via a sound component to the car and it will make noise.

Component-based design with sdl2.ext
------------------------------------

.. note::

   This section will deal with the specialities of COP patterns and
   provide the bare minimum of information. If you are just starting with
   such a design, it is recommended to read through the :ref:`pong-tutorial`
   tutorial.

:mod:`sdl2.ext` provides a :class:`World` class in which all other objects
will reside. The :class:`World` will maintain both, :class:`Entity` and
component items, and allows you to set up the processing logic via
the :class:`System` and :class:`Applicator` classes. ::

   >>> appworld = World()

Components can be created from any class that inherits from the
:class:`object` type and represent the data bag of information for the
entity and application world. Ideally, they should avoid any
application logic (except from getter and setter properties). ::

   class Position2D(object):
       def __init__(self, x=0, y=0):
           self.x = x
           self.y = y

:class:`Entity` objects define the in-application objects and only consist of
component-based attributes. They also require a :class:`World` at
object instantiation time. ::

   class CarEntity(Entity):
       def __init__(self, world, x=0, y=0):
           self.position2d = Position2D(x, y)

.. note::

   The *world* argument in ``__init__()`` is necessary. It will be
   passed to the internal ``__new__()`` constructor of the
   :class:`Entity` and stores a reference to the :class:`World` and also
   allows the :class:`Entity` to store its information in the
   :class:`World`.

The :class:`Entity` also requries its attributes to be named exactly as
their component class name, but in lowercase letters. If you name a
component ``MyAbsolutelyAwesomeDataContainer``, an :class:`Entity` will
force you to write the following: ::

   class SomeEntity(Entity):
       def __init__(self, world):
           self.myabsolutelyawesomedatacontainer = MyAbsolutelyAwesomeDataContainer()

.. note::

   This is not entirely true. A reference of the object will be stored on a
   per-class-in-mro basis. This means that if ``MyAbsolutelyAwesomeDataContainer``
   inherits from ``ShortName``, you can also do: ::

     class SomeEntity(Entity):
         def __init__(self, world):
             self.shortname = MyAbsolutelyAwesomeDataContainer()

Components should be as atomic as possible and avoid complex
inheritance. Since each value of an :class:`Entity` is stored per class
in its mro list, components inheriting from the same class(es) will
overwrite each other on conflicting classes: ::

  class Vector(Position2D):
      def __init__(self, x=0, y=0, z=0):
          super(Vector, self).__init__(x, y)


  class SomeEntity(Entity):
      def __init__(self, world):
          # This will associate self.position2d with the new Position2D
          # value, while the previous Vector association is overwritten
          self.position2d = Position2D(4, 4)

          # self.vector will also associate a self.position2d attribute
          # with the Entity, since Vector inherits from Position2D. The
          # original association will vanish, and each call to
          # entity.position2d will effectively manipulate the vector!
          self.vector = Vector(1,2,3)

API
---

.. class:: Entity(world : World)

    An entity is a specific object living in the application world. It
    does not carry any data or application logic, but merely acts as
    identifier label for data that is maintained in the application
    world itself.

    As such, it is a composition of components, which would not exist
    without the entity identifier. The entity itself is non-existent to
    the application world as long as it does not carry any data that can
    be processed by a system within the application world.

   .. attribute:: id

      The id of the Entity. Every Entity has a unique id, that is
      represented by a :class:`uuid.UUID` instance.

   .. attribute:: world

      The :class:`World` the entity resides in.

   .. method:: delete() -> None

      Deletes the :class:`Entity` from its :class:`World`. This
      basically calls :meth:`World.delete()` with the :class:`Entity`.

.. class:: Applicator()

   A processing system for combined data sets. The :class:`Applicator`
   is an enhanced :class:`System` that receives combined data sets based
   on its set :attr:`System.componenttypes`

   .. attribute:: is_applicator

      A boolean flag indicating that this class operates on combined data sets.

   .. attribute:: componenttypes

      A tuple of class identifiers that shall be processed by the
      :class:`Applicator`.

   .. function:: process(world : World, componentsets : iterable)

      Processes tuples of component items. *componentsets* will
      contain object tuples, that match the :attr:`componenttypes`
      of the :class:`Applicator`. If, for example, the :class:`Applicator`
      is defined as ::

          class MyApplicator(Applicator):
              def __init__(self):
                  self.componenttypes = (Foo, Bar)

      its process method will receive ``(Foo, Bar)`` tuples ::

          def process(self, world, componentsets):
              for foo_item, bar_item in componentsets:
                  ...

      Additionally, the :class:`Applicator` will not process all possible
      combinations of valid components, but only those, which are associated
      with the same :class:`Entity`. That said, an :class:`Entity` *must*
      contain a ``Foo`` as well as a ``Bar`` component in order to
      have them both processed by the :class:`Applicator` (while a
      :class:`System` with the same ``componenttypes`` would pick either of
      them, depending on their availability).

.. class:: System()

   A processing system within an application world consumes the
   components of all entities, for which it was set up. At time of
   processing, the system does not know about any other component type
   that might be bound to any entity.

   Also, the processing system does not know about any specific entity,
   but only is aware of the data carried by all entities.

   .. attribute:: componenttypes

      A tuple of class identifiers that shall be processed by the
      :class:`System`

   .. method:: process(world : World, components : iterable)

      Processes component items.

      This method has to be implemented by inheriting classes.


.. class:: World()

   An application world defines the combination of application data and
   processing logic and how the data will be processed. As such, it is a
   container object in which the application is defined.

   The application world maintains a set of entities and their related
   components as well as a set of systems that process the data of the
   entities. Each processing system within the application world only
   operates on a certain set of components, but not all components of an
   entity at once.

   The order in which data is processed depends on the order of the
   added systems.

   .. attribute:: systems

      The processing system objects bound to the world.

   .. method:: add_system(system : object)

      Adds a processing system to the world. The system will be
      added as last item in the processing order.

      The passed system does not have to inherit from :class:`System`, but
      must feature a ``componenttypes`` attribute and a ``process()`` method,
      which match the signatures of the :class:`System` class ::

        class MySystem(object):
            def __init__(self):
                # componenttypes can be any iterable as long as it
                # contains the classes the system should take care of
                self.componenttypes = [AClass, AnotherClass, ...]

            def process(self, world, components):
                ...

      If the system shall operate on combined component sets as specified
      by the :class:`Applicator`, the class instance must contain a
      ``is_applicator`` property, that evaluates to ``True`` ::

        class MyApplicator(object):
            def __init__(self):
                self.is_applicator = True
                self.componenttypes = [...]

            def process(self, world, components):
                pass

      The behaviour can be changed at run-time. The ``is_applicator`` attribute
      is evaluated for every call to :meth:`World.process()`.

   .. method:: delete(entity : Entity)

      Removes an :class:`Entity` from the World, including all its
      component data.

   .. method:: delete_entities(entities : iterable)

      Removes a set of :class:`Entity` instances from the World,
      including all their component data.

   .. method:: insert_system(index : int, system : System)

      Adds a processing :class:`System` to the world. The system will be
      added at the specified position in the processing order.

   .. method:: get_entities(component : object) -> [Entity, ...]

      Gets the entities using the passed component.

      .. note::

         This will not perform an identity check on the component
         but rely on its ``__eq__`` implementation instead.

   .. method:: process()

      Processes all component items within their corresponding
      :class:`System` instances.

   .. method:: remove_system(system : System)

      Removes a processing :class:`System` from the world.