DannyX
DannyX

Reputation: 405

Simple event system in C++

I am designing a simple event system for my game engine. I want to implement the following event dispatcher interface:

// Create event dispatcher.
Dispatcher dispatcher;

// Create objects b and c.
// Created objects extend base A class.
A* b = new B();
A* c = new C();

// Register b and c in dispatcher.
// I guess I should also pass member function pointer,
// but I am not sure how to do it.
// Like this; dispatcher.Add(b, &B::DoWork) ?
// Member function that I want to accept is always bool func(/*no params*/)
dispatcher.Add(b /*, member function pointer?*/);
dispatcher.Add(c /*, member function pointer?*/);

// Trigger passed functions for registered objects.
dispatcher.Trigger();

I guess, Im gonna need some kind of wrapper struct inside Dispatcher, to hold [pObject, pFunc] pair.

struct Wrapper
{
   A* pObj; // Of base class A.
   // ??    // Pointer to given member function of A.
};

I dont know how to implement member function pointer logic. As I need it for a real time system, I would like prefer a relatively fast solution. I appreciate all suggestions.

I would prefer to avoid 3rd party libraries.

Upvotes: 3

Views: 15676

Answers (2)

skypjack
skypjack

Reputation: 50540

According with the comments, you can use either std::function or a structure similar to the one proposed here.
It follows a minimal and working example of the second suggestion:

class Dispatcher {
    Dispatcher() { }

    template<class C, void(C::*M)() = C::receive>
    static void invoke(void *instance) {
        (static_cast<C*>(instance)->*M)();
    }

public:
    template<class C, void(C::*M)() = &C::receive>
    static Dispatcher create(C *instance) {
        Dispatcher d;
        d.fn = &invoke<C, M>;
        d.instance = instance;
        return d;
    }

    void operator()() {
        (fn)(instance);
    }

private:
    using Fn = void(*)(void *);
    Fn fn;
    void *instance;
};

struct S {
    void receive() { }
};

int main() {
    S s;
    Dispatcher d = Dispatcher::create(&s);
    d();
};

Note that it has to be modified accordingly with your requirements (currently, the targeted member method has no return value and does not accept arguments).
Also, it can be easily extended to store an array of target member methods, that is what you were looking for: simply store a vector of pairs of instances and invoke functions.
In addition, you can use variadic template with the Dispatcher function to let it be more flexible. This way, you'll be able to define Dispatchers having different return types and arguments lists.

EDIT

It follows an example of a Dispatcher that accepts more than one target member function.
To extend it by means of variadic template as above mentioned is still quite easy.

#include <vector>
#include <utility>

class Dispatcher { 
    template<class C, void(C::*M)() = C::receive>
    static void invoke(void *instance) {
        (static_cast<C*>(instance)->*M)();
    }

public:
    template<class C, void(C::*M)() = &C::receive>
    void bind(C *instance) {
        auto pair = std::make_pair(&invoke<C, M>, instance);
        targets.push_back(pair);
    }

    void operator()() {
        for(auto pair: targets) {
            (pair.first)(pair.second);
        }
    }

private:
    using Fn = void(*)(void *);
    std::vector<std::pair<Fn, void*>> targets;
};

struct S {
    void receive() { }
};

struct T {
    void receive() { }
};

int main() {
    S s;
    T t;
    Dispatcher d;
    d.bind(&s);
    d.bind(&t);
    d();
};

Please note that in the examples above there is no guarantee that the pointer submitted as instance is still valid once actually used. You should either take care of the lifetime of your bound objects or slightly modify the example in order to introduce a more safer handle.

Upvotes: 4

eldruin
eldruin

Reputation: 342

If you can structure your A-derived classes appropriately so that you can have a process() method, you could simply use polymorphic dispatch.

Something like:

struct A
{
  virtual void process() = 0;
  virtual ~A() {}
}
struct B : public A
{
  virtual void process() { /* process event B... */ }
}
struct C : public A
{
  virtual void process() { /* process event C... */ }
}

The trigger method of your dispatcher would just have a loop for all registered objects calling the process() method on the pointer and let polymorphism call the appropriate method.

Upvotes: 2

Related Questions