// 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