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>
33 bool _canceled =
false;
37 template<
typename KeyExtractor,
typename T>
39 using type = std::decay_t<
decltype(KeyExtractor{}(std::declval<const T&>()))>;
40 decltype(
auto)
operator()(
const T& arg)
const {
return KeyExtractor{}(arg); }
45 template<
typename...
A>
68 if(n->
_parent == 1)
return nullptr;
72 if(parent ==
nullptr) n->
_parent = 1;
73 else n->
_parent = (
char*)parent - (
char*)n;
76 if(n->
_left == 1)
return nullptr;
80 if(left ==
nullptr) n->
_left = 1;
81 else n->
_left = (
char*)left - (
char*)n;
84 if(n->
_right == 1)
return nullptr;
88 if(right ==
nullptr) n->
_right = 1;
89 else n->
_right = (
char*)right - (
char*)n;
119 template<
typename Node,
typename Tag>
137 static constexpr boost::intrusive::link_mode_type
link_mode = boost::intrusive::normal_link;
139 template<
typename Allocator,
typename T>
140 using rebind_alloc_t =
typename std::allocator_traits<Allocator>::template rebind_alloc<T>;
142 template<
typename Index>
144 template<
template<
typename...>
class Index,
typename Tag,
typename...
T>
146 template<
typename Index>
149 template<
typename Tag,
typename... Indices>
150 using find_tag = boost::mp11::mp_find<boost::mp11::mp_list<index_tag<Indices>...>, Tag>;
152 template<
typename K,
typename Allocator>
155 template<
typename Node,
typename OrderedIndex>
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>>;
162 template<
typename OrderedIndex>
164 template<
typename...
T>
165 constexpr bool is_valid_index<boost::multi_index::ordered_unique<
T...>> =
true;
167 template<
typename Node,
typename Tag>
169 typename Node::value_type,
170 boost::intrusive::value_traits<offset_node_value_traits<Node, Tag>>>;
172 template<
typename L,
typename It,
typename Pred,
typename Disposer>
177 if(next == end)
break;
178 if(
p(*next)) {
l.erase_after_and_dispose(it,
d); }
183 template<
typename T,
typename Allocator,
typename... Indices>
186 template<
typename Node,
typename OrderedIndex>
192 return base_type::find(
static_cast<K&&
>(k), this->key_comp());
196 return base_type::lower_bound(
static_cast<K&&
>(k), this->key_comp());
200 return base_type::upper_bound(
static_cast<K&&
>(k), this->key_comp());
204 return base_type::equal_range(
static_cast<K&&
>(k), this->key_comp());
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>
217 template<
typename T,
typename S>
221 template<
template<
typename>
class A,
typename T>
223 template<
typename T,
typename S>
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>
234 template<
typename T,
typename Allocator,
typename... Indices>
237 using id_type = std::decay_t<decltype(std::declval<T>().id)>;
248 std::get<0>(_indices).clear_and_dispose([&](
pointer p){ dispose_node(*
p); });
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") );
259 template<
typename...
A>
261 const T&
item()
const {
return *
this; }
269 using alloc_traits =
typename std::allocator_traits<Allocator>::template rebind_traits<node>;
271 static_assert(std::is_same_v<typename index0_set_type::key_type, id_type>,
"first index must be id");
273 using index0_type = boost::mp11::mp_first<boost::mp11::mp_list<Indices...>>;
277 template<
typename...
A>
340 template<
typename Constructor>
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;
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);
364 template<
typename Modifier>
368 bool success =
false;
371 if(!post_modify<true, 1>(node_ref)) {
373 node_ref = std::move(*backup);
374 bool success = post_modify<true, 1>(node_ref);
377 assert(backup == &_old_values.front());
378 _old_values.pop_front_and_dispose([
this](
pointer p){ dispose_old(*
p); });
386 auto old_id = obj.id;
389 assert(obj.id == old_id);
392 BOOST_THROW_EXCEPTION( std::logic_error{
"could not modify object, most likely a uniqueness constraint was violated" } );
406 _removed_values.clear_and_dispose([
this](
value_type* obj) { _self->dispose_node(*obj); });
411 return undo_index::get_removed_field(obj) ==
erased_flag;
415 _self->
remove(obj, *
this);
421 _removed_values.push_front(obj);
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);
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);
450 template<
typename CompatibleKey>
452 const auto&
index = std::get<0>(_indices);
453 auto iter =
index.find(
static_cast<CompatibleKey&&
>(key));
454 if (iter !=
index.end()) {
461 template<
typename CompatibleKey>
463 auto ptr =
find(
static_cast<CompatibleKey&&
>(key) );
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() ) );
474 if( !val ) BOOST_THROW_EXCEPTION( std::out_of_range( boost::lexical_cast<std::string>(
id) ) );
483 if(enabled) idx.add_session();
486 : _index(other._index),
489 other._apply =
false;
494 _apply = other._apply;
495 other._apply =
false;
500 void push() { _apply =
false; }
502 if ( _apply ) _index.
squash();
506 if ( _apply ) _index.
undo();
517 return session{*
this, enabled};
521 if( _undo_stack.size() != 0 )
522 BOOST_THROW_EXCEPTION( std::logic_error(
"cannot set revision while there is an existing undo stack") );
524 if(
revision > std::numeric_limits<int64_t>::max() )
525 BOOST_THROW_EXCEPTION( std::logic_error(
"revision to set is too high") );
528 BOOST_THROW_EXCEPTION( std::logic_error(
"revision cannot decrease") );
534 return { _revision - _undo_stack.size(), _revision };
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);
553 template<
typename Tag>
557 const auto&
get()
const {
return std::get<N>(_indices); }
560 return std::get<0>(_indices).size();
564 return std::get<0>(_indices).empty();
567 template<
typename Tag,
typename Iter>
572 template<
int N,
typename Iter>
576 return get<N>().iterator_to(*iter);
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;
588 if(_undo_stack.empty())
590 { _old_values.end(), _old_values.end() },
591 { _removed_values.end(), _removed_values.end() } };
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()) } };
604 while(!_undo_stack.empty()) {
611 if (_undo_stack.empty())
return;
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){
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;
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;
633 post_modify<false, 1>(*iter);
641 _removed_values.erase_after_and_dispose(_removed_values.before_begin(), get_removed_values_end(undo_info), [
this, &undo_info](
pointer p) {
643 get_removed_field(*
p) = 0;
650 _undo_stack.pop_back();
660 if (_undo_stack.empty()) {
662 }
else if (_undo_stack.size() == 1) {
665 _undo_stack.pop_back();
670 if(_undo_stack.size() >= 2) {
671 compress_impl(_undo_stack[_undo_stack.size() - 2]);
677 compress_impl(_undo_stack.back());
688 void compress_impl(undo_state&
session)
noexcept {
689 auto session_start =
session.ctime;
690 auto old_next_id =
session.old_next_id;
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) {
697 to_node(item)._mtime = to_old_node(v)._mtime;
705 return v.id >= old_next_id;
707 [
this](
pointer p) { dispose_node(*p); });
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;
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)) {
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);
743 if (iter != idx.begin()) {
746 if (!idx.value_comp()(*copy,
p)) fixup =
true;
749 if (iter != idx.end()) {
750 if(!idx.value_comp()(
p, *iter)) fixup =
true;
753 auto iter2 = idx.iterator_to(
p);
755 if constexpr (unique) {
756 auto [new_pos, inserted] = idx.insert_unique(
p);
758 idx.insert_before(new_pos,
p);
765 return post_modify<unique, N+1>(
p);
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));
779 void on_create(
const value_type&
value)
noexcept {
780 if(!_undo_stack.empty()) {
782 to_node(
value)._mtime = _monotonic_revision;
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 ) {
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);
799 _old_values.push_front(
p->_item);
800 to_node(obj)._mtime = _monotonic_revision;
807 void clear_impl() noexcept {
808 if constexpr(
N <
sizeof...(Indices)) {
809 std::get<N>(_indices).clear();
813 void dispose_node(node& node_ref)
noexcept {
815 alloc_traits::destroy(_allocator,
p);
816 alloc_traits::deallocate(_allocator,
p, 1);
818 void dispose_node(value_type& node_ref)
noexcept {
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);
826 void dispose_old(value_type& node_ref)
noexcept {
829 void dispose(
typename list_base<old_node, index0_type>::iterator old_start,
typename list_base<node, index0_type>::iterator removed_start)
noexcept {
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); });
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); });
840 static node& to_node(value_type& obj) {
843 static node& to_node(
const value_type& obj) {
846 static old_node& to_old_node(value_type& obj) {
850 auto get_old_values_end(
const undo_state& info) {
851 if(info.old_values_end ==
nullptr) {
852 return _old_values.end();
854 return _old_values.iterator_to(*info.old_values_end);
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));
862 auto get_removed_values_end(
const undo_state& info) {
863 if(info.removed_values_end ==
nullptr) {
864 return _removed_values.end();
866 return _removed_values.iterator_to(*info.removed_values_end);
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));
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 ) {
881 get_removed_field(obj) = erased_flag;
883 _removed_values.push_front(obj);
889 static int& get_removed_field(
const value_type& obj) {
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;
899 id_type _next_id = 0;
902 uint32_t _size_of_value_type =
sizeof(node);
903 uint32_t _size_of_this =
sizeof(undo_index);
906 template<
typename MultiIndexContainer>
909 template<
typename T,
typename I,
typename A>
911 template<
typename T,
typename... I,
typename A>
917 template<
typename State,
typename T>
918 using apply = boost::mpl::identity<boost::mp11::mp_push_back<State, T>>;
921 template<
typename T,
typename I,
typename A>
928 template<
typename MultiIndexContainer>
bool is_removed(const value_type &obj) const
removed_nodes_tracker(const removed_nodes_tracker &)=delete
removed_nodes_tracker(undo_index &idx)
removed_nodes_tracker & operator=(const removed_nodes_tracker &)=delete
void remove(const value_type &obj)
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 modify(const value_type &obj, Modifier &&m)
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::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
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)
const GenericPointer< typename T::ValueType > & pointer
const GenericPointer< typename T::ValueType > T2 T::AllocatorType & a
#define T(meth, val, expected)
unsigned __int64 uint64_t
std::decay_t< decltype(KeyExtractor{}(std::declval< const T & >()))> type
typename mi_to_ui_ii< T, as_mp11, A >::type type
typename boost::mpl::fold< I, boost::mp11::mp_list<>, to_mp11 >::type as_mp11
offset_node_base()=default
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)
const node * const_node_ptr
static void set_left(node_ptr n, node_ptr left)
static void set_previous(node_ptr n, node_ptr previous)
static balance positive()
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 balance negative()
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)
const value_type * const_pointer
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
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