-
-
Save rioki/1290004d7505380f2b1d to your computer and use it in GitHub Desktop.
// | |
// Copyright (c) 2014 Sean Farrell | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
// SOFTWARE. | |
// | |
#include "EventEmitter.h" | |
#include <stdexcept> | |
EventEmitter::EventEmitter() {} | |
EventEmitter::~EventEmitter() {} | |
unsigned int EventEmitter::add_listener(unsigned int event_id, std::function<void ()> cb) | |
{ | |
if (!cb) | |
{ | |
throw std::invalid_argument("EventEmitter::add_listener: No callbak provided."); | |
} | |
std::lock_guard<std::mutex> lock(mutex); | |
unsigned int listener_id = ++last_listener; | |
listeners.insert(std::make_pair(event_id, std::make_shared<Listener<>>(listener_id, cb))); | |
return listener_id; | |
} | |
unsigned int EventEmitter::on(unsigned int event_id, std::function<void ()> cb) | |
{ | |
return add_listener(event_id, cb); | |
} | |
void EventEmitter::remove_listener(unsigned int listener_id) | |
{ | |
std::lock_guard<std::mutex> lock(mutex); | |
auto i = std::find_if(listeners.begin(), listeners.end(), [&] (std::pair<const unsigned int, std::shared_ptr<ListenerBase>> p) { | |
return p.second->id == listener_id; | |
}); | |
if (i != listeners.end()) | |
{ | |
listeners.erase(i); | |
} | |
else | |
{ | |
throw std::invalid_argument("EventEmitter::remove_listener: Invalid listener id."); | |
} | |
} |
// | |
// Copyright (c) 2014 Sean Farrell | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
// SOFTWARE. | |
// | |
#ifndef _EVENT_EMITTER_H_ | |
#define _EVENT_EMITTER_H_ | |
#include <functional> | |
#include <map> | |
#include <memory> | |
#include <mutex> | |
#include <list> | |
#include <algorithm> | |
class EventEmitter | |
{ | |
public: | |
EventEmitter(); | |
~EventEmitter(); | |
template <typename... Args> | |
unsigned int add_listener(unsigned int event_id, std::function<void (Args...)> cb); | |
unsigned int add_listener(unsigned int event_id, std::function<void ()> cb); | |
template <typename... Args> | |
unsigned int on(unsigned int event_id, std::function<void (Args...)> cb); | |
unsigned int on(unsigned int event_id, std::function<void ()> cb); | |
void remove_listener(unsigned int listener_id); | |
template <typename... Args> | |
void emit(unsigned int event_id, Args... args); | |
private: | |
struct ListenerBase | |
{ | |
ListenerBase() {} | |
ListenerBase(unsigned int i) | |
: id(i) {} | |
virtual ~ListenerBase() {} | |
unsigned int id; | |
}; | |
template <typename... Args> | |
struct Listener : public ListenerBase | |
{ | |
Listener() {} | |
Listener(unsigned int i, std::function<void (Args...)> c) | |
: ListenerBase(i), cb(c) {} | |
std::function<void (Args...)> cb; | |
}; | |
std::mutex mutex; | |
unsigned int last_listener; | |
std::multimap<unsigned int, std::shared_ptr<ListenerBase>> listeners; | |
EventEmitter(const EventEmitter&) = delete; | |
const EventEmitter& operator = (const EventEmitter&) = delete; | |
}; | |
template <typename... Args> | |
unsigned int EventEmitter::add_listener(unsigned int event_id, std::function<void (Args...)> cb) | |
{ | |
if (!cb) | |
{ | |
throw std::invalid_argument("EventEmitter::add_listener: No callbak provided."); | |
} | |
std::lock_guard<std::mutex> lock(mutex); | |
unsigned int listener_id = ++last_listener; | |
listeners.insert(std::make_pair(event_id, std::make_shared<Listener<Args...>>(listener_id, cb))); | |
return listener_id; | |
} | |
template <typename... Args> | |
unsigned int EventEmitter::on(unsigned int event_id, std::function<void (Args...)> cb) | |
{ | |
return add_listener(event_id, cb); | |
} | |
template <typename... Args> | |
void EventEmitter::emit(unsigned int event_id, Args... args) | |
{ | |
std::list<std::shared_ptr<Listener<Args...>>> handlers; | |
{ | |
std::lock_guard<std::mutex> lock(mutex); | |
auto range = listeners.equal_range(event_id); | |
handlers.resize(std::distance(range.first, range.second)); | |
std::transform(range.first, range.second, handlers.begin(), [] (std::pair<const unsigned int, std::shared_ptr<ListenerBase>> p) { | |
auto l = std::dynamic_pointer_cast<Listener<Args...>>(p.second); | |
if (l) | |
{ | |
return l; | |
} | |
else | |
{ | |
throw std::logic_error("EventEmitter::emit: Invalid event signature."); | |
} | |
}); | |
} | |
for (auto& h : handlers) | |
{ | |
h->cb(args...); | |
} | |
} | |
#endif |
The solution (with on<int>
) doesn't work for me (Xcode 6.2, LLVM version 6.0 (clang-600.0.57) (based on LLVM 3.5svn)). I copy & pasted the example from above and replaced the call with your suggestion.
The error is:
No matching member function for call to 'on'
Do you know how to solve this?
[EDIT] Nevermind, got it to work with a nice solution from Stack Overflow. Here if you're interested.
How do I return other values, besides int?
for example -
emitter.emit(0, "Test String");
Edit
In case anyone else runs into the problem the solution is :
EventEmitter emitter;
emitter.on<std::string>(0, [](std::string err) {
std::cout << err << std::endl;
});
std::string str = "pass string";
emitter.emit(0, str);
Or, you force the template type:
emitter.emit<std::string>(0, "pass string");
Hey @rioki, how can I register a non-static event handler? The code below can't be compiled
class Data: public EventEmitter{
public:
void activate(){
this->on(1, this->show); // -> error: invalid use of non-static member function
}
void show(const char* message){
cout << message << endl;
}
};
Update
I've managed to invoke it correctly (with std::bind
)
class Data: public EventEmitter{
public:
void activate(){
std::function<void(const char*)> handler = std::bind(&Data::show, this, std::placeholders::_1);
this->on(1, handler);
}
void show(const char* message){
cout << message << endl;
}
};
@anhldbk Alternatively:
class Data: public EventEmitter
{
public:
void activate()
{
this->on(1, [this] (const char* message) {show(msg);});
}
void show(const char* message)
{
cout << message << endl;
}
};
@anhldbk Alternatively:
class Data: public EventEmitter { public: void activate() { this->on(1, [this] (const char* message) {show(msg);}); } void show(const char* message) { cout << message << endl; } };
Mmm, that compiles for you? When I try that it says cannot convert from lambda to std::function.
Yes, you can find the details on the implementation and use under http://www.rioki.org/2014/12/29/eventemitter-in-c.html
But in your case, you need to specify the types:
This is sort of dumb thing by C++. You either must specify the type on the template function or wrap it into a
std::function
object: C++'s template type inference does not work well when working with lambdas.