From 1b63776f2d8e4e382e1b735ed3ec7061f1698e5b Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 16 Nov 2025 23:40:02 -0600 Subject: [PATCH 1/4] Common/ScopeGuard: Fix move constructor. --- Source/Core/Common/ScopeGuard.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Source/Core/Common/ScopeGuard.h b/Source/Core/Common/ScopeGuard.h index 2e313396a90..9e419f4d870 100644 --- a/Source/Core/Common/ScopeGuard.h +++ b/Source/Core/Common/ScopeGuard.h @@ -13,10 +13,7 @@ class ScopeGuard final public: ScopeGuard(Callable&& finalizer) : m_finalizer(std::forward(finalizer)) {} - ScopeGuard(ScopeGuard&& other) : m_finalizer(std::move(other.m_finalizer)) - { - other.m_finalizer = nullptr; - } + ScopeGuard(ScopeGuard&& other) : m_finalizer(std::move(other.m_finalizer)) { other.Dismiss(); } ~ScopeGuard() { Exit(); } void Dismiss() { m_finalizer.reset(); } From 605cc579a436d6028cc5cbe4a3f720d0ad35e24f Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Mon, 17 Nov 2025 18:35:38 -0600 Subject: [PATCH 2/4] Common/Functional: Add InvokerOf template which can convert function pointers to functor types. --- Source/Core/Common/Functional.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Source/Core/Common/Functional.h b/Source/Core/Common/Functional.h index cdb44ebb0c0..7f96664041b 100644 --- a/Source/Core/Common/Functional.h +++ b/Source/Core/Common/Functional.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include @@ -51,4 +52,17 @@ private: std::unique_ptr m_ptr; }; +// A functor type with an invocable non-type template parameter. +// e.g. Providing a function pointer will create a functor type that invokes said function. +// It allows using function pointers in contexts that expect a type, e.g. as a "deleter". +template +struct InvokerOf +{ + template + constexpr auto operator()(Args... args) const + { + return std::invoke(Invocable, std::forward(args)...); + } +}; + } // namespace Common From d25742fe8bb475b6bdf03e80cc09350e9c3d3b5a Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Mon, 17 Nov 2025 02:51:26 -0600 Subject: [PATCH 3/4] Common: Introduce a OneShotEvent class. Unlike Common::Event, OneShotEvent is safe in situations when being immediately destructed. --- Source/Core/Common/CMakeLists.txt | 1 + Source/Core/Common/OneShotEvent.h | 55 +++++++++++++++++++++++++++++++ Source/Core/DolphinLib.props | 1 + 3 files changed, 57 insertions(+) create mode 100644 Source/Core/Common/OneShotEvent.h diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index e833893a9de..2447d1ed401 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -118,6 +118,7 @@ add_library(common NandPaths.h Network.cpp Network.h + OneShotEvent.h PcapFile.cpp PcapFile.h Profiler.cpp diff --git a/Source/Core/Common/OneShotEvent.h b/Source/Core/Common/OneShotEvent.h new file mode 100644 index 00000000000..312848ed918 --- /dev/null +++ b/Source/Core/Common/OneShotEvent.h @@ -0,0 +1,55 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "Common/Functional.h" + +namespace Common +{ + +// A one-time-use single-producer single-consumer thread synchronization class. +// Safe when `Set` will cause a `Wait`ing thread to immediately destruct the event itself. +class OneShotEvent +{ +public: + // One thread should call Set() exactly once. + void Set() { m_semaphore.release(1); } + + // One thread may call Wait() once or WaitFor() until it returns true. + + void Wait() { m_semaphore.acquire(); } + + template + bool WaitFor(const std::chrono::duration& rel_time) + { + return m_semaphore.try_acquire_for(rel_time); + } + +private: + std::binary_semaphore m_semaphore{0}; +}; + +// Invokes Set() on the given object upon destruction. +template +class ScopedSetter +{ +public: + ScopedSetter() = default; + explicit ScopedSetter(EventType* ptr) : m_ptr{ptr} {} + + // Forgets the object without invoking Set(). + void Dismiss() { m_ptr.release(); } + +private: + // Leveraging unique_ptr conveniently makes this class move-only. + // It does no actual deletion, just calls Set(). + using NonOwningSetOnDeletePtr = std::unique_ptr>; + NonOwningSetOnDeletePtr m_ptr; +}; + +} // namespace Common diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 3cfac5838c4..f4a9c04a926 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -149,6 +149,7 @@ + From 5a6fce31b2fcfefd83509c8898fac6dc327b68bf Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 16 Nov 2025 23:40:48 -0600 Subject: [PATCH 4/4] DolphinQt/QtUtils: Simplify RunOnObject and eliminate Common::Event race. --- Source/Core/DolphinQt/QtUtils/RunOnObject.h | 70 +++++---------------- 1 file changed, 15 insertions(+), 55 deletions(-) diff --git a/Source/Core/DolphinQt/QtUtils/RunOnObject.h b/Source/Core/DolphinQt/QtUtils/RunOnObject.h index 73e6c05beed..dfa6bbbf915 100644 --- a/Source/Core/DolphinQt/QtUtils/RunOnObject.h +++ b/Source/Core/DolphinQt/QtUtils/RunOnObject.h @@ -3,74 +3,34 @@ #pragma once -#include -#include -#include -#include +#include +#include #include -#include -#include -#include "Common/Event.h" -#include "DolphinQt/QtUtils/QueueOnObject.h" +#include -class QObject; +#include "Common/OneShotEvent.h" // QWidget and subclasses are not thread-safe! This helper takes arbitrary code from any thread, // safely runs it on the appropriate GUI thread, waits for it to finish, and returns the result. // -// If the target object is destructed before the code gets to run, the QPointer will be nulled and -// the function will return nullopt. +// If the target object is destructed before the code gets to run the function will return nullopt. -template -auto RunOnObject(QObject* object, F&& functor) +auto RunOnObject(QObject* object, std::invocable<> auto&& functor) { - using OptionalResultT = std::optional>; + Common::OneShotEvent task_complete; + std::optional result; - // If we queue up a functor on the current thread, it won't run until we return to the event loop, - // which means waiting for it to finish will never complete. Instead, run it immediately. - if (object->thread() == QThread::currentThread()) - return OptionalResultT(functor()); + QMetaObject::invokeMethod( + object, [&, guard = Common::ScopedSetter{&task_complete}] { result = functor(); }); - class FnInvokeEvent : public QEvent - { - public: - FnInvokeEvent(F&& functor, QObject* obj, Common::Event& event, OptionalResultT& result) - : QEvent(QEvent::None), m_func(std::move(functor)), m_obj(obj), m_event(event), - m_result(result) - { - } - - ~FnInvokeEvent() - { - if (m_obj) - { - m_result = m_func(); - } - else - { - // is already nullopt - } - m_event.Set(); - } - - private: - F m_func; - QPointer m_obj; - Common::Event& m_event; - OptionalResultT& m_result; - }; - - Common::Event event{}; - OptionalResultT result = std::nullopt; - QCoreApplication::postEvent(object, - new FnInvokeEvent(std::forward(functor), object, event, result)); - event.Wait(); + // Wait for the lambda to go out of scope. The result may or may not have been assigned. + task_complete.Wait(); return result; } -template -auto RunOnObject(Receiver* obj, Type Base::* func) +template Receiver> +auto RunOnObject(Receiver* obj, auto Receiver::* func) { - return RunOnObject(obj, [obj, func] { return (obj->*func)(); }); + return RunOnObject(obj, std::bind(func, obj)); }