Pyrogenesis  trunk
Future.h
Go to the documentation of this file.
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>
32 
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  SharedState(std::function<ResultType()>&& func) :
56  ResultHolder<ResultType>{std::nullopt},
57  m_Func(std::move(func))
58  {}
60  {
61  // For safety, wait on started task completion, but not on pending ones (auto-cancelled).
62  if (!Cancel())
63  {
64  Wait();
65  Cancel();
66  }
67  }
68 
69  SharedState(const SharedState&) = delete;
70  SharedState(SharedState&&) = delete;
71 
72  bool IsDoneOrCanceled() const
73  {
74  return m_Status == Status::DONE || m_Status == Status::CANCELED;
75  }
76 
77  void Wait()
78  {
79  // Fast path: we're already done.
80  if (IsDoneOrCanceled())
81  return;
82  // Slow path: we aren't done when we run the above check. Lock and wait until we are.
83  std::unique_lock<std::mutex> lock(m_Mutex);
84  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  bool Cancel()
92  {
93  Status expected = Status::PENDING;
94  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  if (cancelled || m_Status == Status::DONE)
97  {
98  if (m_Status == Status::DONE)
99  m_Status = Status::CANCELED;
100  if constexpr (!VoidResult)
101  this->reset();
102  m_ConditionVariable.notify_all();
103  return cancelled;
104  }
105  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  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  ENSURE(this->has_value());
116  m_Status = Status::CANCELED;
117  ResultType ret = std::move(**this);
118  this->reset();
119  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 class Future
146 {
147  template<typename T>
148  friend class PackagedTask;
149 
150  static constexpr bool VoidResult = std::is_same_v<ResultType, void>;
151 
154 public:
155  Future() = default;
156  Future(const Future& o) = delete;
157  Future(Future&&) = default;
158  Future& operator=(Future&&) = default;
159  ~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  std::enable_if_t<!std::is_same_v<SfinaeType, void>, ResultType> Get()
174  {
176 
177  Wait();
178  if constexpr (VoidResult)
179  return;
180  else
181  {
182  ENSURE(m_SharedState->m_Status != Status::CANCELED);
183 
184  // This mark the state invalid - can't call Get again.
185  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  bool Valid() const
201  {
202  return !!m_SharedState && m_SharedState->m_Status != Status::CANCELED;
203  }
204 
205  void Wait()
206  {
207  if (Valid())
208  m_SharedState->Wait();
209  }
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  */
217  {
218  if (!Valid())
219  return;
220  if (!m_SharedState->Cancel())
221  m_SharedState->Wait();
222  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 class PackagedTask
237 {
238  static constexpr bool VoidResult = std::is_same_v<ResultType, void>;
239 public:
240  PackagedTask() = delete;
241  PackagedTask(std::shared_ptr<typename Future<ResultType>::SharedState> ss) : m_SharedState(std::move(ss)) {}
242 
243  void operator()()
244  {
246  if (!m_SharedState->m_Status.compare_exchange_strong(expected, Future<ResultType>::Status::STARTED))
247  return;
248 
249  if constexpr (VoidResult)
250  m_SharedState->m_Func();
251  else
252  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  std::lock_guard<std::mutex> lock(m_SharedState->m_Mutex);
260  m_SharedState->m_Status = Future<ResultType>::Status::DONE;
261  }
262 
263  m_SharedState->m_ConditionVariable.notify_all();
264 
265  // We no longer need the shared state, drop it immediately.
266  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>
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  m_SharedState = std::make_shared<SharedState>(std::move(func));
285  return PackagedTask<ResultType>(m_SharedState);
286 }
287 
288 #endif // INCLUDED_FUTURE
std::mutex m_Mutex
Definition: Future.h:123
PackagedTask< ResultType > Wrap(T &&func)
Make the future wait for the result of func.
Definition: Future.h:281
Status
Definition: Future.h:35
Definition: Future.h:33
void CancelOrWait()
Cancels the task, waiting if the task is currently started.
Definition: Future.h:216
std::shared_ptr< SharedState > m_SharedState
Definition: Future.h:226
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:173
void Cancel()
Definition: Future.h:269
Definition: ShaderDefines.cpp:30
std::function< ResultType()> m_Func
Definition: Future.h:126
void Wait()
Definition: Future.h:77
void operator()()
Definition: Future.h:243
PackagedTask(std::shared_ptr< typename Future< ResultType >::SharedState > ss)
Definition: Future.h:241
#define ENSURE(expr)
ensure the expression <expr> evaluates to non-zero.
Definition: debug.h:290
m_Func(std::move(func))
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:91
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:112
bool IsDoneOrCanceled() const
Definition: Future.h:72
Corresponds to std::future.
Definition: Future.h:145
#define T(string_literal)
Definition: secure_crt.cpp:77
std::shared_ptr< typename Future< ResultType >::SharedState > m_SharedState
Definition: Future.h:276
~SharedState()
Definition: Future.h:59
SharedState(std::function< ResultType()> &&func)
Definition: Future.h:55
bool Valid() const
Definition: Future.h:200
std::conditional_t< std::is_void_v< T >, std::nullopt_t, std::optional< T > > ResultHolder
Definition: Future.h:44
bool IsReady() const
Definition: Future.h:192
std::condition_variable m_ConditionVariable
Definition: Future.h:124
The shared state between futures and packaged state.
Definition: Future.h:51
void Wait()
Definition: Future.h:205
Corresponds somewhat to std::packaged_task.
Definition: Future.h:31