08 Jan, 2010, donky wrote in the 1st comment:
Votes: 0
I'd appreciate any feedback on my plans for an event notification/subscription system, which follow. I wrote this as a blog post, but I've reposted it here for visibility, if you have any comments please make them here. I don't know about you, but forum posts which go "I MAED THIS BLOG POST, PLEES COMMENT ON IT THIER" tend to put me off.

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.

class Object:
__listenevents__ = [ "OnServicesStarted" ]

def OnServicesStarted(self):
pass

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.

class Object:
def __init__(self):
sorrows.services.AddEventListener(self)

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:

sorrows.events.AddListener(self)


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.

class GameLogicService(Service):
__dependencies__ = [ "events" ]


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.

class Event:
registry = weakref.WeakValueDictionary()

def __init__(self, eventName):
self.name = eventName

def Wait(self):
c = self.registry.get(self.name, None)
if c is None:
c = self.registry[self.name] = stackless.channel()
return c.receive()


This is a break from the magic attribute and handler method pattern. How it would be used highlights this.
class SomeObject:
def __init__(self):
# .. do some setup first
stackless.tasklet(self.HandlePlayerLoginEvent)()

def HandlePlayerLoginEvent(self):
while True:
user = Event("PlayerLogin").Wait()
# .. do what we wanted to do to them


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.

def WaitForEvent(eventName):
return Event(eventName).Wait()


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?

