Skip to content

Implementing EventEmitter

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

The third prototype EventEmitter class code:

template<typename ...M>
class EventEmitter
{
    typedef typename util::tuple_merge<M...>::type merged_map;

    typedef typename util::tuple_even_elements<merged_map>::type events;
    typedef typename util::tuple_odd_elements<merged_map>::type callbacks;

    template<typename E>
    struct evt_idx : public std::integral_constant<std::size_t, util::tuple_index_of<events, E>::value> {};

    template<typename E>
    struct cb_def
    { typedef typename std::tuple_element<evt_idx<E>::value, callbacks>::type type; };

public:
    typedef void* listener_t;

    EventEmitter()
        : set_()
    {}

    virtual ~EventEmitter()
    {}

    template<typename E>
    auto addListener(typename cb_def<E>::type callback) -> decltype(&callback)
    {
        auto& entry = std::get<evt_idx<E>::value>(set_);

        auto ptr = new decltype(callback)(callback);
        entry.push_back(std::shared_ptr<decltype(callback)>(ptr));
        return ptr;
    }

    template<typename E>
    bool removeListener(typename cb_def<E>::type* callback_ptr)
    {
        auto& entry = std::get<evt_idx<E>::value>(set_);

        auto it = entry.begin();
        for(;it!=entry.end();++it)
        {
            if(it->get() == callback_ptr)
            {
                entry.erase(it);
                return true;
            }
        }
        return false;
    }

    template<typename E>
    void removeAllListeners()
    {
        auto& entry = std::get<evt_idx<E>::value>(set_);
        entry.clear();
    }

    template<typename E, typename ...A>
    void emit(A&&... args)
    {
        auto& entry = std::get<evt_idx<E>::value>(set_);
        for(auto x : entry) (*x)(std::forward<A>(args)...);
    }

private:
    template<typename>
    struct set_t;

    template<typename ...T>
    struct set_t<std::tuple<T...>>
    { typedef std::tuple<std::list<std::shared_ptr<T>>...> type; };

    typename set_t<callbacks>::type set_;
};

For template utilities defined in dev::util, please see native/utility.h.

In this implementation, EventEmitter takes any numbers of std::tuple as template parameter.

To define your own event-callback mapping:

struct event1 {};
struct event2 {};
struct event3 {};
struct event4 {};

typedef std::tuple<
    event1, std::function<void(int)>
> foo_events;

typedef std::tuple<
    event2, std::function<void(const std::string&)>,
    event3, std::function<void()>
> bar_events;

And then, you can use this mapping whenever you create an EventEmitter (or its inherited) instance.

template<typename ...maps>
struct foo : public dev::EventEmitter<foo_events, maps...>
{
    // ...
};

template<typename ...maps>
struct bar : public foo<bar_events, maps...>
{
    // ...
};

void test1(int a)
{
    printf("test1(%d)\n", a);
}

void test2()
{
    printf("test2()\n");
}

// ....

int v = 1000;

bar<> bar1;

auto x1 = bar1.addListener<event1>(test1);
auto x2 = bar1.addListener<event1>([&](int n){
    printf("lambda: n=%d v=%d\n", n, v);
});
bar1.emit<event1>(5264);
printf("============================\n");

bar1.addListener<event3>(test2);
bar1.addListener<event3>(test2);
bar1.emit<event3>();
printf("============================\n");

bar1.removeListener<event1>(x1);
bar1.emit<event1>(4432);
printf("============================\n");

bar1.removeAllListeners<event1>();
bar1.emit<event1>(5132);
printf("============================\n");

In the code above, I defined foo class that takes event1, and bar class that takes event2 and event3 additionally.

  • You don't have to specify function signature for each addListener() or emit() calls.
  • Still you have to save the return value from addListener() function to remove that listener using removeListener() function.
  • They're all type-safe in run-time.
  • Event names are types, not ints.
Clone this wiki locally