Last active
July 16, 2024 18:27
-
-
Save martinstarkov/a73f0cdaad8da118434f59c708e72fd2 to your computer and use it in GitHub Desktop.
Convenient Event Handler Implementation
This file contains hidden or 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 <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