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#include <memory>
13
14namespace hi::inline v1 {
15
21template<typename T, typename Allocator = std::allocator<T>>
22class rcu {
23public:
24 using value_type = T;
25 using allocator_type = Allocator;
26
32 constexpr rcu(allocator_type allocator = allocator_type{}) noexcept : _allocator(allocator) {}
33
34 ~rcu() = default;
35 rcu(rcu const&) = delete;
36 rcu(rcu&&) = delete;
37 rcu& operator=(rcu const&) = delete;
38 rcu& operator=(rcu&&) = delete;
39
42 void lock() const noexcept
43 {
44 _idle_count.lock();
45 }
46
49 void unlock() const noexcept
50 {
51 _idle_count.unlock();
52 }
53
59 value_type const *get() const noexcept
60 {
61 return _ptr.load(std::memory_order::acquire);
62 }
63
71 value_type const *unsafe_get() noexcept
72 {
73#if defined(__alpha__) or defined(__alpha) or defined(_M_ALPHA)
74 return _ptr.load(std::memory_order::acquire);
75#else
76 return _ptr.load(std::memory_order::relaxed);
77#endif
78 }
79
85 [[nodiscard]] uint64_t version() const noexcept
86 {
87 return *_idle_count;
88 }
89
94 [[nodiscard]] size_t capacity() const noexcept
95 {
96 hilet lock = std::scoped_lock(_old_ptrs_mutex);
97 return _old_ptrs.size() + not empty();
98 }
99
106 [[nodiscard]] value_type *exchange(value_type *ptr) noexcept
107 {
108 return _ptr.exchange(ptr, std::memory_order::release);
109 }
110
118 [[nodiscard]] value_type *copy(value_type const *ptr) const noexcept
119 {
120 hi_axiom(ptr != nullptr);
121 value_type *new_ptr = std::allocator_traits<allocator_type>::allocate(_allocator, 1);
122 std::allocator_traits<allocator_type>::construct(_allocator, new_ptr, *ptr);
123 return std::launder(new_ptr);
124 }
125
132 [[nodiscard]] value_type *copy() const noexcept
133 {
134 auto *new_ptr = std::allocator_traits<allocator_type>::allocate(_allocator, 1);
135 lock();
136 value_type const * const ptr = get();
137 hi_axiom(ptr != nullptr);
138 std::allocator_traits<allocator_type>::construct(_allocator, new_ptr, *ptr);
139 unlock();
140 return std::launder(new_ptr);
141 }
142
147 void abort(value_type *ptr) const noexcept
148 {
151 }
152
157 void commit(value_type *ptr) noexcept
158 {
159 lock();
160 auto *old_ptr = exchange(ptr);
161 auto old_version = version();
162 unlock();
163 add_old_copy(old_version, old_ptr);
164 }
165
176 void emplace(auto&&...args) noexcept
177 {
178 value_type *const new_ptr = std::allocator_traits<allocator_type>::allocate(_allocator, 1);
180
181 lock();
182 auto *const old_ptr = exchange(new_ptr);
183 hilet old_version = version();
184 unlock();
185
186 add_old_copy(old_version, old_ptr);
187 }
188
189 [[nodiscard]] bool empty() const noexcept
190 {
191 return not to_bool(_ptr.load(std::memory_order::relaxed));
192 }
193
194 explicit operator bool() const noexcept
195 {
196 return not empty();
197 }
198
199 void reset() noexcept
200 {
201 lock();
202 auto *const old_ptr = _ptr.exchange(nullptr, std::memory_order::release);
203 hilet old_version = *_idle_count;
204 unlock();
205
206 add_old_copy(old_version, old_ptr);
207 }
208
209 rcu& operator=(nullptr_t) noexcept
210 {
211 reset();
212 return *this;
213 }
214
224 void add_old_copy(uint64_t old_version, value_type *old_ptr) noexcept
225 {
226 if (not old_ptr) {
227 return;
228 }
229
230 hilet new_version = version();
231
232 hilet lock = std::scoped_lock(_old_ptrs_mutex);
233 _old_ptrs.emplace_back(old_version, old_ptr);
234
235 // Destroy all objects from previous idle-count versions.
236 auto it = _old_ptrs.begin();
237 while (it != _old_ptrs.end() and it->first < new_version) {
239 std::allocator_traits<allocator_type>::deallocate(_allocator, it->second, 1);
240 ++it;
241 }
242 _old_ptrs.erase(_old_ptrs.begin(), it);
243 }
244
245private:
246 std::atomic<value_type *> _ptr = nullptr;
247 mutable wfree_idle_count _idle_count;
248
249 mutable allocator_type _allocator;
250 mutable unfair_mutex _old_ptrs_mutex;
252};
253
254} // namespace hi::inline v1
255
#define hilet
Invariant should be the default for variables.
Definition utility.hpp:23
#define hi_forward(x)
Forward a value, based on the decltype of the value.
Definition utility.hpp:29
DOXYGEN BUG.
Definition algorithm.hpp:15
Read-copy-update.
Definition rcu.hpp:22
constexpr rcu(allocator_type allocator=allocator_type{}) noexcept
Construct a new rcu object.
Definition rcu.hpp:32
void add_old_copy(uint64_t old_version, value_type *old_ptr) noexcept
Add an old copy.
Definition rcu.hpp:224
void unlock() const noexcept
Unlock the rcu pointer for reading.
Definition rcu.hpp:49
void commit(value_type *ptr) noexcept
Commit the copied value.
Definition rcu.hpp:157
value_type * copy() const noexcept
Create a copy of the value.
Definition rcu.hpp:132
value_type const * get() const noexcept
get the rcu-pointer.
Definition rcu.hpp:59
void lock() const noexcept
Lock the rcu pointer for reading.
Definition rcu.hpp:42
value_type * exchange(value_type *ptr) noexcept
Exchange the rcu-pointers.
Definition rcu.hpp:106
value_type const * unsafe_get() noexcept
get the rcu-pointer.
Definition rcu.hpp:71
void emplace(auto &&...args) noexcept
Emplace a new value.
Definition rcu.hpp:176
size_t capacity() const noexcept
Number of objects that are currently allocated.
Definition rcu.hpp:94
uint64_t version() const noexcept
The version of the lock.
Definition rcu.hpp:85
void abort(value_type *ptr) const noexcept
Abort a copy.
Definition rcu.hpp:147
value_type * copy(value_type const *ptr) const noexcept
Create a copy of the value at the given pointer.
Definition rcu.hpp:118
Counts how many times a critical section was idle.
Definition wfree_idle_count.hpp:39
T allocate(T... args)
T construct(T... args)
T deallocate(T... args)
T destroy(T... args)
T lock(T... args)