Sunshine master
Self-hosted game stream host for Moonlight.
task_pool.h
Go to the documentation of this file.
1
5#pragma once
6
7// standard includes
8#include <chrono>
9#include <deque>
10#include <functional>
11#include <future>
12#include <mutex>
13#include <optional>
14#include <type_traits>
15#include <utility>
16#include <vector>
17
18// local includes
19#include "move_by_copy.h"
20#include "utility.h"
21
22namespace task_pool_util {
23
27 class _ImplBase {
28 public:
29 // _unique_base_type _this_ptr;
30
31 inline virtual ~_ImplBase() = default;
32
36 virtual void run() = 0;
37 };
38
42 template<class Function>
43 class _Impl: public _ImplBase {
44 Function _func;
45
46 public:
52 _Impl(Function &&f):
53 _func(std::forward<Function>(f)) {
54 }
55
59 void run() override {
60 _func();
61 }
62 };
63
67 class TaskPool {
68 public:
72 typedef std::unique_ptr<_ImplBase> __task;
77
81 typedef std::chrono::steady_clock::time_point __time_point;
82
86 template<class R>
88 public:
90 std::future<R> future;
91
100 future {std::move(future)} {
101 }
102 };
103
104 protected:
105 std::deque<__task> _tasks;
106 std::vector<std::pair<__time_point, __task>> _timer_tasks;
107 std::mutex _task_mutex;
108
109 public:
110 TaskPool() = default;
111
117 TaskPool(TaskPool &&other) noexcept:
118 _tasks {std::move(other._tasks)},
119 _timer_tasks {std::move(other._timer_tasks)} {
120 }
121
128 TaskPool &operator=(TaskPool &&other) noexcept {
129 std::swap(_tasks, other._tasks);
130 std::swap(_timer_tasks, other._timer_tasks);
131
132 return *this;
133 }
134
142 template<class Function, class... Args>
143 auto push(Function &&newTask, Args &&...args) {
144 static_assert(std::is_invocable_v<Function, Args &&...>, "arguments don't match the function");
145
146 using __return = std::invoke_result_t<Function, Args &&...>;
147 using task_t = std::packaged_task<__return()>;
148
149 auto bind = [task = std::forward<Function>(newTask), tuple_args = std::make_tuple(std::forward<Args>(args)...)]() mutable {
150 return std::apply(task, std::move(tuple_args));
151 };
152
153 task_t task(std::move(bind));
154
155 auto future = task.get_future();
156
157 std::lock_guard<std::mutex> lg(_task_mutex);
158 _tasks.emplace_back(toRunnable(std::move(task)));
159
160 return future;
161 }
162
168 void pushDelayed(std::pair<__time_point, __task> &&task) {
169 std::lock_guard lg(_task_mutex);
170
171 auto it = _timer_tasks.cbegin();
172 for (; it < _timer_tasks.cend(); ++it) {
173 if (std::get<0>(*it) < task.first) {
174 break;
175 }
176 }
177
178 _timer_tasks.emplace(it, task.first, std::move(task.second));
179 }
180
188 template<class Function, class X, class Y, class... Args>
189 auto pushDelayed(Function &&newTask, std::chrono::duration<X, Y> duration, Args &&...args) {
190 static_assert(std::is_invocable_v<Function, Args &&...>, "arguments don't match the function");
191
192 using __return = std::invoke_result_t<Function, Args &&...>;
193 using task_t = std::packaged_task<__return()>;
194
195 __time_point time_point;
196 if constexpr (std::is_floating_point_v<X>) {
197 time_point = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::nanoseconds>(duration);
198 } else {
199 time_point = std::chrono::steady_clock::now() + duration;
200 }
201
202 auto bind = [task = std::forward<Function>(newTask), tuple_args = std::make_tuple(std::forward<Args>(args)...)]() mutable {
203 return std::apply(task, std::move(tuple_args));
204 };
205
206 task_t task(std::move(bind));
207
208 auto future = task.get_future();
209 auto runnable = toRunnable(std::move(task));
210
211 task_id_t task_id = &*runnable;
212
213 pushDelayed(std::pair {time_point, std::move(runnable)});
214
215 return timer_task_t<__return> {task_id, future};
216 }
217
222 template<class X, class Y>
223 void delay(task_id_t task_id, std::chrono::duration<X, Y> duration) {
224 std::lock_guard<std::mutex> lg(_task_mutex);
225
226 auto it = _timer_tasks.begin();
227 for (; it < _timer_tasks.cend(); ++it) {
228 const __task &task = std::get<1>(*it);
229
230 if (&*task == task_id) {
231 std::get<0>(*it) = std::chrono::steady_clock::now() + duration;
232
233 break;
234 }
235 }
236
237 if (it == _timer_tasks.cend()) {
238 return;
239 }
240
241 // smaller time goes to the back
242 auto prev = it - 1;
243 while (it > _timer_tasks.cbegin()) {
244 if (std::get<0>(*it) > std::get<0>(*prev)) {
245 std::swap(*it, *prev);
246 }
247
248 --prev;
249 --it;
250 }
251 }
252
259 bool cancel(task_id_t task_id) {
260 std::lock_guard lg(_task_mutex);
261
262 auto it = _timer_tasks.begin();
263 for (; it < _timer_tasks.cend(); ++it) {
264 const __task &task = std::get<1>(*it);
265
266 if (&*task == task_id) {
267 _timer_tasks.erase(it);
268
269 return true;
270 }
271 }
272
273 return false;
274 }
275
282 std::optional<std::pair<__time_point, __task>> pop(task_id_t task_id) {
283 std::lock_guard lg(_task_mutex);
284
285 auto pos = std::find_if(std::begin(_timer_tasks), std::end(_timer_tasks), [&task_id](const auto &t) {
286 return t.second.get() == task_id;
287 });
288
289 if (pos == std::end(_timer_tasks)) {
290 return std::nullopt;
291 }
292
293 return std::move(*pos);
294 }
295
301 std::optional<__task> pop() {
302 std::lock_guard lg(_task_mutex);
303
304 if (!_tasks.empty()) {
305 __task task = std::move(_tasks.front());
306 _tasks.pop_front();
307 return task;
308 }
309
310 if (!_timer_tasks.empty() && std::get<0>(_timer_tasks.back()) <= std::chrono::steady_clock::now()) {
311 __task task = std::move(std::get<1>(_timer_tasks.back()));
312 _timer_tasks.pop_back();
313 return task;
314 }
315
316 return std::nullopt;
317 }
318
324 bool ready() {
325 std::lock_guard<std::mutex> lg(_task_mutex);
326
327 return !_tasks.empty() || (!_timer_tasks.empty() && std::get<0>(_timer_tasks.back()) <= std::chrono::steady_clock::now());
328 }
329
335 std::optional<__time_point> next() {
336 std::lock_guard<std::mutex> lg(_task_mutex);
337
338 if (_timer_tasks.empty()) {
339 return std::nullopt;
340 }
341
342 return std::get<0>(_timer_tasks.back());
343 }
344
345 private:
346 template<class Function>
347 std::unique_ptr<_ImplBase> toRunnable(Function &&f) {
348 return std::make_unique<_Impl<Function>>(std::forward<Function &&>(f));
349 }
350 };
351} // namespace task_pool_util
Queued delayed task identifier paired with its future result.
Definition task_pool.h:87
std::future< R > future
Future that resolves with the timer task result.
Definition task_pool.h:90
task_id_t task_id
Task ID.
Definition task_pool.h:89
timer_task_t(task_id_t task_id, std::future< R > &future)
Track a queued timer task and the future for its result.
Definition task_pool.h:98
Queue of immediate and delayed tasks executed by worker threads.
Definition task_pool.h:67
bool cancel(task_id_t task_id)
Cancel a queued or delayed task before it runs.
Definition task_pool.h:259
std::optional< __task > pop()
Remove and return the next queued item, waiting when requested.
Definition task_pool.h:301
_ImplBase * task_id_t
Stable pointer used to identify a queued task.
Definition task_pool.h:76
TaskPool & operator=(TaskPool &&other) noexcept
Assign state from another instance while preserving ownership semantics.
Definition task_pool.h:128
TaskPool(TaskPool &&other) noexcept
Move queued tasks and timers from another task pool.
Definition task_pool.h:117
std::unique_ptr< _ImplBase > __task
Type-erased task owned by the task pool.
Definition task_pool.h:72
void pushDelayed(std::pair< __time_point, __task > &&task)
Queue a task that becomes runnable after a delay.
Definition task_pool.h:168
std::optional< std::pair< __time_point, __task > > pop(task_id_t task_id)
Remove and return the next queued item, waiting when requested.
Definition task_pool.h:282
std::optional< __time_point > next()
Return the next timer task ready for execution.
Definition task_pool.h:335
std::vector< std::pair< __time_point, __task > > _timer_tasks
Delayed tasks sorted by their due time.
Definition task_pool.h:106
std::deque< __task > _tasks
Immediate tasks waiting for worker execution.
Definition task_pool.h:105
auto push(Function &&newTask, Args &&...args)
Queue work for asynchronous execution.
Definition task_pool.h:143
void delay(task_id_t task_id, std::chrono::duration< X, Y > duration)
Definition task_pool.h:223
auto pushDelayed(Function &&newTask, std::chrono::duration< X, Y > duration, Args &&...args)
Definition task_pool.h:189
std::chrono::steady_clock::time_point __time_point
Steady-clock timestamp used to schedule delayed tasks.
Definition task_pool.h:81
std::mutex _task_mutex
Mutex protecting task queues and worker wakeups.
Definition task_pool.h:107
bool ready()
Check whether the task pool has active workers.
Definition task_pool.h:324
Type-erased task interface stored by the task pool.
Definition task_pool.h:27
virtual void run()=0
Execute the queued task body.
Concrete task wrapper that stores and invokes a callable.
Definition task_pool.h:43
void run() override
Execute the queued task body.
Definition task_pool.h:59
_Impl(Function &&f)
Store a concrete callable behind the task type-erasure interface.
Definition task_pool.h:52
Declarations for the MoveByCopy utility class.
Functions for handling command line arguments.
Definition entry_handler.cpp:37
Declarations for utility functions.