Everett
Loading...
Searching...
No Matches
nursery_map.h
Go to the documentation of this file.
1
12#pragma once
13
14#include <algorithm>
15#include <cstddef>
16#include <functional>
17#include <memory>
18#include <stdexcept>
19#include <utility>
20
21namespace everett {
22 // One owner edits a transient map. Frozen snapshots share immutable bindings
23 // and can be read or independently thawed while that owner keeps editing.
24 // Keys are copy-constructible, values are move-constructible, and neither
25 // type needs assignment. A traversal callback must not edit that transient.
26 // Comparators must be stable strict weak orders; values must not mutate
27 // externally shared pointees behind the map's const access.
28 //
29 // freeze() closes one shared edit token, without walking the tree. The next
30 // edit copies its AVL path; subsequent private edits reuse owned nodes. A
31 // snapshot copy is O(1), lookups/edits take O(log n) comparisons, and edits
32 // allocate at most O(log n) nodes. Final-owner reclamation is separate work.
33 template <class Key, class Value, class Compare = std::less<Key>> struct nursery_map {
34 private:
35 struct edit_token { bool open = true; };
36 struct binding {
37 Key key;
38 Value value;
39 template <class K, class V> binding(K && next_key, V && next_value)
40 : key(std::forward<K>(next_key)), value(std::forward<V>(next_value)) {}
41 };
42 using binding_pointer = std::shared_ptr<binding const>;
43 struct node;
44 using node_pointer = std::shared_ptr<node>;
45 struct node {
48 std::shared_ptr<edit_token> edit;
49 unsigned height = 1;
50 node(binding_pointer value, std::shared_ptr<edit_token> owner)
51 : entry(std::move(value)), edit(std::move(owner)) {}
52 node(node const & old, std::shared_ptr<edit_token> owner)
53 : entry(old.entry), left(old.left), right(old.right), edit(std::move(owner)), height(old.height) {}
54 };
55
56 public:
57 using key_type = Key;
58 using mapped_type = Value;
59 using compare_type = Compare;
60
61 struct statistics {
62 std::size_t nodes_created = 0;
63 std::size_t path_copies = 0;
64 std::size_t rotations = 0;
65 };
66
68 snapshot_type() : compare_(std::make_shared<Compare const>()) {}
69 snapshot_type(snapshot_type const &) = default;
71 snapshot_type(snapshot_type && other) noexcept
72 : root_(std::move(other.root_)), compare_(other.compare_), size_(std::exchange(other.size_, 0)) {}
73 snapshot_type & operator=(snapshot_type && other) noexcept {
74 if (this != &other) {
75 root_ = std::move(other.root_); compare_ = other.compare_; size_ = std::exchange(other.size_, 0);
76 }
77 return *this;
78 }
79 Value const * find(Key const & key) const { return find_in(root_, key, *compare_); }
80 std::size_t size() const noexcept { return size_; }
81 bool empty() const noexcept { return !size_; }
82 template <class F> void for_each(F && visit) const { visit_in(root_, visit); }
83 private:
84 friend struct nursery_map;
86 std::shared_ptr<Compare const> compare_;
87 std::size_t size_ = 0;
88 snapshot_type(node_pointer root, std::shared_ptr<Compare const> compare, std::size_t size) noexcept
89 : root_(std::move(root)), compare_(std::move(compare)), size_(size) {}
90 };
91
92 explicit nursery_map(Compare compare = {}) : compare_(std::make_shared<Compare const>(std::move(compare))) {}
93 nursery_map(nursery_map const &) = delete;
94 nursery_map & operator=(nursery_map const &) = delete;
95 nursery_map(nursery_map && other) noexcept
96 : root_(std::move(other.root_)), compare_(other.compare_), edit_(std::move(other.edit_)),
97 size_(std::exchange(other.size_, 0)), work_(std::exchange(other.work_, {})),
98 failed_(std::exchange(other.failed_, false)) {}
99 nursery_map & operator=(nursery_map && other) noexcept {
100 if (this != &other) {
101 root_ = std::move(other.root_); compare_ = other.compare_; edit_ = std::move(other.edit_);
102 size_ = std::exchange(other.size_, 0); work_ = std::exchange(other.work_, {});
103 failed_ = std::exchange(other.failed_, false);
104 }
105 return *this;
106 }
107
110 if (edit_) edit_->open = false;
111 return {root_, compare_, size_};
112 }
113 static nursery_map thaw(snapshot_type const & snapshot) {
114 return nursery_map(snapshot.root_, snapshot.compare_, snapshot.size_);
115 }
116 Value const * find(Key const & key) const { require_active(); return find_in(root_, key, *compare_); }
117 std::size_t size() const { require_active(); return size_; }
118 bool empty() const { return !size(); }
119 template <class F> void for_each(F && visit) const { require_active(); visit_in(root_, visit); }
120 bool failed() const noexcept { return failed_; }
121 statistics work() const noexcept { return work_; }
122
123 // Returned pointers belong to this version. A transient pointer must not
124 // survive another edit unless the caller retains a snapshot containing it.
125 // Any exception inside an edit poisons that transient; existing snapshots
126 // remain valid. Argument construction before entry leaves it unchanged.
127 bool insert_or_assign(Key key, Value value) {
129 try {
130 begin_edit();
131 auto inserted = insert(root_, key, value);
132 size_ += std::size_t(inserted);
133 return inserted;
134 } catch (...) { failed_ = true; throw; }
135 }
136 bool erase(Key const & key) {
138 try {
139 begin_edit();
140 auto erased = remove(root_, key);
141 size_ -= std::size_t(erased);
142 return erased;
143 } catch (...) { failed_ = true; throw; }
144 }
145
146 private:
148 std::shared_ptr<Compare const> compare_;
149 std::shared_ptr<edit_token> edit_;
150 std::size_t size_ = 0;
152 bool failed_ = false;
153
154 nursery_map(node_pointer root, std::shared_ptr<Compare const> compare, std::size_t size)
155 : root_(std::move(root)), compare_(std::move(compare)), size_(size) {}
156 void require_active() const {
157 if (failed_) throw std::logic_error("failed nursery map");
158 }
159 void begin_edit() {
160 if (!edit_ || !edit_->open) edit_ = std::make_shared<edit_token>();
161 }
162 void editable(node_pointer & item) {
163 // A child's reference count cannot detect snapshots sharing its parent.
164 // Only the current, still-open edit identity permits mutation in place.
165 if (item->edit != edit_) {
166 item = std::make_shared<node>(*item, edit_);
168 }
169 }
170 static unsigned height(node_pointer const & item) noexcept { return item ? item->height : 0; }
171 static void refresh(node_pointer const & item) noexcept {
172 item->height = 1 + std::max(height(item->left), height(item->right));
173 }
174 static int balance(node_pointer const & item) noexcept {
175 return int(height(item->left)) - int(height(item->right));
176 }
178 editable(item->right);
179 auto next = std::move(item->right);
180 item->right = std::move(next->left);
181 refresh(item);
182 next->left = std::move(item);
183 refresh(next);
184 item = std::move(next);
186 }
188 editable(item->left);
189 auto next = std::move(item->left);
190 item->left = std::move(next->right);
191 refresh(item);
192 next->right = std::move(item);
193 refresh(next);
194 item = std::move(next);
196 }
197 void rebalance(node_pointer & item) {
198 refresh(item);
199 if (balance(item) > 1) {
200 editable(item->left);
201 if (balance(item->left) < 0) rotate_left(item->left);
202 rotate_right(item);
203 } else if (balance(item) < -1) {
204 editable(item->right);
205 if (balance(item->right) > 0) rotate_right(item->right);
206 rotate_left(item);
207 }
208 }
209 bool insert(node_pointer & item, Key & key, Value & value) {
210 if (!item) {
211 auto entry = std::make_shared<binding const>(std::move(key), std::move(value));
212 item = std::make_shared<node>(std::move(entry), edit_);
214 return true;
215 }
216 bool inserted;
217 if ((*compare_)(key, item->entry->key)) {
218 editable(item); inserted = insert(item->left, key, value);
219 } else if ((*compare_)(item->entry->key, key)) {
220 editable(item); inserted = insert(item->right, key, value);
221 } else {
222 // Preserve the original representative of comparator-equivalent keys.
223 auto entry = std::make_shared<binding const>(item->entry->key, std::move(value));
224 editable(item); item->entry = std::move(entry);
225 return false;
226 }
227 rebalance(item);
228 return inserted;
229 }
231 if (!item->left) {
232 auto entry = item->entry;
233 auto next = item->right;
234 item = std::move(next);
235 return entry;
236 }
237 editable(item);
238 auto first = remove_first(item->left);
239 rebalance(item);
240 return first;
241 }
242 bool remove(node_pointer & item, Key const & key) {
243 if (!item) return false;
244 bool erased;
245 if ((*compare_)(key, item->entry->key)) {
246 editable(item); erased = remove(item->left, key);
247 } else if ((*compare_)(item->entry->key, key)) {
248 editable(item); erased = remove(item->right, key);
249 } else {
250 if (!item->left || !item->right) {
251 auto next = item->left ? item->left : item->right;
252 item = std::move(next);
253 return true;
254 }
255 editable(item); item->entry = remove_first(item->right);
256 erased = true;
257 }
258 if (erased) rebalance(item);
259 return erased;
260 }
261 static Value const * find_in(node_pointer const & root, Key const & key, Compare const & compare) {
262 auto item = root.get();
263 while (item) {
264 if (compare(key, item->entry->key)) item = item->left.get();
265 else if (compare(item->entry->key, key)) item = item->right.get();
266 else return &item->entry->value;
267 }
268 return nullptr;
269 }
270 template <class F> static void visit_in(node_pointer const & item, F & visit) {
271 if (!item) return;
272 visit_in(item->left, visit);
273 std::invoke(visit, std::as_const(item->entry->key), std::as_const(item->entry->value));
274 visit_in(item->right, visit);
275 }
276 };
277}
Definition active_engine.h:18
Definition nursery_map.h:36
binding(K &&next_key, V &&next_value)
Definition nursery_map.h:39
Key key
Definition nursery_map.h:37
Value value
Definition nursery_map.h:38
Definition nursery_map.h:35
bool open
Definition nursery_map.h:35
Definition nursery_map.h:45
std::shared_ptr< edit_token > edit
Definition nursery_map.h:48
node(binding_pointer value, std::shared_ptr< edit_token > owner)
Definition nursery_map.h:50
unsigned height
Definition nursery_map.h:49
node_pointer right
Definition nursery_map.h:47
node(node const &old, std::shared_ptr< edit_token > owner)
Definition nursery_map.h:52
node_pointer left
Definition nursery_map.h:47
binding_pointer entry
Definition nursery_map.h:46
Definition nursery_map.h:67
snapshot_type & operator=(snapshot_type const &)=default
std::size_t size_
Definition nursery_map.h:87
void for_each(F &&visit) const
Definition nursery_map.h:82
bool empty() const noexcept
Definition nursery_map.h:81
std::size_t size() const noexcept
Definition nursery_map.h:80
snapshot_type(snapshot_type const &)=default
std::shared_ptr< Compare const > compare_
Definition nursery_map.h:86
snapshot_type(node_pointer root, std::shared_ptr< Compare const > compare, std::size_t size) noexcept
Definition nursery_map.h:88
Value const * find(Key const &key) const
Definition nursery_map.h:79
snapshot_type(snapshot_type &&other) noexcept
Definition nursery_map.h:71
snapshot_type()
Definition nursery_map.h:68
snapshot_type & operator=(snapshot_type &&other) noexcept
Definition nursery_map.h:73
node_pointer root_
Definition nursery_map.h:85
Definition nursery_map.h:61
std::size_t path_copies
Definition nursery_map.h:63
std::size_t nodes_created
Definition nursery_map.h:62
std::size_t rotations
Definition nursery_map.h:64
Definition nursery_map.h:33
std::shared_ptr< binding const > binding_pointer
Definition nursery_map.h:42
std::shared_ptr< node > node_pointer
Definition nursery_map.h:44
static void visit_in(node_pointer const &item, F &visit)
Definition nursery_map.h:270
void begin_edit()
Definition nursery_map.h:159
nursery_map(nursery_map const &)=delete
static unsigned height(node_pointer const &item) noexcept
Definition nursery_map.h:170
static void refresh(node_pointer const &item) noexcept
Definition nursery_map.h:171
Value const * find(Key const &key) const
Definition nursery_map.h:116
std::shared_ptr< Compare const > compare_
Definition nursery_map.h:148
static nursery_map thaw(snapshot_type const &snapshot)
Definition nursery_map.h:113
bool failed() const noexcept
Definition nursery_map.h:120
nursery_map(nursery_map &&other) noexcept
Definition nursery_map.h:95
void require_active() const
Definition nursery_map.h:156
Value mapped_type
Definition nursery_map.h:58
binding_pointer remove_first(node_pointer &item)
Definition nursery_map.h:230
nursery_map & operator=(nursery_map &&other) noexcept
Definition nursery_map.h:99
bool insert_or_assign(Key key, Value value)
Definition nursery_map.h:127
std::size_t size_
Definition nursery_map.h:150
static int balance(node_pointer const &item) noexcept
Definition nursery_map.h:174
void rotate_left(node_pointer &item)
Definition nursery_map.h:177
void editable(node_pointer &item)
Definition nursery_map.h:162
statistics work() const noexcept
Definition nursery_map.h:121
statistics work_
Definition nursery_map.h:151
nursery_map(node_pointer root, std::shared_ptr< Compare const > compare, std::size_t size)
Definition nursery_map.h:154
bool remove(node_pointer &item, Key const &key)
Definition nursery_map.h:242
bool empty() const
Definition nursery_map.h:118
bool erase(Key const &key)
Definition nursery_map.h:136
nursery_map & operator=(nursery_map const &)=delete
void rebalance(node_pointer &item)
Definition nursery_map.h:197
void for_each(F &&visit) const
Definition nursery_map.h:119
static Value const * find_in(node_pointer const &root, Key const &key, Compare const &compare)
Definition nursery_map.h:261
Compare compare_type
Definition nursery_map.h:59
std::shared_ptr< edit_token > edit_
Definition nursery_map.h:149
snapshot_type freeze()
Definition nursery_map.h:108
bool insert(node_pointer &item, Key &key, Value &value)
Definition nursery_map.h:209
node_pointer root_
Definition nursery_map.h:147
std::size_t size() const
Definition nursery_map.h:117
bool failed_
Definition nursery_map.h:152
Key key_type
Definition nursery_map.h:57
void rotate_right(node_pointer &item)
Definition nursery_map.h:187
nursery_map(Compare compare={})
Definition nursery_map.h:92