-
Notifications
You must be signed in to change notification settings - Fork 146
Implementing EventEmitter
This is what we need in the end:
class HTTP : public EventEmitter {
// .. h;
// subscription
h.addListener("request", [=](/*...*/) { /*...*/ });
// Or,
h.addListener<request>([=](/*...*/) { /*...*/ });
// invoke
h.emit("request", /*...*/);
// Or,
h.emit<request>(/*...*/);
In above code, template argument in the latter patter can be anything: int, class type...
The objectives are:
- Easy to use
- Event emitters must be able to validate event name(or id): adding listeners for 'hello' event to HTTP instance should not be allowed.
- Class inheritance and events must be related to each other.
Anyway, to achieve this, I tried many various ways, but nothing yet is proven to work as intended. This is my current work in progress:
class callback_obj_base
{
public:
callback_obj_base() {}
virtual ~callback_obj_base() {}
};
template<typename callback_t>
class callback_obj : public callback_obj_base
{
callback_t c_;
public:
callback_obj(callback_t c) : c_(c) {}
template<typename ...A>
void invoke(A&& ... args)
{
c_(std::forward<A>(args)...);
}
};
enum {
event1,
event2,
event3,
event4,
};
class base1
{
typedef std::vector<std::shared_ptr<callback_obj_base>> listener_list;
public:
base1() : map_() {}
virtual ~base1() {}
template<typename T, int I> struct foo {};
public:
template<typename T, int id>
void addListener(typename foo<T, id>::type callback)
{
auto it = map_.find(id);
if(it == map_.end()) map_.insert(std::make_pair(id, listener_list()));
map_[id].push_back(std::shared_ptr<callback_obj_base>(new callback_obj<typename foo<T, id>::type>(callback)));
}
template<typename T, int id, typename ...A>
void invokeEvent(A&& ... args)
{
typedef typename foo<T, id>::type cb_t;
typedef callback_obj<cb_t> ev_t;
auto e = map_.find(id);
if(e != map_.end())
{
for(auto l : e->second)
{
auto f = dynamic_cast<ev_t*>(l.get());
f->invoke(std::forward<A>(args)...);
}
}
}
private:
std::map<int, listener_list> map_;
};
class class1 : public base1 {};
template<> struct base1::foo<class1, event1> { typedef std::function<void(int)> type; };
template<> struct base1::foo<class1, event2> { typedef std::function<void(int, int)> type; };
class class2 : public base1 {};
template<> struct base1::foo<class2, event1> { typedef std::function<void(int)> type; };
template<> struct base1::foo<class2, event3> { typedef std::function<void(int, int, int)> type;
You can see the limitation of this approach in the following usage code:
int t = 0;
class1 f;
f.addListener<class1, event1>([&](int a){
t++;
std::cout << "[" << t << "] event1: " << a << std::endl;
});
f.addListener<class1, event2>([=](int a, int b){
std::cout << "[" << t << "] event2-1: " << a << " " << b << std::endl;
});
f.addListener<class1, event2>([=](int a, int b){
std::cout << "[" << t << "] event2-2: " << a << " " << b << std::endl;
});
f.invokeEvent<class1, event1>(5264);
f.invokeEvent<class1, event2>(5264, 1000);
f.invokeEvent<class1, event1>(5264);
f.invokeEvent<class1, event2>(5264, 1000);
As you can see, every function call requires evident class name as the first template parameter: class1 in the above code.
This is more enhanced version:
class base1
{
typedef std::vector<std::shared_ptr<callback_obj_base>> listener_list;
public:
base1() : map_() {}
virtual ~base1() {}
template<typename T, int event_id> struct foo {};
protected:
template<typename T, int event_id>
void addListener_(typename foo<T, event_id>::type callback)
{
auto it = map_.find(event_id);
if(it == map_.end()) map_.insert(std::make_pair(event_id, listener_list()));
map_[event_id].push_back(std::shared_ptr<callback_obj_base>(new callback_obj<typename foo<T, event_id>::type>(callback)));
}
template<typename T, int event_id, typename ...A>
void invokeEvent_(A&& ... args)
{
typedef typename foo<T, event_id>::type cb_t;
typedef callback_obj<cb_t> ev_t;
auto e = map_.find(event_id);
if(e != map_.end())
{
for(auto l : e->second)
{
auto f = dynamic_cast<ev_t*>(l.get());
assert(f);
f->invoke(std::forward<A>(args)...);
}
}
}
public:
std::map<int, listener_list> map_;
};
class class1;
template<> struct base1::foo<class1, event1> { typedef std::function<void(int)> type; };
template<> struct base1::foo<class1, event2> { typedef std::function<void(int, int)> type; };
class class1 : public base1
{
public:
template<int event_id>
void addListener(typename foo<class1, event_id>::type callback)
{
addListener_<class1, event_id>(callback);
}
template<int event_id, typename ...A>
void invokeEvent(A&& ... args)
{
invokeEvent_<class1, event_id>(std::forward<A>(args)...);
}
};
And, now usage code looks much prettier:
int t = 0;
class1 f;
f.addListener<event1>([&](int a){
t++;
std::cout << "[" << t << "] event1: " << a << std::endl;
});
f.addListener<event2>([=](int a, int b){
std::cout << "[" << t << "] event2-1: " << a << " " << b << std::endl;
});
f.addListener<event2>([=](int a, int b){
std::cout << "[" << t << "] event2-2: " << a << " " << b << std::endl;
});
f.invokeEvent<event1>(5264);
f.invokeEvent<event2>(5264, 1000);
f.invokeEvent<event1>(5264);
f.invokeEvent<event2>(5264, 1000);
As I intended. But, if you take a look at class1 class closely, addListener() and invokeEvent() functions are not virtual. Oops!
After struggling hard, I found some signal-slot pattern libraries.
And, Signals looks good and can be updated to C++11.