1
0
mirror of synced 2025-02-03 04:47:10 +01:00
ImHex/lib/libimhex/include/hex/api/event_manager.hpp
Wolf b646ece14b
impr: Refactor and restructure Event Manager (#2082)
### 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>
2025-01-25 15:32:07 +00:00

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);
};
}