HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
wfree_unordered_map.hpp
1// Copyright Take Vos 2019, 2021.
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 "../utility/utility.hpp"
8#include "../macros.hpp"
9#include <atomic>
10#include <array>
11#include <optional>
12#include <vector>
13#include <typeinfo>
14#include <typeindex>
15
16
17
18namespace hi::inline v1 {
19
20template<typename K, typename V>
26
36 K key;
37
38 constexpr wfree_unordered_map_item() noexcept requires(std::is_same_v<K, std::type_index>) :
39 value(), hash(0), key(std::type_index(typeid(void)))
40 {
41 }
42
43 constexpr wfree_unordered_map_item() noexcept requires(not std::is_same_v<K, std::type_index>) : value(), hash(0), key() {}
44
45 constexpr wfree_unordered_map_item(wfree_unordered_map_item const &) noexcept = delete;
46 constexpr wfree_unordered_map_item(wfree_unordered_map_item &&) noexcept = delete;
47 ~wfree_unordered_map_item() noexcept = default;
48 constexpr wfree_unordered_map_item &operator=(wfree_unordered_map_item const &) noexcept = delete;
49 constexpr wfree_unordered_map_item &operator=(wfree_unordered_map_item &&) noexcept = delete;
50};
51
57template<typename K, typename V, std::size_t MAX_NR_ITEMS>
59public:
60 using key_type = K;
61 using mapped_type = V;
62
63private:
64 constexpr static std::size_t CAPACITY = MAX_NR_ITEMS * 2;
65
67
68public:
69 constexpr wfree_unordered_map() noexcept = default;
70 constexpr wfree_unordered_map(wfree_unordered_map const &) noexcept = delete;
71 constexpr wfree_unordered_map(wfree_unordered_map &&) noexcept = delete;
72 ~wfree_unordered_map() noexcept = default;
73 constexpr wfree_unordered_map &operator=(wfree_unordered_map const &) noexcept = delete;
74 constexpr wfree_unordered_map &operator=(wfree_unordered_map &&) noexcept = delete;
75
76 static std::size_t make_hash(K const &key) noexcept
77 {
78 hilet hash = std::hash<K>{}(key);
79 return hash >= 3 ? hash : hash + 3;
80 }
81
82 void insert(K key, V value) noexcept
83 {
84 hilet hash = make_hash(key);
85
86 auto index = hash % CAPACITY;
87 while (true) {
88 auto &item = items[index];
89
90 // First look for an empty (0) item, highly likely when doing insert.
91 std::size_t item_hash = 0;
92 if (item.hash.compare_exchange_strong(item_hash, 1, std::memory_order::acquire)) {
93 // Success, we found an empty entry, we marked it as busy (1).
94 item.key = std::move(key);
95 item.value = value;
96 item.hash.store(hash, std::memory_order::release);
97 return;
98
99 } else if (item_hash == hash && key == item.key) {
100 // Key was already in map, replace the value.
101 item.value = value;
102 return;
103
104 } else {
105 // Either this item was already in use by another key, or
106 // Another thread was ahead of us with claiming this item (hopefully the other
107 // thread doesn't insert the same key).
108 // Even though compare_exchange was used here, this algorithm is wait-free since all threads
109 // including the one here are making normal progress.
110 index = (index + 1) % CAPACITY;
111 }
112 }
113 }
114
115 std::vector<K> keys() const noexcept
116 {
118 // XXX - with counting items, we could reserve capacity.
119
120 for (hilet &item : items) {
121 if (item.hash >= 3) {
122 r.push_back(item.key);
123 }
124 }
125 return r;
126 }
127
128 V &operator[](K const &key) noexcept
129 {
130 hilet hash = make_hash(key);
131
132 auto index = hash % CAPACITY;
133 while (true) {
134 auto &item = items[index];
135
136 // First look for an empty (0) item, highly likely when doing insert.
137 std::size_t item_hash = 0;
138 if (item.hash.compare_exchange_strong(item_hash, 1, std::memory_order::acquire)) {
139 // Success, we found an empty entry, we marked it as busy (1).
140 item.key = std::move(key);
141 item.hash.store(hash, std::memory_order::release);
142
143 if constexpr (std::is_default_constructible_v<V>) {
144 item.value = {};
145 } else {
146 // Make sure we default initialize the memory.
147 std::memset(&(item.value), 0, sizeof(item.value));
148 }
149 return item.value;
150
151 } else if (item_hash == hash && key == item.key) {
152 // Key was already in map, replace the value.
153 return item.value;
154
155 } else {
156 // Either this item was already in use by another key, or
157 // Another thread was ahead of us with claiming this item (hopefully the other
158 // thread doesn't insert the same key).
159 // Even though compare_exchange was used here, this algorithm is wait-free since all threads
160 // including the one here are making normal progress.
161 index = (index + 1) % CAPACITY;
162 }
163 }
164 }
165
166 std::optional<V> get(K const &key) const noexcept
167 {
168 hilet hash = make_hash(key);
169
170 auto index = hash % CAPACITY;
171 while (true) {
172 auto &item = items[index];
173
174 auto item_hash = item.hash.load(std::memory_order::acquire);
175
176 if (item_hash == hash && key == item.key) {
177 // Found key
178 return {item.value};
179
180 } else if (item_hash == 0) {
181 // Item is empty.
182 return {};
183
184 } else {
185 index = (index + 1) % CAPACITY;
186 }
187 }
188 }
189
190 V get(K const &key, V const &default_value) const noexcept
191 {
192 if (hilet optional_value = get(key)) {
193 return *optional_value;
194 } else {
195 return default_value;
196 }
197 }
198
199 std::optional<V> erase(K const &key) noexcept
200 {
201 hilet hash = make_hash(key);
202
203 auto index = hash % CAPACITY;
204 while (true) {
205 auto &item = items[index];
206 auto item_hash = item.hash.load(std::memory_order::acquire);
207
208 if (item_hash == hash && key == item.key) {
209 // Set tombstone. Don't actually delete the key or value.
210 item.hash.store(1, std::memory_order::release);
211 return {item.value};
212
213 } else if (item_hash == 0) {
214 // Item is empty.
215 return {};
216
217 } else {
218 index = (index + 1) % CAPACITY;
219 }
220 }
221 }
222};
223
224} // namespace hi::inline v1
STL namespace.
DOXYGEN BUG.
Definition algorithm.hpp:16
constexpr Out narrow_cast(In const &rhs) noexcept
Cast numeric values without loss of precision.
Definition cast.hpp:377
Definition wfree_unordered_map.hpp:21
V value
Definition wfree_unordered_map.hpp:25
std::atomic< std::size_t > hash
Definition wfree_unordered_map.hpp:35
Definition wfree_unordered_map.hpp:58
T memset(T... args)
T move(T... args)
T push_back(T... args)