EventBus - Possible with C++ templates?

Started by
15 comments, last by DareDeveloper 10 years, 8 months ago

{
handlerMap.insert(
id,
[=]( AbstractEvent* ev )
{
cb( *(EventType*)ev;
}
);
}

I'd personally format this as:


handlerMap.insert(id, [=](AbstractEvent* ev)
{
	cb( *(EventType*)ev );
});

This might make it clearer for you. Combined with less obscure variable names you might even start liking it smile.png.

Advertisement

Im not sure why you need one (there might be a better way to do things) but i assume the 'event' can be any type.

Im not going to make a base class for event since one is not needed for this way to do it:


MyEventType //int,string,PlayerMovedEvent....

template<class EventT>
EventHandlerBase
{
typedef EventT EventType;
virtual handleEvent(EventT event);
}

PlayerMovedHandler : public EventHandlerBase<PlayerMovedEvent>
{
void handleEvent(PlayerMovedEvent& event){blehblah}
}

EventBus
{
map<size_t, list<void*>> handlers; //maps hashcode of event type to list of handlers of that event type

template<class EventT>
fire(EventT& event)
{
    for each in handlers.get(typeid(EventT).hash_code()) do
    {
        reinterpret_cast<EventHandlerBase<EventT>*>(handler).handleEvent(event); //ew
    }
}

template<class HandlerT>
insertHandler(HandlerT& handler)
{
    map.get(typeid(HandlerT::EventType).hash_code()).push_front(&handler);
}
}

usage:

EventBus bus;

bus.insertHandler(PlayerMovedHandler);

bus.fire(PlayerMovedEvent()); //calls all handlers with EventT = PlayerMovedEvent

This groups the handlers by the hash code of the events type. It assumes you know the type of event when firing one and when creating handlers.

The run time cost is the virtual call + looking up the hash code from the map (use std::unordered_map for faster lookup times)

The typeid/hash_code stuff is probably done at compile time so it shouldnt matter.

o3o

Would love to answer all your questions ... but I can't think straight right now.

Can't wrap my head around static polymorphism and runtime polymorphism.

Basically what I want to do is avoid adding all modules to all modules. I want an event bus to couple them more loosely.

I still don't see how a function can be more powerful than a class.

The construct I posted (where the event knows the handler and determines how the handler is used) would make so many different things possible.

Don't really want to name a few ... it is a general thing. I don't even know what I will want to do with it in the future so it would just be a waste of time if you'd post solutions.

Guess once I understand the problem I have on a more meta level I'll post again.

I think I will run into similar problems once I want to write a property mechanism that turns strings into other types.

I tried to write a solution with an EventManager that uses one EventBus for each event type ... but I wanted polymorphism in there as well


#ifndef EVENTHANDLER_H
#define EVENTHANDLER_H

#include "types.h"

#include "logger.h"

template <class E>
class EventHandler
{
private:

    std::string m_name;

public:

    EventHandler(std::string name);
    virtual ~EventHandler();

    virtual void handleEvent(E* pEvent)
    {
        Logger* pLogger = new Logger("otherlogger.html", Logger::LOGLEVEL_INFO);
        pLogger->log("handleEvent of EventHandler called");
        delete(pLogger);
        pLogger = NULL;
    }

    std::string getName()
    {
        return m_name;
    }
};

template <class E>
EventHandler<E>::EventHandler(std::string name)
    : m_name(name)
{
}

template <class E>
EventHandler<E>::~EventHandler()
{
}

#endif // EVENTHANDLER_H

...


#ifndef EVENTBUS_H
#define EVENTBUS_H

#include "types.h"

#include "eventhandler.h"
#include "logger.h"
#include "profiler.h"

template <class E>
class EventBus
{
private:

    Logger* m_pLogger;
    Profiler* m_pProfiler;

    std::list< EventHandler<E> > m_handlerList;

public:

    EventBus()
        : m_pLogger(NULL), m_pProfiler(NULL)
    {
    }
    ~EventBus()
    {
    }

    EventBus<E>* initialize(Logger* pLogger, Profiler* pProfiler);
    EventBus<E>* uninitialize();
    EventBus<E>* reset();

    EventBus<E>* fireEvent(E* pEvent);
    EventBus<E>* addEventHandler(EventHandler<E> eventHandler);
    EventBus<E>* removeEventHandler(EventHandler<E> eventHandler);
};

template <class E>
EventBus<E>* EventBus<E>::initialize(Logger* pLogger, Profiler* pProfiler)
{
    m_pLogger = pLogger;
    m_pProfiler = pProfiler;

    return this;
}

template <class E>
EventBus<E>* EventBus<E>::uninitialize()
{
    m_pProfiler = NULL;
    m_pLogger = NULL;

    return this;
}

template <class E>
EventBus<E>* EventBus<E>::reset()
{
    m_handlerList.clear();
    return this;
}

template <class E>
EventBus<E>* EventBus<E>::fireEvent(E* pEvent)
{
    Uint32 i = 1;
    Message* pMessage = new Message();
    for (typename std::list< EventHandler<E> >::iterator it = m_handlerList.begin(); m_handlerList.end() != it ; ++it)
    {
        EventHandler<E> handler = *it;

        *pMessage << "Informing handler nr. " << i << ": " << handler.getName().c_str();
        m_pLogger->log(pMessage, Logger::LOGLEVEL_INFO);

        handler.handleEvent(pEvent);

        i++;
        pMessage->reset();
    }
    delete(pMessage);
    pMessage = NULL;

    return this;
}

template <class E>
EventBus<E>* EventBus<E>::addEventHandler(EventHandler<E> eventHandler)
{
    m_handlerList.push_back(eventHandler);
    return this;
}

template <class E>
EventBus<E>* EventBus<E>::removeEventHandler(EventHandler<E> eventHandler)
{
    for (typename std::list< EventHandler<E> >::iterator it = m_handlerList.begin(); it != m_handlerList.end(); ++it)
    {
        if (&eventHandler == &(*it))
        {
            m_handlerList.erase(it);
            break;
        }
    }

    return this;
}

#endif // EVENTBUS_H

... obviously there I can't overwrite the handleEvent(E*) in a specialized handler with a method like handleEvent(EventInput*).

I just can't really think of other ways to do something similar.

But thanks everybody, I guess I'll just start implementing the above solution and see if I can get used to working with it.

Given enough eyeballs, all mysteries are shallow.

MeAndVR

(0) Would love to answer all your questions ... but I can't think straight right now.

(1) Can't wrap my head around static polymorphism and runtime polymorphism.

Basically what I want to do is avoid adding all modules to all modules. I want an event bus to couple them more loosely.

(2) I still don't see how a function can be more powerful than a class.

(0) I guess trying out a couple of alternative, throwaway but concrete (compilable) solutions might be your best course of action, then.

(1) Go through this and try out the examples, that should do it: http://accu.org/index.php/journals/538

Note that static forms of polymorphism (both parametric/templates and overloading) are somewhat more powerful in C++ than dynamic polymorphism (in form of inclusion polymorphism/inheritance), since they support multiple dispatch out-of-the-box (so there's no need for things like the visitor pattern in Java if you're fine with the compile-time world), see: http://www.codeproject.com/Articles/635264/Open-Multi-Methods-for-Cplusplus11-Part-1

To see how this comes up in game development, consider the asteroids-spaceships collisions example:

https://en.wikipedia.org/wiki/Multiple_dispatch#Examples

// Note that if you need dynamic/run-time polymorphism, you will also need to use something like the visitor pattern (or a library-based solution), even in C++.

(2) Things like std::function support type-erasure and directly support catamorphisms (again, completely eliminate the need for design patterns like the visitor pattern), so a lot of the things you'd normally need a lot of OOP boilerplate for (to implement the design patterns) are simply supported out of the box (no manually written boilerplate code) if you write in FP style:

http://lorgonblog.wordpress.com/2008/04/09/catamorphisms-part-three/

http://stackoverflow.com/questions/2527153/are-design-patterns-specific-to-language-or-technology

In particular, in OOP style you often have to waste your time coding some brittle/rigid boilerplate requiring inheritance, while in FP style with std::function you can completely decouple many of your components. Here's another example in the gamedev context: http://probablydance.com/2012/12/16/the-importance-of-stdfunction/

Consider the traditional (OOP/inheritance solution):

But you’ve got that inheritance in there, and that doesn’t scale. You will probably want to add a render loop and maybe a separate update loop for the editor. Once you’ve got that many base classes it makes sense to combine them into one. And now you’ve just started on the way of having a big base class that gets used everywhere. Soon enough adding something different to the update loop involves a whole lot of unnecessary work.

And compare with the FP one:

This has no inheritance. Meaning I can add anything to the update loop now. If I create a new object that is unrelated to anything I had before and is managed completely differently, I can still add it just as easily as anything else. And I can use the same idea for a render loop or for implementing an observer pattern. It makes everything immensely easier.

Perhaps this is exactly what you're looking for?

Just as with anything in programming, there ain't no such thing as a free lunch, so each solution has upsides and downsides -- it's your wonderful job as a designer to compare and contrast each and chose the least-imperfect one for the task at hand :-)

