-
Notifications
You must be signed in to change notification settings - Fork 147
Implementing EventEmitter
d5 edited this page Feb 9, 2012
·
7 revisions
This is what we need in the end:
class HTTP : public EventEmitter {
// ..
}
HTTP 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 event_base
{
public:
event_base() {}
virtual ~event_base() {}
};
template<int id, typename callback_t>
class event : public event_base
{
callback_t c_;
public:
event(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<event_base>> listener_list;
public:
base1() : map_() {}
virtual ~base1() {}
template<typename T, int I> struct foo {};
public:
template<typename T, int id>
void addEvent(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<event_base>(new event<id, 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 event<id, 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.addEvent<class1, event1>([&](int a){
t++;
std::cout << "[" << t << "] event1: " << a << std::endl;
});
f.addEvent<class1, event2>([=](int a, int b){
std::cout << "[" << t << "] event2-1: " << a << " " << b << std::endl;
});
f.addEvent<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.
I'm still researching for better implementations that will look simpler, intuitive, and even hard-to-use-incorrectly.