0
votes

I try to achieve an asynchronous event loop which can store events in a queue and dispatches them later to different subscribers. All events are derived from one base, also all subscribers are derived from their own base class. Events should hold different types of values. Subscribers shall forward them to instances of other classes. I can only use std library.

I have problems to dispatch events of different types to the proper methods of the subscriber sub class. If I store Event Base in the queue, I lose type information of the derived events and cannot call the proper methods of the subscriber. I also do not want to use If then else or switch on type Info.

I tried using templates and create for every event its own dispatch method in the subscriber class or std::function to wrap the method call in a lambda function.

My approach seams utterly complicated and slow. I cannot forward the events to the classes managed by the subscriber. I always get error no matching function call. I understand why but have no idea how to solve my problem.

Usage:

observer obs;
EventLoop<observer,Event> el;

auto f = [&obs] (Event& e) {
    obs.dispatch((Up&)e);
};
el.subscribe<Up>(f);

auto g = [&obs] (belfsm::Event& e) {
    obs.dispatch((Down&)e);
};

el.subscribe<Down>(g);

Up a;
Down b;
el.sendEvent(a);
el.sendEvent(b);
el.dispatchF();

What I tried so far:

#include <map>
#include <vector>
#include <queue>
#include <typeindex>
#include <functional>
#include <algorithm>
#include <memory>

template<typename T_Subscribe, typename T_Event>
class EventLoop {
public:      
    typedef std::function<void(T_Event&)> eventHandler;

    EventLoop(){}
    virtual ~EventLoop(){}

    void dispatch2subscriber(){
        while(!eventQueue.empty()){
            std::type_index eventType = eventQueue.front().first;

            auto it_s = eventSubscriber.find(eventType);
            if( it_s != eventSubscriber.end() ){
                std::vector<T_Subscribe*> v = eventSubscriber[eventType];

                for(auto const& subscriber: v) {
                    subscriber->dispatch(eventQueue.front().second);
                }
        }

        eventQueue.pop();
    }

    void dispatchFunction(){
        while(!eventQueue.empty()){
            std::type_index eventType = eventQueue.front().first;

            auto it_s = eventSubscriberF.find(eventType);
            if( it_s != eventSubscriberF.end() ){
                std::vector<std::function<void(T_Event&)>> v = eventSubscriberF[eventType];

                for(auto const& subscriber: v) {
                    subscriber(eventQueue.front().second);
                }
            }

            eventQueue.pop();
        }//while
    }

    template<typename U>
    bool subscribe(T_Subscribe& subscriber){
        std::type_index eventType = getEventType<U>();
        eventSubscriber[eventType].push_back(&subscriber);
        return true;
    }



    template<typename U>
    bool subscribe(eventHandler subscriber){
        std::type_index eventType = getEventType<U>();

        eventSubscriberF[eventType].push_back(subscriber);
        return true;
}
private:
    //Event Queue
    std::queue<std::pair<std::type_index,T_Event&>std::deque<std::pair<std::type_index,T_Event&>>> eventQueue;
    //Subscriber map pointer to instance
    std::map<std::type_index,std::vector<T_Subscribe*>> eventSubscriber;
    //Subscriber map to member function
    std::map<std::type_index,std::vector< eventHandler > > eventSubscriberF;
}

Subscribers:

//Events
struct Event {};
struct Up : Event { int i };
struct Down : Event { int i };

//Class which uses events.
class another_class
{
   void react(Event &) {};
}

class yet_another_class : public another_class 
{
    void react(Event &) {};
    void react(Up &) {};
    void react(Down &) {};
}

//Manages other classes which uses events
class BaseSubscribery
{
public:
    std::unique_ptr<another_class> another_class_ptr;
}

class Subscriber: public BaseSubscriber
{
public:
    observer();
    template<typename E>
    void dispatch(E& event);

};

template<typename E>
void observer::dispatch(E& event) {
    *another_class_ptr->react(event);*
}
1
You should work with pointers to another_class interface in queue so the call to react is handled through the vtable and thus not losing type data.Manuel
@Manuel I used subscriber class because another_class_ptr can change to another instance and make the dispatch call illegal. How would I achieve your suggestion? I cannot overload virtual methods in yet_another_class or use templated virtual methods . If I use templated react methods in another_class, lambdas always call Base class methods, not derived ones. I'm a bit lost here.T.bur

1 Answers

0
votes

This code shows how to handle object inherited from a common parent and dynamic_cast to derived one.

There is one base interface: event There are three event types: event_one, event_two and event_no. Their topology is as follows:

event_two -> event_one -> event

So event_two inherits from event_one which inherits from event. And there is an event_no ("no" because it will be unhandled) which inherits directly from event.

event_no -> event

We create a event handler and pass all derived events to it.

The key part is if (dynamic_cast<event_one *>(ievent)) in handler_one::handle_event, if there is no run-time conversion to derived class, this will return a null pointer, so you know you can't handle it. In other case it will return the derived class you want. You should have a chain of derived classes, most derived first, to handle all cases correctly.

I've created two handlers, one partial (handler_one) which handles two event types: event_one and event_two (event_two isn't in the if but as it is an event_one it will be handled and the correct function called; if you don't define guid function, in the derived, the one in event_one will be called); and a second one (handler_two) which handles them all.

As for the queues, you may store event pointers and forget about derived classes.

EDIT:

Now it is managed by templates. No if or switch. You can add other event types specializing a member template.

The output from the program:

manuel@desktop:~/projects$ g++ -Wall main.cc -o main -std=c++11 && ./main 
Handler 1
Event guid: abcd (id=1)
Event guid: abcd with some custom stuff (id=2)
Event no guid: ugh! this is unhandled (id=-1)
End

The program:

//
#include <typeindex>
#include <map>
#include <vector>
#include <string>
#include <iostream>
#include <functional>

class event
{
public:
    event () : _id (0) {}
    virtual ~event () {}
    virtual int id () { return _id; }
protected:
    int _id;
};

class event_one : public event
{
public:
    event_one () : _guid ("abcd") { _id = 1; }
    ~event_one () {}
    virtual std::string guid () { return _guid; }
protected:
    std::string _guid;
};

class event_two : public event_one
{
public:
    event_two () { _id = 2; }
    ~event_two () {}
    virtual std::string guid () { return _guid + " with some custom stuff"; }
protected:
};

class event_no : public event
{
public:
    event_no () : _guid ("ugh!") { _id = -1; }
    ~event_no () {}
    virtual std::string guid_no () { return _guid + " this is unhandled"; }
protected:
    std::string _guid;
};

class handler_one
{
public:
    template<typename Event>
    int handle_event (Event * ievent);
};

template<typename Event>
int handler_one::handle_event (Event * ievent) {
    std::cout << "Event guid: " << ievent->guid()
              << " (id=" << ievent->id() << ")" << std::endl;
    return 0;
}

template<>
int handler_one::handle_event<event_no> (event_no * ievent) {
    std::cout << "Event no guid: " << ievent->guid_no()
              << " (id=" << ievent->id() << ")" << std::endl;
    return 0;
}

int main (void)
{
    event_one eo;
    event_two et;
    event_no en;
    handler_one ho;

    std::cout << "Handler 1" << std::endl;
    ho.handle_event (&eo);
    ho.handle_event (&et);
    ho.handle_event (&en);

    std::cout << "End" << std::endl;
    return 0;
}