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
24class BLMutex;
25class BLRWLock;
26class BLConditionVariable;
27class BLThreadEvent;
28
29struct BLThread;
30struct BLThreadVirt;
31struct BLThreadAttributes;
32
33// ============================================================================
34// [Typedefs]
35// ============================================================================
36
37typedef void (BL_CDECL* BLThreadFunc)(BLThread* thread, void* data) BL_NOEXCEPT;
38
39// ============================================================================
40// [Constants]
41// ============================================================================
42
43enum 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
53static BL_INLINE void blAtomicThreadFence(std::memory_order order = std::memory_order_release) noexcept {
54 std::atomic_thread_fence(order);
55}
56
57template<typename T>
58static 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
63template<typename T>
64static 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
74static BL_INLINE void blThreadYield() noexcept { Sleep(0); }
75#else
76static BL_INLINE void blThreadYield() noexcept { sched_yield(); }
77#endif
78
79#ifndef _WIN32
80static 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.
96class BLMutex {
97public:
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.
127class BLMutexGuard {
128public:
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
146class BLRWLock {
147public:
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
184class BLRWLockReadGuard {
185public:
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
195class BLRWLockWriteGuard {
196public:
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
210class BLConditionVariable {
211public:
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
286BL_HIDDEN BLResult BL_CDECL blThreadEventCreate(BLThreadEvent* self, bool manualReset, bool signaled) noexcept;
287BL_HIDDEN BLResult BL_CDECL blThreadEventDestroy(BLThreadEvent* self) noexcept;
288BL_HIDDEN bool BL_CDECL blThreadEventIsSignaled(const BLThreadEvent* self) noexcept;
289BL_HIDDEN BLResult BL_CDECL blThreadEventSignal(BLThreadEvent* self) noexcept;
290BL_HIDDEN BLResult BL_CDECL blThreadEventReset(BLThreadEvent* self) noexcept;
291BL_HIDDEN BLResult BL_CDECL blThreadEventWait(BLThreadEvent* self) noexcept;
292BL_HIDDEN BLResult BL_CDECL blThreadEventTimedWait(BLThreadEvent* self, uint64_t microseconds) noexcept;
293
294class BLThreadEvent {
295public:
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
318struct BLThreadAttributes {
319 uint32_t stackSize;
320};
321
322struct 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
329struct 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
353BL_HIDDEN BLResult BL_CDECL blThreadCreate(BLThread** threadOut, const BLThreadAttributes* attributes, BLThreadFunc exitFunc, void* exitData) noexcept;
354
355#ifndef _WIN32
356BL_HIDDEN BLResult blThreadCreatePt(BLThread** threadOut, const pthread_attr_t* ptAttr, BLThreadFunc exitFunc, void* exitData) noexcept;
357BL_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.
373struct 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