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 | |
9 | namespace 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 | |