2
\$\begingroup\$

I'm interested in receiving some feedback regarding an Event system that I wrote. Both in style and implementation, but also in overall design and the design decisions that I made.

It is intended to be used as part of a game (hobby project of mine) and is intended to be used on a single thread. While receivers will always receive events on the services owning thread, I may add the ability for other threads to send events in the future. It is intended to have very low overhead in regards to execution time and scale well.

Here it is:

EventService.h

#pragma once

#include "Core\ITimerService.h"
#include "Events\EventListener.h"
#include "Helpers\NonCopyable.h"

#include <thread>
#include <chrono>
#include <functional>
#include <typeindex>
#include <unordered_map>
#include <memory>

namespace quasar
{
    // The EventService provides a service for objects to
    // register an interest in recieving events, which are arbitary typed
    // data and an to send those events. Events are guaranteed to be
    // executed at a known point of execution but in an undefined order.
    // 
    // It is currently not thread safe.
    //
    class EventService : public NonCopyable
    {
    public:

        template <typename EventT>
        using OnEventSignature = void(const EventT& evnt);

        // Constructor
        // Intializes the EventService with the calling thread as the owning thread
        // param    timerService    The timer service to use for timed events
        EventService(ITimerService* timerService);

        // Constructor
        // Intializes the EventService
        // param    timerService    The timer service to use for timed events
        // param    owningThread    The thread that owns this event service
        EventService(ITimerService* timerService, const std::thread::id& owningThread);

        // Queues up an event for execution
        // param    evnt    the event to queue up
        template <typename EventT>
        void send(EventT&& evnt);

        // Queues up an event for execution after a delay
        // param    evnt    the event to queue up
        // param    delay   the time delay until the event should be
        //                  exeucted, guaranteed to wait at least 
        //                  for the delay and execute on the very
        //                  next executeEventQueue call afterwards
        template <typename EventT>
        void send(EventT&& evnt, std::chrono::duration<std::chrono::steady_clock> delay);

        // Registers a Listener interested in handling events
        // tparam   EventT    the exact type of event that the listener
        //                    is interested in.
        // tparam   ListenerT the (exact) type of listener to register,
        //                    will tie the ListenerT's 
        //                    void onEvent(const EventT&) method as the
        //                    trigger function.
        // param    owner     the listener, the lifetime of the registration
        //                    will be tied to the lifetime of the listener
        template <typename EventT, typename ListenerT>
        void registerListener(ListenerT& owner);

        // Registers a Listener interested in handling events
        // tparam   EventT    the exact type of event that the listener
        //                    is interested in.
        // param    owner     the listener, the lifetime of the registration
        //                    will be tied to the lifetime of the listener
        // param    onEventFunction the function to invoke when the expected
        //                    event is executed
        template <typename EventT>
        void registerListener(EventListener& owner, const std::function<OnEventSignature<EventT>>& onEventFunction);

        // Infroms the service that a listener is no longer 
        // interested in recieving events
        // param    handle  the handle of the registration
        //                  to unregister
        // returns true if the listener was removed, false if it was
        // not registered
        bool unregisterListener(const RegisteredEventListenerHandle& handle);

        // Executes all events in the event queue,
        // as well as all delayed events whose time
        // to execute has come
        void executeEventQueue();

    private:
        class IEventTypeNode;

        template <typename EventT>
        class EventNode;


        template <typename EventT>
        EventNode<EventT>& getEventNode();

        ITimerService* timerService;
        std::thread::id owningThread;
        std::unordered_map<std::type_index, std::unique_ptr<IEventTypeNode> > listenerMap;
        uint32 lastListenerId;
    };

}

#include "Events\EventService.hpp"

EventService.hpp

#pragma once

#include "Events\EventService.h"

#include <queue>
#include <utility>

namespace quasar
{
    class EventService::IEventTypeNode
    {
    public:
        virtual ~IEventTypeNode() = default;

        template <typename EventT>
        EventNode<EventT>& getAsEventTypeNode()
        {
#ifdef  _DEBUG
            auto ptr = dynamic_cast<EventNode<EventT>*>(this);
            assert(ptr != nullptr);
            return *ptr;
#else
            return *static_cast<EventNode<EventT>*>(this);
#endif // _DEBUG
        }

        virtual void removeListenerRegistration(uint32 id) = 0;

        virtual void executeEventQueue(const std::chrono::time_point<std::chrono::steady_clock> currentTime) = 0;
    };

    template <typename EventT>
    class EventService::EventNode : public EventService::IEventTypeNode
    {
    public:
        using OnEventFuncT = std::function<EventService::OnEventSignature<EventT>>;

        void queueUp(EventT&& evnt)
        {
            this->eventQueue.push_back(evnt);
        }

        void queueUp(EventT&& evnt, std::chrono::time_point<std::chrono::steady_clock> triggerPoint)
        {
            this->timedEventQueue.push(std::make_pair(std::forward(evnt), triggerPoint));
        }

        void addListenerRegistration(uint32 id,
            const OnEventFuncT& onEventFunc)
        {
            assert(this->onEventFunctions.find(id) == this->onEventFunctions.end());
            this->onEventFunctions[id] = onEventFunc;
        }

        void addListenerRegistration(uint32 id,
            OnEventFuncT&& onEventFunc)
        {
            assert(this->onEventFunctions.find(id) == this->onEventFunctions.end());
            this->onEventFunctions[id] = onEventFunc;
        }

        void removeListenerRegistration(uint32 id) override
        {
            assert(this->onEventFunctions.find(id) != this->onEventFunctions.end());
            this->onEventFunctions.erase(this->onEventFunctions.find(id));
        }

