Wire Sysio Wire Sysion 1.0.0
Loading...
Searching...
No Matches
undo_index.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <boost/multi_index_container_fwd.hpp>
4#include <boost/intrusive/set.hpp>
5#include <boost/intrusive/avltree.hpp>
6#include <boost/intrusive/slist.hpp>
7#include <boost/container/deque.hpp>
8#include <boost/throw_exception.hpp>
9#include <boost/mpl/fold.hpp>
10#include <boost/mp11/list.hpp>
11#include <boost/mp11/algorithm.hpp>
12#include <boost/iterator/transform_iterator.hpp>
13#include <boost/lexical_cast.hpp>
14#include <boost/core/demangle.hpp>
15#include <boost/interprocess/interprocess_fwd.hpp>
16#include <cassert>
17#include <memory>
18#include <type_traits>
19#include <sstream>
20
21namespace chainbase {
22
23 template<typename F>
24 struct scope_exit {
25 public:
26 scope_exit(F&& f) : _f(f) {}
27 scope_exit(const scope_exit&) = delete;
28 scope_exit& operator=(const scope_exit&) = delete;
29 ~scope_exit() { if(!_canceled) _f(); }
30 void cancel() { _canceled = true; }
31 private:
32 F _f;
33 bool _canceled = false;
34 };
35
36 // Adapts multi_index's idea of keys to intrusive
37 template<typename KeyExtractor, typename T>
38 struct get_key {
39 using type = std::decay_t<decltype(KeyExtractor{}(std::declval<const T&>()))>;
40 decltype(auto) operator()(const T& arg) const { return KeyExtractor{}(arg); }
41 };
42
43 template<typename T>
44 struct value_holder {
45 template<typename... A>
46 value_holder(A&&... a) : _item(static_cast<A&&>(a)...) {}
48 };
49
50 template<class Tag>
52 offset_node_base() = default;
54 constexpr offset_node_base& operator=(const offset_node_base&) { return *this; }
55 std::ptrdiff_t _parent;
56 std::ptrdiff_t _left;
57 std::ptrdiff_t _right;
58 int _color;
59 };
60
61 template<class Tag>
64 using node_ptr = node*;
65 using const_node_ptr = const node*;
66 using color = int;
68 if(n->_parent == 1) return nullptr;
69 return (node_ptr)((char*)n + n->_parent);
70 }
71 static void set_parent(node_ptr n, node_ptr parent) {
72 if(parent == nullptr) n->_parent = 1;
73 else n->_parent = (char*)parent - (char*)n;
74 }
76 if(n->_left == 1) return nullptr;
77 return (node_ptr)((char*)n + n->_left);
78 }
79 static void set_left(node_ptr n, node_ptr left) {
80 if(left == nullptr) n->_left = 1;
81 else n->_left = (char*)left - (char*)n;
82 }
84 if(n->_right == 1) return nullptr;
85 return (node_ptr)((char*)n + n->_right);
86 }
87 static void set_right(node_ptr n, node_ptr right) {
88 if(right == nullptr) n->_right = 1;
89 else n->_right = (char*)right - (char*)n;
90 }
91 // red-black tree
93 return n->_color;
94 }
95 static void set_color(node_ptr n, color c) {
96 n->_color = c;
97 }
98 static color black() { return 0; }
99 static color red() { return 1; }
100 // avl tree
101 using balance = int;
103 return n->_color;
104 }
105 static void set_balance(node_ptr n, balance c) {
106 n->_color = c;
107 }
108 static balance negative() { return -1; }
109 static balance zero() { return 0; }
110 static balance positive() { return 1; }
111
112 // list
113 static node_ptr get_next(const_node_ptr n) { return get_right(n); }
114 static void set_next(node_ptr n, node_ptr next) { set_right(n, next); }
116 static void set_previous(node_ptr n, node_ptr previous) { set_left(n, previous); }
117 };
118
119 template<typename Node, typename Tag>
124 using value_type = typename Node::value_type;
126 using const_pointer = const value_type*;
127
129 return node_ptr{static_cast<Node*>(boost::intrusive::get_parent_from_member(&value, &value_holder<value_type>::_item))};
130 }
132 return const_node_ptr{static_cast<const Node*>(boost::intrusive::get_parent_from_member(&value, &value_holder<value_type>::_item))};
133 }
134 static pointer to_value_ptr(node_ptr n) { return pointer{&static_cast<Node*>(&*n)->_item}; }
135 static const_pointer to_value_ptr(const_node_ptr n) { return const_pointer{&static_cast<const Node*>(&*n)->_item}; }
136
137 static constexpr boost::intrusive::link_mode_type link_mode = boost::intrusive::normal_link;
138 };
139 template<typename Allocator, typename T>
140 using rebind_alloc_t = typename std::allocator_traits<Allocator>::template rebind_alloc<T>;
141
142 template<typename Index>
143 struct index_tag_impl { using type = void; };
144 template<template<typename...> class Index, typename Tag, typename... T>
145 struct index_tag_impl<Index<boost::multi_index::tag<Tag>, T...>> { using type = Tag; };
146 template<typename Index>
148
149 template<typename Tag, typename... Indices>
150 using find_tag = boost::mp11::mp_find<boost::mp11::mp_list<index_tag<Indices>...>, Tag>;
151
152 template<typename K, typename Allocator>
154
155 template<typename Node, typename OrderedIndex>
156 using set_base = boost::intrusive::avltree<
157 typename Node::value_type,
158 boost::intrusive::value_traits<offset_node_value_traits<Node, OrderedIndex>>,
159 boost::intrusive::key_of_value<get_key<typename OrderedIndex::key_from_value_type, typename Node::value_type>>,
160 boost::intrusive::compare<typename OrderedIndex::compare_type>>;
161
162 template<typename OrderedIndex>
163 constexpr bool is_valid_index = false;
164 template<typename... T>
165 constexpr bool is_valid_index<boost::multi_index::ordered_unique<T...>> = true;
166
167 template<typename Node, typename Tag>
168 using list_base = boost::intrusive::slist<
169 typename Node::value_type,
170 boost::intrusive::value_traits<offset_node_value_traits<Node, Tag>>>;
171
172 template<typename L, typename It, typename Pred, typename Disposer>
173 void remove_if_after_and_dispose(L& l, It it, It end, Pred&& p, Disposer&& d) {
174 for(;;) {
175 It next = it;
176 ++next;
177 if(next == end) break;
178 if(p(*next)) { l.erase_after_and_dispose(it, d); }
179 else { it = next; }
180 }
181 }
182
183 template<typename T, typename Allocator, typename... Indices>
184 class undo_index;
185
186 template<typename Node, typename OrderedIndex>
187 struct set_impl : private set_base<Node, OrderedIndex> {
189 // Allow compatible keys to match multi_index
190 template<typename K>
191 auto find(K&& k) const {
192 return base_type::find(static_cast<K&&>(k), this->key_comp());
193 }
194 template<typename K>
195 auto lower_bound(K&& k) const {
196 return base_type::lower_bound(static_cast<K&&>(k), this->key_comp());
197 }
198 template<typename K>
199 auto upper_bound(K&& k) const {
200 return base_type::upper_bound(static_cast<K&&>(k), this->key_comp());
201 }
202 template<typename K>
203 auto equal_range(K&& k) const {
204 return base_type::equal_range(static_cast<K&&>(k), this->key_comp());
205 }
206 using base_type::begin;
207 using base_type::end;
208 using base_type::rbegin;
209 using base_type::rend;
210 using base_type::size;
211 using base_type::iterator_to;
212 using base_type::empty;
213 template<typename T, typename Allocator, typename... Indices>
214 friend class undo_index;
215 };
216
217 template<typename T, typename S>
219
220 // Allows nested object to use a different allocator from the container.
221 template<template<typename> class A, typename T>
222 auto& propagate_allocator(A<T>& a) { return a; }
223 template<typename T, typename S>
224 auto& propagate_allocator(boost::interprocess::allocator<T, S>& a) { return a; }
225 template<typename T, typename S, std::size_t N>
226 auto propagate_allocator(boost::interprocess::node_allocator<T, S, N>& a) { return boost::interprocess::allocator<T, S>{a.get_segment_manager()}; }
227 template<typename T, typename S, std::size_t N>
228 auto propagate_allocator(boost::interprocess::private_node_allocator<T, S, N>& a) { return boost::interprocess::allocator<T, S>{a.get_segment_manager()}; }
229 template<typename T, typename S>
230 auto propagate_allocator(chainbase::chainbase_node_allocator<T, S>& a) { return boost::interprocess::allocator<T, S>{a.get_segment_manager()}; }
231
232 // Similar to boost::multi_index_container with an undo stack.
233 // Indices should be instances of ordered_unique.
234 template<typename T, typename Allocator, typename... Indices>
236 public:
237 using id_type = std::decay_t<decltype(std::declval<T>().id)>;
238 using value_type = T;
240
241 static_assert((... && is_valid_index<Indices>), "Only ordered_unique indices are supported");
242
243 undo_index() = default;
244 explicit undo_index(const Allocator& a) : _undo_stack{a}, _allocator{a}, _old_values_allocator{a} {}
246 dispose_undo();
247 clear_impl<1>();
248 std::get<0>(_indices).clear_and_dispose([&](pointer p){ dispose_node(*p); });
249 }
250
251 void validate()const {
252 if( sizeof(node) != _size_of_value_type || sizeof(*this) != _size_of_this )
253 BOOST_THROW_EXCEPTION( std::runtime_error("content of memory does not match data expected by executable") );
254 }
255
256 struct node : hook<Indices, Allocator>..., value_holder<T> {
257 using value_type = T;
259 template<typename... A>
260 explicit node(A&&... a) : value_holder<T>{static_cast<A&&>(a)...} {}
261 const T& item() const { return *this; }
262 uint64_t _mtime = 0; // _monotonic_revision when the node was last modified or created.
263 };
264 static constexpr int erased_flag = 2; // 0,1,and -1 are used by the tree
265
266 using indices_type = std::tuple<set_impl<node, Indices>...>;
267
268 using index0_set_type = std::tuple_element_t<0, indices_type>;
269 using alloc_traits = typename std::allocator_traits<Allocator>::template rebind_traits<node>;
270
271 static_assert(std::is_same_v<typename index0_set_type::key_type, id_type>, "first index must be id");
272
273 using index0_type = boost::mp11::mp_first<boost::mp11::mp_list<Indices...>>;
274 struct old_node : hook<index0_type, Allocator>, value_holder<T> {
275 using value_type = T;
277 template<typename... A>
278 explicit old_node(const T& t) : value_holder<T>{t} {}
279 uint64_t _mtime = 0; // Backup of the node's _mtime, to be restored on undo
280 typename alloc_traits::pointer _current; // pointer to the actual node
281 };
282
285 using const_iterator = typename index0_set_type::const_iterator;
286
287 // The undo stack is implemented as a deque of undo_states
288 // that index into a pair of singly linked lists.
289 //
290 // The primary key (id) is managed by the undo_index. The id's are assigned sequentially to
291 // objects in the order of insertion. User code MUST NOT modify the primary key.
292 // A given id can only be reused if its insertion is undone.
293 //
294 // Each undo session remembers the state of the table at the point when it was created.
295 //
296 // Within the undo state at the top of the undo stack:
297 // A primary key is new if it is at least old_next_id.
298 //
299 // A primary key is removed if it exists in the removed_values list before removed_values_end.
300 // A node has a flag which indicates whether it has been removed.
301 //
302 // A primary key is modified if it exists in the old_values list before old_values_end
303 //
304 // A primary key exists at most once in either the main table or removed values.
305 // Every primary key in old_values also exists in either the main table OR removed_values.
306 // If a primary key exists in both removed_values AND old_values, undo will restore the value from old_values.
307 // A primary key may appear in old_values any number of times. If it appears more than once
308 // within a single undo session, undo will restore the oldest value.
309 //
310 // The following describes the minimal set of operations required to maintain the undo stack:
311 // start session: remember next_id and the current heads of old_values and removed_values.
312 // squash: drop the last undo state
313 // create: nop
314 // modify: push a copy of the object onto old_values
315 // remove: move node to removed index and set removed flag
316 //
317 // Operations on a given key MUST always follow the sequence: CREATE MODIFY* REMOVE?
318 // When undoing multiple operations on the same key, the final result is determined
319 // by the oldest operation. Therefore, the following optimizations can be applied:
320 // - A primary key which is new may be discarded from old_values and removed_values
321 // - If a primary key has multiple modifications, all but the oldest can be discarded.
322 // - If a primary key is both modified and removed, the modified value can replace
323 // the removed value, and can then be discarded.
324 // These optimizations may be applied at any time, but are not required by the class
325 // invariants.
326 //
327 // Notes regarding memory:
328 // Nodes in the main table share the same layout as nodes in removed_values and may
329 // be freely moved between the two. This permits undo to restore removed nodes
330 // without allocating memory.
331 //
332 struct undo_state {
333 typename std::allocator_traits<Allocator>::pointer old_values_end;
334 typename std::allocator_traits<Allocator>::pointer removed_values_end;
336 uint64_t ctime = 0; // _monotonic_revision at the point the undo_state was created
337 };
338
339 // Exception safety: strong
340 template<typename Constructor>
341 const value_type& emplace( Constructor&& c ) {
342 auto p = alloc_traits::allocate(_allocator, 1);
343 auto guard0 = scope_exit{[&]{ alloc_traits::deallocate(_allocator, p, 1); }};
344 auto new_id = _next_id;
345 auto constructor = [&]( value_type& v ) {
346 v.id = new_id;
347 c( v );
348 };
349 alloc_traits::construct(_allocator, &*p, constructor, propagate_allocator(_allocator));
350 auto guard1 = scope_exit{[&]{ alloc_traits::destroy(_allocator, &*p); }};
351 if(!insert_impl<1>(p->_item))
352 BOOST_THROW_EXCEPTION( std::logic_error{ "could not insert object, most likely a uniqueness constraint was violated" } );
353 std::get<0>(_indices).push_back(p->_item); // cannot fail and we know that it will definitely insert at the end.
354 on_create(p->_item);
355 ++_next_id;
356 guard1.cancel();
357 guard0.cancel();
358 return p->_item;
359 }
360
361 // Exception safety: basic.
362 // If the modifier leaves the object in a state that conflicts
363 // with another object, it will either be reverted or erased.
364 template<typename Modifier>
365 void modify( const value_type& obj, Modifier&& m) {
366 value_type* backup = on_modify(obj);
367 value_type& node_ref = const_cast<value_type&>(obj);
368 bool success = false;
369 {
370 auto guard0 = scope_exit{[&]{
371 if(!post_modify<true, 1>(node_ref)) { // The object id cannot be modified
372 if(backup) {
373 node_ref = std::move(*backup);
374 bool success = post_modify<true, 1>(node_ref);
375 (void)success;
376 assert(success);
377 assert(backup == &_old_values.front());
378 _old_values.pop_front_and_dispose([this](pointer p){ dispose_old(*p); });
379 } else {
380 remove(obj);
381 }
382 } else {
383 success = true;
384 }
385 }};
386 auto old_id = obj.id;
387 m(node_ref);
388 (void)old_id;
389 assert(obj.id == old_id);
390 }
391 if(!success)
392 BOOST_THROW_EXCEPTION( std::logic_error{ "could not modify object, most likely a uniqueness constraint was violated" } );
393 }
394
395 // Allows testing whether a value has been removed from the undo_index.
396 //
397 // The lifetime of an object removed through a removed_nodes_tracker
398 // does not end before the removed_nodes_tracker is destroyed or invalidated.
399 //
400 // A removed_nodes_tracker is invalidated by the following members of undo_index:
401 // start_undo_session, commit, squash, and undo.
403 public:
404 explicit removed_nodes_tracker(undo_index& idx) : _self(&idx) {}
406 _removed_values.clear_and_dispose([this](value_type* obj) { _self->dispose_node(*obj); });
407 }
410 bool is_removed(const value_type& obj) const {
411 return undo_index::get_removed_field(obj) == erased_flag;
412 }
413 // Must be used in place of undo_index::remove
414 void remove(const value_type& obj) {
415 _self->remove(obj, *this);
416 }
417 private:
418 friend class undo_index;
419 void save(value_type& obj) {
420 undo_index::get_removed_field(obj) = erased_flag;
421 _removed_values.push_front(obj);
422 }
423 undo_index* _self;
424 list_base<node, index0_type> _removed_values;
425 };
427 return removed_nodes_tracker(*this);
428 }
429
430 void remove( const value_type& obj ) noexcept {
431 auto& node_ref = const_cast<value_type&>(obj);
432 erase_impl(node_ref);
433 if(on_remove(node_ref)) {
434 dispose_node(node_ref);
435 }
436 }
437
438 private:
439
440 void remove( const value_type& obj, removed_nodes_tracker& tracker ) noexcept {
441 auto& node_ref = const_cast<value_type&>(obj);
442 erase_impl(node_ref);
443 if(on_remove(node_ref)) {
444 tracker.save(node_ref);
445 }
446 }
447
448 public:
449
450 template<typename CompatibleKey>
451 const value_type* find( CompatibleKey&& key) const {
452 const auto& index = std::get<0>(_indices);
453 auto iter = index.find(static_cast<CompatibleKey&&>(key));
454 if (iter != index.end()) {
455 return &*iter;
456 } else {
457 return nullptr;
458 }
459 }
460
461 template<typename CompatibleKey>
462 const value_type& get( CompatibleKey&& key )const {
463 auto ptr = find( static_cast<CompatibleKey&&>(key) );
464 if( !ptr ) {
465 std::stringstream ss;
466 ss << "key not found (" << boost::core::demangle( typeid( key ).name() ) << "): " << key;
467 BOOST_THROW_EXCEPTION( std::out_of_range( ss.str().c_str() ) );
468 }
469 return *ptr;
470 }
471
473 const value_type* val = find( typename value_type::id_type(id) );
474 if( !val ) BOOST_THROW_EXCEPTION( std::out_of_range( boost::lexical_cast<std::string>(id) ) );
475 remove( *val );
476 }
477
478 class session {
479 public:
480 session(undo_index& idx, bool enabled)
481 : _index(idx),
482 _apply(enabled) {
483 if(enabled) idx.add_session();
484 }
486 : _index(other._index),
487 _apply(other._apply)
488 {
489 other._apply = false;
490 }
492 if(this != &other) {
493 undo();
494 _apply = other._apply;
495 other._apply = false;
496 }
497 return *this;
498 }
499 ~session() { if(_apply) _index.undo(); }
500 void push() { _apply = false; }
501 void squash() {
502 if ( _apply ) _index.squash();
503 _apply = false;
504 }
505 void undo() {
506 if ( _apply ) _index.undo();
507 _apply = false;
508 }
509 private:
510 undo_index& _index;
511 bool _apply = true;
512 };
513
514 int64_t revision() const { return _revision; }
515
516 session start_undo_session( bool enabled ) {
517 return session{*this, enabled};
518 }
519
521 if( _undo_stack.size() != 0 )
522 BOOST_THROW_EXCEPTION( std::logic_error("cannot set revision while there is an existing undo stack") );
523
524 if( revision > std::numeric_limits<int64_t>::max() )
525 BOOST_THROW_EXCEPTION( std::logic_error("revision to set is too high") );
526
527 if( static_cast<int64_t>(revision) < _revision )
528 BOOST_THROW_EXCEPTION( std::logic_error("revision cannot decrease") );
529
530 _revision = static_cast<int64_t>(revision);
531 }
532
533 std::pair<int64_t, int64_t> undo_stack_revision_range() const {
534 return { _revision - _undo_stack.size(), _revision };
535 }
536
540 void commit( int64_t revision ) noexcept {
541 revision = std::min(revision, _revision);
542 if (revision == _revision) {
543 dispose_undo();
544 _undo_stack.clear();
545 } else if( static_cast<uint64_t>(_revision - revision) < _undo_stack.size() ) {
546 auto iter = _undo_stack.begin() + (_undo_stack.size() - (_revision - revision));
547 dispose(get_old_values_end(*iter), get_removed_values_end(*iter));
548 _undo_stack.erase(_undo_stack.begin(), iter);
549 }
550 }
551
552 const undo_index& indices() const { return *this; }
553 template<typename Tag>
554 const auto& get() const { return std::get<find_tag<Tag, Indices...>::value>(_indices); }
555
556 template<int N>
557 const auto& get() const { return std::get<N>(_indices); }
558
559 std::size_t size() const {
560 return std::get<0>(_indices).size();
561 }
562
563 bool empty() const {
564 return std::get<0>(_indices).empty();
565 }
566
567 template<typename Tag, typename Iter>
568 auto project(Iter iter) const {
569 return project<find_tag<Tag, Indices...>::value>(iter);
570 }
571
572 template<int N, typename Iter>
573 auto project(Iter iter) const {
574 if(iter == get<boost::mp11::mp_find<boost::mp11::mp_list<typename set_impl<node, Indices>::const_iterator...>, Iter>::value>().end())
575 return get<N>().end();
576 return get<N>().iterator_to(*iter);
577 }
578
579 bool has_undo_session() const { return !_undo_stack.empty(); }
580
581 struct delta {
582 boost::iterator_range<typename index0_set_type::const_iterator> new_values;
583 boost::iterator_range<typename list_base<old_node, index0_type>::const_iterator> old_values;
584 boost::iterator_range<typename list_base<node, index0_type>::const_iterator> removed_values;
585 };
586
588 if(_undo_stack.empty())
589 return { { get<0>().end(), get<0>().end() },
590 { _old_values.end(), _old_values.end() },
591 { _removed_values.end(), _removed_values.end() } };
592 // Warning: This is safe ONLY as long as nothing exposes the undo stack to client code.
593 // Compressing the undo stack does not change the logical state of the undo_index.
594 const_cast<undo_index*>(this)->compress_last_undo_session();
595 return { { get<0>().lower_bound(_undo_stack.back().old_next_id), get<0>().end() },
596 { _old_values.begin(), get_old_values_end(_undo_stack.back()) },
597 { _removed_values.begin(), get_removed_values_end(_undo_stack.back()) } };
598 }
599
600 auto begin() const { return get<0>().begin(); }
601 auto end() const { return get<0>().end(); }
602
603 void undo_all() {
604 while(!_undo_stack.empty()) {
605 undo();
606 }
607 }
608
609 // Resets the contents to the state at the top of the undo stack.
610 void undo() noexcept {
611 if (_undo_stack.empty()) return;
612 undo_state& undo_info = _undo_stack.back();
613 // erase all new_ids
614 auto& by_id = std::get<0>(_indices);
615 auto new_ids_iter = by_id.lower_bound(undo_info.old_next_id);
616 by_id.erase_and_dispose(new_ids_iter, by_id.end(), [this](pointer p){
617 erase_impl<1>(*p);
618 dispose_node(*p);
619 });
620 // replace old_values
621 _old_values.erase_after_and_dispose(_old_values.before_begin(), get_old_values_end(undo_info), [this, &undo_info](pointer p) {
622 auto restored_mtime = to_old_node(*p)._mtime;
623 // Skip restoring values that overwrite an earlier modify in the same session.
624 // Duplicate modifies can only happen because of squash.
625 if(restored_mtime < undo_info.ctime) {
626 auto iter = &to_old_node(*p)._current->_item;
627 *iter = std::move(*p);
628 auto& node_mtime = to_node(*iter)._mtime;
629 node_mtime = restored_mtime;
630 if (get_removed_field(*iter) != erased_flag) {
631 // Non-unique items are transient and are guaranteed to be fixed
632 // by the time we finish processing old_values.
633 post_modify<false, 1>(*iter);
634 } else {
635 // The item was removed. It will be inserted when we process removed_values
636 }
637 }
638 dispose_old(*p);
639 });
640 // insert all removed_values
641 _removed_values.erase_after_and_dispose(_removed_values.before_begin(), get_removed_values_end(undo_info), [this, &undo_info](pointer p) {
642 if (p->id < undo_info.old_next_id) {
643 get_removed_field(*p) = 0; // Will be overwritten by tree algorithms, because we're reusing the color.
644 insert_impl(*p);
645 } else {
646 dispose_node(*p);
647 }
648 });
649 _next_id = undo_info.old_next_id;
650 _undo_stack.pop_back();
651 --_revision;
652 }
653
654 // Combines the top two states on the undo stack
655 void squash() noexcept {
657 }
658
659 void squash_fast() noexcept {
660 if (_undo_stack.empty()) {
661 return;
662 } else if (_undo_stack.size() == 1) {
663 dispose_undo();
664 }
665 _undo_stack.pop_back();
666 --_revision;
667 }
668
669 void squash_and_compress() noexcept {
670 if(_undo_stack.size() >= 2) {
671 compress_impl(_undo_stack[_undo_stack.size() - 2]);
672 }
673 squash_fast();
674 }
675
677 compress_impl(_undo_stack.back());
678 }
679
680 private:
681
682 // Removes elements of the last undo session that would be redundant
683 // if all the sessions after @c session were squashed.
684 //
685 // WARNING: This function leaves any undo sessions after @c session in
686 // an indeterminate state. The caller MUST use squash to restore the
687 // undo stack to a sane state.
688 void compress_impl(undo_state& session) noexcept {
689 auto session_start = session.ctime;
690 auto old_next_id = session.old_next_id;
691 remove_if_after_and_dispose(_old_values, _old_values.before_begin(), get_old_values_end(_undo_stack.back()),
692 [session_start](value_type& v){
693 if(to_old_node(v)._mtime >= session_start) return true;
694 auto& item = to_old_node(v)._current->_item;
695 if (get_removed_field(item) == erased_flag) {
696 item = std::move(v);
697 to_node(item)._mtime = to_old_node(v)._mtime;
698 return true;
699 }
700 return false;
701 },
702 [&](pointer p) { dispose_old(*p); });
703 remove_if_after_and_dispose(_removed_values, _removed_values.before_begin(), get_removed_values_end(_undo_stack.back()),
704 [old_next_id](value_type& v){
705 return v.id >= old_next_id;
706 },
707 [this](pointer p) { dispose_node(*p); });
708 }
709
710 // starts a new undo session.
711 // Exception safety: strong
712 int64_t add_session() {
713 _undo_stack.emplace_back();
714 _undo_stack.back().old_values_end = _old_values.empty()?nullptr:&*_old_values.begin();
715 _undo_stack.back().removed_values_end = _removed_values.empty()?nullptr:&*_removed_values.begin();
716 _undo_stack.back().old_next_id = _next_id;
717 _undo_stack.back().ctime = ++_monotonic_revision;
718 return ++_revision;
719 }
720
721 template<int N = 0>
722 bool insert_impl(value_type& p) {
723 if constexpr (N < sizeof...(Indices)) {
724 auto [iter, inserted] = std::get<N>(_indices).insert_unique(p);
725 if(!inserted) return false;
726 auto guard = scope_exit{[this,iter=iter]{ std::get<N>(_indices).erase(iter); }};
727 if(insert_impl<N+1>(p)) {
728 guard.cancel();
729 return true;
730 }
731 return false;
732 }
733 return true;
734 }
735
736 // Moves a modified node into the correct location
737 template<bool unique, int N = 0>
738 bool post_modify(value_type& p) {
739 if constexpr (N < sizeof...(Indices)) {
740 auto& idx = std::get<N>(_indices);
741 auto iter = idx.iterator_to(p);
742 bool fixup = false;
743 if (iter != idx.begin()) {
744 auto copy = iter;
745 --copy;
746 if (!idx.value_comp()(*copy, p)) fixup = true;
747 }
748 ++iter;
749 if (iter != idx.end()) {
750 if(!idx.value_comp()(p, *iter)) fixup = true;
751 }
752 if(fixup) {
753 auto iter2 = idx.iterator_to(p);
754 idx.erase(iter2);
755 if constexpr (unique) {
756 auto [new_pos, inserted] = idx.insert_unique(p);
757 if (!inserted) {
758 idx.insert_before(new_pos, p);
759 return false;
760 }
761 } else {
762 idx.insert_equal(p);
763 }
764 }
765 return post_modify<unique, N+1>(p);
766 }
767 return true;
768 }
769
770 template<int N = 0>
771 void erase_impl(value_type& p) {
772 if constexpr (N < sizeof...(Indices)) {
773 auto& setN = std::get<N>(_indices);
774 setN.erase(setN.iterator_to(p));
775 erase_impl<N+1>(p);
776 }
777 }
778
779 void on_create(const value_type& value) noexcept {
780 if(!_undo_stack.empty()) {
781 // Not in old_values, removed_values, or new_ids
782 to_node(value)._mtime = _monotonic_revision;
783 }
784 }
785
786 value_type* on_modify( const value_type& obj) {
787 if (!_undo_stack.empty()) {
788 auto& undo_info = _undo_stack.back();
789 if ( to_node(obj)._mtime >= undo_info.ctime ) {
790 // Nothing to do
791 } else {
792 // Not in removed_values
793 auto p = old_alloc_traits::allocate(_old_values_allocator, 1);
794 auto guard0 = scope_exit{[&]{ _old_values_allocator.deallocate(p, 1); }};
795 old_alloc_traits::construct(_old_values_allocator, &*p, obj);
796 p->_mtime = to_node(obj)._mtime;
797 p->_current = &to_node(obj);
798 guard0.cancel();
799 _old_values.push_front(p->_item);
800 to_node(obj)._mtime = _monotonic_revision;
801 return &p->_item;
802 }
803 }
804 return nullptr;
805 }
806 template<int N = 0>
807 void clear_impl() noexcept {
808 if constexpr(N < sizeof...(Indices)) {
809 std::get<N>(_indices).clear();
810 clear_impl<N+1>();
811 }
812 }
813 void dispose_node(node& node_ref) noexcept {
814 node* p{&node_ref};
815 alloc_traits::destroy(_allocator, p);
816 alloc_traits::deallocate(_allocator, p, 1);
817 }
818 void dispose_node(value_type& node_ref) noexcept {
819 dispose_node(static_cast<node&>(*boost::intrusive::get_parent_from_member(&node_ref, &value_holder<value_type>::_item)));
820 }
821 void dispose_old(old_node& node_ref) noexcept {
822 old_node* p{&node_ref};
823 old_alloc_traits::destroy(_old_values_allocator, p);
824 old_alloc_traits::deallocate(_old_values_allocator, p, 1);
825 }
826 void dispose_old(value_type& node_ref) noexcept {
827 dispose_old(static_cast<old_node&>(*boost::intrusive::get_parent_from_member(&node_ref, &value_holder<value_type>::_item)));
828 }
829 void dispose(typename list_base<old_node, index0_type>::iterator old_start, typename list_base<node, index0_type>::iterator removed_start) noexcept {
830 // This will leave one element around. That's okay, because we'll clean it up the next time.
831 if(old_start != _old_values.end())
832 _old_values.erase_after_and_dispose(old_start, _old_values.end(), [this](pointer p){ dispose_old(*p); });
833 if(removed_start != _removed_values.end())
834 _removed_values.erase_after_and_dispose(removed_start, _removed_values.end(), [this](pointer p){ dispose_node(*p); });
835 }
836 void dispose_undo() noexcept {
837 _old_values.clear_and_dispose([this](pointer p){ dispose_old(*p); });
838 _removed_values.clear_and_dispose([this](pointer p){ dispose_node(*p); });
839 }
840 static node& to_node(value_type& obj) {
841 return static_cast<node&>(*boost::intrusive::get_parent_from_member(&obj, &value_holder<value_type>::_item));
842 }
843 static node& to_node(const value_type& obj) {
844 return to_node(const_cast<value_type&>(obj));
845 }
846 static old_node& to_old_node(value_type& obj) {
847 return static_cast<old_node&>(*boost::intrusive::get_parent_from_member(&obj, &value_holder<value_type>::_item));
848 }
849
850 auto get_old_values_end(const undo_state& info) {
851 if(info.old_values_end == nullptr) {
852 return _old_values.end();
853 } else {
854 return _old_values.iterator_to(*info.old_values_end);
855 }
856 }
857
858 auto get_old_values_end(const undo_state& info) const {
859 return static_cast<decltype(_old_values.cend())>(const_cast<undo_index*>(this)->get_old_values_end(info));
860 }
861
862 auto get_removed_values_end(const undo_state& info) {
863 if(info.removed_values_end == nullptr) {
864 return _removed_values.end();
865 } else {
866 return _removed_values.iterator_to(*info.removed_values_end);
867 }
868 }
869
870 auto get_removed_values_end(const undo_state& info) const {
871 return static_cast<decltype(_removed_values.cend())>(const_cast<undo_index*>(this)->get_removed_values_end(info));
872 }
873
874 // returns true if the node should be destroyed
875 bool on_remove( value_type& obj) {
876 if (!_undo_stack.empty()) {
877 auto& undo_info = _undo_stack.back();
878 if ( obj.id >= undo_info.old_next_id ) {
879 return true;
880 }
881 get_removed_field(obj) = erased_flag;
882
883 _removed_values.push_front(obj);
884 return false;
885 }
886 return true;
887 }
888 // Returns the field indicating whether the node has been removed
889 static int& get_removed_field(const value_type& obj) {
890 return static_cast<hook<index0_type, Allocator>&>(to_node(obj))._color;
891 }
892 using old_alloc_traits = typename std::allocator_traits<Allocator>::template rebind_traits<old_node>;
893 indices_type _indices;
894 boost::container::deque<undo_state, rebind_alloc_t<Allocator, undo_state>> _undo_stack;
896 list_base<node, index0_type> _removed_values;
898 rebind_alloc_t<Allocator, old_node> _old_values_allocator;
899 id_type _next_id = 0;
900 int64_t _revision = 0;
901 uint64_t _monotonic_revision = 0;
902 uint32_t _size_of_value_type = sizeof(node);
903 uint32_t _size_of_this = sizeof(undo_index);
904 };
905
906 template<typename MultiIndexContainer>
908
909 template<typename T, typename I, typename A>
911 template<typename T, typename... I, typename A>
912 struct mi_to_ui_ii<T, boost::mp11::mp_list<I...>, A> {
913 using type = undo_index<T, A, I...>;
914 };
915
916 struct to_mp11 {
917 template<typename State, typename T>
918 using apply = boost::mpl::identity<boost::mp11::mp_push_back<State, T>>;
919 };
920
921 template<typename T, typename I, typename A>
922 struct multi_index_to_undo_index_impl<boost::multi_index_container<T, I, A>> {
923 using as_mp11 = typename boost::mpl::fold<I, boost::mp11::mp_list<>, to_mp11>::type;
925 };
926
927 // Converts a multi_index_container to a corresponding undo_index.
928 template<typename MultiIndexContainer>
930}
const mie::Vuint & p
Definition bn.cpp:27
std::string name
bool is_removed(const value_type &obj) const
removed_nodes_tracker(const removed_nodes_tracker &)=delete
removed_nodes_tracker & operator=(const removed_nodes_tracker &)=delete
session(undo_index &idx, bool enabled)
session & operator=(session &&other)
boost::mp11::mp_first< boost::mp11::mp_list< Indices... > > index0_type
typename std::allocator_traits< Allocator >::template rebind_traits< node > alloc_traits
void undo() noexcept
void modify(const value_type &obj, Modifier &&m)
int64_t revision() const
void squash() noexcept
const auto & get() const
void squash_fast() noexcept
delta last_undo_session() const
void remove_object(int64_t id)
void compress_last_undo_session() noexcept
const value_type & get(CompatibleKey &&key) const
static constexpr int erased_flag
typename index0_set_type::const_iterator const_iterator
bool has_undo_session() const
std::size_t size() const
std::pair< int64_t, int64_t > undo_stack_revision_range() const
undo_index(const Allocator &a)
std::tuple< set_impl< node, Indices >... > indices_type
const undo_index & indices() const
void squash_and_compress() noexcept
void set_revision(uint64_t revision)
std::tuple_element_t< 0, indices_type > index0_set_type
auto project(Iter iter) const
session start_undo_session(bool enabled)
void commit(int64_t revision) noexcept
const value_type * find(CompatibleKey &&key) const
std::decay_t< decltype(std::declval< T >().id)> id_type
const value_type & emplace(Constructor &&c)
void remove(const value_type &obj) noexcept
Concept for allocating, resizing and freeing memory block.
CK_SESSION_HANDLE session
const uint64 K
Definition make_512.cpp:78
auto & propagate_allocator(A< T > &a)
boost::intrusive::avltree< typename Node::value_type, boost::intrusive::value_traits< offset_node_value_traits< Node, OrderedIndex > >, boost::intrusive::key_of_value< get_key< typename OrderedIndex::key_from_value_type, typename Node::value_type > >, boost::intrusive::compare< typename OrderedIndex::compare_type > > set_base
typename index_tag_impl< Index >::type index_tag
boost::mp11::mp_find< boost::mp11::mp_list< index_tag< Indices >... >, Tag > find_tag
boost::intrusive::slist< typename Node::value_type, boost::intrusive::value_traits< offset_node_value_traits< Node, Tag > > > list_base
typename multi_index_to_undo_index_impl< MultiIndexContainer >::type multi_index_to_undo_index
constexpr bool is_valid_index
typename std::allocator_traits< Allocator >::template rebind_alloc< T > rebind_alloc_t
void remove_if_after_and_dispose(L &l, It it, It end, Pred &&p, Disposer &&d)
offset_node_base< K > hook
void copy(const path &from, const path &to)
uint8_t value_type
Definition types.hpp:24
#define value
Definition pkcs11.h:157
const GenericPointer< typename T::ValueType > & pointer
Definition pointer.h:1181
const GenericPointer< typename T::ValueType > T2 T::AllocatorType & a
Definition pointer.h:1181
#define T(meth, val, expected)
const int N
Definition quantize.cpp:54
signed __int64 int64_t
Definition stdint.h:135
unsigned int uint32_t
Definition stdint.h:126
unsigned __int64 uint64_t
Definition stdint.h:136
std::decay_t< decltype(KeyExtractor{}(std::declval< const T & >()))> type
typename boost::mpl::fold< I, boost::mp11::mp_list<>, to_mp11 >::type as_mp11
constexpr offset_node_base & operator=(const offset_node_base &)
offset_node_base(const offset_node_base &)
static void set_color(node_ptr n, color c)
static node_ptr get_next(const_node_ptr n)
static node_ptr get_parent(const_node_ptr n)
static void set_next(node_ptr n, node_ptr next)
static void set_left(node_ptr n, node_ptr left)
static void set_previous(node_ptr n, node_ptr previous)
static void set_parent(node_ptr n, node_ptr parent)
static void set_balance(node_ptr n, balance c)
static node_ptr get_previous(const_node_ptr n)
static color get_color(node_ptr n)
static node_ptr get_right(const_node_ptr n)
static void set_right(node_ptr n, node_ptr right)
static balance get_balance(node_ptr n)
static node_ptr get_left(const_node_ptr n)
typename node_traits::const_node_ptr const_node_ptr
typename Node::value_type value_type
static const_pointer to_value_ptr(const_node_ptr n)
static const_node_ptr to_node_ptr(const value_type &value)
static pointer to_value_ptr(node_ptr n)
static constexpr boost::intrusive::link_mode_type link_mode
static node_ptr to_node_ptr(value_type &value)
typename node_traits::node_ptr node_ptr
scope_exit(const scope_exit &)=delete
scope_exit & operator=(const scope_exit &)=delete
auto lower_bound(K &&k) const
auto upper_bound(K &&k) const
auto equal_range(K &&k) const
auto find(K &&k) const
set_base< Node, OrderedIndex > base_type
boost::mpl::identity< boost::mp11::mp_push_back< State, T > > apply
boost::iterator_range< typename index0_set_type::const_iterator > new_values
boost::iterator_range< typename list_base< old_node, index0_type >::const_iterator > old_values
boost::iterator_range< typename list_base< node, index0_type >::const_iterator > removed_values
alloc_traits::pointer _current
std::allocator_traits< Allocator >::pointer old_values_end
std::allocator_traits< Allocator >::pointer removed_values_end
#define A
uint8_t key[16]
Definition yubico_otp.c:41
yh_object_type type
Definition yubihsm.h:672
CK_ULONG d
int l