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 :

https://github.com/Niriel/Infiniworld/tree/v0.0.1

The important module is evtman.py, “evtman” standing for “Event Management”.  It contains three classes:

  • Event
  • Listener
  • EventManager
Event Manager, events and Listeners.

The Listeners communicate only with Events that transit through an Event Manager.

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[0] * 32
            self._y = 480 - event.position_to[1] * 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[0] * 32
                self._y = 480 - event.position_to[1] * 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

Result:

    hamster: pos = (96, 384)

In this example, the model doesn’t listen to anything, but in a real situation it would.


Hero Quest, the toughest monster of all.

HamsterView moving on the tile map and destroying Orcs as a response to a GoBerserkEvent.

That’s all there is to it ! It just works.

Just don’t forget to unregister the Listeners you don’t use any longer:

event_manager.unregister(hamster_view)

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.

EDIT:

  • 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.

 

About these ads

About Niriel

Cynical utopist who likes red wines from Languedoc and playing Minecraft instead of working on his own game.

Posted on 08/08/2011, in Infiniworld and tagged , , , , , . Bookmark the permalink. Leave a comment.

What do you think about it?

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: