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
7namespace bs
8{
9 /** @addtogroup Internal-Utility
10 * @{
11 */
12
13 /** @addtogroup General-Internal
14 * @{
15 */
16
17 /** Data common to all event connections. */
18 class BaseConnectionData
19 {
20 public:
21 BaseConnectionData() = default;
22
23 virtual ~BaseConnectionData()
24 {
25 assert(!handleLinks && !isActive);
26 }
27
28 virtual void deactivate()
29 {
30 isActive = false;
31 }
32
33 BaseConnectionData* prev = nullptr;
34 BaseConnectionData* next = nullptr;
35 bool isActive = true;
36 UINT32 handleLinks = 0;
37 };
38
39 /** Internal data for an Event, storing all connections. */
40 struct EventInternalData
41 {
42 EventInternalData() = default;
43
44 ~EventInternalData()
45 {
46 BaseConnectionData* conn = mConnections;
47 while (conn != nullptr)
48 {
49 BaseConnectionData* next = conn->next;
50 bs_free(conn);
51
52 conn = next;
53 }
54
55 conn = mFreeConnections;
56 while (conn != nullptr)
57 {
58 BaseConnectionData* next = conn->next;
59 bs_free(conn);
60
61 conn = next;
62 }
63
64 conn = mNewConnections;
65 while (conn != nullptr)
66 {
67 BaseConnectionData* next = conn->next;
68 bs_free(conn);
69
70 conn = next;
71 }
72 }
73
74 /** Appends a new connection to the active connection array. */
75 void connect(BaseConnectionData* conn)
76 {
77 conn->prev = mLastConnection;
78
79 if (mLastConnection != nullptr)
80 mLastConnection->next = conn;
81
82 mLastConnection = conn;
83
84 // First connection
85 if (mConnections == nullptr)
86 mConnections = conn;
87 }
88
89 /**
90 * Disconnects the connection with the specified data, ensuring the event doesn't call its callback again.
91 *
92 * @note Only call this once.
93 */
94 void disconnect(BaseConnectionData* conn)
95 {
96 RecursiveLock lock(mMutex);
97
98 conn->deactivate();
99 conn->handleLinks--;
100
101 if (conn->handleLinks == 0)
102 free(conn);
103 }
104
105 /** Disconnects all connections in the event. */
106 void clear()
107 {
108 RecursiveLock lock(mMutex);
109
110 BaseConnectionData* conn = mConnections;
111 while (conn != nullptr)
112 {
113 BaseConnectionData* next = conn->next;
114 conn->deactivate();
115
116 if (conn->handleLinks == 0)
117 free(conn);
118
119 conn = next;
120 }
121
122 mConnections = nullptr;
123 mLastConnection = nullptr;
124 }
125
126 /**
127 * Called when the event handle no longer keeps a reference to the connection data. This means we might be able to
128 * free (and reuse) its memory if the event is done with it too.
129 */
130 void freeHandle(BaseConnectionData* conn)
131 {
132 RecursiveLock lock(mMutex);
133
134 conn->handleLinks--;
135
136 if (conn->handleLinks == 0 && !conn->isActive)
137 free(conn);
138 }
139
140 /** Releases connection data and makes it available for re-use when next connection is formed. */
141 void free(BaseConnectionData* conn)
142 {
143 if (conn->prev != nullptr)
144 conn->prev->next = conn->next;
145 else
146 mConnections = conn->next;
147
148 if (conn->next != nullptr)
149 conn->next->prev = conn->prev;
150 else
151 mLastConnection = conn->prev;
152
153 conn->prev = nullptr;
154 conn->next = nullptr;
155
156 if (mFreeConnections != nullptr)
157 {
158 conn->next = mFreeConnections;
159 mFreeConnections->prev = conn;
160 }
161
162 mFreeConnections = conn;
163 mFreeConnections->~BaseConnectionData();
164 }
165
166 BaseConnectionData* mConnections = nullptr;
167 BaseConnectionData* mLastConnection = nullptr;
168 BaseConnectionData* mFreeConnections = nullptr;
169 BaseConnectionData* mNewConnections = nullptr;
170
171 RecursiveMutex mMutex;
172 bool mIsCurrentlyTriggering = false;
173 };
174
175 /** @} */
176 /** @} */
177
178 /** @addtogroup General
179 * @{
180 */
181
182 /** Event handle. Allows you to track to which events you subscribed to and disconnect from them when needed. */
183 class HEvent
184 {
185 public:
186 HEvent() = default;
187
188 explicit HEvent(SPtr<EventInternalData> eventData, BaseConnectionData* connection)
189 :mConnection(connection), mEventData(std::move(eventData))
190 {
191 connection->handleLinks++;
192 }
193
194 ~HEvent()
195 {
196 if (mConnection != nullptr)
197 mEventData->freeHandle(mConnection);
198 }
199
200 /** Disconnect from the event you are subscribed to. */
201 void disconnect()
202 {
203 if (mConnection != nullptr)
204 {
205 mEventData->disconnect(mConnection);
206 mConnection = nullptr;
207 mEventData = nullptr;
208 }
209 }
210
211 /** @cond IGNORE */
212
213 struct Bool_struct
214 {
215 int _Member;
216 };
217
218 /** @endcond */
219
220 /**
221 * Allows direct conversion of a handle to bool.
222 *
223 * @note
224 * Additional struct is needed because we can't directly convert to bool since then we can assign pointer to bool
225 * and that's wrong.
226 */
227 operator int Bool_struct::*() const
228 {
229 return (mConnection != nullptr ? &Bool_struct::_Member : 0);
230 }
231
232 HEvent& operator=(const HEvent& rhs)
233 {
234 mConnection = rhs.mConnection;
235 mEventData = rhs.mEventData;
236
237 if (mConnection != nullptr)
238 mConnection->handleLinks++;
239
240 return *this;
241 }
242
243 private:
244 BaseConnectionData* mConnection = nullptr;
245 SPtr<EventInternalData> mEventData;
246 };
247
248 /** @} */
249
250 /** @addtogroup Internal-Utility
251 * @{
252 */
253
254 /** @addtogroup General-Internal
255 * @{
256 */
257
258 /**
259 * Events allows you to register method callbacks that get notified when the event is triggered.
260 *
261 * @note Callback method return value is ignored.
262 */
263 // Note: I could create a policy template argument that allows creation of
264 // lockable and non-lockable events in the case mutex is causing too much overhead.
265 template <class RetType, class... Args>
266 class TEvent
267 {
268 struct ConnectionData : BaseConnectionData
269 {
270 public:
271 void deactivate() override
272 {
273 func = nullptr;
274
275 BaseConnectionData::deactivate();
276 }
277
278 std::function<RetType(Args...)> func;
279 };
280
281 public:
282 TEvent()
283 :mInternalData(bs_shared_ptr_new<EventInternalData>())
284 { }
285
286 ~TEvent()
287 {
288 clear();
289 }
290
291 /** Register a new callback that will get notified once the event is triggered. */
292 HEvent connect(std::function<RetType(Args...)> func)
293 {
294 RecursiveLock lock(mInternalData->mMutex);
295
296 ConnectionData* connData = nullptr;
297 if (mInternalData->mFreeConnections != nullptr)
298 {
299 connData = static_cast<ConnectionData*>(mInternalData->mFreeConnections);
300 mInternalData->mFreeConnections = connData->next;
301
302 new (connData)ConnectionData();
303 if (connData->next != nullptr)
304 connData->next->prev = nullptr;
305
306 connData->isActive = true;
307 }
308
309 if (connData == nullptr)
310 connData = bs_new<ConnectionData>();
311
312 // If currently iterating over the connection list, delay modifying it until done
313 if(mInternalData->mIsCurrentlyTriggering)
314 {
315 connData->prev = mInternalData->mNewConnections;
316
317 if (mInternalData->mNewConnections != nullptr)
318 mInternalData->mNewConnections->next = connData;
319
320 mInternalData->mNewConnections = connData;
321 }
322 else
323 {
324 mInternalData->connect(connData);
325 }
326
327 connData->func = func;
328
329 return HEvent(mInternalData, connData);
330 }
331
332 /** Trigger the event, notifying all register callback methods. */
333 void operator() (Args... args)
334 {
335 // Increase ref count to ensure this event data isn't destroyed if one of the callbacks
336 // deletes the event itself.
337 SPtr<EventInternalData> internalData = mInternalData;
338
339 RecursiveLock lock(internalData->mMutex);
340 internalData->mIsCurrentlyTriggering = true;
341
342 ConnectionData* conn = static_cast<ConnectionData*>(internalData->mConnections);
343 while (conn != nullptr)
344 {
345 // Save next here in case the callback itself disconnects this connection
346 ConnectionData* next = static_cast<ConnectionData*>(conn->next);
347
348 if (conn->func != nullptr)
349 conn->func(std::forward<Args>(args)...);
350
351 conn = next;
352 }
353
354 internalData->mIsCurrentlyTriggering = false;
355
356 // If any new connections were added during the above calls, add them to the connection list
357 if(internalData->mNewConnections != nullptr)
358 {
359 BaseConnectionData* lastNewConnection = internalData->mNewConnections;
360 while (lastNewConnection != nullptr)
361 lastNewConnection = lastNewConnection->next;
362
363 BaseConnectionData* currentConnection = lastNewConnection;
364 while(currentConnection != nullptr)
365 {
366 BaseConnectionData* prevConnection = currentConnection->prev;
367 currentConnection->next = nullptr;
368 currentConnection->prev = nullptr;
369
370 mInternalData->connect(currentConnection);
371 currentConnection = prevConnection;
372 }
373
374 internalData->mNewConnections = nullptr;
375 }
376 }
377
378 /** Clear all callbacks from the event. */
379 void clear()
380 {
381 mInternalData->clear();
382 }
383
384 /**
385 * Check if event has any callbacks registered.
386 *
387 * @note It is safe to trigger an event even if no callbacks are registered.
388 */
389 bool empty() const
390 {
391 RecursiveLock lock(mInternalData->mMutex);
392
393 return mInternalData->mConnections == nullptr;
394 }
395
396 private:
397 SPtr<EventInternalData> mInternalData;
398 };
399
400 /** @} */
401 /** @} */
402
403 /** @addtogroup General
404 * @{
405 */
406
407 /************************************************************************/
408 /* SPECIALIZATIONS */
409 /* SO YOU MAY USE FUNCTION LIKE SYNTAX FOR DECLARING EVENT SIGNATURE */
410 /************************************************************************/
411
412 /** @copydoc TEvent */
413 template <typename Signature>
414 class Event;
415
416 /** @copydoc TEvent */
417 template <class RetType, class... Args>
418 class Event<RetType(Args...) > : public TEvent <RetType, Args...>
419 { };
420
421 /** @} */
422}
423