1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#pragma once
4
5#include "Prerequisites/BsPrerequisitesUtil.h"
6#include "Error/BsException.h"
7#include "Utility/BsAny.h"
8
9namespace bs
10{
11 /** @addtogroup Internal-Utility
12 * @{
13 */
14
15 /** @addtogroup Threading-Internal
16 * @{
17 */
18
19 /** Thread synchronization primitives used by AsyncOps and their callers. */
20 class BS_UTILITY_EXPORT AsyncOpSyncData
21 {
22 public:
23 Mutex mMutex;
24 Signal mCondition;
25 };
26
27 /**
28 * Flag used for creating async operations signaling that we want to create an empty AsyncOp with no internal
29 * memory storage.
30 */
31 struct BS_UTILITY_EXPORT AsyncOpEmpty {};
32
33 /** @} */
34 /** @} */
35
36 /** @addtogroup Threading
37 * @{
38 */
39
40 /** Common base for all TAsyncOp specializations. */
41 class BS_UTILITY_EXPORT AsyncOpBase
42 {
43 private:
44 struct AsyncOpData
45 {
46 AsyncOpData() = default;
47
48 Any mReturnValue;
49 volatile std::atomic<bool> mIsCompleted{false};
50 };
51
52 public:
53 AsyncOpBase()
54 :mData(bs_shared_ptr_new<AsyncOpData>())
55 { }
56
57 AsyncOpBase(AsyncOpEmpty empty)
58 { }
59
60 AsyncOpBase(const SPtr<AsyncOpSyncData>& syncData)
61 :mData(bs_shared_ptr_new<AsyncOpData>()), mSyncData(syncData)
62 { }
63
64 AsyncOpBase(AsyncOpEmpty empty, const SPtr<AsyncOpSyncData>& syncData)
65 :mSyncData(syncData)
66 { }
67
68 /** Returns true if the async operation has completed. */
69 bool hasCompleted() const
70 {
71 return mData->mIsCompleted.load(std::memory_order_acquire);
72 }
73
74 /**
75 * Blocks the caller thread until the AsyncOp completes.
76 *
77 * @note
78 * Do not call this on the thread that is completing the async op, as it will cause a deadlock. Make sure the
79 * command you are waiting for is actually queued for execution because a deadlock will occur otherwise.
80 */
81 void blockUntilComplete() const
82 {
83 if (mSyncData == nullptr)
84 {
85 LOGERR("No sync data is available. Cannot block until AsyncOp is complete.");
86 return;
87 }
88
89 Lock lock(mSyncData->mMutex);
90 while (!hasCompleted())
91 mSyncData->mCondition.wait(lock);
92 }
93
94 /**
95 * Retrieves the value returned by the async operation as a generic type. Only valid if hasCompleted() returns
96 * true.
97 */
98 Any getGenericReturnValue() const
99 {
100#if BS_DEBUG_MODE
101 if(!hasCompleted())
102 LOGERR("Trying to get AsyncOp return value but the operation hasn't completed.");
103#endif
104
105 return mData->mReturnValue;
106 }
107
108 protected:
109 SPtr<AsyncOpData> mData;
110 SPtr<AsyncOpSyncData> mSyncData;
111 };
112
113 /**
114 * Object you may use to check on the results of an asynchronous operation. Contains uninitialized data until
115 * hasCompleted() returns true.
116 *
117 * @note
118 * You are allowed (and meant to) to copy this by value.
119 */
120 template<class ReturnType>
121 class BS_UTILITY_EXPORT TAsyncOp : public AsyncOpBase
122 {
123 public:
124 using ReturnValueType = ReturnType;
125
126 TAsyncOp() = default;
127
128 TAsyncOp(AsyncOpEmpty empty)
129 :AsyncOpBase(empty)
130 { }
131
132 TAsyncOp(const SPtr<AsyncOpSyncData>& syncData)
133 :AsyncOpBase(syncData)
134 { }
135
136 TAsyncOp(AsyncOpEmpty empty, const SPtr<AsyncOpSyncData>& syncData)
137 :AsyncOpBase(empty, syncData)
138 { }
139
140 /** Retrieves the value returned by the async operation. Only valid if hasCompleted() returns true. */
141 ReturnType getReturnValue() const
142 {
143#if BS_DEBUG_MODE
144 if(!hasCompleted())
145 LOGERR("Trying to get AsyncOp return value but the operation hasn't completed.");
146#endif
147
148 return any_cast<ReturnType>(mData->mReturnValue);
149 }
150
151 public: // ***** INTERNAL ******
152 /** @name Internal
153 * @{
154 */
155
156 /** Mark the async operation as completed, without setting a return value. */
157 void _completeOperation()
158 {
159 mData->mIsCompleted.store(true, std::memory_order_release);
160
161 if (mSyncData != nullptr)
162 mSyncData->mCondition.notify_all();
163 }
164
165 /** Mark the async operation as completed. */
166 void _completeOperation(const ReturnType& returnValue)
167 {
168 mData->mReturnValue = returnValue;
169 _completeOperation();
170 }
171
172 /** @} */
173 protected:
174 template<class ReturnType2> friend bool operator==(const TAsyncOp<ReturnType2>&, std::nullptr_t);
175 template<class ReturnType2> friend bool operator!=(const TAsyncOp<ReturnType2>&, std::nullptr_t);
176 };
177
178 /** Checks if an AsyncOp is null. */
179 template<class ReturnType>
180 bool operator==(const TAsyncOp<ReturnType>& lhs, std::nullptr_t rhs)
181 {
182 return lhs.mData == nullptr;
183 }
184
185 /** Checks if an AsyncOp is not null. */
186 template<class ReturnType>
187 bool operator!=(const TAsyncOp<ReturnType>& lhs, std::nullptr_t rhs)
188 {
189 return lhs.mData != nullptr;
190 }
191
192 /** @copydoc TAsyncOp */
193 using AsyncOp = TAsyncOp<Any>;
194
195 /** @} */
196}
197