Creating your own event dispatcher

pyglet provides only the Window and Player event dispatchers, but exposes a public interface for creating and dispatching your own events.

The steps for creating an event dispatcher are:

  1. Subclass EventDispatcher
  2. Call the register_event_type class method on your subclass for each event your subclass will recognise.
  3. Call dispatch_event to create and dispatch an event as needed.

In the following example, a hypothetical GUI widget provides several events:

class ClankingWidget(pyglet.event.EventDispatcher):
    def clank(self):
        self.dispatch_event('on_clank')

    def click(self, clicks):
        self.dispatch_event('on_clicked', clicks)

    def on_clank(self):
        print 'Default clank handler.'

ClankingWidget.register_event_type('on_clank')
ClankingWidget.register_event_type('on_clicked')

Event handlers can then be attached as described in the preceding sections:

widget = ClankingWidget()

@widget.event
def on_clank():
    pass

@widget.event
def on_clicked(clicks):
    pass

def override_on_clicked(clicks):
    pass

widget.push_handlers(on_clicked=override_on_clicked)

The EventDispatcher takes care of propogating the event to all attached handlers or ignoring it if there are no handlers for that event.

There is zero instance overhead on objects that have no event handlers attached (the event stack is created only when required). This makes EventDispatcher suitable for use even on light-weight objects that may not always have handlers. For example, Player is an EventDispatcher even though potentially hundreds of these objects may be created and destroyed each second, and most will not need an event handler.

Implementing the Observer pattern

The Observer design pattern, also known as Publisher/Subscriber, is a simple way to decouple software components. It is used extensively in many large software projects; for example, Java's AWT and Swing GUI toolkits and the Python logging module; and is fundamental to any Model-View-Controller architecture.

EventDispatcher can be used to easily add observerable components to your application. The following example recreates the ClockTimer example from Design Patterns (pages 300-301), though without needing the bulky Attach, Detach and Notify methods:

# The subject
class ClockTimer(pyglet.event.EventDispatcher):
    def tick(self):
        self.dispatch_events('on_update')
ClockTimer.register_event('on_update')

# Abstract observer class
class Observer(object):
    def __init__(self, subject):
        subject.push_handlers(self)

# Concrete observer
class DigitalClock(Observer):
    def on_update(self):
        pass

# Concrete observer
class AnalogClock(Observer):
    def on_update(self):
        pass

timer = ClockTimer()
digital_clock = DigitalClock(timer)
analog_clock = AnalogClock(timer)

The two clock objects will be notified whenever the timer is "ticked", though neither the timer nor the clocks needed prior knowledge of the other. During object construction any relationships between subjects and observers can be created.

Documenting events

pyglet uses a modified version of Epydoc to construct its API documentation. One of these modifications is the inclusion of an "Events" summary for event dispatchers. If you plan on releasing your code as a library for others to use, you may want to consider using the same tool to document code.

The patched version of Epydoc is included in the pyglet repository under trunk/tools/epydoc (it is not included in distributions). It has special notation for document event methods, and allows conditional execution when introspecting source code.

If the sys.is_epydoc attribute exists and is True, the module is currently being introspected for documentation. pyglet places event documentation only within this conditional, to prevent extraneous methods appearing on the class.

To document an event, create a method with the event's signature and add a blank event field to the docstring:

import sys

class MyDispatcher(object):
    if getattr(sys, 'is_epydoc'):
        def on_update():
            '''The object was updated.

            :event:
            '''

Note that the event parameters should not include self. The function will appear in the "Events" table and not as a method.