3

I'm currently trying to implement an event-system following a broadcast/observer scheme. I have events/notifications that contain information about what is happening:

class Event
{
public:
    Event(const std::string& _name="event");
    ~Event() = default;

protected:
    std::string name;
};

/* I want to flexibly extend my Event base-class to accommodate for more involved behavior */
// example 
class InputEvent : public Event
{
public:
    InputEvent(const std::string& _name="input_event");
    ~InputEvent() = default;
    
    uint keycode; // or whatever...

};

and event handlers that manage to whom to broadcast these events and what to do if they themselves receive one:

class EventHandler
{
public:
    EventHandler() = default;
    ~EventHandler() = default;
    
    /* React to incoming events (can/shall be overloaded for different subtypes of Event) */
    virtual void event_callback(const std::shared_ptr<Event> event);

    /* Broadcast events to recipients */
    template<typename EventType>
    void event_dispatch(const std::shared_ptr<EventType> event);
    
protected:
    // Addresses of all event recipients
    std::set<std::shared_ptr<EventHandler>> event_recipients;
    // Possible (???) implementation of event queue
    std::queue<std::shared_ptr<Event>> event_queue;
};

The event_queue acts as a buffer to store received events until they're scheduled to be processed by event_callback. However with my current implementation all of my events stored in this buffer will be upcasted into base-class Event pointers and thus render the overloads that different EventHandler derivatives may posses useless.

Is there a better container than a Queue or should I consider dynamic casting (and if so how)? Or do you have an altogether superior design in mind?

1 Answer 1

1

Double Dispatch

Or the visitor pattern.

Don't presume you understand the event, instead ask the event to describe itself to you.

class EventDispatch
{
    virtual ~EventDispatch() {}
    virtual void FooEvent(int) =0;
    virtual void BooEvent(Exception&) =0;
    virtual void ZooEvent(Animal&) =0;
    virtual void DirectEvent(DirectEvent&) =0;
};
class Event
{
    virtual ~Event() {}
    virtual void Dispatch(EventDispatch& dispatcher) =0;
}
class FooEvent : Event
{
   int data;
   void Dispatch(EventDispatch& dispatcher) { dispatcher.FooEvent(data); }
}
class DirectEvent: Event
{
   void Dispatch(EventDispatch& dispatcher) { dispatcher.DirectEvent(*this); }
}

This way later on a New event can be either mapped to one of the known descriptions. Or an event can be redesigned to describe itself differently. Without changing the dispatching implementation.

4
  • Doesn't this mean that whenever I create a new type of Event in the future, that can not be represented by the existing functions in the EventDispatch interface, that I would have to go back and make an addition there? Commented Apr 4, 2021 at 13:14
  • Yes, you would. but this shouldn't upset any of the other events as its a purely additive change.
    – Kain0_0
    Commented Apr 5, 2021 at 23:07
  • What can I do if changing the EventDispatch interface isn't an option either? Commented Apr 6, 2021 at 0:49
  • Pass structured data. EG: xml, json, yaml, or any other data descriptive language. You can go one step further and pass an interpreter object when visiting the event. The interpreter can then be used like a state machine to process the structured data. Ignoring what it doesn't care about and responding to what it does. Note: this is quickly turning a light weight event into a heavy weight task.
    – Kain0_0
    Commented Apr 6, 2021 at 4:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.