diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index 608e2f270a..e778a755c8 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -134,6 +134,7 @@ add_library(common SocketContext.h SpanUtils.h SPSCQueue.h + StdJThread.h StringLiteral.h StringUtil.cpp StringUtil.h diff --git a/Source/Core/Common/StdJThread.h b/Source/Core/Common/StdJThread.h new file mode 100644 index 0000000000..d0c203556b --- /dev/null +++ b/Source/Core/Common/StdJThread.h @@ -0,0 +1,164 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +// This header provides a basic implementation of jthread in the StdCompat namespace +// It pulls the stdlib provided std::jthread into the StdCompat namespace when available. + +// TODO: Eliminate this when we can rely on std::jthread being in the stdlib. +// Clang libc++ provides P0660R10 in version 18 (with -fexperimental-library) or version 20. +// GCC libstdc++ provides it in version 10. + +#include + +#if defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L +namespace StdCompat +{ +using std::jthread; +using std::stop_source; +using std::stop_token; +} // namespace StdCompat +#else +#include +#include + +#include "Common/TypeUtils.h" + +namespace StdCompat +{ +class stop_source; + +class stop_token +{ + friend stop_source; + +public: + stop_token() = default; + + bool stop_requested() const + { + return stop_possible() && m_stop_state->stop_requested_flag.test(); + } + + bool stop_possible() const { return m_stop_state != nullptr; } + + void swap(stop_token& other) { m_stop_state.swap(other.m_stop_state); } + + friend bool operator==(const stop_token&, const stop_token&) = default; + +private: + struct StopState + { + std::atomic_flag stop_requested_flag = false; + }; + + explicit stop_token(std::shared_ptr stop_state) : m_stop_state{std::move(stop_state)} + { + } + + std::shared_ptr m_stop_state; +}; + +struct nostopstate_t +{ + explicit nostopstate_t() = default; +}; +inline constexpr nostopstate_t nostopstate{}; + +class stop_source +{ +public: + stop_source() : m_stop_token{std::make_shared()} {} + explicit stop_source(nostopstate_t) {} + + stop_source(const stop_source&) = default; + stop_source(stop_source&&) = default; + + stop_source& operator=(const stop_source&) = default; + stop_source& operator=(stop_source&&) = default; + + ~stop_source() = default; + + bool request_stop() + { + return m_stop_token.stop_possible() && + !m_stop_token.m_stop_state->stop_requested_flag.test_and_set(); + } + + void swap(stop_source& other) { m_stop_token.swap(other.m_stop_token); } + + stop_token get_token() const { return m_stop_token; } + + bool stop_requested() const { return m_stop_token.stop_requested(); } + bool stop_possible() const { return m_stop_token.stop_possible(); } + + friend bool operator==(const stop_source&, const stop_source&) = default; + +private: + stop_token m_stop_token; +}; + +class jthread +{ +public: + using native_handle_type = std::thread::native_handle_type; + + jthread() : m_stop_source(nostopstate) {} + + template + explicit jthread(F&& f, Args&&... args) + { + if constexpr (std::is_invocable_v, stop_token, std::decay_t...>) + { + m_thread = std::thread{std::forward(f), get_stop_token(), std::forward(args)...}; + } + else if constexpr (std::is_invocable_v, std::decay_t...>) + { + m_thread = std::thread{std::forward(f), std::forward(args)...}; + } + else + { + static_assert(Common::DependentFalse(), + "thread function is not invocable with provided arguments."); + } + } + + jthread(jthread&& other) = default; + jthread& operator=(jthread&& other) = default; + + jthread(const jthread&) = delete; + jthread& operator=(const jthread&) = delete; + + ~jthread() + { + if (joinable()) + { + request_stop(); + join(); + } + } + + bool joinable() const { return m_thread.joinable(); } + auto get_id() const { return m_thread.get_id(); }; + auto native_handle() { return m_thread.native_handle(); } + static auto hardware_concurrency() { return std::thread::hardware_concurrency(); } + + void join() { m_thread.join(); } + void detach() { m_thread.detach(); } + void swap(jthread& other) + { + m_thread.swap(other.m_thread); + m_stop_source.swap(other.m_stop_source); + } + + stop_source get_stop_source() { return m_stop_source; } + stop_token get_stop_token() const { return m_stop_source.get_token(); } + bool request_stop() { return m_stop_source.request_stop(); } + +private: + std::thread m_thread; + stop_source m_stop_source; +}; +} // namespace StdCompat +#endif diff --git a/Source/Core/Common/TypeUtils.h b/Source/Core/Common/TypeUtils.h index 714f5a718d..8079abf4da 100644 --- a/Source/Core/Common/TypeUtils.h +++ b/Source/Core/Common/TypeUtils.h @@ -82,4 +82,15 @@ static_assert(!IsNOf::value); static_assert(IsNOf::value); static_assert(IsNOf::value); // Type conversions ARE allowed static_assert(!IsNOf::value); + +template +struct DependentFalse : std::false_type +{ +}; + +template +struct DependentTrue : std::true_type +{ +}; + } // namespace Common diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 67e3a15f4c..71d495cc98 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -158,6 +158,7 @@ +