#pragma once #include #include #include #include #include #include #include #include #include #include #define EVENT_DEF_IMPL(event_name, event_name_string, should_log, ...) \ struct event_name final : public hex::impl::Event<__VA_ARGS__> { \ constexpr static auto Id = [] { return hex::impl::EventId(event_name_string); }(); \ constexpr static auto ShouldLog = (should_log); \ explicit event_name(Callback func) noexcept : Event(std::move(func)) { } \ \ static EventManager::EventList::iterator subscribe(Event::Callback function) { return EventManager::subscribe(std::move(function)); } \ static void subscribe(void *token, Event::Callback function) { EventManager::subscribe(token, std::move(function)); } \ static void unsubscribe(const EventManager::EventList::iterator &token) noexcept { EventManager::unsubscribe(token); } \ static void unsubscribe(void *token) noexcept { EventManager::unsubscribe(token); } \ static void post(auto &&...args) { EventManager::post(std::forward(args)...); } \ } #define EVENT_DEF(event_name, ...) EVENT_DEF_IMPL(event_name, #event_name, true, __VA_ARGS__) #define EVENT_DEF_NO_LOG(event_name, ...) EVENT_DEF_IMPL(event_name, #event_name, false, __VA_ARGS__) namespace hex { namespace impl { class EventId { public: explicit constexpr EventId(const char *eventName) { m_hash = 0x811C'9DC5; for (const char c : std::string_view(eventName)) { m_hash = (m_hash >> 5) | (m_hash << 27); m_hash ^= c; } } constexpr bool operator==(const EventId &other) const { return m_hash == other.m_hash; } constexpr auto operator<=>(const EventId &other) const { return m_hash <=> other.m_hash; } private: u32 m_hash; }; struct EventBase { EventBase() noexcept = default; virtual ~EventBase() = default; }; template struct Event : EventBase { using Callback = std::function; explicit Event(Callback func) noexcept : m_func(std::move(func)) { } template void call(Params... params) const { try { m_func(params...); } catch (const std::exception &e) { log::error("An exception occurred while handling event {}: {}", wolv::type::getTypeName(), e.what()); throw; } } private: Callback m_func; }; template concept EventType = std::derived_from; } /** * @brief The EventManager allows subscribing to and posting events to different parts of the program. * To create a new event, use the EVENT_DEF macro. This will create a new event type with the given name and parameters. * Events should be created in an `events_*.hpp` category file under the `events` folder, and never directly here. */ class EventManager { public: using EventList = std::multimap>; /** * @brief Subscribes to an event * @tparam E Event * @param function Function to call when the event is posted * @return Token to unsubscribe from the event */ template static EventList::iterator subscribe(typename E::Callback function) { std::scoped_lock lock(getEventMutex()); auto &events = getEvents(); return events.insert({ E::Id, std::make_unique(function) }); } /** * @brief Subscribes to an event * @tparam E Event * @param token Unique token to register the event to. Later required to unsubscribe again * @param function Function to call when the event is posted */ template static void subscribe(void *token, typename E::Callback function) { std::scoped_lock lock(getEventMutex()); if (isAlreadyRegistered(token, E::Id)) { log::fatal("The token '{}' has already registered the same event ('{}')", token, wolv::type::getTypeName()); return; } getTokenStore().insert({ token, subscribe(function) }); } /** * @brief Unsubscribes from an event * @param token Token returned by subscribe */ static void unsubscribe(const EventList::iterator &token) noexcept { std::scoped_lock lock(getEventMutex()); getEvents().erase(token); } /** * @brief Unsubscribes from an event * @tparam E Event * @param token Token passed to subscribe */ template static void unsubscribe(void *token) noexcept { std::scoped_lock lock(getEventMutex()); unsubscribe(token, E::Id); } /** * @brief Posts an event to all subscribers of it * @tparam E Event * @param args Arguments to pass to the event */ template static void post(auto && ...args) { std::scoped_lock lock(getEventMutex()); auto [begin, end] = getEvents().equal_range(E::Id); for (auto it = begin; it != end; ++it) { const auto &[id, event] = *it; (*static_cast(event.get())).template call(std::forward(args)...); } #if defined (DEBUG) if constexpr (E::ShouldLog) log::debug("Event posted: '{}'", wolv::type::getTypeName()); #endif } /** * @brief Unsubscribe all subscribers from all events */ static void clear() noexcept { std::scoped_lock lock(getEventMutex()); getEvents().clear(); getTokenStore().clear(); } private: static std::multimap& getTokenStore(); static EventList& getEvents(); static std::recursive_mutex& getEventMutex(); static bool isAlreadyRegistered(void *token, impl::EventId id); static void unsubscribe(void *token, impl::EventId id); }; }