Skip to content

Instantly share code, notes, and snippets.

@martinfinke
Forked from rioki/EventEmitter.cpp
Last active August 21, 2023 09:53
Show Gist options
  • Save martinfinke/a636dcddbcf112344b59 to your computer and use it in GitHub Desktop.
Save martinfinke/a636dcddbcf112344b59 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 LambdaType>
unsigned int add_listener(unsigned int event_id, LambdaType lambda) {
return add_listener(event_id, make_function(lambda));
}
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);
template<typename LambdaType>
unsigned int on(unsigned int event_id, LambdaType lambda) {
return on(event_id, make_function(lambda));
}
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;
// http://stackoverflow.com/a/21000981
template <typename T>
struct function_traits
: public function_traits<decltype(&T::operator())>
{};
template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const> {
typedef std::function<ReturnType (Args...)> f_type;
};
template <typename L>
typename function_traits<L>::f_type make_function(L l){
return (typename function_traits<L>::f_type)(l);
}
};
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
#include "EventEmitter.h"
#include <iostream>
#include <functional>
using namespace std;
void print(const char* str) {
cout << str << endl;
}
class Baz {
public:
void act(const char* action) {
cout << "Baz doing " << action << endl;
}
};
int main() {
EventEmitter emitter;
// Using lambda:
emitter.on(0, [](int foo, double bar) {
cout << "foo: " << foo << endl;
cout << "bar: " << bar << endl;
});
emitter.emit(0, 2, 3.7);
// Using std::function
emitter.on(1, std::function<void(const char*)>(print));
emitter.emit(1, "Hello World!");
// Using std::bind
Baz baz;
std::function<void(const char*)> action = std::bind(&Baz::act, &baz, std::placeholders::_1);
emitter.on(2, action);
emitter.emit(2, "something awesome");
return 0;
}
@anhldbk
Copy link

anhldbk commented Jun 30, 2017

Hey @martinfinke , 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;        
    }
};

@rioki
Copy link

rioki commented Jul 3, 2019

How about a lambda function?

class Data: public EventEmitter{
public:
    void activate(){
        this->on(1, [this] (auto message) {
            this->show(message);
        }
    }
    
    void show(const char* message){
        cout << message << endl;        
    }
};

Also you probably want to use enums as event identifiers. See the associated blog post: http://www.rioki.org/2014/12/29/eventemitter-in-c.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment