@window.event
def on_draw():
….
# 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.
import hooks
class ClockTimer(object):
def tick(self):
hooks.run("on_update")
class Observer(object):
def __init__(self):
hooks.add("on_update", self.on_update)
def __unload__(self):
hooks.remove("on_update", self.on_update)
def on_update(self):
pass
def DigitalClock(Observer):
def on_update(self):
pass
def AnalogClock(Observer):
def on_update(self):
pass
hooks.run("on_update", hooks.build_info("ch str", (ch, "blah",)))
hooks.add("on_update", on_update)
def on_update(info):
ch, s, = hooks.parse_info(info)
pass
def something_happened_to_an_entity(self, entityID):Is much more work to maintain than:
self.lootHandlerReference.hey_something_happened_to_an_entity(entityID)
self.killHandlerReference.hey_something_happened_to_an_entity(entityID)
self.spawnHandlerReference.hey_something_happened_to_an_entity(entityID)
def something_happened_to_an_entity(self, entityID):With registered subscription methods in the relevant handlers.
BLOCKING_BROADCAST_EVENT("something_happened_to_an_entity", entityID)
def event_OnServicesStarted(self):However, when I think about functions simply receiving an event when it happens, using a simple decorator has some appeal.
@eventBut I am not sure how extensive my event model is going to get. Do I want to allow a subscription to specify when it gets received, or how it gets received?
def OnServicesStarted(self):
@event.preEach decorated function will have the same name, and will overwrite any previous attribute on the class, specifically the event function before it. This means that this specific approach using decorators will not allow an object to register for more only one stage in the broadcast of a given event.
def OnServicesStarted(self): pass
@event
def OnServicesStarted(self): pass
@event.post
def OnServicesStarted(self): pass
def event_OnServicesStarted_pre(self): passThis is a little more versatile than the decorator approach, as it allows an object to register for every stage in the broadcast of an event. Of course, to address this the function naming could be brought partially into the decorator approach, where the use of the _pre or _post suffix would have the same effect.
def event_OnServicesStarted(self): pass
def event_OnServicesStarted_post(self): pass
event("OnServicesStarted")Or avoiding the clunky string used for an event name.
event.OnServicesStarted()It is reasonable to assume this is how blocking event broadcasts are made, given that the natural expectation for a function call is for it to do processing and return. So, that leaves the question of how to do non-blocking event broadcasts.
event.noblock.OnServicesStarted()Here, the noblock attribute would simply return a secondary event object that starts event broadcasts in a new microthread.
class LawAndOrderService(Service):If I were to broadcast a detect magic event, anything that listened to the event in game, would receive it. Not just local objects. This is why it is not something my event system is designed to handle. But writing this post has been a good exercise in thought about how I want my action system to work, what I envision it doing, and how I am now seeing that as being much harder to do in a straightforward way.
def event_OnActionPerformed(self, actor, target, action):
reaction = self.GetReaction(actor.municipality, action.type)
if reaction:
if reaction.requiresObservers and not action.npcObservers:
return
for npc in action.npcObservers:
npc.MaybePerformReaction(reaction)
Read on…
One of the most important design decisions for my MUD codebase, is to be as decoupled as possible. The codebase is reasonable clean as a result of this, but there are many systems that need to be revisited. Some are a legacy of earlier versions of the codebase, and others have not had a real use made of them that would drive the evolution of their design.
One of these systems, the one I will examine in this blog post, is a generic system to both subscribe to events, and to broadcast events to existing subscribers. As I am currently at the stage where I am ready to build other systems that make use of this lower-level one, I need to reexamine how it currently works and consider whether it does what is needed, or if I can do better.
The current implementation
Currently, an object that wants to listen to the OnServicesStarted event needs to define specific elements on its class.
That is, for each event it wishes to receive, the name has to be present within a magic attribute. And of course, a handler function needs to be defined on the class with the same name as the matching event. Defining these elements does not provide automatic subscription however, the object still has to subscribe for the events it wishes to listen to.
The main downside of this approach is the boilerplate involved, where the name of the event needs to be declared and then a function of the same name defined. A lesser downside is that the services service does not seem like the most relevant place to go to for event registration.
This will be referred to as the magic attribute and handler method pattern.
Idea: An event service
Moving this functionality to a custom service seems a little cleaner. Objects would then call in the following way to subscribe:
But having this functionality in a custom service, means that any other service that wants to use it might need to declare the custom service as a dependency.
This adds boilerplate and complication where it can otherwise be avoided. Chances are that most services would end up with a dependency on the events service. One advantage of having this system within the services service, was that it was guaranteed to be available for all services regardless of whether they had started fully yet or not.
This is a dead end.
Idea: An event handler
A way to avoid the dependency problem, is to move this functionality outside of the service infrastructure, perhaps as an independent singleton object. The following code pretty much wrote itself, following the conception of this idea.
This is a break from the magic attribute and handler method pattern. How it would be used highlights this.
One of the advantages mooted about Stackless, or coroutines in general, is the ability to lessen boilerplate and to be able to write more readable code. This solution illustrates that advantage, although an obvious next step would be encapsulating the Event class instantiation and Wait method call into a simple function. It might be called WaitForEvent, taking the event name as an argument and blocking until it occurs, returning the relevant parameters.
At this stage, I am distracting myself with details. The goal is a cleaner event system, rather than the lesser details of how that system would be used. But before I move on and forget, one last thought.. a thought occurs that passing the event name as a string is clunky, and that it might be cleaner to be able to do eventHandler.eventName and have that implicitly do the blocking the Wait method from above does. But these things can be explored when I have decided on the best system to make use of them with.
The current idea is quite appealing, especially because as described above, it moves towards the ideal of more readable code rather than disjoint callback-based boilerplate. But after sleeping on it, I remembered the advantage of having events declared as data, rather than within code. Or what the last idea had over this one. My mudlib uses a code reloading framework, and if things like events are declared within code, the code reloading support cannot be extended to automatically correct things like putting in place new event subscriptions. What if SomeObject was extended to handle a second event?
When the script defining this code is modified, and reloaded, instances of this class may already be out there in use. Their __init__ method will have already run as its old version, and it is unreasonable to expect the code reloading system to compare the two and reconcile the differences. They will inherit the new HandlePlayerLogoffEvent (not shown above of course), but will not be registered for the event.
With the magic attribute and handler method pattern, it is trivial to subscribe and unsubscribe objects for events as entries in the magic method come and go. Maybe it is time to take a step back..
Design requirements
So having examined two approaches, I have two highlighted design requirements. They are listed in order of priority.
Idea: Inline event declaration
The synchronous approach as shown above is very appealing, but it is unreasonable to infer event registrations from within code. The events that need to be subscribed to, or unsubscribed from, have to be declared. If we were to go with the synchronous approach, we need to adapt it in some way to do this.
One solution is wrapping event handlers with a decorator that declares what event it handles.
In my experience with other people making use of decorators, they tend to be more of a pollution than a benefit. Of course, with restraint, they are fine used only where really suitable and actually needed.
Considering this solution, a minor downside is the duplication between the event name and the decorated function name. Or, given that the function name is not important, that this is a little clunky in the same way that the magic attribute and handler method pattern is. A cleaner variation to the solution would be to use the function name as the event name, and to have the decorator take no arguments. However, a larger downside is that the behaviour of decorators within the code reloading framework is undefined and as yet, undetermined.
Another solution is encoding a function name to indicate that it handles a given event. In the system currently in use we already have the __listenevents__ magic method, and of course Python itself has support for private methods (def __Func(self), so this is not necessarily a bad approach to go with. Perhaps something like.
This is a little ugly, but there are a wealth of possibilities: _event_PlayerLogin, event_PlayerLogin, etc.. This is beginning to look acceptable to me.
An additional use case
The last described solution is all very well, but it is intended to provide for the case where desired events are declared and subscription is taken case of automatically. This will be the most common use case. However, it should still be possible to dynamically register for events.
The ability to dynamically register for events, allows flexible subsystems to be written that build on the underlying event system, by being able to register or unregister for arbitrary events as needed. It is easy enough to add an API to allow this, but it is worth considering how the code reloading system factors into this.
As I do not have a need for this functionality at this time, and it will not affect the design chosen to suit my current needs, I will hold off on designing this for now. But I expect it will make use of the following handy idiom:
Conclusion
I will most likely go with the final solution I described. It is the simplest and cleanest from a usage point of view, and this is what is important to me. The event system would take care of enough to make a developer's work simpler and more straightforward, without abstracting away functionality needlessly.