Line data Source code
1 : /* Copyright (C) 2022 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 :
30 : template<typename ResultType>
31 : class PackagedTask;
32 :
33 : namespace FutureSharedStateDetail
34 : {
35 : enum class Status
36 : {
37 : PENDING,
38 : STARTED,
39 : DONE,
40 : CANCELED
41 : };
42 :
43 : template<typename T>
44 : using ResultHolder = std::conditional_t<std::is_void_v<T>, std::nullopt_t, std::optional<T>>;
45 :
46 : /**
47 : * The shared state between futures and packaged state.
48 : * Holds all relevant data.
49 : */
50 : template<typename ResultType>
51 : class SharedState : public ResultHolder<ResultType>
52 : {
53 : static constexpr bool VoidResult = std::is_same_v<ResultType, void>;
54 : public:
55 100016 : SharedState(std::function<ResultType()>&& func) :
56 : ResultHolder<ResultType>{std::nullopt},
57 100016 : m_Func(std::move(func))
58 100016 : {}
59 100016 : ~SharedState()
60 : {
61 : // For safety, wait on started task completion, but not on pending ones (auto-cancelled).
62 100016 : if (!Cancel())
63 : {
64 100014 : Wait();
65 100014 : Cancel();
66 : }
67 100016 : }
68 :
69 : SharedState(const SharedState&) = delete;
70 : SharedState(SharedState&&) = delete;
71 :
72 203031 : bool IsDoneOrCanceled() const
73 : {
74 203031 : return m_Status == Status::DONE || m_Status == Status::CANCELED;
75 : }
76 :
77 200027 : void Wait()
78 : {
79 : // Fast path: we're already done.
80 200027 : if (IsDoneOrCanceled())
81 198408 : return;
82 : // Slow path: we aren't done when we run the above check. Lock and wait until we are.
83 3238 : std::unique_lock<std::mutex> lock(m_Mutex);
84 4623 : m_ConditionVariable.wait(lock, [this]() -> bool { return IsDoneOrCanceled(); });
85 : }
86 :
87 : /**
88 : * If the task is pending, cancel it: the status becomes CANCELED and if the task was completed, the result is destroyed.
89 : * @return true if the task was indeed cancelled, false otherwise (the task is running or already done).
90 : */
91 200030 : bool Cancel()
92 : {
93 200030 : Status expected = Status::PENDING;
94 200030 : bool cancelled = m_Status.compare_exchange_strong(expected, Status::CANCELED);
95 : // If we're done, invalidate, if we're pending, atomically cancel, otherwise fail.
96 200030 : if (cancelled || m_Status == Status::DONE)
97 : {
98 12 : if (m_Status == Status::DONE)
99 10 : m_Status = Status::CANCELED;
100 : if constexpr (!VoidResult)
101 1 : this->reset();
102 12 : m_ConditionVariable.notify_all();
103 12 : return cancelled;
104 : }
105 200018 : return false;
106 : }
107 :
108 : /**
109 : * Move the result away from the shared state, mark the future invalid.
110 : */
111 : template<typename _ResultType = ResultType>
112 100004 : std::enable_if_t<!std::is_same_v<_ResultType, void>, ResultType> GetResult()
113 : {
114 : // The caller must ensure that this is only called if we have a result.
115 100004 : ENSURE(this->has_value());
116 100004 : m_Status = Status::CANCELED;
117 100004 : ResultType ret = std::move(**this);
118 100004 : this->reset();
119 100004 : return ret;
120 : }
121 :
122 : std::atomic<Status> m_Status = Status::PENDING;
123 : std::mutex m_Mutex;
124 : std::condition_variable m_ConditionVariable;
125 :
126 : std::function<ResultType()> m_Func;
127 : };
128 :
129 : } // namespace FutureSharedStateDetail
130 :
131 : /**
132 : * Corresponds to std::future.
133 : * Unlike std::future, Future can request the cancellation of the task that would produce the result.
134 : * This makes it more similar to Java's CancellableTask or C#'s Task.
135 : * The name Future was kept over Task so it would be more familiar to C++ users,
136 : * but this all should be revised once Concurrency TS wraps up.
137 : *
138 : * Future is _not_ thread-safe. Call it from a single thread or ensure synchronization externally.
139 : *
140 : * The destructor is never blocking. The promise may still be running on destruction.
141 : * TODO:
142 : * - Handle exceptions.
143 : */
144 : template<typename ResultType>
145 100002 : class Future
146 : {
147 : template<typename T>
148 : friend class PackagedTask;
149 :
150 : static constexpr bool VoidResult = std::is_same_v<ResultType, void>;
151 :
152 : using Status = FutureSharedStateDetail::Status;
153 : using SharedState = FutureSharedStateDetail::SharedState<ResultType>;
154 : public:
155 100016 : Future() = default;
156 : Future(const Future& o) = delete;
157 0 : Future(Future&&) = default;
158 : Future& operator=(Future&&) = default;
159 200032 : ~Future() = default;
160 :
161 : /**
162 : * Make the future wait for the result of @a func.
163 : */
164 : template<typename T>
165 : PackagedTask<ResultType> Wrap(T&& func);
166 :
167 : /**
168 : * Move the result out of the future, and invalidate the future.
169 : * If the future is not complete, calls Wait().
170 : * If the future is canceled, asserts.
171 : */
172 : template<typename SfinaeType = ResultType>
173 100004 : std::enable_if_t<!std::is_same_v<SfinaeType, void>, ResultType> Get()
174 : {
175 100004 : ENSURE(!!m_SharedState);
176 :
177 100004 : Wait();
178 : if constexpr (VoidResult)
179 : return;
180 : else
181 : {
182 100004 : ENSURE(m_SharedState->m_Status != Status::CANCELED);
183 :
184 : // This mark the state invalid - can't call Get again.
185 100004 : return m_SharedState->GetResult();
186 : }
187 : }
188 :
189 : /**
190 : * @return true if the shared state is valid and has a result (i.e. Get can be called).
191 : */
192 : bool IsReady() const
193 : {
194 : return !!m_SharedState && m_SharedState->m_Status == Status::DONE;
195 : }
196 :
197 : /**
198 : * @return true if the future has a shared state and it's not been invalidated, ie. pending, started or done.
199 : */
200 100028 : bool Valid() const
201 : {
202 100028 : return !!m_SharedState && m_SharedState->m_Status != Status::CANCELED;
203 : }
204 :
205 100013 : void Wait()
206 : {
207 100013 : if (Valid())
208 100013 : m_SharedState->Wait();
209 100013 : }
210 :
211 : /**
212 : * Cancels the task, waiting if the task is currently started.
213 : * Use this function over Cancel() if you need to ensure determinism (i.e. in the simulation).
214 : * @see Cancel.
215 : */
216 15 : void CancelOrWait()
217 : {
218 15 : if (!Valid())
219 15 : return;
220 0 : if (!m_SharedState->Cancel())
221 0 : m_SharedState->Wait();
222 0 : m_SharedState.reset();
223 : }
224 :
225 : protected:
226 : std::shared_ptr<SharedState> m_SharedState;
227 : };
228 :
229 : /**
230 : * Corresponds somewhat to std::packaged_task.
231 : * Like packaged_task, this holds a function acting as a promise.
232 : * This type is mostly just the shared state and the call operator,
233 : * handling the promise & continuation logic.
234 : */
235 : template<typename ResultType>
236 300048 : class PackagedTask
237 : {
238 : static constexpr bool VoidResult = std::is_same_v<ResultType, void>;
239 : public:
240 : PackagedTask() = delete;
241 100016 : PackagedTask(std::shared_ptr<typename Future<ResultType>::SharedState> ss) : m_SharedState(std::move(ss)) {}
242 :
243 100014 : void operator()()
244 : {
245 100014 : typename Future<ResultType>::Status expected = Future<ResultType>::Status::PENDING;
246 100014 : if (!m_SharedState->m_Status.compare_exchange_strong(expected, Future<ResultType>::Status::STARTED))
247 0 : return;
248 :
249 : if constexpr (VoidResult)
250 10 : m_SharedState->m_Func();
251 : else
252 100004 : m_SharedState->emplace(m_SharedState->m_Func());
253 :
254 : // Because we might have threads waiting on us, we need to make sure that they either:
255 : // - don't wait on our condition variable
256 : // - receive the notification when we're done.
257 : // This requires locking the mutex (@see Wait).
258 : {
259 200028 : std::lock_guard<std::mutex> lock(m_SharedState->m_Mutex);
260 100014 : m_SharedState->m_Status = Future<ResultType>::Status::DONE;
261 : }
262 :
263 100014 : m_SharedState->m_ConditionVariable.notify_all();
264 :
265 : // We no longer need the shared state, drop it immediately.
266 100014 : m_SharedState.reset();
267 : }
268 :
269 : void Cancel()
270 : {
271 : m_SharedState->Cancel();
272 : m_SharedState.reset();
273 : }
274 :
275 : protected:
276 : std::shared_ptr<typename Future<ResultType>::SharedState> m_SharedState;
277 : };
278 :
279 : template<typename ResultType>
280 : template<typename T>
281 100016 : PackagedTask<ResultType> Future<ResultType>::Wrap(T&& func)
282 : {
283 : static_assert(std::is_convertible_v<std::invoke_result_t<T>, ResultType>, "The return type of the wrapped function cannot be converted to the type of the Future.");
284 100016 : m_SharedState = std::make_shared<SharedState>(std::move(func));
285 100016 : return PackagedTask<ResultType>(m_SharedState);
286 : }
287 :
288 : #endif // INCLUDED_FUTURE
|