8
\$\begingroup\$

For my own game engine I need an event system. I tried to avoid the single-huge-enum-approach for minimizing compile times.

The BasicEventListener is the (obviously) base class of the EventListener and is needed for storing EventListeners in a list:

class BaseEventListener {
    friend class EventDispatcher;
    std::map<EventId, EventDispatcher&> dispatchers;
public:
    virtual ~BaseEventListener() {
        for (auto& pair : dispatchers)
             pair.second.listeners[pair.first].erase(this);
    }
};

The Destructor of BaseEventListener ensures, that the dispatchers will not try to dispatch an Event to an deleted EventListener. It will remove this from the corresponding EventDispatcher.

The EventListener itself looks as follow:

template <typename Event>
class EventListener : public virtual BaseEventListener {
public:
    virtual void handleEvent(const Event&) = 0;
};

The user now is able to inherit from EventListener with

class ConcreteEventListener : public EventListener<KeyPressed>, 
                              public EventListener<KeyReleased> {...}

Now I'm getting to the Dispatcher:

class EventDispatcher final {
    friend class BaseEventListener;
    std::map<EventId, std::set<BaseEventListener*>> listeners;
    std::queue<std::function<void()>> eventQueue;
public:
    // register an EventListener to the Dispatcher
    template <typename Event>
    bool add(EventListener<Event>& listener) {
        EventId eventId = getEventId<Event>();

        if (!listener.dispatchers.try_emplace(eventId, *this).second) return false;
        bool success = listeners[eventId].insert(&listener);
        assert(success); // shouldn't fail

        return true;
    }

    // remove an EventListener from the Dispatcher
    template <typename Event>
    void remove(EventListener<Event>& listener) {
        EventId eventId = getEventId<Event>();

        if (!listener.dispatchers.erase(eventId)) return;
        bool success = listeners[eventId].erase(&listener);
        assert(success); // shouldn't fail
    }

    // (2) Directly dispatch to EventListeners
    template <typename Event, typename... Args>
    void dispatch(Args&&... args) {
        Event ev(std::forward<Args>(args)...);
        for (auto& listener : listeners[getEventId<Event>()])
            dynamic_cast<EventListener<Event>*>(listener)->handleEvent(ev);
    }

    // (3) Queue Event for later dispatching
    template <typename Event, typename... Args>
    void queue(Args&&... args) {
        eventQueue.emplace([event{std::make_shared<Event>(std::forward<Args>(args)...)}, this] {
            for (auto& listener : listeners[getEventId<Event>()])
                dynamic_cast<EventListener<Event>*>(listener)->handleEvent(*event);
        });
    }

    // dispatch queued Events
    void tick() {
        while (!eventQueue.empty()) {
            eventQueue.front()();
            eventQueue.pop();
        }
    }

    // clean up EventListeners
    ~EventDispatcher() {
        for (auto& eventListenerSet : listeners)
            for (auto& eventListener : eventListenerSet.second)
                eventListener->dispatchers.erase(eventListenerSet.first);
    }
};

Now the questioning :)

  • (2), (3): Is this well defined behavior/good practice/clean/performant?
  • For a multithreaded engine, I guess all the sections should be mutex locked?
\$\endgroup\$
2
  • 1
    \$\begingroup\$ It may be useful to review Qt's model of threads and event loops. Essentially, every Qt object "belongs" to a particular thread, and each thread is responsible for delivering events to its own objects. So the only cross-thread communication (unless explicitly bypassed by the programmer) is when posting events to another thread, and everything else needs no mutex. \$\endgroup\$ – Toby Speight Jun 15 '17 at 11:12
  • \$\begingroup\$ This is indeed a nice approach. I will look into it, thanks! \$\endgroup\$ – Tim Diekmann Jun 15 '17 at 15:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.