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