Skip to content

Instantly share code, notes, and snippets.

@LukasKalbertodt
Created July 4, 2016 15:38
Show Gist options
  • Save LukasKalbertodt/b2bb98bfb19b3a2545a4e85c9ec31adb to your computer and use it in GitHub Desktop.
Save LukasKalbertodt/b2bb98bfb19b3a2545a4e85c9ec31adb to your computer and use it in GitHub Desktop.
Just found this snippet of code... it's at least 3 years old. An exception-like event manager for games...
/**
* Template based Event-System by Lukas Kalbertodt <[email protected]>
*/
#pragma once
// needed std headers
#include <map>
#include <memory>
#include <cstring>
#include <set>
/**
* \brief base class for all events
* Has one static method to get a unique id to a event type (nicer than typeid)
* Derived classes should always have a
* typedef MyBase base;
* where "MyBase" is the base class of the derived class.
* Without this typedef the event can not be used with the EventManager!
*/
class Event
{
public:
template <typename T> static void* GetEventID()
{
static int id;
return static_cast<void*>(&id);
}
};
/**
* \brief helper traits for lambda function types
* Helper traits to get return type and arg type of a lambda function.
* Uses decltype(&T::operator()) to get types. Works just for one function
* argument, but it is enough for this event system.
* result_type -> type of the return value
* arg_type -> type of the argument
*/
template <typename T>
struct lambda_traits
: public lambda_traits<decltype(&T::operator())>
{};
// specialization for lambdas without parameter (this is an error)
template <typename ClassType, typename ReturnType>
struct lambda_traits<ReturnType(ClassType::*)() const>
{
static_assert(std::is_void<ClassType>::value,
"Given lambda function does not have any arguments. It must have const Event& as argument!");
};
// specialization for lambdas we accept
template <typename ClassType, typename ReturnType, typename Arg>
struct lambda_traits<ReturnType(ClassType::*)(Arg) const>
{
typedef ReturnType result_type;
typedef Arg arg_type;
};
/**
* \brief Wrapper for different functions
* Wrapper for member functions, normal functions and lambda functions. Is
* sortable, based on
* 1. Size of the function pointer/function object
* 2. Type of the function
* 3. Raw compare of function pointer/function object data
* Attention: Different compilers have different sizes for function pointer/
* function objects. The sort order can differ between different compilers!
*
* Uses type erasure to save different function pointer/objects. Member functions
* do not save the pointer to the object at which it will be called. Object has
* to be passed while calling. This object is ignored when calling a normal
* function or a lambda function.
*/
class EventFunctionWrapper
{
private:
// Base class for type erasure
struct FunctionConcept
{
FunctionConcept() : m_cmpData(nullptr) {}
// calling the underlying function on the memberHost (if the underlying
// function is a member function)
virtual void operator()(void* memberHost, const Event& event) const = 0;
// Helper functions for comparing raw data
virtual void* GetRawData() = 0;
virtual int GetDataSize() = 0;
// pointer to data buffer with the comparable data
char* m_cmpData;
};
// class for member functions
template <typename THost, typename TEvent>
struct MemberFunctionModel : public FunctionConcept
{
// member function pointer
void(THost::*m_func)(const TEvent&);
char m_cmpData[sizeof(void(THost::*)(const TEvent&)) + 1];
MemberFunctionModel(void(THost::*ptr)(const TEvent&)) : m_func(ptr)
{
memset(m_cmpData, 0, 1); // set identifier byte for member function (0)
memcpy(m_cmpData + 1, &m_func, sizeof(m_func)); // copy member pointer in buffer
}
virtual void operator()(void* memberHost, const Event& event) const
{
(static_cast<THost*>(memberHost)->*m_func)(static_cast<const TEvent&>(event));
}
virtual void* GetRawData()
{
return m_cmpData;
}
virtual int GetDataSize()
{
return sizeof(m_func);
}
};
// class for lambda function
template <typename TLambda>
struct LambdaFunctionModel : public FunctionConcept
{
// lambda function object of strange type
TLambda m_func;
char m_cmpData[sizeof(TLambda) + 1 + sizeof(&TLambda::operator())];
LambdaFunctionModel(TLambda f) : m_func(f)
{
memset(m_cmpData, 1, 1); // set identifier byte for lambda functions (1)
memcpy(m_cmpData + 1, &m_func, sizeof(m_func)); // copy lambda data
auto ptr = &TLambda::operator();
memcpy(m_cmpData + 1 + sizeof(m_func), &ptr, sizeof(ptr)); // copy function pointer data
}
virtual void operator()(void* memberHost, const Event& event) const
{
m_func(static_cast<typename lambda_traits<TLambda>::arg_type>(event));
}
virtual void* GetRawData()
{
return m_cmpData;
}
virtual int GetDataSize()
{
return sizeof(m_func);
}
};
// class for normal functions
template <typename TEvent>
struct RawFunctionModel : public FunctionConcept
{
// function pointer
void(*m_func)(const TEvent&);
char m_cmpData[sizeof(void(*)(const TEvent&)) + 1];
RawFunctionModel(void(*ptr)(const TEvent&)) : m_func(ptr)
{
memset(m_cmpData, 2, 1); // set identifier byte for normal function (2)
memcpy(m_cmpData + 1, &m_func, sizeof(m_func)); // copy function pointer
}
virtual void operator()(void* memberHost, const Event& event) const
{
m_func(static_cast<const TEvent&>(event));
}
virtual void* GetRawData()
{
return m_cmpData;
}
virtual int GetDataSize()
{
return sizeof(m_func);
}
};
// save erased type
std::shared_ptr<FunctionConcept> m_funcConcept;
public:
// constructor for member functions
template <typename THost, typename TEvent>
EventFunctionWrapper(void(THost::*ptr)(const TEvent&))
: m_funcConcept(new MemberFunctionModel<THost, TEvent>(ptr))
{}
// constructor for lambda functions
template <typename TLambda>
EventFunctionWrapper(TLambda ptr)
: m_funcConcept(new LambdaFunctionModel<TLambda>(ptr))
{}
// constructor for normal functions
template <typename TEvent>
EventFunctionWrapper(void(*ptr)(const TEvent&))
: m_funcConcept(new RawFunctionModel<TEvent>(ptr))
{}
// calling the underlying function
void operator()(void* memberHost, const Event& event) const
{
(*m_funcConcept)(memberHost, event);
}
// compare operator (for more information see above in class description)
bool operator==(const EventFunctionWrapper& other)
{
if(other.m_funcConcept->GetDataSize() != m_funcConcept->GetDataSize())
return false;
return (memcmp(other.m_funcConcept->GetRawData(), m_funcConcept->GetRawData(), m_funcConcept->GetDataSize()) == 0);
}
// compare operator (for more information see above in class description)
bool operator<(const EventFunctionWrapper& other) const
{
if(other.m_funcConcept->GetDataSize() != m_funcConcept->GetDataSize())
return m_funcConcept->GetDataSize() < other.m_funcConcept->GetDataSize();
return (memcmp(other.m_funcConcept->GetRawData(), m_funcConcept->GetRawData(), m_funcConcept->GetDataSize()) > 0);
}
// compare operator (for more information see above in class description)
bool operator>(const EventFunctionWrapper& other) const
{
if(other.m_funcConcept->GetDataSize() != m_funcConcept->GetDataSize())
return m_funcConcept->GetDataSize() > other.m_funcConcept->GetDataSize();
return (memcmp(other.m_funcConcept->GetRawData(), m_funcConcept->GetRawData(), m_funcConcept->GetDataSize()) < 0);
}
};
/**
* \brief Manages everything with Events
* Manages Listener to specific event types and calls those Listener when a
* event of that type or a derived type is triggered.
* Works as a singleton!
*/
class EventManager
{
public:
// typedef for better reading of source code
typedef void* EventType;
typedef void* HostType;
// ---------------------------------------------------------------
// ------- PUBLIC FUNCTIONS --------------------------------------
// ---------------------------------------------------------------
~EventManager() {}
/**
* \brief Returns one instance of EventManager
*/
static EventManager* GetInstance()
{
static EventManager instance;
return &instance;
}
/**
* \brief Triggers Event and call all Listener
* When the Event is triggered all Listener on that event are called. Then
* all Listener on the base event are called (recursive).
* Note: Never specify the second parameter! You also do not need to specify the template
* argument, which should be specified with param e.
* This overloaded function will be called with T!=Event. If T=Event the overloaded method
* TriggerEvent(T& e, ...) is invoked.
*
* \param e The event to trigger. Must be of Type Event or any derived Type.
*/
template <typename T>
void TriggerEvent(const T& e)
{
// check if T is derived from Event
static_assert(std::is_base_of<Event, T>::value, "Given Type is not derived from Event!");
// call all functions listening on T
this->CallAllListener(GetEventID<T>(), e);
// call all functions listening on base class of T
this->TriggerEvent(static_cast<const typename T::base&>(e));
}
/**
* \brief Triggers Event and call all Listener
* When the Event is triggered all Listener on that event are called.
* Note: Never specify a second parameter! You also do not need to specify the template
* argument, which should be specified with param e.
* This overloaded function will be called with T=Event. Every other T will invoke the
* overloaded function TriggerEvent(T& e, typename T::base* = nullptr).
*
* \param e The event to trigger. Must be of Type Event or any derived Type.
*/
void TriggerEvent(const Event& e)
{
// call all functions listening on T
this->CallAllListener(GetEventID<Event>(), e);
}
/**
* \brief Add a function as listening function to a specific EventType.
* After registering the function is called when a event of the given type or a derived type is triggered.
* This method should be used for member functions! For registering other function types see the overloaded
* methods of RegisterListener below.
*
* \param callback pointer to member to the function that should be called if the event is triggered
* \param obj the object on which the member function should be called
* \tparam T The type of Event to listen on (must be given as explicit template parameter).
* Must be equal to Event or a derived class of Event
* \tparam F The type of argument, which the member function given with callback accepts.
* Implicit given by param callback. F must be T or a baseclass of T!
*/
template <typename T, typename O, typename F>
void RegisterListener(void(O::*callback)(const F&), O* obj)
{
// check if T is derived from Event or equal to Event
static_assert(std::is_base_of<Event, T>::value, "Given EventType (Parameter 1) is not derived from Event!");
// check if the member function fits to T (F must be a baseclass of T or equal to T)
static_assert(std::is_base_of<F, T>::value, "Given function (Parameter 2) does not accept a Event from type T or any baseclass of T!");
// wrap pointer to member in our Wrapper and insert it in the set associated with the host and event type
EventType eID = GetEventID<T>();
m_Listener[eID][obj].insert(EventFunctionWrapper(callback));
}
// RegisterListener for lambda functions (for more information see above)
template <typename T, typename L>
void RegisterListener(L lambda, HostType listenerHost = nullptr)
{
// check if T is derived from Event or equal to Event
static_assert(std::is_base_of<Event, T>::value, "Given EventType (Parameter 1) is not derived from Event!");
// check if lambda function accepts a valid argument
static_assert(std::is_base_of<typename std::remove_reference<typename lambda_traits<L>::arg_type>::type, T>::value,
"Given function (Parameter 2) does not accept a Event from type T or any baseclass of T!");
// wrap lambda in our Wrapper and insert it in the set associated with the event type (host)
EventType eID = GetEventID<T>();
m_Listener[eID][listenerHost].insert(EventFunctionWrapper(lambda));
}
// RegisterListener for normal functions (for more information see above)
template <typename T, typename F>
void RegisterListener(void(*fPtr)(const F&), HostType listenerHost = nullptr)
{
// check if T is derived from Event or equal to Event
static_assert(std::is_base_of<Event, T>::value, "Given EventType (Parameter 1) is not derived from Event!");
// check if function accepts a valid argument
static_assert(std::is_base_of<F, T>::value, "Given function (Parameter 2) does not accept a Event from type T or any baseclass of T!");
// wrap function in our Wrapper and insert it in the set associated with the event type (host)
EventType eID = GetEventID<T>();
m_Listener[eID][listenerHost].insert(EventFunctionWrapper(fPtr));
}
/**
* \brief Removes the listener associated with the given event type, function and object
* This function removes a listener from the intern listener-list. Listener will be
* deleted, which listen on type T, are associated with the object 'listenerHost' and which
* function pointer equals the function pointer 'callback'.
*
* \param callback pointer to member to a function associated with the listeners to be deleted
* \param listenerHost the object associated with the listeners to be deleted
* \tparam T The type of Event associated with the listeners to be deleted
*/
template <typename T, typename O, typename F>
void RemoveListener(void(O::*callback)(const F&), O* listenerHost)
{
// check if T is derived from Event or equal to Event
static_assert(std::is_base_of<Event, T>::value, "Given EventType (Parameter 1) is not derived from Event!");
// check if the member function fits to T (F must be a baseclass of T or equal to T)
static_assert(std::is_base_of<F, T>::value, "Given function (Parameter 2) does not accept a Event from type T or any baseclass of T!");
// delete function from set
EventType eID = GetEventID<T>();
m_Listener[eID][listenerHost].erase(EventFunctionWrapper(callback));
}
/**
* \brief Removes the listener associated with the given event type and object
* This function removes a listener from the intern listener-list. Listener will be
* deleted, which listen on type T and are associated with the object 'obj'
*
* \param obj the object associated with the listeners to be deleted
* \tparam T The type of Event associated with the listeners to be deleted
*/
template <typename T, typename O>
void RemoveListener(O* obj)
{
EventType eID = GetEventID<T>();
m_Listener[eID].erase(obj);
}
/**
* \brief Removes the listener associated with the given member function
*
* \param callback pointer to member to a function associated with the listeners to be deleted
* \tparam T The type of Event associated with the listeners to be deleted
*/
template <typename T, typename O, typename F>
void RemoveListener(void(O::*callback)(const F&))
{
EventType eID = GetEventID<T>();
for(auto itHosts = m_Listener[eID].begin(); itHosts != m_Listener[eID].end(); itHosts++)
{
itHosts->second.erase(EventFunctionWrapper(callback));
}
}
/**
* \brief Removes the listener associated with the given function and object
* This function removes a listener from the intern listener-list. Listener will be
* deleted, which are associated with the object 'obj' and which function pointer
* equals the function pointer 'callback'. Listener are deleted regardless of event type.
*
* \param callback pointer to member to a function associated with the listeners to be deleted
* \param obj the object associated with the listeners to be deleted
*/
template <typename O, typename F>
void RemoveAllListener(void(O::*callback)(const F&), O* listenerHost)
{
for(auto itEvents = m_Listener.begin(); itEvents != m_Listener.end(); itEvents++)
{
itEvents->second[listenerHost].erase(callback);
}
}
/**
* \brief Removes the listener associated with the given object
* This function removes a listener from the intern listener-list. Listener will be
* deleted, which are associated with the object 'obj', regardless of event type.
*
* \param obj the object associated with the listeners to be deleted
*/
template <typename O>
void RemoveAllListener(O* obj)
{
for(auto it = m_Listener.begin(); it != m_Listener.end(); it++)
{
(it->second).erase(obj);
}
}
/**
* \brief Removes the listener associated with the given member function
* of all event types
*
* \param callback pointer to member to a function associated with the listeners to be deleted
*/
template <typename O, typename F>
void RemoveAllListener(void(O::*callback)(const F&))
{
for(auto itEvents = m_Listener.begin(); itEvents != m_Listener.end(); itEvents++)
{
for(auto itHosts = itEvents->second.begin(); itHosts != itEvents->second.end(); itHosts++)
{
itHosts->second.erase(EventFunctionWrapper(callback));
}
}
}
private:
// ---------------------------------------------------------------
// ------- PRIVATE FUNCTIONS -------------------------------------
// ---------------------------------------------------------------
// private constructor
EventManager() {}
/**
* \brief Calls all listening function to specific EventID
*
* \param eID The ID from the EventType
* \param e The triggered event
*/
void CallAllListener(EventType eID, const Event& e)
{
auto hostMap = m_Listener[eID];
for(auto itHosts = hostMap.begin(); itHosts != hostMap.end(); itHosts++)
{
auto functionSet = itHosts->second;
for(auto itFunctions = functionSet.begin(); itFunctions != functionSet.end(); itFunctions++)
{
(*itFunctions)(itHosts->first, e);
}
}
}
// small helper to get real event id, independent of references
template <typename T>
void* GetEventID()
{
return Event::GetEventID<typename std::remove_reference<T>::type>();
}
// ---------------------------------------------------------------
// ------- MEMBER VARIABLEs --------------------------------------
// ---------------------------------------------------------------
std::map<EventType, std::map<HostType, std::set<EventFunctionWrapper>>> m_Listener;
};
/**
* \brief Helper to manage event listeners
* Works as base class. The derived class should then register listener
* over the function of this helper class. Then all listener are removed,
* when the destructor of the derived object is called.
*/
template <typename T>
class EventListenerHelper
{
protected:
EventListenerHelper()
{
m_Obj = static_cast<T*>(this);
m_Mng = EventManager::GetInstance();
}
virtual ~EventListenerHelper()
{
m_Mng->RemoveAllListener(m_Obj);
}
/**
* \brief Wrapper for EventManager::RegisterListener (see there for further description)
* notice: There is no wrapper for normal function because it makes no
* sense to bind normal functions to object
*/
template <typename E, typename F>
void RegisterListener(void(T::*callback)(const F&))
{
m_Mng->RegisterListener<E>(callback, m_Obj);
}
// wrapper for lambda function
// notice: If the object is deleted, the registered lambda function will
// be unregistered. If this is not intended use the RegisterListener
// method directly from EventManager
template <typename E, typename L>
void RegisterListener(L lambda)
{
m_Mng->RegisterListener<E>(lambda, m_Obj);
}
private:
T* m_Obj;
EventManager* m_Mng;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment