b646ece14b
### Problem description This PR addresses issue #2013 that described a cluttered Event Manager. This is a DX issue and should not impact the users whatsoever. ### Implementation description The changes revolve around three main points: 1. the Event Manager (`event_manager.hpp`) was split into four categories: GUI, Interaction, Lifecycle, and Provider, and two types: Events, and Requests. This results in the following files: - `events_gui.hpp` - `events_interaction.hpp` - `events_lifecycle.hpp` - `events_provider.hpp` - `requests_gui.hpp` - `requests_interaction.hpp` - `requests_lifecycle.hpp` - `requests_provider.hpp` 2. Every event and request now has its own piece of documentation, with a `@brief`, accompanied by a longer comment if needed, and gets its `@param`s described. 3. The old `event_manager.hpp` import was removed and replaced by the correct imports wherever needed, as to reduce spread of those files only to where they are truly useful. ### Additional things The commits have been split into (chrono-)logical steps: - `feat`: split the Event Manager, and replace the imports - `refactor`, `chore`: make various small changes to match the required structure - `docs`: add documentation for events and requests Hopefully, this will help to review the PR. *Note: Beware of very long rebuild times in between the commits, use them sparingly! The Actions will ensure this PR builds anyways* Closes #2013 --------- Signed-off-by: BioTheWolff <47079795+BioTheWolff@users.noreply.github.com> Co-authored-by: Nik <werwolv98@gmail.com>
194 lines
7.3 KiB
C++
194 lines
7.3 KiB
C++
#pragma once
|
|
|
|
#include <hex.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <functional>
|
|
#include <list>
|
|
#include <mutex>
|
|
#include <map>
|
|
#include <string_view>
|
|
|
|
#include <hex/api/imhex_api.hpp>
|
|
#include <hex/helpers/logger.hpp>
|
|
|
|
#include <wolv/types/type_name.hpp>
|
|
|
|
#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<event_name>(std::move(function)); } \
|
|
static void subscribe(void *token, Event::Callback function) { EventManager::subscribe<event_name>(token, std::move(function)); } \
|
|
static void unsubscribe(const EventManager::EventList::iterator &token) noexcept { EventManager::unsubscribe(token); } \
|
|
static void unsubscribe(void *token) noexcept { EventManager::unsubscribe<event_name>(token); } \
|
|
static void post(auto &&...args) { EventManager::post<event_name>(std::forward<decltype(args)>(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<typename... Params>
|
|
struct Event : EventBase {
|
|
using Callback = std::function<void(Params...)>;
|
|
|
|
explicit Event(Callback func) noexcept : m_func(std::move(func)) { }
|
|
|
|
template<typename E>
|
|
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>(), e.what());
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private:
|
|
Callback m_func;
|
|
};
|
|
|
|
template<typename T>
|
|
concept EventType = std::derived_from<T, EventBase>;
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* @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<impl::EventId, std::unique_ptr<impl::EventBase>>;
|
|
|
|
/**
|
|
* @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<impl::EventType E>
|
|
static EventList::iterator subscribe(typename E::Callback function) {
|
|
std::scoped_lock lock(getEventMutex());
|
|
|
|
auto &events = getEvents();
|
|
return events.insert({ E::Id, std::make_unique<E>(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<impl::EventType E>
|
|
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<E>());
|
|
return;
|
|
}
|
|
|
|
getTokenStore().insert({ token, subscribe<E>(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<impl::EventType E>
|
|
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<impl::EventType E>
|
|
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<E *const>(event.get())).template call<E>(std::forward<decltype(args)>(args)...);
|
|
}
|
|
|
|
#if defined (DEBUG)
|
|
if constexpr (E::ShouldLog)
|
|
log::debug("Event posted: '{}'", wolv::type::getTypeName<E>());
|
|
#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<void *, EventList::iterator>& 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);
|
|
};
|
|
|
|
} |