In particular, both std::function and OOP/inheritance will have a run-time cost related to type-erasure/virtual function call; in contrast, if it's possible for you to know the types at compile-time and you can design & code a GP solution with templates, you won't suffer any overhead of this type (although may have icache trade-offs to consider, etc.).

Thanks ... trying to move towards clarity by playing around with more basic things. Can anybody explain why the following doesn't work?

I thought that should be a very trivial case of inheritance.


#ifndef EVENTHANDLER_H
#define EVENTHANDLER_H

#include "types.h"

#include "busevent.h"
#include "logger.h"

class EventHandler
{
private:

    std::string m_name;

public:

    EventHandler(std::string name);
    virtual ~EventHandler();

    virtual void handleEvent(BusEvent* pEvent)
    {
    }

    std::string getName()
    {
        return m_name;
    }
};

#endif // EVENTHANDLER_H

I have event handlers and they only handle BusEvent pointers ...

The engine is one of those event handlers ...


#ifndef ENGINE_H
#define ENGINE_H

#include "types.h"

#include "logger.h"
#include "module.h"
#include "profiler.h"
#include "eventmanager.h"

class Engine: public EventHandler
{

private:

    bool m_isRunning;

    Logger* m_pLogger;
    Profiler* m_pProfiler;
    EventManager* m_pEventManager;

