Events are Talking to me

We all have event systems, there’s almost no program that doesn’t use events these days. Their proliferation belies their performance. They help neaten a whole bunch of code that, otherwise, would be a hideous nest of coupled code. If you don’t use event systems and I’ve lured you here under the pretence of a funny title, they are a programming practice that separates event triggers and event listeners. This removes a lot of the triggers looking for listeners or vice versa as you make an Event Handler that has lists of things listening for a particular event.

Event systems use these three roles in the following way. We’ll have the trigger notice something which requires an event, it’ll then send the event off to the event handler¬†and think no more of it. The event handler will go through the list of things it has listening out for that event and send a call to the corresponding function and think no more of it. The event listeners will then do whatever they need to for that event and then the chain will have ended.

 
class EventTrigger : Object
{
...
    void SomeFunction() 
    {
        if(WeNeedToTriggerThisEvent) 
        {
             EventHandler.TriggerEvent(ThisEvent, EventArguments);
        }
    }
...
} 

static class EventHandler 
{
...
    Array<Array<EventListenerDelegate>> Listeners;

    void TriggerEvent(Event::Type EventType, EventArgs Args)
    {
        foreach(EventListener Listener in Listeners[EventType])
        {
            Listener.Execute(Args);
        }
    }
...
} 

class EventListener : Object 
{ 
    void Init()
    {
        EventHandler.AddListener(OnEvent, EventType);
    }

    void OnEvent(EventArgs Args)
    {
        ...
    }
} 

Event handlers are often part and parcel of modern game engines, they each have their own handlers that you can add triggers and events to. However, also in modern game engines, you are likely to have large inherited objects from all of the engines base classes and then yours on top of that. That’s a lot of classes and within that inheritance list there’s likely to be some event overlap. A base class will want to do something on an event and you want to do something else.

How do you control which event gets triggered, you can call super functions that will invoke an inherited event response. You can invoke these at the beginning of the function for bottom to top event priority or at the end for top to bottom event priority. If you want even more flexibility you can invoke it within conditional statements – whatever you wish.

 
class EventListener : Object 
{
    void OnEvent(EventArgs Args)
    {
        Super::OnEvent(Args);
        ...
    }

    void OnOtherEvent(EventArgs Args)
    { 
        ...
        Super::OnOtherEvent(Args);
    }
} 

The makers of one of the oldest game engines have had another neatening tweak. In UE4 the event handler waits for a reply from the event listener. The listener will come back with a reply that is handled or unhandled, not only this but there is some choice of what the handler should do with the reply for example¬†you may set the user focus after you have come out of the event listener. Crucially it’s only after the event listener chain is complete that any action is taken.

 
static class EventHandler 
{
...
    Array<Array<EventListenerDelegate>>; Listeners;

    void TriggerEvent(Event::Type EventType, EventArgs Args)
    {
        EventReply Reply;
        foreach(EventListener Listener in Listeners[EventType])
        {
            Reply = Listener.Execute(Args);
            Reply.Execute();
        }
    }
...
} 

class EventListener : Object 
{ 
    void Init()
    {
        EventHandler.AddListener(OnEvent, EventType);
    }

    EventReply OnEvent(EventArgs Args)
    {
        ...
        EventReply Reply = EventReply::Handled().SetUserFocus(this);

        return Reply;
    }
} 

This is an interesting addition to the common pattern of event handlers. As now you can add to, overwrite or ignore previous replies, we get added flexibility and power for no added coupling. This system still give priority to base behaviour, or whichever priority you go with, but it’s not outright – other classes can override it giving you more flexible event responses.

The separation of event listeners and event action is potentially very powerful. Why, then, limit the post chain actions to a few options. Simply store a delegate that will execute if bound after the event listener chain is complete. This gives us full control over how we handle triggered events.

 
static class EventHandler 
{
...
    Array<Array<EventListenerDelegate>>; Listeners;

    void TriggerEvent(Event::Type EventType, EventArgs Args)
    {
        EventReply Reply;
        foreach(EventListener Listener in Listeners[EventType])
        {
            Reply = Listener.Execute(Args);
            Reply.Execute();
        }
    }
...
} 

class EventListener : Object 
{ 
    void Init()
    {
        EventHandler.AddListener(OnEvent, EventType);
    }

    EventReply OnEvent(EventArgs Args)
    {
        ...
        EventReply Reply = EventReply::Handled();
        Reply.Action = OnEventAction;
        Reply.Args = ReplyArgs;

        return Reply;
    }

    void OnEventAction(ReplyArgs Args)
    {
        ...
    }
}