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