HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
rcu.hpp
1// Copyright Take Vos 2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "wfree_idle_count.hpp"
8#include "unfair_mutex.hpp"
9#include <vector>
10#include <tuple>
11#include <mutex>
12
13namespace hi::inline v1 {
14
20template<typename T, typename Allocator = std::allocator<T>>
21class rcu {
22public:
23 using value_type = T;
24 using allocator_type = Allocator;
25
31 constexpr rcu(allocator_type allocator = allocator_type{}) noexcept : _allocator(allocator) {}
32
33 ~rcu() = default;
34 rcu(rcu const&) = delete;
35 rcu(rcu&&) = delete;
36 rcu& operator=(rcu const&) = delete;
37 rcu& operator=(rcu&&) = delete;
38
41 void read_lock() noexcept
42 {
43 _idle_count.lock();
44 }
45
48 void read_unlock() noexcept
49 {
50 _idle_count.unlock();
51 }
52
55 void write_lock() noexcept
56 {
57 _idle_count.lock();
58 }
59
62 void write_unlock() noexcept
63 {
64 _idle_count.unlock();
65 }
66
69 value_type const *get() noexcept
70 {
71 return _ptr.load(std::memory_order::acquire);
72 }
73
79 value_type const *unsafe_get() noexcept
80 {
81#if defined(__alpha__) or defined(__alpha) or defined(_M_ALPHA)
82 return _ptr.load(std::memory_order::acquire);
83#else
84 return _ptr.load(std::memory_order::relaxed);
85#endif
86 }
87
92 [[nodiscard]] uint64_t version() const noexcept
93 {
94 return *_idle_count;
95 }
96
99 [[nodiscard]] size_t capacity() const noexcept
100 {
101 hilet lock = std::scoped_lock(_old_ptrs_mutex);
102 return _old_ptrs.size() + not empty();
103 }
104
111 [[nodiscard]] value_type *exchange(value_type *ptr) noexcept
112 {
113 return _ptr.exchange(ptr, std::memory_order::release);
114 }
115
118 [[nodiscard]] value_type *copy() const noexcept
119 {
120 hilet *allocation = std::allocator_traits<allocator_type>::allocate(_allocator, 1);
121 read_lock();
122 hilet *new_ptr = std::construct_at(allocation, *get());
123 read_unlock();
124 return new_ptr;
125 }
126
137 template<typename... Args>
138 void emplace(Args&&...args) noexcept
139 {
140 auto *const allocation = std::allocator_traits<allocator_type>::allocate(_allocator, 1);
141 auto *const new_ptr = std::construct_at(allocation, std::forward<Args>(args)...);
142
143 write_lock();
144 auto *const old_ptr = exchange(new_ptr);
145 hilet old_version = version();
146 write_unlock();
147
148 add_old_copy(old_version, old_ptr);
149 }
150
151 [[nodiscard]] bool empty() const noexcept
152 {
153 return not to_bool(_ptr.load(std::memory_order::relaxed));
154 }
155
156 explicit operator bool() const noexcept
157 {
158 return not empty();
159 }
160
161 void reset() noexcept
162 {
163 write_lock();
164 auto *const old_ptr = _ptr.exchange(nullptr, std::memory_order::release);
165 hilet old_version = *_idle_count;
166 write_unlock();
167
168 add_old_copy(old_version, old_ptr);
169 }
170
171 rcu& operator=(nullptr_t) noexcept
172 {
173 reset();
174 return *this;
175 }
176
186 void add_old_copy(uint64_t old_version, value_type *old_ptr) noexcept
187 {
188 if (not old_ptr) {
189 return;
190 }
191
192 hilet new_version = version();
193
194 hilet lock = std::scoped_lock(_old_ptrs_mutex);
195 _old_ptrs.emplace_back(old_version, old_ptr);
196
197 // Destroy all objects from previous idle-count versions.
198 auto it = _old_ptrs.begin();
199 while (it != _old_ptrs.end() and it->first < new_version) {
200 std::destroy_at(it->second);
201 std::allocator_traits<allocator_type>::deallocate(_allocator, it->second, 1);
202 ++it;
203 }
204 _old_ptrs.erase(_old_ptrs.begin(), it);
205 }
206
207private:
208 std::atomic<value_type *> _ptr = nullptr;
209 mutable wfree_idle_count _idle_count;
210
211 allocator_type _allocator;
212 mutable unfair_mutex _old_ptrs_mutex;
214};
215
216template<typename RCU>
217class rcu_read {
218public:
219 using rcu_type = RCU;
220 using value_type = rcu_type::value_type;
221
222 rcu_read(RCU& rcu) noexcept : _rcu(rcu)
223 {
224 _rcu.read_lock();
225 _ptr = _rcu.get();
226 }
227
228 ~rcu_read()
229 {
230 if (_rcu) {
231 _rcu.read_unlock();
232 }
233 }
234
235 constexpr rcu_read() noexcept = default;
236
237 rcu_read(rcu_read const& other) noexcept : _rcu(other._rcu), _ptr(other._ptr)
238 {
239 _rcu.read_lock();
240 }
241
242 rcu_read(rcu_read&& other) noexcept : _rcu(std::exchange(other._rcu, nullptr)), _ptr(std::exchange(other._ptr, nullptr)) {}
243
244 rcu_read& operator=(rcu_read const& other)
245 {
246 if (_rcu != other._rcu) {
247 if (other._rcu) {
248 other._rcu->read_lock();
249 }
250 if (_rcu) {
251 _rcu->read_unlock();
252 }
253 }
254
255 _rcu = other._rcu;
256 _ptr = other._ptr;
257 _rcu.read_lock();
258 return *this;
259 }
260
261 rcu_read& operator=(rcu_read&& other) noexcept
262 {
263 if (_rcu and _rcu != other._rcu) {
264 _rcu->read_unlock();
265 }
266
267 _rcu = std::exchange(other._rcu, nullptr);
268 _ptr = std::exchange(other._ptr, nullptr);
269 return *this;
270 }
271
272 [[nodiscard]] constexpr value_type const *operator->() const noexcept
273 {
274 return _ptr;
275 }
276
277 [[nodiscard]] constexpr value_type const& operator*() const noexcept
278 {
279 hi_axiom(_ptr);
280 return *_ptr;
281 }
282
283 [[nodiscard]] constexpr bool empty() const noexcept
284 {
285 return _ptr == nullptr;
286 }
287
288 constexpr operator bool() const noexcept
289 {
290 return not empty();
291 }
292
293 [[nodiscard]] constexpr bool operator==(nullptr_t) const noexcept
294 {
295 return _rcu == nullptr;
296 }
297
298 void reset() noexcept
299 {
300 if (_rcu) {
301 _rcu.read_unlock();
302 }
303 _rcu = nullptr;
304 }
305
306 rcu_read& operator=(nullptr_t) noexcept
307 {
308 reset();
309 return *this;
310 }
311
312private:
313 rcu_type *_rcu = nullptr;
314 value_type const *_ptr = nullptr;
315};
316
317template<typename RCU>
319public:
320 using rcu_type = RCU;
321 using value_type = rcu_type::value_type;
322
323 rcu_write(rcu_type const& rcu) noexcept : _rcu(rcu), _ptr(rcu.copy()) {}
324
325 ~rcu_write()
326 {
327 reset();
328 }
329
330 constexpr rcu_write() noexcept = default;
331 rcu_write(rcu_write const&) = delete;
332 rcu_write& operator=(rcu_write const&) = delete;
333
334 rcu_write(rcu_write&& other) noexcept : _rcu(std::exchange(other._rcu, nullptr)), _ptr(std::exchange(other._ptr, nullptr)) {}
335
336 rcu_write& operator=(rcu_write&& other) noexcept
337 {
338 reset();
339 _rcu = std::exchange(other._rcu, nullptr);
340 _ptr = std::exchange(other._ptr, nullptr);
341 return *this;
342 }
343
344 [[nodiscard]] constexpr bool empty() const noexcept
345 {
346 return _ptr == nullptr;
347 }
348
349 constexpr operator bool() const noexcept
350 {
351 return not empty();
352 }
353
354 [[nodiscard]] constexpr bool operator==(nullptr_t) const noexcept
355 {
356 return _ptr == nullptr;
357 }
358
359 void reset() noexcept
360 {
361 if (_rcu) {
362 _rcu.write_lock();
363 hilet *old_ptr = _rcu.exchange(_ptr);
364 hilet *old_version = rcu.version();
365 _rcu.write_unlock();
366
367 _rcu.add_old_copy(old_version, old_ptr);
368 _rcu.cleanup();
369 _rcu = nullptr;
370 _ptr = nullptr;
371 }
372 }
373
374 rcu_write& operator=(nullptr_t) noexcept
375 {
376 reset();
377 }
378
379private:
380 rcu_type *_rcu = nullptr;
381 value_type *_ptr = nullptr;
382};
383
384} // namespace hi::inline v1
#define hilet
Invariant should be the default for variables.
Definition required.hpp:23
Read-copy-update.
Definition rcu.hpp:21
constexpr rcu(allocator_type allocator=allocator_type{}) noexcept
Construct a new rcu object.
Definition rcu.hpp:31
value_type const * get() noexcept
get the rcu-pointer.
Definition rcu.hpp:69
void add_old_copy(uint64_t old_version, value_type *old_ptr) noexcept
Add an old copy.
Definition rcu.hpp:186
void emplace(Args &&...args) noexcept
Emplace a new value.
Definition rcu.hpp:138
void read_lock() noexcept
Lock the rcu pointer for reading.
Definition rcu.hpp:41
void write_unlock() noexcept
Unlock the rcu pointer for writing.
Definition rcu.hpp:62
value_type * copy() const noexcept
Create a copy of the value.
Definition rcu.hpp:118
value_type * exchange(value_type *ptr) noexcept
Exchange the rcu-pointers.
Definition rcu.hpp:111
value_type const * unsafe_get() noexcept
Derefence the rcu-pointer.
Definition rcu.hpp:79
size_t capacity() const noexcept
Number of object that are currently allocated.
Definition rcu.hpp:99
void write_lock() noexcept
Lock the rcu pointer for writing.
Definition rcu.hpp:55
uint64_t version() const noexcept
The version of the lock.
Definition rcu.hpp:92
void read_unlock() noexcept
Unlock the rcu pointer for reading.
Definition rcu.hpp:48
Definition rcu.hpp:217
Definition rcu.hpp:318
Counts how many times a critical section was idle.
Definition wfree_idle_count.hpp:39
T allocate(T... args)
T deallocate(T... args)