The event management is in place.
Hello hello !
As promised I worked on what I called “Event Management”, the bare bones of my lousy implementation of the Model-View-Controller pattern. It’s uploaded on GitHub :
The important module is evtman.py, “evtman” standing for “Event Management”. It contains three classes:
The classes Event and Listener are abstract, and are meant to be subclassed. The Event class looks a bit complicated, but that’s because it automatizes things in order to make its subclasses extremely easy to define. For example :
>>> class CharacterMovedEvent(Event): ... name = "Character Moved Event" ... attributes = ('character_id', 'position_from', 'position_to') ... >>> event = CharacterMovedEvent('Bunny', (0, 0), (32, 0)) >>> print event.character_id Bunny >>> print event.position_to (32, 0) >>> print repr(event) CharacterMovedEvent(character_id='Bunny', position_from=(0, 0), position_to=(32, 0)) >>> print str(event) Character Moved Event character_id = 'Bunny' position_from = (0, 0) position_to = (32, 0)
See ? Very easy. Now that I think of it, the name attribute should go away because it’s pretty useless; I put it there to have a clean human-readable name but the class name is enough. I made sure that repr would not lead to insanely long lines by capping at 50 characters per attribute. Very useful when events carry a complete dungeon map in a huge list/dictionary/tuple/whatever.
It is also very easy to subclass Listeners:
class CharacterView(Listener): def __init__(self, character_id): self._character_id = character_id self._x = self._y = 0 def onCharacterMovedEvent(self, event): if event.character_id == self._character_id: # 32 pixels per meter, and the display starts at # the bottom while my Y axis goes up. self._x = event.position_to * 32 self._y = 480 - event.position_to * 32
That’s it, you have your View. It should be a pygame Sprite, the zoom level (32) and the window size (480) should not be hardcoded here, but that’s not more complicated than that.
And to use it is very simple:
def example(): class CharacterMovedEvent(Event): attributes = ('character_id', 'position_from', 'position_to') class CharacterView(Listener): def __init__(self, character_id): self._character_id = character_id self._x = self._y = 0 def __str__(self): return "%s: pos = (%i, %i)" % (self._character_id, self._x, self._y) def onCharacterMovedEvent(self, event): if event.character_id == self._character_id: self._x = event.position_to * 32 self._y = 480 - event.position_to * 32 # bunny_view = CharacterView('bunny') hamster_view = CharacterView('hamster') event_manager = EventManager() event_manager.register(bunny_view) event_manager.register(hamster_view) # event = CharacterMovedEvent('bunny', (0, 0), (1, 2)) event_manager.post(event) event_manager.pump() # print bunny_view print hamster_view
And the result is:
bunny: pos = (32, 416) hamster: pos = (0, 0)
There is one thing missing, though: some Listeners may want to post events. For example, the CharacterModel is a Listener which should be able to post the CharacterMovedEvent. This is achieved by giving the event manager to the listener. Add this to the previous code:
class CharacterModel(Listener): def __init__(self, event_manager, character_id): Listener.__init__(self) self._event_manager = event_manager self._character_id = character_id self._x = self._y = 0 def moveTo(self, new_x, new_y): old_x = self._x old_y = self._y self._x = new_x self._y = new_y event = CharacterMovedEvent(self._character_id, (old_x, old_y), (new_x, new_y)) self._event_manager.post(event) hamster_model = CharacterModel(event_manager, 'hamster') event_manager.register(hamster_model) hamster_model.moveTo(3, 3) event_manager.pump() print hamster
hamster: pos = (96, 384)
In this example, the model doesn’t listen to anything, but in a real situation it would.
That’s all there is to it ! It just works.
Just don’t forget to unregister the Listeners you don’t use any longer:
Sure the event_manager keeps only Weak References to the Listeners, so they automatically disappear from its lists. But if they disappear right in the middle of an iteration, that may explode. If you see such an explosion, it means you forgot to properly unregister.
A last word: you can post as many events as you want. They will all be processed, in the order you posted them, when you call the pump method of the event manager. Some event handlers will post events, even register new Listener. That is perfectly okay, the event manager is happy with that, the pump will go on until the event queue is empty.
- I got rid of the ‘name’ variable of events. It was 100 % useless.
- I introduced a SingleListener class, which is for Listeners that are interested in ONE event manager only. And to be fair, that’s the case most of the time.