// 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 #include #include #include #include #include namespace Common { struct HookBase { virtual ~HookBase() = default; 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; }; // EventHook is a handle a registered listener holds. // When the handle is destroyed, the HookableEvent will automatically remove the listener. using EventHook = std::unique_ptr; // 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); // template class HookableEvent { public: using CallbackType = std::function; private: struct HookImpl final : public HookBase { ~HookImpl() override { HookableEvent::Remove(this); } HookImpl(CallbackType callback, std::string name) : m_fn(std::move(callback)), m_name(std::move(name)) { } CallbackType m_fn; std::string m_name; }; struct Storage { std::recursive_mutex m_mutex; std::vector 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); } public: // Returns a handle that will unregister the listener when destroyed. [[nodiscard]] static EventHook Register(CallbackType callback, std::string name) { auto& storage = GetStorage(); std::lock_guard lock(storage.m_mutex); DEBUG_LOG_FMT(COMMON, "Registering {} handler at {} event hook", name, EventName.value); auto handle = std::make_unique(std::move(callback), std::move(name)); storage.m_listeners.push_back(handle.get()); return handle; } static void Trigger(const CallbackArgs&... args) { auto& storage = GetStorage(); std::lock_guard lock(storage.m_mutex); for (const auto& handle : storage.m_listeners) handle->m_fn(args...); } }; } // namespace Common