class SomeObject:
def __init__(self):
# .. do some setup first
stackless.tasklet(self.HandlePlayerLoginEvent)()
stackless.tasklet(self.HandlePlayerLogoffEvent)()


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.
  • Integrated with code reloading support.
  • Avoid boilerplate and allow use of synchronous syntax.


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

    @event("PlayerLogin")
    def HandlePlayerLogins(self):
    user = WaitForEvent()


    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.

    def _EVENT_PlayerLogin(self):

    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:

    def Register(self, eventName, func):
    funcName = func.__name__
    obRef = weakref.ref(func.im_self)
    self.listeners[eventName].append((funcName, obRef))

    def Send(self, eventName, *args, **kwargs):
    for funcName, obRef in self.listeners[eventName]:
    ob = obRef()
    if ob is not None:
    func = getattr(ob, funcName)
    func(*args, **kwargs)


    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.
    08 Jan, 2010, elanthis wrote in the 2nd comment:
    Votes: 0
    Quote
    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.


    If used properly and designed intelligently, they are a godsend. After you use any language with decorators/annotations, going back to a language without them is pure torture. Some types of apps more than others, of course. (Web development with them is a complete nightmare, and yet the most popular Web programming language, PHP, does not have them… this is one of the many facts that proves that the PHP developers are some of the biggest freaking idiots that have ever managed to figure out how to write code. I cannot accurately express how many times I've wished actual bodily harm to befall those dipshits. /tangent-rant)

    So far as event subscription, I've never seen a good use for it in a MUD, or even most games. Most events have very clear definitions of who needs to know about them, and given a structured world (which pretty much every game has), it's not at all hard to ensure delivery. If you have a room-based MUD, I would go so far as to suggest you directly rip off the DOM/JavaScript event model. You've more or less gotten the registration system there, but that's a tiny and (quite honestly) irrelevant part of it. There are 10 other ways you could get the exact same result. The important part is figuring out where events go and who receives them, and that's a policy decision that gets mired in the code. Picking a single, consistent, understandable event delivery model is key.

    Back on the implementation, I would personally (and this is mostly taste, of course), go with a combination of your last and second-to-last approaches. When you have an object that always needs to respond to a particular event, being able to just add an OnFoo (for the Foo event) method is just really damn easy. For cases where objects need to listen for events on other objects, they can still register custom event handlers. This, again, is almost directly parallel to how DOM/JavaScript events work, where DOM elements can have an onfoo method as well as registered event listeners with the addEventListener method.

    You should also make sure you add a way to remove an event listener, especially in a reference-counted language like Python (i think it's cycle GC is still off by default? or is it on by default now?) where your event functions and their closure values can and will create all sorts of reference loops.
    09 Jan, 2010, Idealiad wrote in the 3rd comment:
    Votes: 0
    I can't say I followed the OP with 100% comprehension, but wanted to note that the pyglet game lib uses a technique similar to the decorator described, like:

    @window.event
    def on_draw():
    ….


    Here's a publisher/subscriber pattern from the pyglet manual:

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


    And I find it to be really easy to work with. I'm not sure how applicable it would be to a mud though.
    09 Jan, 2010, Confuto wrote in the 4th comment:
    Votes: 0
    I quite like the way NakedMUD does this, myself. It's simple and versatile. Using the above example (which it's fairly similar to):

    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

    It's a bit more verbose because NM's hooks are more functional than OO and also need to be manually removed when the module/class is unloaded. You can also pass arguments to the subscriber (as a tuple) when you're running a hook:

    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

    Unfortunately, as can be seen in the above example, you can't just pass/receive a plain tuple to/from the hook - presumably because NM's C core can't handle them.
    09 Jan, 2010, Twisol wrote in the 5th comment:
    Votes: 0
    Heh, wow. I've been puzzling out an events system for the past week, although it's for my MUSHclient widget framework rather than a MUD, but I just haven't seen many articles/posts that deal with it. Maybe I wasn't searching properly. Anyways, this post was a great read, and it really helped make sense of a few things for me. :smile:

    @Confuto and @Idealiad: Your solutions are very similar to what I've been considering, too. My main issue is how event propogation works, to be honest. I want other widgets to be able to hook into, say, a button's built-in OnMouseDown event (which all widgets can register for on themselves, too), but if the button's parent type also has a hook, does it get executed too? If not, how does the button tell which handlers to fire and which not to fire? At any rate, it's nice to see what I'm at least partially on the right track.
    09 Jan, 2010, Confuto wrote in the 6th comment:
    Votes: 0
    Twisol said:
    @Confuto and @Idealiad: Your solutions are very similar to what I've been considering, too. My main issue is how event propogation works, to be honest. I want other widgets to be able to hook into, say, a button's built-in OnMouseDown event (which all widgets can register for on themselves, too), but if the button's parent type also has a hook, does it get executed too? If not, how does the button tell which handlers to fire and which not to fire? At any rate, it's nice to see what I'm at least partially on the right track.


    Something that I found to be a fairly interesting read was BabbleMUD's Engine Specs, which discusses events that have 'originators', which I think might be something you'd want to consider. I don't fully understand it myself, but the general idea seems to be that subscribers can listen for specific event types, or all events from specific originators (I suppose this would be the button in your case), or a combination of the two. Sounds pretty neat - to me, at least.
    09 Jan, 2010, donky wrote in the 7th comment:
    Votes: 0
    elanthis said:
    So far as event subscription, I've never seen a good use for it in a MUD, or even most games.


    I've worked on a commercial game code base heavily structured around it and I found it very convincing. From the point of one rewrite made to it, decoupling was an important goal, and the use of events made it possible to separate game systems as much as possible.

    Having an event source that does the following:

    def something_happened_to_an_entity(self, entityID):
    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)
    Is much more work to maintain than:

    def something_happened_to_an_entity(self, entityID):
    BLOCKING_BROADCAST_EVENT("something_happened_to_an_entity", entityID)
    With registered subscription methods in the relevant handlers.

    Quote
    Most events have very clear definitions of who needs to know about them, and given a structured world (which pretty much every game has), it's not at all hard to ensure delivery.


    In my experience it is not that simple. Sometimes I want those other systems informed before I continue, sometimes some of those systems need to be informed before others, sometimes I want to start the process of letting the systems know but continue and finish what I am doing before they get notified. And when I've built up a block of line by line calls on explicit systems to notify them, this becomes too much work to maintain in the long run.

    Quote
    You should also make sure you add a way to remove an event listener, especially in a reference-counted language like Python (i think it's cycle GC is still off by default? or is it on by default now?) where your event functions and their closure values can and will create all sorts of reference loops.


    Never happens in practice. Simple use of weak references, as shown above in my example, results in subscriptions disappearing as the subscriber is garbage collected.
    09 Jan, 2010, Idealiad wrote in the 8th comment:
    Votes: 0
    Twisol said:
    My main issue is how event propogation works, to be honest. I want other widgets to be able to hook into, say, a button's built-in OnMouseDown event (which all widgets can register for on themselves, too), but if the button's parent type also has a hook, does it get executed too? If not, how does the button tell which handlers to fire and which not to fire? At any rate, it's nice to see what I'm at least partially on the right track.


    Pyglet at least uses an event stack with a concept of frames, where you can either set a handler at the top of the stack, or a frame of handlers (a group of handlers that will all handle an event), and then you can pop off frames or single handlers as needed. A handler can kill event propagation through the stack by returning True.
    09 Jan, 2010, donky wrote in the 9th comment:
    Votes: 0
    Confuto said:
    I quite like the way NakedMUD does this, myself. It's simple and versatile.


    The basic model is very straightforward. However, the weird argument passing is something I do not understand. And the hooks page on the NakedMud web site does not justify it, which is surprising. It is however good inspiration for simplifying things. It has made me take a step back to consider creating a similar standalone library, although I have a need for a more extensive model.
    09 Jan, 2010, donky wrote in the 10th comment:
    Votes: 0
    Idealiad said:
    I can't say I followed the OP with 100% comprehension, but wanted to note that the pyglet game lib uses a technique similar to the decorator described


    I've used Pyglet, but the decorator usage came across as a little ill-fitting to me. Ignoring that, the source code constructed using them did look clean.
    09 Jan, 2010, Idealiad wrote in the 11th comment:
    Votes: 0
    Quote
    Unfortunately, as can be seen in the above example, you can't just pass/receive a plain tuple to/from the hook - presumably because NM's C core can't handle them.


    Yeah, that more than likely is creating something passable to the Python ctype library or something similar.
    09 Jan, 2010, Barm wrote in the 12th comment:
    Votes: 0
    How attached are you to the code-reloading? While reading the OP, I kept getting the feeling that dumping it would simplify some of the issues. Is it a feature that would be used constantly on a live, stable server or a nicety during development?

    I see that it's one of your projects and can appreciate the appeal of building on your own handiwork.
    09 Jan, 2010, donky wrote in the 13th comment:
    Votes: 0
    Barm said:
    How attached are you to the code-reloading? While reading the OP, I kept getting the feeling that dumping it would simplify some of the issues. Is it a feature that would be used constantly on a live, stable server or a nicety during development?

    I see that it's one of your projects and can appreciate the appeal of building on your own handiwork.


    I'm not making a MUD server for the sake of it. I am making a MUD server that uses certain technologies which I find interesting, and which I have subsequently developed for use by that MUD server. Not using them, would remove a degree of the interest I have in the project. I have a stack of ideas that relate to the ability to write dynamic systems, many working with, or modifying, the code reloading system.

    While asking how useful it is outside of development seems like a relevant question, in practice, not so much. Let's be honest, how many MUD code bases make it out of development? I know I have worked on two previous MUD code bases that have never been used in production. But assuming I get out of development and release a live MUD, yes, there are variety of potential uses that may or may not pan out, to put it to at that point.
    09 Jan, 2010, donky wrote in the 14th comment:
    Votes: 0
    Idealiad said:
    Quote
    Unfortunately, as can be seen in the above example, you can't just pass/receive a plain tuple to/from the hook - presumably because NM's C core can't handle them.


    Yeah, that more than likely is creating something passable to the Python ctype library or something similar.


    When I look at this, it seems like an opportunity to make things easier for the end user. Many of the arguments that need to be built into a string, are those which the API can introspect and determine the type of. It should be possible to generate that string automatically, allowing the end user to pass in the raw tuple directly.
    10 Jan, 2010, donky wrote in the 15th comment:
    Votes: 0
    Alright, here's part two. I may leave pre and post events for a later point in time, as I do not have a need for them yet. But otherwise, implementing this system should be a relatively quick process, perhaps 20 minutes. Integrating it into my mudlib, specifically the code reloading hooks, may be take a little longer. And writing this now, I am wondering whether launch might be a better modifier than noblock.

    Again, any thoughts, concerns or suggestions are appreciated :cyclops:

    Part two

    At this time, my preferred approach is to identify event subscriptions by how functions are named within a given object.

    def event_OnServicesStarted(self):
    However, when I think about functions simply receiving an event when it happens, using a simple decorator has some appeal.

    @event
    def OnServicesStarted(self):
    But 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?

    When events might get received

    For some events, it is useful to know that you are receiving a notification before an event has had side-effects through its subscribers, when it is appropriate to act on it and cause side-effects or when all direct side-effects have happened.

    A simple way to structure this is to allow subscription for one of three different partial broadcasts. The first, a pre-broadcast. The second, the actual broadcast. And the third, a post-broadcast.

    Using the decorator syntax, subscription might happen in the following way.

    @event.pre
    def OnServicesStarted(self): pass

    @event
    def OnServicesStarted(self): pass

    @event.post
    def OnServicesStarted(self): pass
    Each 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.

    Using the name-based syntax, subscription might happen in the following way.

    def event_OnServicesStarted_pre(self): pass

    def event_OnServicesStarted(self): pass

    def event_OnServicesStarted_post(self): pass
    This 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.

    How events might get received

    Using Stackless Python, my MUD framework cooperatively schedules the microthreads it is built upon. Cooperative scheduling is a lot simpler to program than preemptive scheduling, because you know where your code is going to block. You do not always want your code to block when you want to send an event, although at other times it might be acceptable.

    So the broadcaster needs to be in control of how events get sent. They need to be able specify that a broadcast can block or not.

    It might be done through a global function.

    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.

    One possibility, is a special attribute that injects differing behaviour.

    event.noblock.OnServicesStarted()
    Here, the noblock attribute would simply return a secondary event object that starts event broadcasts in a new microthread.

    Does this effect subscribers? Not unless a subscriber has the ability to interfere or interrupt a broadcast, which is not a desired part of this model. Event subscription does not need to know about, or cater for this. It is a broadcast related detail.

    To decorate, or not

    Not. There is no compelling reason, beyond appreciation of how it looks, to choose to use decorators. The function naming approach is fine for now, and does not cost an extra line of source code.

    When are objects registered

    It is all very well for objects to specify functions named in such a way that it is recognised they should be called when events happen. However, the object still needs to be registered for those events. What is the best way to go about this? How is the use of code reloading affected by this?

    It is possible within the code reloading system, to set things up in such a way that when an object is instantiated, it automatically gets registered for events. The way this would work, is that when a class is first loaded, or subsequently reloaded, it would have event related post-processing applied. The code reloading system does not currently support this, but it is an easy feature to add to it.

    A simpler approach, is to have any object which defines events, make an explicit call to the event system to register it for any events that it may handle. This would have to happen within the constructor for that object, the __init__ method. The problem is that this method is called when the object is instantiated, and if the registration call is added within a subsequent code change, existing instances of the object will never get registered. It complicates the model a programmer has to hold in their head about how effective code reloading is.

    Taking care of registration with the aid of the code reloading system looks like the way to go.

    Conclusion

    I am pretty sure I have gone over what I need out of an event system. I have a preferred approach to how event subscriptions are declared. I have a preferred approach to how event broadcasts are made. At this point, implementation looks like the next step.
    11 Jan, 2010, donky wrote in the 16th comment:
    Votes: 0
    Source code here: events.py

    It took a little bit longer than twenty minutes, and is not quite complete. It still needs work to flesh out the actual subscription side of things, but the broadcast aspect is more or less complete. I was inspired by the NakedMud hooks into writing it in a standalone module outside my framework, in order to keep it simple, and open to possible usage other than within my framework.

    I was considering registering objects for events by hooking into instantiation of classes that have event functions. But a less intrusive method has just occurred to me, which is to collect references to found event functions in a code reloading pass, and when broadcasting events just find all the instances through garbage collection tracking. I'll have to think this over, while I am a big fan of using garbage collection to locate referrers so that useful functionality can be written, it might not be the fastest approach.
    12 Jan, 2010, Barm wrote in the 17th comment:
    Votes: 0
    donky said:
    Not using them, would remove a degree of the interest I have in the project.


    I can dig that.

    Earlier you said you weren't sure to what degree you want to generate events – have you given this any more thought? I keep picturing something along the lines of "So-and-so casts Detect Magic" which propagates through the room, containers in the room, actors in the room, and items on the actor. I especially like the weakref dictionary which covers one of my worries.

    How do you plan to handle messaging from events so that you see, "Fred's detect magic spell causes your sword to glow" while Fred sees, "Your detect magic spell causes Donky's sword to glow"?

    I was reading this post and rather liked the author's use of the += and -= operators for registration;
    http://www.voidspace.org.uk/python/weblo...
    12 Jan, 2010, donky wrote in the 18th comment:
    Votes: 0
    Barm said:
    Earlier you said you weren't sure to what degree you want to generate events – have you given this any more thought? I keep picturing something along the lines of "So-and-so casts Detect Magic" which propagates through the room, containers in the room, actors in the room, and items on the actor.

    This is not a use case for my event system.

    What you are describing is the performance of an action, with targets of that action and observers of that action. The targets are implicitly defined by the action, and the observers are implicitly defined by the room the action is in. The action can easily obtain target objects, and its observers, and enact the required behaviour without a need for events.

    As I see it, most of the subscribers to my events will be generic systems. Like, for instance, a "law and order" system. It would have rules registered for different municipalities, and on an actor performing an action, it would determine the municipality in which it was performed and whether there should be a reaction.

    Perhaps any use of magic is a crime in certain municipalities. Just writing some imagined code gives me this pseudo-code.

    class LawAndOrderService(Service):
    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)
    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.

    Quote
    How do you plan to handle messaging from events so that you see, "Fred's detect magic spell causes your sword to glow" while Fred sees, "Your detect magic spell causes Donky's sword to glow"?

    I am not sure I see the problem. Display of descriptions has been a solved problem, in the domain of MUDs, since at least the late 90's. The Lima LPC mudlib, for instance, had special string formatting. Although, admittedly, it was arcane IMO.

    I have not approached it yet in my framework, but if I did, it seems that using "str.format" looks like a readable and easy way to go.

    "{0.possessive} detect magic spell causes {1.possessive} {2} to glow.".format(WRAP(actor), WRAP(owner), WRAP(target))

    Where WRAP, or something similar in effect, would provide a facade around objects, adding the magic helper attributes like 'possessive' that structure what gets displayed in a string.

    Quote
    I was reading this post and rather liked the author's use of the += and -= operators for registration;
    http://www.voidspace.org.uk/python/weblo...

    Personally, I find there is a point where syntactical sugar has gone beyond clear understanding, and overloading += and -= crosses this line.
    12 Jan, 2010, elanthis wrote in the 19th comment:
    Votes: 0
    If you're solely interested in reception of events in regions/municipalities, and those are broken up into logical areas/zones (which is a good thing to use areas/zones for), then using a bubble-up event system would handle that without event subscription.

    MUDs (and other games) have a lot of special cases with their event systems. On the one hand, "observable" events naturally propogate to everything that can, er, observe them. On the other hand, AI and various game logic doesn't really operate within the bounds of what is naturally observable.

    If it works for your design, that's all that matters. People can argue the best way to do this stuff 'till they're blue in the face, and nobody will be right. At least until someone with Real Genius figures out the world's perfect design (hasn't happened yet).

    Quote
    Personally, I find there is a point where syntactical sugar has gone beyond clear understanding, and overloading += and -= crosses this line.


    Agreed. If += isn't adding a numerical value – or maybe a proper set union or array append, if you're feeling particularly generous and the whole language and stdlib is already using += for such purposes – it's being abused. C# popularized that abuse with delegates, unfortunately, and C++ long ago popularized the abuse of the shift operators.
    0.0/19