Files
dolphin/Source/Core/Common/HookableEvent.h
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

116 lines
3.0 KiB
C++
Raw Normal View History

2023-01-30 22:36:25 +13:00
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Common/Logging/Log.h"
#include "Common/StringLiteral.h"
#include <functional>
#include <memory>
2023-02-03 13:18:37 +13:00
#include <mutex>
2023-01-30 22:36:25 +13:00
#include <string>
#include <string_view>
#include <vector>
2023-02-03 13:18:37 +13:00
namespace Common
{
2023-01-30 22:36:25 +13:00
struct HookBase
{
virtual ~HookBase() = default;
2023-02-03 14:24:26 +13:00
protected:
HookBase() = default;
// This shouldn't be copied. And since we always wrap it in unique_ptr, no need to move it either
HookBase(const HookBase&) = delete;
HookBase(HookBase&&) = delete;
HookBase& operator=(const HookBase&) = delete;
HookBase& operator=(HookBase&&) = delete;
2023-01-30 22:36:25 +13:00
};
2023-02-03 14:24:26 +13:00
// EventHook is a handle a registered listener holds.
// When the handle is destroyed, the HookableEvent will automatically remove the listener.
2023-01-30 22:36:25 +13:00
using EventHook = std::unique_ptr<HookBase>;
2023-02-03 14:24:26 +13:00
// A hookable event system.
//
// Define Events in a header as:
//
// using MyLoveyEvent = HookableEvent<"My lovely event", std::string, u32>;
//
// Register listeners anywhere you need them as:
// EventHook myHook = MyLoveyEvent::Register([](std::string foo, u32 bar) {
// fmt::print("I've been triggered with {} and {}", foo, bar)
// }, "NameOfHook");
//
// The hook will be automatically unregistered when the EventHook object goes out of scope.
// Trigger events by calling Trigger as:
//
// MyLoveyEvent::Trigger("Hello world", 42);
//
2023-01-31 17:29:16 +13:00
template <StringLiteral EventName, typename... CallbackArgs>
class HookableEvent
2023-01-30 22:36:25 +13:00
{
public:
using CallbackType = std::function<void(CallbackArgs...)>;
private:
2023-02-03 13:18:37 +13:00
struct HookImpl final : public HookBase
2023-01-30 22:36:25 +13:00
{
~HookImpl() override { HookableEvent::Remove(this); }
2023-02-03 13:18:37 +13:00
HookImpl(CallbackType callback, std::string name)
: m_fn(std::move(callback)), m_name(std::move(name))
{
}
2023-01-30 22:36:25 +13:00
CallbackType m_fn;
std::string m_name;
};
struct Storage
{
std::mutex m_mutex;
std::vector<HookImpl*> m_listeners;
};
// We use the "Construct On First Use" idiom to avoid the static initialization order fiasco.
// https://isocpp.org/wiki/faq/ctors#static-init-order
static Storage& GetStorage()
{
static Storage storage;
return storage;
}
static void Remove(HookImpl* handle)
{
auto& storage = GetStorage();
std::lock_guard lock(storage.m_mutex);
std::erase(storage.m_listeners, handle);
}
2023-01-31 17:29:16 +13:00
public:
2023-01-30 22:36:25 +13:00
// Returns a handle that will unregister the listener when destroyed.
[[nodiscard]] static EventHook Register(CallbackType callback, std::string name)
2023-01-30 22:36:25 +13:00
{
auto& storage = GetStorage();
std::lock_guard lock(storage.m_mutex);
2023-02-03 14:24:26 +13:00
2023-01-30 22:36:25 +13:00
DEBUG_LOG_FMT(COMMON, "Registering {} handler at {} event hook", name, EventName.value);
2023-02-03 13:00:26 +13:00
auto handle = std::make_unique<HookImpl>(callback, std::move(name));
storage.m_listeners.push_back(handle.get());
2023-01-30 22:36:25 +13:00
return handle;
}
2023-02-03 13:00:26 +13:00
static void Trigger(const CallbackArgs&... args)
2023-01-30 22:36:25 +13:00
{
auto& storage = GetStorage();
std::lock_guard lock(storage.m_mutex);
2023-02-03 14:24:26 +13:00
for (const auto& handle : storage.m_listeners)
2023-01-30 22:36:25 +13:00
handle->m_fn(args...);
}
};
2023-02-03 13:18:37 +13:00
} // namespace Common