mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-12-16 04:09:39 +00:00
HookableEvent: Allow hooks to be added and removed from within a Trigger callback. This fixes a deadlock in FIFOFifoRecorder.
This commit is contained in:
parent
f38a2bbb0e
commit
11318e0be5
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
@ -49,37 +50,59 @@ public:
|
|||||||
using CallbackType = std::function<void(CallbackArgs...)>;
|
using CallbackType = std::function<void(CallbackArgs...)>;
|
||||||
|
|
||||||
// Returns a handle that will unregister the listener when destroyed.
|
// Returns a handle that will unregister the listener when destroyed.
|
||||||
// Note: Attempting to add/remove hooks of the event within the callback itself will NOT work.
|
|
||||||
[[nodiscard]] EventHook Register(CallbackType callback)
|
[[nodiscard]] EventHook Register(CallbackType callback)
|
||||||
{
|
{
|
||||||
DEBUG_LOG_FMT(COMMON, "Registering event hook handler");
|
DEBUG_LOG_FMT(COMMON, "Registering event hook handler");
|
||||||
auto handle = std::make_unique<HookImpl>(m_storage, std::move(callback));
|
|
||||||
|
|
||||||
std::lock_guard lg(m_storage->listeners_mutex);
|
std::lock_guard lg(m_storage->listeners_mutex);
|
||||||
m_storage->listeners.push_back(handle.get());
|
|
||||||
return handle;
|
auto& new_listener =
|
||||||
|
m_storage->listeners.emplace_back(std::make_unique<Listener>(std::move(callback)));
|
||||||
|
|
||||||
|
return std::make_unique<HookImpl>(m_storage, new_listener.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Invokes all registered callbacks.
|
||||||
|
// Hooks added from within a callback will be invoked.
|
||||||
|
// Hooks removed from within a callback will be skipped,
|
||||||
|
// but destruction of the hook's callback will be delayed until Trigger() completes.
|
||||||
void Trigger(const CallbackArgs&... args)
|
void Trigger(const CallbackArgs&... args)
|
||||||
{
|
{
|
||||||
std::lock_guard lg(m_storage->listeners_mutex);
|
std::lock_guard lg(m_storage->listeners_mutex);
|
||||||
for (auto* const handle : m_storage->listeners)
|
m_storage->is_triggering = true;
|
||||||
std::invoke(handle->callback, args...);
|
|
||||||
|
// Avoiding an actual iterator because the container may be modified.
|
||||||
|
for (std::size_t i = 0; i != m_storage->listeners.size(); ++i)
|
||||||
|
{
|
||||||
|
auto& listener = m_storage->listeners[i];
|
||||||
|
|
||||||
|
if (listener->is_pending_removal)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::invoke(listener->callback, args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_storage->is_triggering = false;
|
||||||
|
std::erase_if(m_storage->listeners, std::mem_fn(&Listener::is_pending_removal));
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct HookImpl;
|
struct Listener
|
||||||
|
{
|
||||||
|
const CallbackType callback;
|
||||||
|
bool is_pending_removal{};
|
||||||
|
};
|
||||||
|
|
||||||
struct Storage
|
struct Storage
|
||||||
{
|
{
|
||||||
std::mutex listeners_mutex;
|
std::recursive_mutex listeners_mutex;
|
||||||
std::vector<HookImpl*> listeners;
|
std::vector<std::unique_ptr<Listener>> listeners;
|
||||||
|
bool is_triggering{};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct HookImpl final : HookBase
|
struct HookImpl final : HookBase
|
||||||
{
|
{
|
||||||
HookImpl(const std::shared_ptr<Storage> storage, CallbackType func)
|
HookImpl(std::weak_ptr<Storage> storage, Listener* listener)
|
||||||
: weak_storage{storage}, callback{std::move(func)}
|
: weak_storage{std::move(storage)}, listener_ptr{listener}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,11 +118,23 @@ private:
|
|||||||
DEBUG_LOG_FMT(COMMON, "Removing event hook handler");
|
DEBUG_LOG_FMT(COMMON, "Removing event hook handler");
|
||||||
|
|
||||||
std::lock_guard lg(storage->listeners_mutex);
|
std::lock_guard lg(storage->listeners_mutex);
|
||||||
std::erase(storage->listeners, this);
|
|
||||||
|
if (storage->is_triggering)
|
||||||
|
{
|
||||||
|
// Just mark our listener for removal.
|
||||||
|
// Trigger() will erase it for us.
|
||||||
|
listener_ptr->is_pending_removal = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Remove our listener.
|
||||||
|
storage->listeners.erase(std::ranges::find_if(
|
||||||
|
storage->listeners, [&](auto& ptr) { return ptr.get() == listener_ptr; }));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::weak_ptr<Storage> weak_storage;
|
const std::weak_ptr<Storage> weak_storage;
|
||||||
const CallbackType callback;
|
Listener* const listener_ptr; // "owned" by the above Storage.
|
||||||
};
|
};
|
||||||
|
|
||||||
// shared_ptr storage allows hooks to forget their connection if they outlive the event itself.
|
// shared_ptr storage allows hooks to forget their connection if they outlive the event itself.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user