Last active
July 13, 2020 21:25
-
-
Save h3r/32fb6880b639b2ca6a61a81af5ded7f5 to your computer and use it in GitHub Desktop.
C++ Header only Event system
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
#pragma once | |
#ifndef INC_ENTITY_MSGS_ | |
#define INC_ENTITY_MSGS_ | |
/* | |
A super basic event system based on LEvent implementation from @jagenjo. | |
Some improvements should be done: | |
* swap to numerical ids instead of strings: https://github.com/mariusbancila/stduuid | |
* add sorting method to controll the call order on trigger | |
This is a little example to test it works: | |
void onUpdate(const Event::onUpdate& msg){ | |
std::cout << "hola" << std::endl; | |
} | |
void main() { | |
std::cout << "START!" << std::endl; | |
EventManager::bind<Event::onUpdate>("Core", "me", &onUpdate); | |
Event::onUpdate msg = {}; | |
EventManager::trigger<Event::onUpdate>("Core",&msg); | |
EM.unbind<Event::onBeforeRender>("Render", "me"); | |
std::cout << "END!" << std::endl; | |
system("PAUSE"); | |
} | |
*/ | |
#include <cassert> | |
#include <algorithm> | |
#include <unordered_map> | |
#include <functional> | |
class EventManager { | |
//Hacks para guardarme un puntero a una clase templatizad | |
struct IMsgBaseCallback { | |
virtual void trigger(const void* msg) = 0; | |
}; | |
struct TCallbackSlot { | |
std::string m_sender = nullptr; | |
std::string m_observer = nullptr; | |
IMsgBaseCallback* m_callback = nullptr; | |
}; | |
template< typename TMsg > | |
struct TMsgCallback : public IMsgBaseCallback { | |
// La signature de un metodo de la class TComp que recibe | |
// como argumento una referencia a una instancia de TMsg | |
// y que no devuelve nada. | |
typedef void (*TMethodCallback)(const TMsg&); | |
using Signature = std::function<void(const TMsg&)>; | |
// Una instancia de un puntero a uno de esos metodos | |
Signature m_method; | |
TMsgCallback(Signature new_method) | |
: m_method(new_method) | |
{ } | |
void trigger(const void* generic_msg) override | |
{ | |
const TMsg* msg = static_cast<const TMsg*>(generic_msg); | |
assert(m_method != nullptr); | |
m_method(*msg); | |
} | |
}; | |
inline static std::unordered_map < std::string, std::unordered_multimap<uint32_t, TCallbackSlot> > all_events; | |
public: | |
EventManager() {} | |
template< typename TMsg, typename TMethod > | |
static void bind(std::string sender, std::string observer, TMethod method) | |
{ | |
auto it = all_events.find(sender); | |
if (it == all_events.end()) | |
all_events.emplace(sender, std::unordered_multimap<uint32_t, TCallbackSlot>()); | |
auto id = TMsg::getMsgID(); | |
all_events[sender].emplace( | |
id, | |
TCallbackSlot({ sender, observer, new TMsgCallback<TMsg>(method) }) | |
); | |
} | |
template< typename TMsg > | |
static void unbind(std::string sender, std::string observer) | |
{ | |
auto it = all_events.find(sender); | |
if (it == all_events.end()) | |
return; | |
//remove all if observer not provided | |
if (observer.empty()) { | |
all_events[sender].clear(); | |
all_events.erase(sender); | |
return; | |
} | |
auto result = all_events[sender].equal_range(TMsg::getMsgID()); | |
auto iter = result.first; | |
for (; iter != result.second; ) | |
{ | |
iter = (iter->second.m_observer == observer) ? all_events[sender].erase(iter) : ++iter; | |
} | |
} | |
template< typename TMsg > | |
static void trigger(std::string sender, const void* generic_msg = nullptr) | |
{ | |
#ifndef NDEBUG | |
//Special tracking to avoid callback hell issues | |
static bool msg_being_triggered = false; | |
static std::pair< uint32_t, std::string > msg; | |
if (!msg_being_triggered) { | |
msg_being_triggered = true; | |
msg = std::make_pair(TMsg::getMsgID(), sender); | |
} | |
else{ | |
assert(msg.first != TMsg::getMsgID() && msg.second != sender | |
|| fatal("[Error] Triggering same event from same origin while resolving an event trigger!")); | |
} | |
#endif | |
auto it = all_events.find(sender); | |
if (it == all_events.end()) | |
return; | |
auto result = it->second.equal_range(TMsg::getMsgID()); | |
for (auto& it = result.first; it != result.second; it++) { | |
it->second.m_callback->trigger(generic_msg); | |
} | |
#ifndef NDEBUG | |
msg_being_triggered = false; | |
#endif | |
} | |
}; | |
namespace Event { | |
namespace { | |
struct TEvent { | |
virtual ~TEvent() {} | |
//Base event and event uuid generator | |
static uint32_t getNextUniqueMsgID() { | |
static uint32_t unique_msg_id = 0; | |
++unique_msg_id; | |
return unique_msg_id; | |
} | |
// Each TMsgStruct defining this will get assigned a new uint32_t | |
// associated and stored to this static method of each struct type | |
#define DECL_MSG_ID() \ | |
static uint32_t getMsgID() { \ | |
static uint32_t msg_id = getNextUniqueMsgID(); \ | |
return msg_id; \ | |
} | |
}; | |
} | |
//Gameloop events | |
struct onUpdate : public TEvent { DECL_MSG_ID() }; | |
struct onBeforeRender : public TEvent { DECL_MSG_ID() }; | |
struct onRenderDebugMenu : public TEvent { DECL_MSG_ID() }; | |
//Imgui events | |
struct onImGuiInit : public TEvent { DECL_MSG_ID() }; | |
struct onImGuiEnd : public TEvent { DECL_MSG_ID() }; | |
//Input events | |
struct onPlayerJoin : public TEvent { std::string who = "undefined"; DECL_MSG_ID() }; | |
struct onPlayerLeft : public TEvent { std::string who = "undefined"; DECL_MSG_ID() }; | |
} | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment