mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2024-12-28 17:37:53 +01:00
9e0755a598
This fixes a crash when recording fifologs, as the mutex is acquired when BPWritten calls AfterFrameEvent::Trigger, but then acquired again when FifoRecorder::EndFrame calls m_end_of_frame_event.reset(). std::mutex does not allow calling lock() if the thread already owns the mutex, while std::recursive_mutex does allow this. This is a regression from #11522 (which introduced the HookableEvent system).
116 lines
3.0 KiB
C++
116 lines
3.0 KiB
C++
// 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>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
|
|
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<HookBase>;
|
|
|
|
// 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 <StringLiteral EventName, typename... CallbackArgs>
|
|
class HookableEvent
|
|
{
|
|
public:
|
|
using CallbackType = std::function<void(CallbackArgs...)>;
|
|
|
|
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<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);
|
|
}
|
|
|
|
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<HookImpl>(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
|