    std::list<Module*> m_pModuleList;

public:

    Engine();
    virtual ~Engine();

    Engine* initialize(Logger* pLogger, Profiler* pProfiler, EventManager* pEventManager);
    Engine* uninitialize();

    Engine* registerModule(Module* pModule);

    bool isRunning()
    {
        return m_isRunning;
    }

    Engine* start();

    Engine* beginFrame();
    Engine* doFrame();
    Engine* endFrame();

    void handleEvent(BusEvent* pEvent);
};

#endif // ENGINE_H

the event bus calls handleEvent using the engine pointer (the logfile says it is the engine) ... but it seems that the handleEvent of EventHandler is called, not Engine.

Why is that?

Here is the EventBus fireEvent(...) code ...


EventBus* EventBus::fireEvent(BusEvent* pEvent)
{
    Uint32 i = 1;
    Message* pMessage = new Message();
    for (typename std::list< EventHandler >::iterator it = m_handlerList.begin(); m_handlerList.end() != it ; ++it)
    {
        EventHandler handler = *it;

        *pMessage << "Informing handler nr. " << i << ": " << handler.getName().c_str();
        m_pLogger->log(pMessage, Logger::LOGLEVEL_DEBUG);

        handler.handleEvent(pEvent);

        i++;
        pMessage->reset();
    }
    delete(pMessage);
    pMessage = NULL;

    return this;
}

... and handleEvent(...) of the engine:


void Engine::handleEvent(BusEvent* pEvent)
{
    m_pLogger->log("Handling event ...", Logger::LOGLEVEL_INFO);
    if (BusEvent::TYPE_QUIT == pEvent->getEventType())
    {
        m_isRunning = false;
    }
    else
    {
        Message* pMessage = new Message();
        *pMessage << "event unknown: " << pEvent->getEventType();
        m_pLogger->log(pMessage, Logger::LOGLEVEL_INFO);
        delete(pMessage);
        pMessage = NULL;
    }

    EventHandler::handleEvent(pEvent);
}

Doesn't virtual mean that it shouldn't matter that the EventBus is working with an EventHandler pointer?

Given enough eyeballs, all mysteries are shallow.

MeAndVR

the event bus calls handleEvent using the engine pointer (the logfile says it is the engine) ... but it seems that the handleEvent of EventHandler is called, not Engine.

Why is that?

Doesn't virtual mean that it shouldn't matter that the EventBus is working with an EventHandler pointer?

Only if you store & call it via a pointer (or its variants, like std::unique_ptr) or a reference (or its variants, like std::reference_wrapper).

Otherwise, object slicing occurs.

Just like here, where you store by value:

std::list< EventHandler<E> > m_handlerList; // oops

and here, where you call your stored-by-value (and sliced) handler

for (typename std::list< EventHandler >::iterator it = m_handlerList.begin(); m_handlerList.end() != it ; ++it) // oops
{
EventHandler handler = *it; // oops

*pMessage << "Informing handler nr. " << i << ": " << handler.getName().c_str();
m_pLogger->log(pMessage, Logger::LOGLEVEL_DEBUG);
handler.handleEvent(pEvent); // this is sliced

Oops thanks ... yeah that was stooopid wacko.png

Given enough eyeballs, all mysteries are shallow.

MeAndVR

This topic is closed to new replies.

Advertisement