Skip to content

Instantly share code, notes, and snippets.

@martinstarkov
Last active July 16, 2024 18:27
Show Gist options
  • Select an option

  • Save martinstarkov/a73f0cdaad8da118434f59c708e72fd2 to your computer and use it in GitHub Desktop.

Select an option

Save martinstarkov/a73f0cdaad8da118434f59c708e72fd2 to your computer and use it in GitHub Desktop.
Convenient Event Handler Implementation
#include <iostream>
#include <functional>
#include <unordered_map>
#include <type_traits>
#include <cstdint>
struct Event {
public:
virtual ~Event() = default;
};
template <typename T>
class EventDispatcher {
static_assert(std::is_enum_v<T>);
private:
using GeneralEventCallback = std::function<void(T, const Event&)>;
template <typename TEvent>
using TEventCallback = std::function<void(const TEvent&)>;
using EventCallback = TEventCallback<Event>;
using EventCallbacks = std::unordered_map<T, EventCallback>;
public:
// General event observation where type is passed to callback.
void Subscribe(void* ptr, GeneralEventCallback&& func) {
general_observers_[ptr] = func;
}
// Specific event observation.
template <typename TEvent>
void Subscribe(T type, void* ptr, TEventCallback<TEvent>&& func) {
using TEventType = std::decay_t<TEvent>;
static_assert(std::is_base_of_v<Event, TEventType>, "Events must inherit from Event class");
auto it = observers_.find(ptr);
std::pair<T, EventCallback> entry{
type,
[&, func](const Event& event) {
const TEventType& e = static_cast<const TEventType&>(event);
func(e);
}
};
if (it == observers_.end()) {
observers_[ptr] = EventCallbacks{ entry };
} else {
it->second[entry.first] = entry.second;
}
}
void Unsubscribe(void* ptr) {
observers_.erase(ptr);
general_observers_.erase(ptr);
}
template <typename TEvent>
void Post(T type, TEvent&& event) {
// This ensures that if a posted function modified observers, it does
// not invalidate the iterators.
auto observers = observers_;
auto general_observers = general_observers_;
for (auto&& [ptr, callback] : general_observers) {
if (ptr == nullptr) {
continue;
}
callback(type, event);
}
for (auto&& [ptr, callbacks] : observers) {
if (ptr == nullptr) {
continue;
}
auto it = callbacks.find(type);
if (it == callbacks.end()) {
continue;
}
auto func = it->second;
func(event);
}
};
[[nodiscard]] bool IsSubscribed(void* ptr) const {
return observers_.find(ptr) != observers_.end() ||
general_observers_.find(ptr) != general_observers_.end();
}
private:
std::unordered_map<void*, EventCallbacks> observers_;
std::unordered_map<void*, GeneralEventCallback> general_observers_;
};
// Example events
enum class WindowEvent {
First = 0,
Second = 1,
Third = 2
};
enum class MouseEvent {
Left = 0,
Right = 1,
Middle = 2
};
struct FirstWindow : public Event {
FirstWindow(int i) : i{ i } {}
int i = 0;
};
struct SecondWindow : public Event {
SecondWindow(double t) : t{ t } {}
double t = 0.0;
};
struct ThirdWindow : public Event {
ThirdWindow(float f) : f{ f } {}
float f = 0.0f;
};
struct FirstMouse : public Event {
FirstMouse(int i) : i{ i } {}
int i = 0;
};
struct SecondMouse : public Event {
SecondMouse(double t) : t{ t } {}
double t = 0.0;
};
struct ThirdMouse : public Event {
ThirdMouse(float f) : f{ f } {}
float f = 0.0f;
};
// Example observer functions
struct TestClass {
static void ObservingSecond(const SecondWindow& e) {
std::cout << "Second arg (double): " << e.t << std::endl;
}
void ObservingThird(const ThirdWindow& e) {
std::cout << "Third arg (float): " << e.f << std::endl;
}
};
struct EventHandler {
EventDispatcher<WindowEvent> window;
EventDispatcher<MouseEvent> mouse;
};
int main() {
EventHandler event;
TestClass obs;
// Dummies used as unique ids for subscribing to events
bool all = false;
bool first = false;
bool second = false;
bool third = false;
std::cout << "Started" << std::endl;
std::cout << "----------------" << std::endl;
event.window.Post(WindowEvent::First, FirstWindow{ 11 });
event.window.Post(WindowEvent::Second, SecondWindow{ 12 });
event.window.Post(WindowEvent::Third, ThirdWindow{ 13 });
event.mouse.Post(MouseEvent::Left, FirstMouse{ 1199 });
event.mouse.Post(MouseEvent::Right, SecondMouse{ 1299 });
event.mouse.Post(MouseEvent::Middle, ThirdMouse{ 1399 });
std::cout << "----------------" << std::endl;
event.window.Subscribe(WindowEvent::First, (void*)&first, std::function([&](const FirstWindow& i) {
std::cout << "WrongFirst arg (int): " << i.i << std::endl;
}));
// Resubscribing overrides previous subscription.
event.window.Subscribe(WindowEvent::First, (void*)&first, std::function([&](const FirstWindow& i) {
std::cout << "First arg (int): " << i.i << std::endl;
}));
// Fails to compile due to event.window instead of event.mouse (lambda mismatch).
/*event.window.Subscribe((void*)&first, std::function([&](FirstMouse i) {
std::cout << "First mouse arg (int): " << i.i << std::endl;
}));*/
// Event with same id works because of EventDispatcher template specification.
event.mouse.Subscribe(MouseEvent::Left, (void*)&first, std::function([&](const FirstMouse& i) {
std::cout << "First mouse arg (int): " << i.i << std::endl;
}));
// Multiple subscriptions per object instance.
event.window.Subscribe(WindowEvent::Second, (void*)&first, std::function([&](const SecondWindow& i) {
std::cout << "Constant Second arg (double): " << i.t << std::endl;
}));
event.window.Subscribe((void*)&all, std::function([&](WindowEvent type, const Event& e) {
switch (type) {
case WindowEvent::First: {
const FirstWindow& ei = static_cast<const FirstWindow&>(e);
std::cout << "All event first: " << ei.i << std::endl;
break;
}
case WindowEvent::Second: {
const SecondWindow& ei = static_cast<const SecondWindow&>(e);
std::cout << "All event second: " << ei.t << std::endl;
break;
}
case WindowEvent::Third: {
const ThirdWindow& ei = static_cast<const ThirdWindow&>(e);
std::cout << "All event third: " << ei.f << std::endl;
break;
}
}
}));
event.window.Post(WindowEvent::First, FirstWindow{ 21 });
event.window.Post(WindowEvent::Second, SecondWindow{ 22 });
event.window.Post(WindowEvent::Third, ThirdWindow{ 23 });
event.mouse.Post(MouseEvent::Left, FirstMouse{ 2199 });
event.mouse.Post(MouseEvent::Right, SecondMouse{ 2299 });
event.mouse.Post(MouseEvent::Middle, ThirdMouse{ 2399 });
std::cout << "----------------" << std::endl;
event.window.Subscribe(WindowEvent::Second, (void*)&second, std::function(&TestClass::ObservingSecond));
event.window.Post(WindowEvent::First, FirstWindow{ 31 });
event.window.Post(WindowEvent::Second, SecondWindow{ 32 });
event.window.Post(WindowEvent::Third, ThirdWindow{ 33 });
event.mouse.Post(MouseEvent::Left, FirstMouse{ 3199 });
event.mouse.Post(MouseEvent::Right, SecondMouse{ 3299 });
event.mouse.Post(MouseEvent::Middle, ThirdMouse{ 3399 });
std::cout << "----------------" << std::endl;
event.window.Subscribe(WindowEvent::Third, (void*)&third, std::function([&](const ThirdWindow& i) {
obs.ObservingThird(i);
}));
event.window.Post(WindowEvent::First, FirstWindow{ 41 });
event.window.Post(WindowEvent::Second, SecondWindow{ 42 });
event.window.Post(WindowEvent::Third, ThirdWindow{ 43 });
std::cout << "----------------" << std::endl;
event.window.Unsubscribe((void*)&first);
event.window.Post(WindowEvent::First, FirstWindow{ 51 });
event.window.Post(WindowEvent::Second, SecondWindow{ 52 });
event.window.Post(WindowEvent::Third, ThirdWindow{ 53 });
std::cout << "----------------" << std::endl;
event.window.Unsubscribe((void*)&second);
event.window.Unsubscribe((void*)&all);
event.window.Post(WindowEvent::First, FirstWindow{ 61 });
event.window.Post(WindowEvent::Second, SecondWindow{ 62 });
event.window.Post(WindowEvent::Third, ThirdWindow{ 63 });
std::cout << "----------------" << std::endl;
event.window.Unsubscribe((void*)&third);
event.window.Post(WindowEvent::First, FirstWindow{ 71 });
event.window.Post(WindowEvent::Second, SecondWindow{ 72 });
event.window.Post(WindowEvent::Third, ThirdWindow{ 73 });
std::cout << "----------------" << std::endl;
std::cout << "Finished" << std::endl;
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment