简体   繁体   中英

Typesafe implementation of an event system

I have written an event system for my game. It works fine but has one big flaw - it's not typesafe thus requiring the subscribed callback function to cast the received base event manually. Now, I am trying to implement a typesafe version using templates. I am generally ok with the topic but clearly not an expert.

Firstly, here is some code demonstrating how I want to use the event:

Define the derived events

// Derived Events
class ClickEvent : public Event
{
public:
    float x;
    float y;
};

class RenderNodeCreatedEvent : public Event
{
public:
    unsigned long long int renderNodeId;
};

Create them and use them (eg for test purposes in the main program)

// Create the on event functions
std::function<void(const ClickEvent &)> onClickFunction = [](const ClickEvent & event)
{
    std::cout << std::endl << "Mouse clicked at position: " << event.x << event.y;
};

std::function<void(const RenderNodeCreatedEvent &)> onRenderNodeCreatedFunction = [](const RenderNodeCreatedEvent & event)
{
    std::cout << std::endl << "Render node created with id: " << event.renderNodeId;
};

// Create the events
ClickEvent clickEvent;
clickEvent.x = 300.f;
clickEvent.y = 255.5f;

RenderNodeCreatedEvent renderNodeCreatedEvent;
renderNodeCreatedEvent.renderNodeId = 26234628374324;

// Create the event manager and subscribe the event functions
EventManager eventManager;
eventManager.Subscribe(onClickFunction);
eventManager.Subscribe(onRenderNodeCreatedFunction);

// Raise the events
eventManager.Raise(clickEvent);
eventManager.Raise(renderNodeCreatedEvent);

Here is what I have tried to generate a unique id of type std::size_t for each derived event class:

class BaseEvent
{
public:
    typedef std::size_t ID;

    BaseEvent() = default;
    virtual ~BaseEvent() = default;

protected:
    static ID GetNextID()
    {
        static ID id = 0;
        return id++;
    }
};

class Event : public BaseEvent
{
public:
    Event() = default;
    virtual ~Event() = default;

    // Sets a unique id for the event the first time this function is called
    ID GetID()
    {
        static ID id = BaseEvent::GetNextID();
        return id;
    }
};

Finally, this is my (terrible) try at an event manager that can provide the functionality described above. I am really struggling to manage correct casting and/or store different types of callback functions. The callback wrapper doesn't work - its just a basic idea I had to get around this problem, so I have included it in the post.

class EventManager
{
public:
    // Define the template callback type
    template <class DerivedEvent>
    using TCallback = std::function<void(const DerivedEvent &)>;

public:
    EventManager() = default;
    ~EventManager() = default;

    template<class DerivedEvent>
    void Subscribe(TCallback<DerivedEvent> callback)
    {
        // Get the index of the callback list this callback will be added to
        Event::ID id = DerivedEvent::GetID();

        // This won't work sinve TCallback is a different type than TCallback<Event>
        callbackListList[id].push_back(callback);
    }

    template <class DerivedEvent>
    void Raise(DerivedEvent event)
    {
        // Get the type of the event and therefore the index in the callbackListList
        Event::ID = DerivedEvent::GetID();

        /// How to cast the events back
        // Get the respective list of callback functions
        std::vector<TCallback<DerivedEvent>> /*&*/ callbackList;

        // Create a callback wrapper of with the type 'derived event' ?????
        CallbackWrapper<DerivedEvent> callbackWrapper(/*derived callback*/);
        // Call the callback wrapper using the base event ?????
    }

    template <typename DerivedEvent>
    class CallbackWrapper
    {
    public:
        CallbackWrapper(TCallback<DerivedEvent> callback) : callback(callback) {}

        void operator () (const Event & event)
        {
            callback(static_cast<const Event<DerivedEvent> &>(event).event);
        }

    private:
        TCallback<DerivedEvent> callback;
    };

private:
    std::vector<std::vector<TCallback<Event>>> callbackListList;
};

I know this is a lot of code, but I felt like this would be the easiest way to demonstrate what I am talking about.

Thanks for your help, Adrian

EDIT: The Event class would have to be declared as a template to get unique id for the derived types.

template <class DerivedEvent>
class Event : public BaseEvent

Now when inheriting from Event:

class ClickEvent : public Event<ClickEvent>
class RenderNodeCreatedEvent : public Event<RenderNodeCreatedEvent>

Lastly, the EventManager could only store a vector of a vector of callbacks of type BaseEvent

private:
    std::vector<std::vector<TCallback<BaseEvent>>> callbackListList;

After some longer research, I found this post on an ECS event/messenger implementation which basically solved the issue:

https://codereview.stackexchange.com/questions/79211/ecs-event-messaging-implementation

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM