Events
This part will be about events, event systems, their implementation with their pros and cons.
I have been thinking about events and event-systems for some time now. Pygame has just a
basic way to deliver events, but that isn't enough for more complex code (like
MVC or other structures).
Pyglet on the other
side has a ready to use event system which has also its quirks. Also a good tutorial is
sjbrown's Guide to Writing Games.
I will try to show you different aspects, issues and ways to implement an event system.
top |
back to tutorials overview
To start with, any event system is basically an observer pattern
(
observer pattern).
There is the subject which can have more than one observer (also called listeners). The idea is, that when
the subject changes, it will notify the observers. How the notification is done depends on the event system
and there are many different ways to implement some sort of observer pattern or dispatcher.
This leads to a loose coupling of the components. So lets take a look at some ways to implement
an observer pattern.
Normally the binding of observers to the subjects is done at the initialize phase because
there you have access to all components. But it can be done dynamically too at runtime.
Let's start with the subject. The subject has to provide a way to register and unregister
the observers to that subject. The observer on the other hand should implement the observer
interface. Let's look at a naive implementation:
class Subject(object):
def __init__(self):
self._observers = []
def register(self, observer):
self._observers.append(observer)
def unregister(self, observer):
if observer in self._observers:
self._observers.remove(observer)
def notify(self, event):
for observer in self._observers:
observer.notify(event)
class IObserver(object):
def notify(self, event):
raise NotImplementedError("Must subclass and overwrite me")
And to use this mechanism you would do something like this:
# overwrite the observer's notify so
# custom code is executed
class PrintObserver(IObserver):
def notify(self, event):
print "observer", self, "got event:", event
# create a subject
subject = Subject()
# create two observers and register them to the subject
observer1 = PrintObserver()
subject.register(observer1)
observer2 = PrintObserver()
subject.register(observer2)
# send an event
subject.notify("hello")
Tis naive implementation has some issues like the overhead to send all events
to all observers, event if they do not process this event type. I will discuss
some other issues in the following sections.
top |
back to tutorials overview
The naive implementation is fully functional, but may have some potential problems. You can use it
as long you don't remove or add other observers from within a observer and you want your observers
be kept alive through the strong reference from the subject. Following is a deeper discussion
about solving this issues and some other potentially problematic things.
The order of events is an important aspect that should be understood.
Assuming we have a subject S1 and two observers O1 and O2. O1 listens to events of type A, B and C (also from O2)
and O2 listens to events of type A and sends an event of type C as soon it receives a A-event.
In which order will O1 get the events if S1 send the events A, B in this order? Well you guessed right
in the wrong order, but why? S1 starts by sending out event A. O1 will receive it, process it and then
O2 receives it. While processing it, it generates event C which is received by O1. Then B is generated
by S1 and only O1 gets it. So O1 has received the events in this order: A, C, B. Maybe this order isn't
desired. The only way I see to get the order A, B and then C for O1 is to use a
mediator
pattern and a
event queue. Instead to send out the events itself (by a subject or observer), they should be queued in a
event manager (which would be the mediator, also called a dispatcher sometimes) and send then out by it. In this case the event C would be queued
after the event B and therefore O1 would get the order A, B and C. Be sure to understands
what is going on and in which order the events will be received.
top |
tutorials |
downloads
When 'notify(event)' is called in the subject, it will iterate over the stored observers. But it would
break if you have an observer that adds or removes observers from that subject, e.g.:
Observer(IObserver):
def __init__(self, subject):
self.subject = subject
subject.register(self)
def notify(self, event):
self.subject.unregister(self)
So the question is how to prevent the change of the list? The simplest solution I found so
far, is to make a copy of the list before iterating over it:
def notify(self, event):
for observer in list(self._observers):
observer.notify(event)
But this has the drawback that a list is copied in memory every time
a event is generated. As long this waste of performance and memory isn't
a concern to you, this solution is fine. But if performance matters (and
in my opinion it does, an event system should have good performance) another
way is needed. So I came up with a better solution that might look a
bit more complicated, but does the same thing by storing the 'add'- and
'remove'-requests. Something like this:
class Subject(object):
def __init__(self):
self._observers = []
# set up helper lists and a flag
self._to_add = []
self._to_remove = []
self._changed = False
def register(self, observer):
# each observer should only be added once
if observer not in self._observers:
self._to_add.append(observer)
self._changed = True
def unregister(self, observer):
if observer in self._observers:
self._to_remove.append(observer)
self._changed = True
def notify(self, event):
# update the changes first if necessary
if self._changed:
self._changed = False
self._observers.extend(self._to_add)
self._to_add[:] = []
while self._to_remove:
self._observers.remove(self._to_remove.pop())
# call the callbacks
for observer in self._observers:
observer.notify(event)
Frankly, I haven't profiled the two versions, which I really should do, but I'm pretty sure that checking an if
is faster than making a copy of the list (might depend on the size of the list).
top |
tutorials |
downloads
There are many ways to design the event itself. This an important part
because the other parts depend on this design. The already presented observer pattern
(see
Basics: Observer Pattern) is basically the
infrastructure to send and receive events, but the event itself could be actually
anything. So if you want different types of events you might have something like this:
class Event:
pass
class TickEvent:
def __init__(self, dt):
self.dt = dt
class ClickEvent:
def __init__(self, source):
self.source = source
class Observer(IObserver):
def __init__(self, subject):
self.subject = subject
def notify(self, event):
if isinstance(event, TickEvent):
# do what is needed for the TickEvent here, maybe self.update()
pass
elif isinstance(event, ClickEvent):
if event.source == self.subject:
# do something
pass
...
One thing to notice is, that you get a if...elif...elif... block in the notify() method.
It will have an if for each event to catch. I do not like this if...elif... thing
so I would like to do it differently. A simple way would be to have different
callbacks for each event-type. But then, the subject has to keep track of the different
callbacks. Maybe something like this:
class Subject:
def __init__(self):
self._listeners = {} #{event_type:[listeners]}
def register(self, event_type, listener):
self._listeners.get(event_type, []).append(listener)
def unregister(self, event_type, listener):
listeners = self._listeners.get(event_type, [])
if listener in listeners:
listeners.remove(listener)
def update(self, event_type, event):
for listener in self._listeners.get(event_type, []):
listener(event)
Since here you would register a callback rather than an observer you don't have
a notify method anymore, but you have a method for each different event type
you have registered (of course, you could register the same method for every event
type, but that would defeat the idea). Now the if...elif... block went into the
dictionary in the subject. We even can take this idea further by splitting
the dictionary into single subject for each event-type. From a slightly different
point of view this means that each of this object represent an event-type by itself
and could therefore be called the channel or signal. A signal represents the event-type
and the subject at the same time.
# this class is the event-type and the subject at the same time
# it looks very similar to the subject, but it hold listeners to only
# one event-type
class Signal:
def __init__(self):
self._listeners = []
def register(self, listener):
if listener not in self._listeners:
self._listeners.append(listener)
def unregister(self, listener):
if listener in self._listeners:
self._listener.remove(listener)
def emit(self, *args, **kwargs):
for listener in self._listeners:
listener(*args, **kwargs)
# example:
class EventDispatcher:
def __init__(self):
self.signal_mouse_down = Signal()
self.signal_move = Signal()
self.signal_click = Signal()
class ClickObserver:
def on_click(self, click_pos):
# do something when click event is emitted
pass
# usage
click_observer = ClickObserver()
event_dispatcher = EventDispatcher()
event_dispatcher.signal_click.register(click_observer.on_click)
This system does exactly the right amount of work to get the job done. It does
not iterate over all observers and call notify on them, event if the observer
throws away this particular event (as with using subject-observer). The only thing
that could be a drawback is, that you have a method for each different event type.
But in my opinion it even makes the code clearer to read. Another difference is
that the data of an event is passed directly as the arguments instead of an event
instance. Of course you could pass an event instance if you wish. I'm really undecided
what I prefer. Each event can have a different method signature if the number of
arguments differ. My point is, that there are different ways to design the handler
method:
# as in observer
def handler1(event): pass
# adding sender
def handler2(sender, event): pass
# using explicit arguments
def handler3(posx, posy, button): pass
# using generic arguments
def handler4(*args, **kwargs): pass
Another important thing I haven't mentioned until now is the return value of the handler
method. Sometimes it is convenient, if the processing stops after a certain handler call.
This only can be done by a return value which is interpreted by the sender. The return
value might just be True and False. When returning True, it means the event has been
handled and no further processing should be done, maybe like this:
def update(self, event):
for handler in self._handlers:
if handler(event):
break;
top |
tutorials |
downloads
Exception handling is important and until now I haven't said anything about.
What if a handler raises an exception? It shouldn't or it should catch any exception.
Either the event-dispatcher does not care (as in all examples until right now), so if an
exception is raised, the iteration is terminated and the exception is going onw level up.
Or the dispatcher does care. The question is, how should it react to an exception. Should it
remove just the faulty handlers or should it stop processing. Either way a try...except block
will be needed in the code.
...
# removing faulty handlers
def update1(self, event):
for handler in self._handlers:
try:
handler(event)
except:
self._to_remove.append(handler)
...
# or just stop processing
def update2(self, event):
try:
for handler in self._handlers:
handler(event)
except:
# log the exception, do something
pass
I'm not entirely sure if exception handling should be done here and what performance
effects it could have.
top |
tutorials |
downloads
So far we have a good subject-observer implementation, but what, if we don't want
a observer to stay alive, only because it is registered in one or more subjects?
Well yes, you might think that a weakref is the solution. That is only partially true. As
long you save references to instance of a class or unbound functions, you can use
a weakref or a WeakKeyDictionary as usual.
class Subject:
def __init__(self):
from weakref import WeakKeyDictionary
self._observers = WeakKeyDictionary() # the keys are weakrefs
def register(self, observer):
self._observers[observer] = 1
def unregister(self, observer):
if observer in self._observers:
del self._observers[observer]
def update(self, event):
# dead observer are removed automatically, this should also
# avoid dead references if they die while iterating (other
# methods might cause problems, see WeakKeyDictionary)
for observer in self._observers:
observer.notify(event)
This will let the observers die even if they are registered at a subject.
But there is a little flaw in python's weakref (well, actually
it's not the weakref module itself). It cannot store a weakref to a bound
method. The problem is, that you get a wrapper for the bound method which
is reported as dead as soon you want to use the weak reference. The trick here
is to store a weak reference to the instance and generate a new call to that method
using the stored data. Here is a wrapper that does that:
from weakref import ref
from new import instancemethod
class UnboundMethodWeakref:
def __init__(self, method):
# check that it is a bound method (use __func__, __self__ for
# python 2.6 or python 3.0)
if hasattr(method, 'im_func'):
self._im_self = ref(method.im_self)
self._im_func = method.im_func
else:
self._im_self = None
self._im_func = ref(method)
def __call__(self):
# check if it was a bound or unbound function
if self._im_self:
instance = self._im_self()
# check if the weakref is dead
if instance :
# return new wrapper for bound method (bound to instance)
return instancemethod(self._im_func, instance, instance.__class__)
return None # else dead
else:
# check if unbound function is alive
if self._im_func():
return self._im_func() # alive
return None # dead
def __eq__(self, other):
# needed to compare to be able to remove same instance from a list
return (self._im_func == other._im_func)
And here how you would use it:
class Subject:
#...
def register(self, method):
self._callbacks.append(UnboundMethodWeakref(method)
def update(self, event):
# ...
for to_remove in self._callbacks:
for remove_t in [t for t in self._to_remove if t==to_remove]:
self._callbacks.remove(t)
for weak_callback in self._callbacks:
callback = weak_callback()
# check if callback is alive
if callback is not None:
callback(event)
else:
# remove dead reference
# while we are iterating over the list we cannot remove it
# directly, also we need to compare instances of
# UnboundMethodWeakref and remove the one that has the same
# references saved
self._to_remove(UnboundMethodWeakref(method))
top |
tutorials |
downloads
...building...
top |
tutorials |
downloads
...building...
top |
tutorials |
downloads
...building...
top |
tutorials |
downloads
...building...
top |
back to tutorials overview
...building...
top |
tutorials |
downloads
...building...
top |
tutorials |
downloads
...building...
top |
tutorials |
downloads
...building...
top |
tutorials |
downloads
...building...
top |
tutorials |
downloads
...building...
top |
tutorials |
downloads
...building...
top |
tutorials |
downloads
...building...
top |
tutorials |
downloads