Skip to content

Instantly share code, notes, and snippets.

@3p3r
Last active August 29, 2015 14:27
Show Gist options
  • Save 3p3r/dba11491f2c066a44a5e to your computer and use it in GitHub Desktop.
Save 3p3r/dba11491f2c066a44a5e to your computer and use it in GitHub Desktop.
Simple C# Delegate like EventManager in C++11
#include <type_traits>
#include <functional>
#include <typeinfo>
#include <memory>
#include <map>
/*
* @class EventManager
* @brief A simple C# Delegate like class that can host callbacks and
* fire them when the need arises. Callbacks are identified by
* their argument types.
* @example
* EventManager event_manager;
* // List of arguments should be explicitly passed
* event_manager.AddListener<int>([](int n){ cout << n << endl; });
* event_manager.SendEvent(23); //calls the callback immidiately
* event_manager.SendMessage(45); //calls the on PollMessage()
* event_manager.PollMessages(); //pending messages
* @author
* Sepehr Laal.
* @license
* MIT (http://opensource.org/licenses/MIT)
*/
class EventManager
{
struct Function;
// 1 level Type indirection, needed for GCC to pass compilation.
template <typename T> struct id { typedef T type; };
public:
// Traits used by this class
using channel_id_type = std::size_t;
using event_callback_type = std::unique_ptr< Function >;
using event_callback_container = std::multimap< channel_id_type, event_callback_type >;
using message_callback_type = std::function<void()>;
using message_callback_container = std::multimap< channel_id_type, message_callback_type >;
public:
template < typename... Args >
/*!
* @fn AddListener
* @brief Registers a callback that listens to the propagation of
* certain event types, in the order passed to Args...
* @return The channel identifier of the passed callback.
* @note Does std::decay transformation on passed Args... types
* @see AddListenerExplicit
*/
std::size_t AddListener( const typename id< std::function< void(Args...) > >::type& cb ) {
auto key = GetChannelId< Args... >(IdPolicy::Lazy);
mCallbacks.insert(std::make_pair(key,
std::unique_ptr<Function>(new FunctionStorage<Args...>(cb))));
return key;
}
template < typename... Args >
/*!
* @fn SendEvent
* @brief Fires an event. Callbacks registered with Args... will
* be fired. Perfect forwards R-Values.
* @see SendEventExplicit
*/
void SendEvent(Args&&... args) {
auto index = GetChannelId<Args...>(IdPolicy::Lazy);
for (auto it = mCallbacks.lower_bound(index), end = mCallbacks.upper_bound(index); it != end; ++it) {
(static_cast<FunctionStorage<Args...>*>(it->second.get()))->mFunction(std::forward<Args...>(args...));
}
}
template < typename... Args >
void SendEvent(Args&... args) {
auto index = GetChannelId<Args...>(IdPolicy::Lazy);
for (auto it = mCallbacks.lower_bound(index), end = mCallbacks.upper_bound(index); it != end; ++it) {
(static_cast<FunctionStorage<Args...>*>(it->second.get()))->mFunction(args...);
}
}
template < typename... Args >
/*!
* @fn SendMessage
* @brief Buffers an event. Callbacks registered with Args... will
* be fired later. Owns and makes copy of args passed.
* @see SendMessageExplicit
*/
void SendMessage(Args... args) {
SendMessageImpl< Args... >( GetChannelId< Args... > ( IdPolicy::Lazy ), args... );
}
// Calls all buffered Messages with their callbacks, flushes buffer.
void PollMessages() {
if (mPendingMessages.empty()) return;
for (auto it = mPendingMessages.cbegin(), end = mPendingMessages.cend(); it != end; ++it)
it->second(); // Fire the previously saved callback
mPendingMessages.clear();
}
// Removed Event channels with certain IDs. ID is returned by AddListener()
void RemoveListeners(std::size_t channel_id) {
if (mCallbacks.find(channel_id) != mCallbacks.cend())
mCallbacks.erase(channel_id);
if (mPendingMessages.find(channel_id) != mPendingMessages.cend())
mPendingMessages.erase(channel_id);
}
// The policy for channel ID generation. If Strict, Args... types
// passed to GetChannelId() will remain untouched. If Lazy passed
// the std::decay will ease them up a little.
enum class IdPolicy { Strict, Lazy };
template < typename... Args >
// Channel ID generator. It generated IDs based on Args...
std::size_t GetChannelId(IdPolicy policy = IdPolicy::Lazy) {
std::size_t id = 0;
if (policy == IdPolicy::Lazy) {
id = reinterpret_cast<std::size_t>(&typeid(std::function<void(typename std::decay<Args...>::type)>));
}
else if (policy == IdPolicy::Strict) {
id = reinterpret_cast<std::size_t>(&typeid(std::function<void(Args...)>));
}
return id;
}
template < typename... Args >
// Strict version of AddListener.
std::size_t AddListenerStrict(const typename id<std::function<void(Args...)>>::type& cb) {
auto key = GetChannelId< Args... >(IdPolicy::Strict);
mCallbacks.insert(std::make_pair(key,
std::unique_ptr<Function>(new FunctionStorage<Args...>(cb))));
return key;
}
template < typename... Args >
// Strict version of SendEvent.
void SendEventStrict(Args... args) {
auto index = GetChannelId<Args...>(IdPolicy::Strict);
for (auto it = mCallbacks.lower_bound(index), end = mCallbacks.upper_bound(index); it != end; ++it) {
(static_cast<FunctionStorage<Args...>*>(it->second.get()))->mFunction(args...);
}
}
template < typename... Args >
// Strict version of SendMessage.
void SendMessageStrict(Args... args) {
SendMessageImpl< Args... >(GetChannelId<Args...>(IdPolicy::Strict), args...);
}
private:
template< typename... Args >
// Send Message impl
void SendMessageImpl(channel_id_type id, Args... args) {
for (auto it = mCallbacks.lower_bound(id), end = mCallbacks.upper_bound(id); it != end; ++it) {
// Capturing arguments and fn ptr by value since they will be called later.
Function* fn_ptr = it->second.get();
mPendingMessages.insert(std::make_pair(id, [fn_ptr, args...](){
(static_cast<FunctionStorage<Args...>*>(fn_ptr))->mFunction(args...);
}));
}
}
// Base proxy struct to be stored in a std::unique_ptr
struct Function { virtual ~Function() {} };
template < typename... Args >
// The specialized storage, holding callable objects
struct FunctionStorage : public Function {
FunctionStorage(const std::function<void(Args...)>& fn) : mFunction(fn) {}
std::function<void(Args...)> mFunction;
};
private:
event_callback_container mCallbacks;
message_callback_container mPendingMessages;
};
@3p3r
Copy link
Author

3p3r commented Aug 11, 2015

Tested on the following platforms:

  • MSVC 2013 (professional w/ update 5)
  • GCC x86 5.2.0
  • Clang x86 3.5.1

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