1 | // [Blend2D] |
2 | // 2D Vector Graphics Powered by a JIT Compiler. |
3 | // |
4 | // [License] |
5 | // Zlib - See LICENSE.md file in the package. |
6 | |
7 | #ifndef BLEND2D_BLTHREADING_P_H |
8 | #define BLEND2D_BLTHREADING_P_H |
9 | |
10 | #include "./blapi-internal_p.h" |
11 | |
12 | #ifndef _WIN32 |
13 | #include <sys/time.h> |
14 | #endif |
15 | |
16 | //! \cond INTERNAL |
17 | //! \addtogroup blend2d_internal |
18 | //! \{ |
19 | |
20 | // ============================================================================ |
21 | // [Forward Declarations] |
22 | // ============================================================================ |
23 | |
24 | class BLMutex; |
25 | class BLRWLock; |
26 | class BLConditionVariable; |
27 | class BLThreadEvent; |
28 | |
29 | struct BLThread; |
30 | struct BLThreadVirt; |
31 | struct BLThreadAttributes; |
32 | |
33 | // ============================================================================ |
34 | // [Typedefs] |
35 | // ============================================================================ |
36 | |
37 | typedef void (BL_CDECL* BLThreadFunc)(BLThread* thread, void* data) BL_NOEXCEPT; |
38 | |
39 | // ============================================================================ |
40 | // [Constants] |
41 | // ============================================================================ |
42 | |
43 | enum BLThreadStatus : uint32_t { |
44 | BL_THREAD_STATUS_IDLE = 0, |
45 | BL_THREAD_STATUS_RUNNING = 1, |
46 | BL_THREAD_STATUS_QUITTING = 2 |
47 | }; |
48 | |
49 | // ============================================================================ |
50 | // [Atomics] |
51 | // ============================================================================ |
52 | |
53 | static BL_INLINE void blAtomicThreadFence(std::memory_order order = std::memory_order_release) noexcept { |
54 | std::atomic_thread_fence(order); |
55 | } |
56 | |
57 | template<typename T> |
58 | static BL_INLINE typename std::remove_volatile<T>::type blAtomicFetch(const T* p, std::memory_order order = std::memory_order_relaxed) noexcept { |
59 | typedef typename BLInternal::StdInt<sizeof(T), 0>::Type RawT; |
60 | return (typename std::remove_volatile<T>::type)((const std::atomic<RawT>*)p)->load(order); |
61 | } |
62 | |
63 | template<typename T> |
64 | static BL_INLINE void blAtomicStore(T* p, typename std::remove_volatile<T>::type value, std::memory_order order = std::memory_order_release) noexcept { |
65 | typedef typename BLInternal::StdInt<sizeof(T), 0>::Type RawT; |
66 | return ((std::atomic<RawT>*)p)->store((RawT)value, order); |
67 | } |
68 | |
69 | // ============================================================================ |
70 | // [Utilities] |
71 | // ============================================================================ |
72 | |
73 | #ifdef _WIN32 |
74 | static BL_INLINE void blThreadYield() noexcept { Sleep(0); } |
75 | #else |
76 | static BL_INLINE void blThreadYield() noexcept { sched_yield(); } |
77 | #endif |
78 | |
79 | #ifndef _WIN32 |
80 | static void blGetAbsTimeForWaitCondition(struct timespec& out, uint64_t microseconds) noexcept { |
81 | struct timeval now; |
82 | gettimeofday(&now, nullptr); |
83 | |
84 | out.tv_sec = now.tv_sec + int64_t(microseconds / 1000000u); |
85 | out.tv_nsec = (now.tv_usec + int64_t(microseconds % 1000000u)) * 1000; |
86 | out.tv_sec += out.tv_nsec / 1000000000; |
87 | out.tv_nsec %= 1000000000; |
88 | } |
89 | #endif |
90 | |
91 | // ============================================================================ |
92 | // [BLMutex] |
93 | // ============================================================================ |
94 | |
95 | //! Mutex abstraction over Windows or POSIX threads. |
96 | class BLMutex { |
97 | public: |
98 | BL_NONCOPYABLE(BLMutex) |
99 | |
100 | #ifdef _WIN32 |
101 | SRWLOCK handle; |
102 | |
103 | BL_INLINE BLMutex() noexcept : handle(SRWLOCK_INIT) {} |
104 | |
105 | BL_INLINE void lock() noexcept { AcquireSRWLockExclusive(&handle); } |
106 | BL_INLINE bool tryLock() noexcept { return TryAcquireSRWLockExclusive(&handle) != 0; } |
107 | BL_INLINE void unlock() noexcept { ReleaseSRWLockExclusive(&handle); } |
108 | #else |
109 | pthread_mutex_t handle; |
110 | |
111 | #ifdef PTHREAD_MUTEX_INITIALIZER |
112 | BL_INLINE BLMutex() noexcept : handle(PTHREAD_MUTEX_INITIALIZER) {} |
113 | #else |
114 | BL_INLINE BLMutex() noexcept { pthread_mutex_init(&handle, nullptr); } |
115 | #endif |
116 | BL_INLINE ~BLMutex() noexcept { pthread_mutex_destroy(&handle); } |
117 | |
118 | BL_INLINE void lock() noexcept { pthread_mutex_lock(&handle); } |
119 | BL_INLINE bool tryLock() noexcept { return pthread_mutex_trylock(&handle) == 0; } |
120 | BL_INLINE void unlock() noexcept { pthread_mutex_unlock(&handle); } |
121 | #endif |
122 | }; |
123 | |
124 | //! Mutex guard. |
125 | //! |
126 | //! Automatically locks the given mutex when created and unlocks it when destroyed. |
127 | class BLMutexGuard { |
128 | public: |
129 | BL_NONCOPYABLE(BLMutexGuard) |
130 | |
131 | BLMutex* mutex; |
132 | |
133 | //! Creates an instance of `BLMutexGuard` and locks the given `mutex`. |
134 | BL_INLINE BLMutexGuard(BLMutex& mutex) noexcept : mutex(&mutex) { this->mutex->lock(); } |
135 | //! Creates an instance of `BLMutexGuard` and locks the given `mutex`. |
136 | BL_INLINE BLMutexGuard(BLMutex* mutex) noexcept : mutex(mutex) { this->mutex->lock(); } |
137 | |
138 | //! Unlocks the mutex that has been locked by the constructor. |
139 | BL_INLINE ~BLMutexGuard() noexcept { this->mutex->unlock(); } |
140 | }; |
141 | |
142 | // ============================================================================ |
143 | // [BLRWLock] |
144 | // ============================================================================ |
145 | |
146 | class BLRWLock { |
147 | public: |
148 | BL_NONCOPYABLE(BLRWLock) |
149 | |
150 | #ifdef _WIN32 |
151 | SRWLOCK handle; |
152 | |
153 | BL_INLINE BLRWLock() noexcept : handle(SRWLOCK_INIT) {} |
154 | |
155 | BL_INLINE void lockRead() noexcept { AcquireSRWLockShared(&handle); } |
156 | BL_INLINE void lockWrite() noexcept { AcquireSRWLockExclusive(&handle); } |
157 | |
158 | BL_INLINE void tryLockRead() noexcept { TryAcquireSRWLockShared(&handle); } |
159 | BL_INLINE void tryLockWrite() noexcept { TryAcquireSRWLockExclusive(&handle); } |
160 | |
161 | BL_INLINE void unlockRead() noexcept { ReleaseSRWLockShared(&handle); } |
162 | BL_INLINE void unlockWrite() noexcept { ReleaseSRWLockExclusive(&handle); } |
163 | #else |
164 | pthread_rwlock_t handle; |
165 | |
166 | #ifdef PTHREAD_RWLOCK_INITIALIZER |
167 | BL_INLINE BLRWLock() noexcept : handle(PTHREAD_RWLOCK_INITIALIZER) {} |
168 | #else |
169 | BL_INLINE BLRWLock() noexcept { pthread_rwlock_init(&handle, nullptr); } |
170 | #endif |
171 | BL_INLINE ~BLRWLock() noexcept { pthread_rwlock_destroy(&handle); } |
172 | |
173 | BL_INLINE void lockRead() noexcept { pthread_rwlock_rdlock(&handle); } |
174 | BL_INLINE void lockWrite() noexcept { pthread_rwlock_wrlock(&handle); } |
175 | |
176 | BL_INLINE bool tryLockRead() noexcept { return pthread_rwlock_tryrdlock(&handle) == 0; } |
177 | BL_INLINE bool tryLockWrite() noexcept { return pthread_rwlock_trywrlock(&handle) == 0; } |
178 | |
179 | BL_INLINE void unlockRead() noexcept { pthread_rwlock_unlock(&handle); } |
180 | BL_INLINE void unlockWrite() noexcept { pthread_rwlock_unlock(&handle); } |
181 | #endif |
182 | }; |
183 | |
184 | class BLRWLockReadGuard { |
185 | public: |
186 | BL_NONCOPYABLE(BLRWLockReadGuard) |
187 | |
188 | BLRWLock* lock; |
189 | |
190 | BL_INLINE BLRWLockReadGuard(BLRWLock& lock) noexcept : lock(&lock) { this->lock->lockRead(); } |
191 | BL_INLINE BLRWLockReadGuard(BLRWLock* lock) noexcept : lock(lock) { this->lock->lockRead(); } |
192 | BL_INLINE ~BLRWLockReadGuard() noexcept { this->lock->unlockRead(); } |
193 | }; |
194 | |
195 | class BLRWLockWriteGuard { |
196 | public: |
197 | BL_NONCOPYABLE(BLRWLockWriteGuard) |
198 | |
199 | BLRWLock* lock; |
200 | |
201 | BL_INLINE BLRWLockWriteGuard(BLRWLock& lock) noexcept : lock(&lock) { this->lock->lockWrite(); } |
202 | BL_INLINE BLRWLockWriteGuard(BLRWLock* lock) noexcept : lock(lock) { this->lock->lockWrite(); } |
203 | BL_INLINE ~BLRWLockWriteGuard() noexcept { this->lock->unlockWrite(); } |
204 | }; |
205 | |
206 | // ============================================================================ |
207 | // [BLConditionalVariable] |
208 | // ============================================================================ |
209 | |
210 | class BLConditionVariable { |
211 | public: |
212 | BL_NONCOPYABLE(BLConditionVariable) |
213 | |
214 | #ifdef _WIN32 |
215 | CONDITION_VARIABLE handle; |
216 | |
217 | BL_INLINE BLConditionVariable() noexcept { InitializeConditionVariable(&handle); } |
218 | BL_INLINE ~BLConditionVariable() noexcept {} |
219 | |
220 | BL_INLINE void signal() noexcept { WakeConditionVariable(&handle); } |
221 | BL_INLINE void broadcast() noexcept { WakeAllConditionVariable(&handle); } |
222 | |
223 | BL_INLINE BLResult wait(BLMutex& mutex) noexcept { |
224 | BOOL ret = SleepConditionVariableSRW(&handle, &mutex.handle, INFINITE, 0); |
225 | return ret ? BL_SUCCESS : blTraceError(BL_ERROR_INVALID_STATE); |
226 | } |
227 | |
228 | BL_INLINE BLResult timedWait(BLMutex& mutex, uint64_t microseconds) noexcept { |
229 | uint32_t milliseconds = uint32_t(blMin<uint64_t>(microseconds / 1000u, INFINITE)); |
230 | BOOL ret = SleepConditionVariableSRW(&handle, &mutex.handle, milliseconds, 0); |
231 | |
232 | if (ret) |
233 | return BL_SUCCESS; |
234 | |
235 | // We don't trace `BL_ERROR_TIMED_OUT` as it's not unexpected. |
236 | return BL_ERROR_TIMED_OUT; |
237 | } |
238 | #else |
239 | pthread_cond_t handle; |
240 | |
241 | #ifdef PTHREAD_COND_INITIALIZER |
242 | BL_INLINE BLConditionVariable() noexcept : handle(PTHREAD_COND_INITIALIZER) {} |
243 | #else |
244 | BL_INLINE BLConditionVariable() noexcept { pthread_cond_init(&handle, nullptr); } |
245 | #endif |
246 | BL_INLINE ~BLConditionVariable() noexcept { pthread_cond_destroy(&handle); } |
247 | |
248 | BL_INLINE void signal() noexcept { |
249 | int ret = pthread_cond_signal(&handle); |
250 | BL_ASSERT(ret == 0); |
251 | BL_UNUSED(ret); |
252 | } |
253 | |
254 | BL_INLINE void broadcast() noexcept { |
255 | int ret = pthread_cond_broadcast(&handle); |
256 | BL_ASSERT(ret == 0); |
257 | BL_UNUSED(ret); |
258 | } |
259 | |
260 | BL_INLINE BLResult wait(BLMutex& mutex) noexcept { |
261 | int ret = pthread_cond_wait(&handle, &mutex.handle); |
262 | return ret == 0 ? BL_SUCCESS : blTraceError(BL_ERROR_INVALID_STATE); |
263 | } |
264 | |
265 | BL_INLINE BLResult timedWait(BLMutex& mutex, const struct timespec* absTime) noexcept { |
266 | int ret = pthread_cond_timedwait(&handle, &mutex.handle, absTime); |
267 | if (ret == 0) |
268 | return BL_SUCCESS; |
269 | |
270 | // We don't trace `BL_ERROR_TIMED_OUT` as it's not unexpected. |
271 | return BL_ERROR_TIMED_OUT; |
272 | } |
273 | |
274 | BL_INLINE BLResult timedWait(BLMutex& mutex, uint64_t microseconds) noexcept { |
275 | struct timespec absTime; |
276 | blGetAbsTimeForWaitCondition(absTime, microseconds); |
277 | return timedWait(mutex, &absTime); |
278 | } |
279 | #endif |
280 | }; |
281 | |
282 | // ============================================================================ |
283 | // [BLThreadEvent] |
284 | // ============================================================================ |
285 | |
286 | BL_HIDDEN BLResult BL_CDECL blThreadEventCreate(BLThreadEvent* self, bool manualReset, bool signaled) noexcept; |
287 | BL_HIDDEN BLResult BL_CDECL blThreadEventDestroy(BLThreadEvent* self) noexcept; |
288 | BL_HIDDEN bool BL_CDECL blThreadEventIsSignaled(const BLThreadEvent* self) noexcept; |
289 | BL_HIDDEN BLResult BL_CDECL blThreadEventSignal(BLThreadEvent* self) noexcept; |
290 | BL_HIDDEN BLResult BL_CDECL blThreadEventReset(BLThreadEvent* self) noexcept; |
291 | BL_HIDDEN BLResult BL_CDECL blThreadEventWait(BLThreadEvent* self) noexcept; |
292 | BL_HIDDEN BLResult BL_CDECL blThreadEventTimedWait(BLThreadEvent* self, uint64_t microseconds) noexcept; |
293 | |
294 | class BLThreadEvent { |
295 | public: |
296 | BL_NONCOPYABLE(BLThreadEvent) |
297 | |
298 | intptr_t handle; |
299 | |
300 | explicit BL_INLINE BLThreadEvent(bool manualReset = false, bool signaled = false) noexcept { |
301 | blThreadEventCreate(this, manualReset, signaled); |
302 | } |
303 | BL_INLINE ~BLThreadEvent() noexcept { blThreadEventDestroy(this); } |
304 | |
305 | BL_INLINE bool isInitialized() const noexcept { return handle != -1; } |
306 | BL_INLINE bool isSignaled() const noexcept { return blThreadEventIsSignaled(this); } |
307 | |
308 | BL_INLINE BLResult signal() noexcept { return blThreadEventSignal(this); } |
309 | BL_INLINE BLResult reset() noexcept { return blThreadEventReset(this); } |
310 | BL_INLINE BLResult wait() noexcept { return blThreadEventWait(this); } |
311 | BL_INLINE BLResult timedWait(uint64_t microseconds) noexcept { return blThreadEventTimedWait(this, microseconds); } |
312 | }; |
313 | |
314 | // ============================================================================ |
315 | // [BLThread] |
316 | // ============================================================================ |
317 | |
318 | struct BLThreadAttributes { |
319 | uint32_t stackSize; |
320 | }; |
321 | |
322 | struct BLThreadVirt { |
323 | BLResult (BL_CDECL* destroy)(BLThread* self) BL_NOEXCEPT; |
324 | uint32_t (BL_CDECL* status)(const BLThread* self) BL_NOEXCEPT; |
325 | BLResult (BL_CDECL* run)(BLThread* self, BLThreadFunc workFunc, BLThreadFunc doneFunc, void* data) BL_NOEXCEPT; |
326 | BLResult (BL_CDECL* quit)(BLThread* self) BL_NOEXCEPT; |
327 | }; |
328 | |
329 | struct BLThread { |
330 | BLThreadVirt* virt; |
331 | |
332 | // -------------------------------------------------------------------------- |
333 | #ifdef __cplusplus |
334 | BL_INLINE BLResult destroy() noexcept { |
335 | return virt->destroy(this); |
336 | } |
337 | |
338 | BL_INLINE uint32_t status() const noexcept { |
339 | return virt->status(this); |
340 | } |
341 | |
342 | BL_INLINE BLResult run(BLThreadFunc workFunc, BLThreadFunc doneFunc, void* data) noexcept { |
343 | return virt->run(this, workFunc, doneFunc, data); |
344 | } |
345 | |
346 | BL_INLINE BLResult quit() noexcept { |
347 | return virt->quit(this); |
348 | } |
349 | #endif |
350 | // -------------------------------------------------------------------------- |
351 | }; |
352 | |
353 | BL_HIDDEN BLResult BL_CDECL blThreadCreate(BLThread** threadOut, const BLThreadAttributes* attributes, BLThreadFunc exitFunc, void* exitData) noexcept; |
354 | |
355 | #ifndef _WIN32 |
356 | BL_HIDDEN BLResult blThreadCreatePt(BLThread** threadOut, const pthread_attr_t* ptAttr, BLThreadFunc exitFunc, void* exitData) noexcept; |
357 | BL_HIDDEN BLResult blThreadSetPtAttributes(pthread_attr_t* ptAttr, const BLThreadAttributes* src) noexcept; |
358 | #endif |
359 | |
360 | // ============================================================================ |
361 | // [BLAtomicUInt64Generator] |
362 | // ============================================================================ |
363 | |
364 | //! A context that can be used to generate unique 64-bit IDs in a thread-safe |
365 | //! manner. It uses atomic operations to make the generation as fast as possible |
366 | //! and provides an implementation for both 32-bit and 64-bit targets. |
367 | //! |
368 | //! The implementation choses a different startegy between 32-bit and 64-bit hosts. |
369 | //! On a 64-bit host the implementation always returns sequential IDs starting |
370 | //! from 1, on 32-bit host the implementation would always return a number which |
371 | //! is higher than the previous one, but it doesn't have to be sequential as it |
372 | //! uses the highest bit of LO value as an indicator to increment HI value. |
373 | struct BLAtomicUInt64Generator { |
374 | #if BL_TARGET_ARCH_BITS < 64 |
375 | std::atomic<uint32_t> _hi; |
376 | std::atomic<uint32_t> _lo; |
377 | |
378 | BL_INLINE void reset() noexcept { |
379 | _hi = 0; |
380 | _lo = 0; |
381 | } |
382 | |
383 | BL_INLINE uint64_t next() noexcept { |
384 | // This implementation doesn't always return an incrementing value as it's |
385 | // not the point. The requirement is to never return the same value, so it |
386 | // sacrifices one bit in `_lo` counter that would tell us to increment `_hi` |
387 | // counter and try again. |
388 | const uint32_t kThresholdLo32 = 0x80000000u; |
389 | |
390 | for (;;) { |
391 | uint32_t hiValue = _hi.load(); |
392 | uint32_t loValue = ++_lo; |
393 | |
394 | // This MUST support even cases when the thread executing this function |
395 | // right now is terminated. When we reach the threshold we increment |
396 | // `_hi`, which would contain a new HIGH value that will be used |
397 | // immediately, then we remove the threshold mark from LOW value and try |
398 | // to get a new LOW and HIGH values to return. |
399 | if (BL_UNLIKELY(loValue & kThresholdLo32)) { |
400 | _hi++; |
401 | |
402 | // If the thread is interrupted here we only incremented the HIGH value. |
403 | // In this case another thread that might call `next()` would end up |
404 | // right here trying to clear `kThresholdLo32` from LOW value as well, |
405 | // which is fine. |
406 | _lo.fetch_and(uint32_t(~kThresholdLo32)); |
407 | continue; |
408 | } |
409 | |
410 | return (uint64_t(hiValue) << 32) | loValue; |
411 | } |
412 | } |
413 | #else |
414 | std::atomic<uint64_t> _counter; |
415 | |
416 | BL_INLINE void reset() noexcept { _counter = 0; } |
417 | BL_INLINE uint64_t next() noexcept { return ++_counter; } |
418 | #endif |
419 | }; |
420 | |
421 | //! \} |
422 | //! \endcond |
423 | |
424 | #endif // BLEND2D_BLTHREADING_P_H |
425 | |