diff options
Diffstat (limited to '')
| -rw-r--r-- | src/common/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/common/thread_worker.cpp | 58 | ||||
| -rw-r--r-- | src/common/thread_worker.h | 102 | ||||
| -rw-r--r-- | src/common/unique_function.h | 62 | ||||
| -rw-r--r-- | src/tests/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/tests/common/unique_function.cpp | 108 |
6 files changed, 266 insertions, 67 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index a6fa9a85d..e03fffd8d 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt | |||
| @@ -180,7 +180,6 @@ add_library(common STATIC | |||
| 180 | thread.cpp | 180 | thread.cpp |
| 181 | thread.h | 181 | thread.h |
| 182 | thread_queue_list.h | 182 | thread_queue_list.h |
| 183 | thread_worker.cpp | ||
| 184 | thread_worker.h | 183 | thread_worker.h |
| 185 | threadsafe_queue.h | 184 | threadsafe_queue.h |
| 186 | time_zone.cpp | 185 | time_zone.cpp |
| @@ -188,6 +187,7 @@ add_library(common STATIC | |||
| 188 | tiny_mt.h | 187 | tiny_mt.h |
| 189 | tree.h | 188 | tree.h |
| 190 | uint128.h | 189 | uint128.h |
| 190 | unique_function.h | ||
| 191 | uuid.cpp | 191 | uuid.cpp |
| 192 | uuid.h | 192 | uuid.h |
| 193 | vector_math.h | 193 | vector_math.h |
diff --git a/src/common/thread_worker.cpp b/src/common/thread_worker.cpp deleted file mode 100644 index 8f9bf447a..000000000 --- a/src/common/thread_worker.cpp +++ /dev/null | |||
| @@ -1,58 +0,0 @@ | |||
| 1 | // Copyright 2020 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include "common/thread.h" | ||
| 6 | #include "common/thread_worker.h" | ||
| 7 | |||
| 8 | namespace Common { | ||
| 9 | |||
| 10 | ThreadWorker::ThreadWorker(std::size_t num_workers, const std::string& name) { | ||
| 11 | for (std::size_t i = 0; i < num_workers; ++i) | ||
| 12 | threads.emplace_back([this, thread_name{std::string{name}}] { | ||
| 13 | Common::SetCurrentThreadName(thread_name.c_str()); | ||
| 14 | |||
| 15 | // Wait for first request | ||
| 16 | { | ||
| 17 | std::unique_lock lock{queue_mutex}; | ||
| 18 | condition.wait(lock, [this] { return stop || !requests.empty(); }); | ||
| 19 | } | ||
| 20 | |||
| 21 | while (true) { | ||
| 22 | std::function<void()> task; | ||
| 23 | |||
| 24 | { | ||
| 25 | std::unique_lock lock{queue_mutex}; | ||
| 26 | condition.wait(lock, [this] { return stop || !requests.empty(); }); | ||
| 27 | if (stop || requests.empty()) { | ||
| 28 | return; | ||
| 29 | } | ||
| 30 | task = std::move(requests.front()); | ||
| 31 | requests.pop(); | ||
| 32 | } | ||
| 33 | |||
| 34 | task(); | ||
| 35 | } | ||
| 36 | }); | ||
| 37 | } | ||
| 38 | |||
| 39 | ThreadWorker::~ThreadWorker() { | ||
| 40 | { | ||
| 41 | std::unique_lock lock{queue_mutex}; | ||
| 42 | stop = true; | ||
| 43 | } | ||
| 44 | condition.notify_all(); | ||
| 45 | for (std::thread& thread : threads) { | ||
| 46 | thread.join(); | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | void ThreadWorker::QueueWork(std::function<void()>&& work) { | ||
| 51 | { | ||
| 52 | std::unique_lock lock{queue_mutex}; | ||
| 53 | requests.emplace(work); | ||
| 54 | } | ||
| 55 | condition.notify_one(); | ||
| 56 | } | ||
| 57 | |||
| 58 | } // namespace Common | ||
diff --git a/src/common/thread_worker.h b/src/common/thread_worker.h index f1859971f..8272985ff 100644 --- a/src/common/thread_worker.h +++ b/src/common/thread_worker.h | |||
| @@ -7,24 +7,110 @@ | |||
| 7 | #include <atomic> | 7 | #include <atomic> |
| 8 | #include <functional> | 8 | #include <functional> |
| 9 | #include <mutex> | 9 | #include <mutex> |
| 10 | #include <stop_token> | ||
| 10 | #include <string> | 11 | #include <string> |
| 12 | #include <thread> | ||
| 13 | #include <type_traits> | ||
| 11 | #include <vector> | 14 | #include <vector> |
| 12 | #include <queue> | 15 | #include <queue> |
| 13 | 16 | ||
| 17 | #include "common/thread.h" | ||
| 18 | #include "common/unique_function.h" | ||
| 19 | |||
| 14 | namespace Common { | 20 | namespace Common { |
| 15 | 21 | ||
| 16 | class ThreadWorker final { | 22 | template <class StateType = void> |
| 23 | class StatefulThreadWorker { | ||
| 24 | static constexpr bool with_state = !std::is_same_v<StateType, void>; | ||
| 25 | |||
| 26 | struct DummyCallable { | ||
| 27 | int operator()() const noexcept { | ||
| 28 | return 0; | ||
| 29 | } | ||
| 30 | }; | ||
| 31 | |||
| 32 | using Task = | ||
| 33 | std::conditional_t<with_state, UniqueFunction<void, StateType*>, UniqueFunction<void>>; | ||
| 34 | using StateMaker = std::conditional_t<with_state, std::function<StateType()>, DummyCallable>; | ||
| 35 | |||
| 17 | public: | 36 | public: |
| 18 | explicit ThreadWorker(std::size_t num_workers, const std::string& name); | 37 | explicit StatefulThreadWorker(size_t num_workers, std::string name, StateMaker func = {}) |
| 19 | ~ThreadWorker(); | 38 | : workers_queued{num_workers}, thread_name{std::move(name)} { |
| 20 | void QueueWork(std::function<void()>&& work); | 39 | const auto lambda = [this, func](std::stop_token stop_token) { |
| 40 | Common::SetCurrentThreadName(thread_name.c_str()); | ||
| 41 | { | ||
| 42 | std::conditional_t<with_state, StateType, int> state{func()}; | ||
| 43 | while (!stop_token.stop_requested()) { | ||
| 44 | Task task; | ||
| 45 | { | ||
| 46 | std::unique_lock lock{queue_mutex}; | ||
| 47 | if (requests.empty()) { | ||
| 48 | wait_condition.notify_all(); | ||
| 49 | } | ||
| 50 | condition.wait(lock, stop_token, [this] { return !requests.empty(); }); | ||
| 51 | if (stop_token.stop_requested()) { | ||
| 52 | break; | ||
| 53 | } | ||
| 54 | task = std::move(requests.front()); | ||
| 55 | requests.pop(); | ||
| 56 | } | ||
| 57 | if constexpr (with_state) { | ||
| 58 | task(&state); | ||
| 59 | } else { | ||
| 60 | task(); | ||
| 61 | } | ||
| 62 | ++work_done; | ||
| 63 | } | ||
| 64 | } | ||
| 65 | ++workers_stopped; | ||
| 66 | wait_condition.notify_all(); | ||
| 67 | }; | ||
| 68 | threads.reserve(num_workers); | ||
| 69 | for (size_t i = 0; i < num_workers; ++i) { | ||
| 70 | threads.emplace_back(lambda); | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | StatefulThreadWorker& operator=(const StatefulThreadWorker&) = delete; | ||
| 75 | StatefulThreadWorker(const StatefulThreadWorker&) = delete; | ||
| 76 | |||
| 77 | StatefulThreadWorker& operator=(StatefulThreadWorker&&) = delete; | ||
| 78 | StatefulThreadWorker(StatefulThreadWorker&&) = delete; | ||
| 79 | |||
| 80 | void QueueWork(Task work) { | ||
| 81 | { | ||
| 82 | std::unique_lock lock{queue_mutex}; | ||
| 83 | requests.emplace(std::move(work)); | ||
| 84 | ++work_scheduled; | ||
| 85 | } | ||
| 86 | condition.notify_one(); | ||
| 87 | } | ||
| 88 | |||
| 89 | void WaitForRequests(std::stop_token stop_token = {}) { | ||
| 90 | std::stop_callback callback(stop_token, [this] { | ||
| 91 | for (auto& thread : threads) { | ||
| 92 | thread.request_stop(); | ||
| 93 | } | ||
| 94 | }); | ||
| 95 | std::unique_lock lock{queue_mutex}; | ||
| 96 | wait_condition.wait(lock, [this] { | ||
| 97 | return workers_stopped >= workers_queued || work_done >= work_scheduled; | ||
| 98 | }); | ||
| 99 | } | ||
| 21 | 100 | ||
| 22 | private: | 101 | private: |
| 23 | std::vector<std::thread> threads; | 102 | std::queue<Task> requests; |
| 24 | std::queue<std::function<void()>> requests; | ||
| 25 | std::mutex queue_mutex; | 103 | std::mutex queue_mutex; |
| 26 | std::condition_variable condition; | 104 | std::condition_variable_any condition; |
| 27 | std::atomic_bool stop{}; | 105 | std::condition_variable wait_condition; |
| 106 | std::atomic<size_t> work_scheduled{}; | ||
| 107 | std::atomic<size_t> work_done{}; | ||
| 108 | std::atomic<size_t> workers_stopped{}; | ||
| 109 | std::atomic<size_t> workers_queued{}; | ||
| 110 | std::string thread_name; | ||
| 111 | std::vector<std::jthread> threads; | ||
| 28 | }; | 112 | }; |
| 29 | 113 | ||
| 114 | using ThreadWorker = StatefulThreadWorker<>; | ||
| 115 | |||
| 30 | } // namespace Common | 116 | } // namespace Common |
diff --git a/src/common/unique_function.h b/src/common/unique_function.h new file mode 100644 index 000000000..ca0559071 --- /dev/null +++ b/src/common/unique_function.h | |||
| @@ -0,0 +1,62 @@ | |||
| 1 | // Copyright 2021 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <memory> | ||
| 8 | #include <utility> | ||
| 9 | |||
| 10 | namespace Common { | ||
| 11 | |||
| 12 | /// General purpose function wrapper similar to std::function. | ||
| 13 | /// Unlike std::function, the captured values don't have to be copyable. | ||
| 14 | /// This class can be moved but not copied. | ||
| 15 | template <typename ResultType, typename... Args> | ||
| 16 | class UniqueFunction { | ||
| 17 | class CallableBase { | ||
| 18 | public: | ||
| 19 | virtual ~CallableBase() = default; | ||
| 20 | virtual ResultType operator()(Args&&...) = 0; | ||
| 21 | }; | ||
| 22 | |||
| 23 | template <typename Functor> | ||
| 24 | class Callable final : public CallableBase { | ||
| 25 | public: | ||
| 26 | Callable(Functor&& functor_) : functor{std::move(functor_)} {} | ||
| 27 | ~Callable() override = default; | ||
| 28 | |||
| 29 | ResultType operator()(Args&&... args) override { | ||
| 30 | return functor(std::forward<Args>(args)...); | ||
| 31 | } | ||
| 32 | |||
| 33 | private: | ||
| 34 | Functor functor; | ||
| 35 | }; | ||
| 36 | |||
| 37 | public: | ||
| 38 | UniqueFunction() = default; | ||
| 39 | |||
| 40 | template <typename Functor> | ||
| 41 | UniqueFunction(Functor&& functor) | ||
| 42 | : callable{std::make_unique<Callable<Functor>>(std::move(functor))} {} | ||
| 43 | |||
| 44 | UniqueFunction& operator=(UniqueFunction&& rhs) noexcept = default; | ||
| 45 | UniqueFunction(UniqueFunction&& rhs) noexcept = default; | ||
| 46 | |||
| 47 | UniqueFunction& operator=(const UniqueFunction&) = delete; | ||
| 48 | UniqueFunction(const UniqueFunction&) = delete; | ||
| 49 | |||
| 50 | ResultType operator()(Args&&... args) const { | ||
| 51 | return (*callable)(std::forward<Args>(args)...); | ||
| 52 | } | ||
| 53 | |||
| 54 | explicit operator bool() const noexcept { | ||
| 55 | return static_cast<bool>(callable); | ||
| 56 | } | ||
| 57 | |||
| 58 | private: | ||
| 59 | std::unique_ptr<CallableBase> callable; | ||
| 60 | }; | ||
| 61 | |||
| 62 | } // namespace Common | ||
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 96bc30cac..c4c012f3d 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt | |||
| @@ -5,6 +5,7 @@ add_executable(tests | |||
| 5 | common/host_memory.cpp | 5 | common/host_memory.cpp |
| 6 | common/param_package.cpp | 6 | common/param_package.cpp |
| 7 | common/ring_buffer.cpp | 7 | common/ring_buffer.cpp |
| 8 | common/unique_function.cpp | ||
| 8 | core/core_timing.cpp | 9 | core/core_timing.cpp |
| 9 | core/network/network.cpp | 10 | core/network/network.cpp |
| 10 | tests.cpp | 11 | tests.cpp |
diff --git a/src/tests/common/unique_function.cpp b/src/tests/common/unique_function.cpp new file mode 100644 index 000000000..ac9912738 --- /dev/null +++ b/src/tests/common/unique_function.cpp | |||
| @@ -0,0 +1,108 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <string> | ||
| 6 | |||
| 7 | #include <catch2/catch.hpp> | ||
| 8 | |||
| 9 | #include "common/unique_function.h" | ||
| 10 | |||
| 11 | namespace { | ||
| 12 | struct Noisy { | ||
| 13 | Noisy() : state{"Default constructed"} {} | ||
| 14 | Noisy(Noisy&& rhs) noexcept : state{"Move constructed"} { | ||
| 15 | rhs.state = "Moved away"; | ||
| 16 | } | ||
| 17 | Noisy& operator=(Noisy&& rhs) noexcept { | ||
| 18 | state = "Move assigned"; | ||
| 19 | rhs.state = "Moved away"; | ||
| 20 | } | ||
| 21 | Noisy(const Noisy&) : state{"Copied constructed"} {} | ||
| 22 | Noisy& operator=(const Noisy&) { | ||
| 23 | state = "Copied assigned"; | ||
| 24 | } | ||
| 25 | |||
| 26 | std::string state; | ||
| 27 | }; | ||
| 28 | } // Anonymous namespace | ||
| 29 | |||
| 30 | TEST_CASE("UniqueFunction", "[common]") { | ||
| 31 | SECTION("Capture reference") { | ||
| 32 | int value = 0; | ||
| 33 | Common::UniqueFunction<void> func = [&value] { value = 5; }; | ||
| 34 | func(); | ||
| 35 | REQUIRE(value == 5); | ||
| 36 | } | ||
| 37 | SECTION("Capture pointer") { | ||
| 38 | int value = 0; | ||
| 39 | int* pointer = &value; | ||
| 40 | Common::UniqueFunction<void> func = [pointer] { *pointer = 5; }; | ||
| 41 | func(); | ||
| 42 | REQUIRE(value == 5); | ||
| 43 | } | ||
| 44 | SECTION("Move object") { | ||
| 45 | Noisy noisy; | ||
| 46 | REQUIRE(noisy.state == "Default constructed"); | ||
| 47 | |||
| 48 | Common::UniqueFunction<void> func = [noisy = std::move(noisy)] { | ||
| 49 | REQUIRE(noisy.state == "Move constructed"); | ||
| 50 | }; | ||
| 51 | REQUIRE(noisy.state == "Moved away"); | ||
| 52 | func(); | ||
| 53 | } | ||
| 54 | SECTION("Move construct function") { | ||
| 55 | int value = 0; | ||
| 56 | Common::UniqueFunction<void> func = [&value] { value = 5; }; | ||
| 57 | Common::UniqueFunction<void> new_func = std::move(func); | ||
| 58 | new_func(); | ||
| 59 | REQUIRE(value == 5); | ||
| 60 | } | ||
| 61 | SECTION("Move assign function") { | ||
| 62 | int value = 0; | ||
| 63 | Common::UniqueFunction<void> func = [&value] { value = 5; }; | ||
| 64 | Common::UniqueFunction<void> new_func; | ||
| 65 | new_func = std::move(func); | ||
| 66 | new_func(); | ||
| 67 | REQUIRE(value == 5); | ||
| 68 | } | ||
| 69 | SECTION("Default construct then assign function") { | ||
| 70 | int value = 0; | ||
| 71 | Common::UniqueFunction<void> func; | ||
| 72 | func = [&value] { value = 5; }; | ||
| 73 | func(); | ||
| 74 | REQUIRE(value == 5); | ||
| 75 | } | ||
| 76 | SECTION("Pass arguments") { | ||
| 77 | int result = 0; | ||
| 78 | Common::UniqueFunction<void, int, int> func = [&result](int a, int b) { result = a + b; }; | ||
| 79 | func(5, 4); | ||
| 80 | REQUIRE(result == 9); | ||
| 81 | } | ||
| 82 | SECTION("Pass arguments and return value") { | ||
| 83 | Common::UniqueFunction<int, int, int> func = [](int a, int b) { return a + b; }; | ||
| 84 | REQUIRE(func(5, 4) == 9); | ||
| 85 | } | ||
| 86 | SECTION("Destructor") { | ||
| 87 | int num_destroyed = 0; | ||
| 88 | struct Foo { | ||
| 89 | Foo(int* num_) : num{num_} {} | ||
| 90 | Foo(Foo&& rhs) : num{std::exchange(rhs.num, nullptr)} {} | ||
| 91 | Foo(const Foo&) = delete; | ||
| 92 | |||
| 93 | ~Foo() { | ||
| 94 | if (num) { | ||
| 95 | ++*num; | ||
| 96 | } | ||
| 97 | } | ||
| 98 | |||
| 99 | int* num = nullptr; | ||
| 100 | }; | ||
| 101 | Foo object{&num_destroyed}; | ||
| 102 | { | ||
| 103 | Common::UniqueFunction<void> func = [object = std::move(object)] {}; | ||
| 104 | REQUIRE(num_destroyed == 0); | ||
| 105 | } | ||
| 106 | REQUIRE(num_destroyed == 1); | ||
| 107 | } | ||
| 108 | } | ||