Pyrogenesis trunk
Future.h
Go to the documentation of this file.
1/* Copyright (C) 2024 Wildfire Games.
2 * This file is part of 0 A.D.
3 *
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#ifndef INCLUDED_FUTURE
19#define INCLUDED_FUTURE
20
21#include "ps/FutureForward.h"
22
23#include <atomic>
24#include <condition_variable>
25#include <functional>
26#include <mutex>
27#include <optional>
28#include <type_traits>
29
30template<typename Callback>
31class PackagedTask;
32
34{
35enum class Status
36{
37 PENDING,
38 STARTED,
39 DONE,
41};
42
43template<typename T>
44using ResultHolder = std::conditional_t<std::is_void_v<T>, std::nullopt_t, std::optional<T>>;
45
46/**
47 * Responsible for syncronization between the task and the receiving thread.
48 */
49template<typename ResultType>
50class Receiver : public ResultHolder<ResultType>
51{
52 static constexpr bool VoidResult = std::is_same_v<ResultType, void>;
53public:
55 ResultHolder<ResultType>{std::nullopt}
56 {}
58 {
60 }
61
62 Receiver(const Receiver&) = delete;
63 Receiver(Receiver&&) = delete;
64
65 bool IsDoneOrCanceled() const
66 {
68 }
69
70 void Wait()
71 {
72 // Fast path: we're already done.
73 if (IsDoneOrCanceled())
74 return;
75 // Slow path: we aren't done when we run the above check. Lock and wait until we are.
76 std::unique_lock<std::mutex> lock(m_Mutex);
77 m_ConditionVariable.wait(lock, [this]() -> bool { return IsDoneOrCanceled(); });
78 }
79
80 /**
81 * If the task is pending, cancel it: the status becomes CANCELED and if the task was completed, the result is destroyed.
82 * @return true if the task was indeed cancelled, false otherwise (the task is running or already done).
83 */
84 bool Cancel()
85 {
86 Status expected = Status::PENDING;
87 bool cancelled = m_Status.compare_exchange_strong(expected, Status::CANCELED);
88 // If we're done, invalidate, if we're pending, atomically cancel, otherwise fail.
89 if (cancelled || m_Status == Status::DONE)
90 {
93 if constexpr (!VoidResult)
94 this->reset();
95 m_ConditionVariable.notify_all();
96 return cancelled;
97 }
98 return false;
99 }
100
101 /**
102 * Move the result away from the shared state, mark the future invalid.
103 */
104 template<typename _ResultType = ResultType>
105 std::enable_if_t<!std::is_same_v<_ResultType, void>, ResultType> GetResult()
106 {
107 // The caller must ensure that this is only called if we have a result.
108 ENSURE(this->has_value());
110 ResultType ret = std::move(**this);
111 this->reset();
112 return ret;
113 }
114
115 std::atomic<Status> m_Status = Status::PENDING;
116 std::mutex m_Mutex;
117 std::condition_variable m_ConditionVariable;
118};
119
120/**
121 * The shared state between futures and packaged state.
122 */
123template<typename Callback>
125{
126 SharedState(Callback&& callbackFunc) :
127 callback{std::forward<Callback>(callbackFunc)}
128 {}
129
130 Callback callback;
132};
133
134} // namespace FutureSharedStateDetail
135
136/**
137 * Corresponds to std::future.
138 * Unlike std::future, Future can request the cancellation of the task that would produce the result.
139 * This makes it more similar to Java's CancellableTask or C#'s Task.
140 * The name Future was kept over Task so it would be more familiar to C++ users,
141 * but this all should be revised once Concurrency TS wraps up.
142 *
143 * Future is _not_ thread-safe. Call it from a single thread or ensure synchronization externally.
144 *
145 * The callback never runs after the @p Future is destroyed.
146 * TODO:
147 * - Handle exceptions.
148 */
149template<typename ResultType>
151{
152 template<typename T>
153 friend class PackagedTask;
154
155 static constexpr bool VoidResult = std::is_same_v<ResultType, void>;
156
158public:
159 Future() = default;
160 Future(const Future& o) = delete;
161 Future(Future&&) = default;
163 {
164 CancelOrWait();
165 m_Receiver = std::move(other.m_Receiver);
166 return *this;
167 }
169 {
170 CancelOrWait();
171 }
172
173 /**
174 * Make the future wait for the result of @a callback.
175 */
176 template<typename Callback>
177 PackagedTask<Callback> Wrap(Callback&& callback);
178
179 /**
180 * Move the result out of the future, and invalidate the future.
181 * If the future is not complete, calls Wait().
182 * If the future is canceled, asserts.
183 */
184 template<typename SfinaeType = ResultType>
185 std::enable_if_t<!std::is_same_v<SfinaeType, void>, ResultType> Get()
186 {
188
189 Wait();
190 if constexpr (VoidResult)
191 return;
192 else
193 {
194 ENSURE(m_Receiver->m_Status != Status::CANCELED);
195
196 // This mark the state invalid - can't call Get again.
197 return m_Receiver->GetResult();
198 }
199 }
200
201 /**
202 * @return true if the shared state is valid and has a result (i.e. Get can be called).
203 */
204 bool IsReady() const
205 {
206 return !!m_Receiver && m_Receiver->m_Status == Status::DONE;
207 }
208
209 /**
210 * @return true if the future has a shared state and it's not been invalidated, ie. pending, started or done.
211 */
212 bool Valid() const
213 {
214 return !!m_Receiver && m_Receiver->m_Status != Status::CANCELED;
215 }
216
217 void Wait()
218 {
219 if (Valid())
220 m_Receiver->Wait();
221 }
222
223 /**
224 * Cancels the task, waiting if the task is currently started.
225 * Use this function over Cancel() if you need to ensure determinism (i.e. in the simulation).
226 * @see Cancel.
227 */
229 {
230 if (!Valid())
231 return;
232 if (!m_Receiver->Cancel())
233 m_Receiver->Wait();
234 m_Receiver.reset();
235 }
236
237protected:
238 std::shared_ptr<FutureSharedStateDetail::Receiver<ResultType>> m_Receiver;
239};
240
241/**
242 * Corresponds somewhat to std::packaged_task.
243 * Like packaged_task, this holds a function acting as a promise.
244 * This type is mostly just the shared state and the call operator,
245 * handling the promise & continuation logic.
246 */
247template<typename Callback>
249{
250public:
251 PackagedTask() = delete;
253 m_SharedState(std::move(ss))
254 {}
255
257 {
259 if (!m_SharedState->receiver.m_Status.compare_exchange_strong(expected,
261 {
262 return;
263 }
264
265 if constexpr (std::is_void_v<std::invoke_result_t<Callback>>)
266 m_SharedState->callback();
267 else
268 m_SharedState->receiver.emplace(m_SharedState->callback());
269
270 // Because we might have threads waiting on us, we need to make sure that they either:
271 // - don't wait on our condition variable
272 // - receive the notification when we're done.
273 // This requires locking the mutex (@see Wait).
274 {
275 std::lock_guard<std::mutex> lock(m_SharedState->receiver.m_Mutex);
277 }
278
279 m_SharedState->receiver.m_ConditionVariable.notify_all();
280
281 // We no longer need the shared state, drop it immediately.
282 m_SharedState.reset();
283 }
284
285 void Cancel()
286 {
287 m_SharedState->Cancel();
288 m_SharedState.reset();
289 }
290
291private:
292 std::shared_ptr<FutureSharedStateDetail::SharedState<Callback>> m_SharedState;
293};
294
295template<typename ResultType>
296template<typename Callback>
298{
299 static_assert(std::is_same_v<std::invoke_result_t<Callback>, ResultType>,
300 "The return type of the wrapped function is not the same as the type the Future expects.");
301 CancelOrWait();
302 auto temp = std::make_shared<FutureSharedStateDetail::SharedState<Callback>>(std::move(callback));
303 m_Receiver = {temp, &temp->receiver};
304 return PackagedTask<Callback>(std::move(temp));
305}
306
307#endif // INCLUDED_FUTURE
Responsible for syncronization between the task and the receiving thread.
Definition: Future.h:51
std::condition_variable m_ConditionVariable
Definition: Future.h:117
Receiver()
Definition: Future.h:54
static constexpr bool VoidResult
Definition: Future.h:52
~Receiver()
Definition: Future.h:57
bool Cancel()
If the task is pending, cancel it: the status becomes CANCELED and if the task was completed,...
Definition: Future.h:84
std::mutex m_Mutex
Definition: Future.h:116
std::enable_if_t<!std::is_same_v< _ResultType, void >, ResultType > GetResult()
Move the result away from the shared state, mark the future invalid.
Definition: Future.h:105
Receiver(const Receiver &)=delete
std::atomic< Status > m_Status
Definition: Future.h:115
bool IsDoneOrCanceled() const
Definition: Future.h:65
void Wait()
Definition: Future.h:70
Corresponds to std::future.
Definition: Future.h:151
static constexpr bool VoidResult
Definition: Future.h:155
bool Valid() const
Definition: Future.h:212
~Future()
Definition: Future.h:168
PackagedTask< Callback > Wrap(Callback &&callback)
Make the future wait for the result of callback.
Definition: Future.h:297
Future(Future &&)=default
Future & operator=(Future &&other)
Definition: Future.h:162
void Wait()
Definition: Future.h:217
std::enable_if_t<!std::is_same_v< SfinaeType, void >, ResultType > Get()
Move the result out of the future, and invalidate the future.
Definition: Future.h:185
bool IsReady() const
Definition: Future.h:204
Future()=default
void CancelOrWait()
Cancels the task, waiting if the task is currently started.
Definition: Future.h:228
std::shared_ptr< FutureSharedStateDetail::Receiver< ResultType > > m_Receiver
Definition: Future.h:238
Future(const Future &o)=delete
Corresponds somewhat to std::packaged_task.
Definition: Future.h:249
PackagedTask()=delete
std::shared_ptr< FutureSharedStateDetail::SharedState< Callback > > m_SharedState
Definition: Future.h:292
void operator()()
Definition: Future.h:256
PackagedTask(std::shared_ptr< FutureSharedStateDetail::SharedState< Callback > > ss)
Definition: Future.h:252
void Cancel()
Definition: Future.h:285
#define ENSURE(expr)
ensure the expression <expr> evaluates to non-zero.
Definition: debug.h:290
Definition: Future.h:34
std::conditional_t< std::is_void_v< T >, std::nullopt_t, std::optional< T > > ResultHolder
Definition: Future.h:44
Status
Definition: Future.h:36
Definition: ShaderDefines.cpp:31
The shared state between futures and packaged state.
Definition: Future.h:125
Receiver< std::invoke_result_t< Callback > > receiver
Definition: Future.h:131
Callback callback
Definition: Future.h:130
SharedState(Callback &&callbackFunc)
Definition: Future.h:126