Gaia-ECS v1.0.0
A simple and powerful entity component system
Loading...
Searching...
No Matches
futex.h
1#pragma once
2#include "gaia/config/config.h"
3#include "gaia/config/profiler.h"
4
5#include <atomic>
6#include <mutex>
7
8#include "gaia/core/utility.h"
9#include "gaia/mt/event.h"
10
11namespace gaia {
12 namespace mt {
13 namespace detail {
15 inline static constexpr uint32_t WaitMaskAll = 0x7FFFFFFF;
16 inline static constexpr uint32_t WaitMaskAny = ~0u;
17
18 struct FutexWaitNode {
19 FutexWaitNode* pNext = nullptr;
20 const std::atomic_uint32_t* pFutexValue = nullptr;
21 uint32_t waitMask = WaitMaskAny;
22 Event evt;
23 };
24
25 struct FutexBucket {
26 GAIA_PROF_MUTEX(std::mutex, mtx);
27 FutexWaitNode* pFirst = nullptr;
28
29 // Since there shouldn't be that many threads waiting at any one time, this seems like a good
30 // number of hash table buckets. Making it prime number for better spread.
31 static constexpr uint32_t BUCKET_SIZE = 37;
32
33 static FutexBucket& get(const std::atomic_uint32_t* pFutexValue) {
34 static FutexBucket s_buckets[BUCKET_SIZE];
35 return s_buckets[(uintptr_t(pFutexValue) >> 2) % BUCKET_SIZE];
36 }
37 };
38
41 inline thread_local FutexWaitNode t_WaitNode;
43
44 } // namespace detail
45
57 struct Futex {
59 enum class Result {
61 Change,
63 WakeUp
64 };
65
73 static Result wait(const std::atomic_uint32_t* pFutexValue, uint32_t expected, uint32_t waitMask) {
74 GAIA_PROF_SCOPE(futex::wait);
75
76 GAIA_ASSERT(waitMask != 0);
77
78 auto& bucket = detail::FutexBucket::get(pFutexValue);
79 auto& node = detail::t_WaitNode;
80 node.pFutexValue = pFutexValue;
81 node.waitMask = waitMask;
82
83 {
84 auto& mtx = GAIA_PROF_EXTRACT_MUTEX(bucket.mtx);
85 core::lock_scope lock(mtx);
86 GAIA_PROF_LOCK_MARK(bucket.mtx);
87
88 const uint32_t futexValue = pFutexValue->load(std::memory_order_relaxed);
89 if (futexValue != expected)
90 return Result::Change;
91
92 node.pNext = bucket.pFirst;
93 bucket.pFirst = &node;
94 }
95
96 node.evt.wait();
97 return Result::WakeUp;
98 }
99
105 static uint32_t
106 wake(const std::atomic_uint32_t* pFutexValue, uint32_t wakeCount, uint32_t wakeMask = detail::WaitMaskAny) {
107 GAIA_PROF_SCOPE(futex::wake);
108
109 GAIA_ASSERT(wakeMask != 0);
110
111 auto& bucket = detail::FutexBucket::get(pFutexValue);
112 auto& mtx = GAIA_PROF_EXTRACT_MUTEX(bucket.mtx);
113 core::lock_scope lock(mtx);
114 GAIA_PROF_LOCK_MARK(bucket.mtx);
115
116 uint32_t numAwoken = 0;
117 auto** ppNode = &bucket.pFirst;
118 for (auto* pNode = *ppNode; numAwoken < wakeCount && pNode != nullptr; pNode = *ppNode) {
119 if (pNode->pFutexValue == pFutexValue && (pNode->waitMask & wakeMask) != 0) {
120 ++numAwoken;
121
122 // Unlink the node
123 *ppNode = pNode->pNext;
124 pNode->pNext = nullptr;
125
126 pNode->evt.set();
127 } else {
128 ppNode = &pNode->pNext;
129 }
130 }
131
132 return numAwoken;
133 }
134 };
135 } // namespace mt
136} // namespace gaia
RAII helper that calls lock() on construction and unlock() on destruction.
Definition utility.h:188
An implementation of a simple futex (fast userspace mutex). Only wait and wake are implemented.
Definition futex.h:57
static Result wait(const std::atomic_uint32_t *pFutexValue, uint32_t expected, uint32_t waitMask)
Suspends the caller on the futex while its value remains expected.
Definition futex.h:73
static uint32_t wake(const std::atomic_uint32_t *pFutexValue, uint32_t wakeCount, uint32_t wakeMask=detail::WaitMaskAny)
Wakes up to wakeCount waiters whose waitMask matches wakeMask.
Definition futex.h:106
Result
Outcome of a futex wait attempt.
Definition futex.h:59
@ WakeUp
Futex woken up as a result of wake()
@ Change
Futex value didn't match the expected one.