Last active
August 29, 2015 14:27
-
-
Save 3p3r/dba11491f2c066a44a5e to your computer and use it in GitHub Desktop.
Simple C# Delegate like EventManager in C++11
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Tested on the following platforms: