Skip to content

Implementing EventEmitter

Daniel Kang edited this page Feb 11, 2012 · 7 revisions

This is prototype EventEmitter class code:

namespace internal
{
    class sigslot_base
    {
    public:
        sigslot_base() {}
        virtual ~sigslot_base() {}

        virtual void add_callback(void*) = 0;
        virtual void remove_callback(void*) = 0;
        virtual void reset() = 0;
    };

    template<typename ...P>
    class sigslot: public sigslot_base
    {
    public:
        typedef std::function<void(P...)> callback_type;
        typedef std::shared_ptr<callback_type> callback_ptr;

    public:
        sigslot()
            : callbacks_()
        {}
        virtual ~sigslot()
        {}

    public:
        virtual void add_callback(void* callback)
        {
            callbacks_.insert(callback_ptr(reinterpret_cast<callback_type*>(callback)));
        }

        virtual void remove_callback(void* callback)
        {
            callbacks_.erase(callback_ptr(reinterpret_cast<callback_type*>(callback)));
        }

        virtual void reset()
        {
            callbacks_.clear();
        }

        void invoke(P&&... args)
        {
            for(auto c : callbacks_) (*c)(std::forward<P>(args)...);
        }

    private:
        std::set<callback_ptr> callbacks_;
    };
}

class EventEmitter
{
protected:
    EventEmitter() : events_() {}

public:
    virtual ~EventEmitter() {}

    typedef void* listener_t;

    template<typename ...A>
    listener_t addListener(
        int event_id,
        typename internal::sigslot<A...>::callback_type callback)
    {
        auto s = events_.find(event_id);
        if(s != events_.end())
        {
            // test if function signature matches or not.
            assert(dynamic_cast<typename internal::sigslot<A...>*>(s->second.get()));

            auto ptr = new typename internal::sigslot<A...>::callback_type(callback);
            s->second->add_callback(ptr);
            return ptr;
        }
        else
        {
            throw "Not registered event.";
        }
    }

    void removeListener(int event_id, listener_t listener)
    {
        auto s = events_.find(event_id);
        if(s != events_.end())
        {
            s->second->remove_callback(listener);
        }
        else
        {
            throw "Not registered event.";
        }
    }

    void reset(int event_id)
    {
        auto s = events_.find(event_id);
        if(s != events_.end())
        {
            s->second->reset();
        }
        else
        {
            throw "Not registered event.";
        }
    }

    template<typename ...A>
    void operator()(int event_id, A&&... args)
    {
        emit(event_id, std::forward<A>(args)...);
    }

    template<typename ...A>
    void emit(int event_id, A&&... args)
    {
        auto s = events_.find(event_id);
        if(s != events_.end())
        {
            auto x = dynamic_cast<internal::sigslot<A...>*>(s->second.get());
            if(x)
            {
                x->invoke(std::forward<A>(args)...);
            }
            else
            {
                throw "Invalid callback type.";
            }
        }
        else
        {
            throw "Not registered event.";
        }
    }

protected:
    template<typename ...A>
    void registerEvent(int event_id)
    {
        auto it = events_.find(event_id);
        if(it != events_.end())
        {
            throw "Event is already registered.";
        }

        events_.insert(std::make_pair(
            event_id,
            std::shared_ptr<internal::sigslot_base>(new internal::sigslot<A...>())));
    }

    void unregisterEvent(int event_id)
    {
        events_.erase(event_id);
    }

private:
    std::map<int, std::shared_ptr<internal::sigslot_base>> events_;
};

To implement your own EventEmitter, you must inherit from EventEmitter and then register your event ids:

enum
{
    event1,
    event2,
    event3
};

class foo : public dev::EventEmitter
{
public:
    foo()
    {
        registerEvent<int>(event1);
        registerEvent<const std::string&>(event2);
        registerEvent(event3);
    }

    virtual ~foo()
    {}
};

User of your EventEmitter class can do the following code:

int test1(int n)
{
    printf("test1(%d)\n", n);
    return 0;
}

// ...

printf("Hello~\n");

int v = 5;

foo f;

// add listeners
auto l1 = f.addListener<int>(event1, test1);
f.addListener<int>(event1, test1);
f.addListener<int>(event1, [&](int t){
    printf("lambda: t=%d v=%d\n", t, v);
    v++;
});

// invoke #1
printf("----------------------\n");
f.emit(event1, 5264);

// invoke #2 with one listener removed
printf("----------------------\n");
f.removeListener(event1, l1);
f.emit(event1, 5264);

// invoke #3 with all listeners removed
printf("----------------------\n");
f.reset(event1);
f.emit(event1, 5264);

printf("Good-bye~\n");

As you can see in the above code, to remove a specific listener, you must save a return value from addListener() function.

And, one thing to note is that you must provide the parameter types as template parameters of addListener() function. I'm trying to make this implicit:

f.addListener(event1, [&](int t){
    printf("lambda: t=%d v=%d\n", t, v);
    v++;
}); 

But, it is not that easy as it seems.

Clone this wiki locally