Common: Add a std::jthread implementation.

This commit is contained in:
Jordan Woyak 2025-04-23 21:23:59 -05:00
parent 879a8889aa
commit 12e29828f8
4 changed files with 177 additions and 0 deletions

View file

@ -134,6 +134,7 @@ add_library(common
SocketContext.h
SpanUtils.h
SPSCQueue.h
StdJThread.h
StringLiteral.h
StringUtil.cpp
StringUtil.h

View file

@ -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 <thread>
#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 <atomic>
#include <memory>
#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<StopState> stop_state) : m_stop_state{std::move(stop_state)}
{
}
std::shared_ptr<StopState> 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<stop_token::StopState>()} {}
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 <typename F, typename... Args>
explicit jthread(F&& f, Args&&... args)
{
if constexpr (std::is_invocable_v<std::decay_t<F>, stop_token, std::decay_t<Args>...>)
{
m_thread = std::thread{std::forward<F>(f), get_stop_token(), std::forward<Args>(args)...};
}
else if constexpr (std::is_invocable_v<std::decay_t<F>, std::decay_t<Args>...>)
{
m_thread = std::thread{std::forward<F>(f), std::forward<Args>(args)...};
}
else
{
static_assert(Common::DependentFalse<F>(),
"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

View file

@ -82,4 +82,15 @@ static_assert(!IsNOf<int, 1, int, int>::value);
static_assert(IsNOf<int, 2, int, int>::value);
static_assert(IsNOf<int, 2, int, short>::value); // Type conversions ARE allowed
static_assert(!IsNOf<int, 2, int, char*>::value);
template <typename... T>
struct DependentFalse : std::false_type
{
};
template <typename... T>
struct DependentTrue : std::true_type
{
};
} // namespace Common

View file

@ -158,6 +158,7 @@
<ClInclude Include="Common\SocketContext.h" />
<ClInclude Include="Common\SpanUtils.h" />
<ClInclude Include="Common\SPSCQueue.h" />
<ClInclude Include="Common\StdJThread.h" />
<ClInclude Include="Common\StringLiteral.h" />
<ClInclude Include="Common\StringUtil.h" />
<ClInclude Include="Common\Swap.h" />