        void executeEventQueue(const std::chrono::time_point<std::chrono::steady_clock> currentTime) override
        {
            for (const auto& function : this->onEventFunctions)
            {
                for (const EventT& evnt : this->eventQueue)
                {
                    function.second(evnt);
                }
            }
            this->eventQueue.clear();
            while (!this->timedEventQueue.empty())
            {
                const TimedEventT& timedEvent = this->timedEventQueue.top();
                if (currentTime >= timedEvent.second)
                {
                    for (const auto& function : this->onEventFunctions)
                    {
                        function.second(timedEvent.first);
                    }
                    this->timedEventQueue.pop();
                }
                else
                {
                    break;
                }
            }
        }

    private:
        using TimedEventT = std::pair<EventT, std::chrono::time_point<std::chrono::steady_clock>>;

        struct IsScheduledEarlier
        {
            bool operator()(const TimedEventT& left, const TimedEventT& right) const
            {
                return left.second < right.second;
            }
        };

        std::vector<EventT> eventQueue;
        std::priority_queue<TimedEventT, std::vector<TimedEventT>, IsScheduledEarlier> timedEventQueue;
        std::unordered_map<uint32, OnEventFuncT> onEventFunctions;
    };


    template <typename EventT>
    EventService::EventNode<EventT>& EventService::getEventNode()
    {
        std::type_index index(typeid(EventT));
        auto found = this->listenerMap.find(index);
        if (found == this->listenerMap.end())
        {
            auto newNode = std::make_unique<EventNode<EventT>>();
            this->listenerMap[index] = std::move(newNode);
            found = this->listenerMap.find(index);
        }
        return found->second->getAsEventTypeNode<EventT>();
    }


    template <typename EventT>
    void EventService::send(EventT&& evnt)
    {
        assert(std::this_thread::get_id() == this->owningThread);
        this->getEventNode<EventT>().queueUp(std::forward<EventT>(evnt));
    }

    template <typename EventT>
    void EventService::send(EventT&& evnt, std::chrono::duration<std::chrono::steady_clock> delay)
    {
        assert(std::this_thread::get_id() == this->owningThread);
        this->getEventNode<EventT>().queueUp(std::forward<EventT>(evnt), this->timerService->timestamp() + delay);
    }

    template <typename EventT, typename ListenerT>
    void EventService::registerListener(ListenerT& owner)
    {
        using OnEventMemberType = void(ListenerT::*)(const EventT&);
        this->registerListener<EventT>(owner, 
            std::bind(static_cast<OnEventMemberType>(&ListenerT::onEvent), &owner, std::placeholders::_1));
    }

    template <typename EventT>
    void EventService::registerListener(EventListener& owner, 
                                        const std::function<OnEventSignature<EventT>>& onEventFunction)
    {
        assert(std::this_thread::get_id() == this->owningThread);
        uint32 id = lastListenerId++;
        this->getEventNode<EventT>().addListenerRegistration(id, onEventFunction);
        owner.addRegistrationHandle(RegisteredEventListenerHandle(typeid(EventT), this, id));
    }

}

EventService.cpp

#include "Events\EventService.h"

namespace quasar
{
    EventService::EventService(ITimerService* timerService)
        : EventService(timerService, std::this_thread::get_id())
    {

    }
    EventService::EventService(ITimerService* timerService, const std::thread::id& owningThread)
        : timerService(timerService)
        , owningThread(owningThread)
        , lastListenerId(1)
    {

    }


    bool EventService::unregisterListener(const RegisteredEventListenerHandle& handle)
    {
        auto found = this->listenerMap.find(handle.typeIndex);
        if (found != this->listenerMap.end())
        {
            found->second->removeListenerRegistration(handle.id);
            return true;
        }
        return false;
    }

    void EventService::executeEventQueue()
    {
        auto timestamp = this->timerService->timestamp();
        for (const auto& node : this->listenerMap)
        {
            node.second->executeEventQueue(timestamp);
        }
    }
}

EventListener.h

#pragma once

#include "Core\NumTypes.h"

#include "Helpers/NonCopyable.h"

#include <vector>
#include <typeindex>

namespace quasar
{
    class EventService;

    // Handle that identifies a uniqe event listener registration
    struct RegisteredEventListenerHandle
    {
        RegisteredEventListenerHandle(const std::type_index& typeIndex, 
                                      EventService* eventService,
                                      uint32 id);

        std::type_index typeIndex;
        EventService* eventService;
        uint32 id;
    };

    //
    // Base class for objects capable of listening for events
    // Handles lifetime of listener registration
    //
    class EventListener : public NonCopyable
    {
    public:
        virtual ~EventListener();

        // Ties the lifetime of a listener regitration to this listener 
        // param    handle  the handle of the registration
        void addRegistrationHandle(const RegisteredEventListenerHandle& handle);

    private:
        std::vector<RegisteredEventListenerHandle> handles;
    };

}

EventListener.cpp

#include "Events\EventListener.h"

#include "Events\EventService.h"

namespace quasar
{
    RegisteredEventListenerHandle::RegisteredEventListenerHandle(const std::type_index& typeIndex, 
                                                                 EventService* eventService,
                                                                 uint32 id)
        : typeIndex(typeIndex)
        , eventService(eventService)
        , id(id)
    {

    }


    EventListener::~EventListener()
    {
        for (auto& handle : this->handles)
        {
            handle.eventService->unregisterListener(handle);
        }
    }

    void EventListener::addRegistrationHandle(const RegisteredEventListenerHandle& handle)
    {
        this->handles.push_back(handle);
    }

}
\$\endgroup\$

0

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.