Pixie
Loading...
Searching...
No Matches
immer_sequence.h
Go to the documentation of this file.
1#pragma once
2
7
9
10#include <algorithm>
11#include <cstddef>
12#include <cstdint>
13#include <immer/flex_vector.hpp>
14#include <immer/flex_vector_transient.hpp>
15#include <limits>
16#include <stdexcept>
17#include <type_traits>
18#include <utility>
19
20namespace pixie {
21
36template <std::copy_constructible T = std::uint64_t,
37 class HeapPolicy = immer::default_heap_policy>
39 : public PermutableSequenceBase<ImmerPermutableSequence<T, HeapPolicy>,
40 T,
41 T> {
43 friend Base;
44 using Policy = immer::memory_policy<HeapPolicy,
45 immer::default_refcount_policy,
46 immer::default_lock_policy>;
47 using Vector = immer::flex_vector<T, Policy>;
48 using Tree = std::remove_cvref_t<decltype(std::declval<Vector>().impl())>;
49 using Node = typename Tree::node_t;
50 static_assert(Node::keep_headroom);
51 static_assert(alignof(T) <= alignof(std::max_align_t));
52
53 public:
55 static constexpr std::size_t fanout = std::size_t{1} << Vector::bits;
57 static constexpr std::size_t leaf_capacity = std::size_t{1}
58 << Vector::bits_leaf;
59
61 ImmerPermutableSequence() noexcept = default;
68 : values_(std::exchange(other.values_, Vector{})) {}
69
71 if (this != &other) {
72 values_ = std::exchange(other.values_, Vector{});
73 }
74 return *this;
75 }
76
78 struct MemoryUsage {
80 std::size_t blocks = 0;
82 std::size_t nodes = 0;
84 std::size_t block_bytes = 0;
86 std::size_t node_bytes = 0;
89 std::size_t total_bytes = sizeof(ImmerPermutableSequence);
90 };
91
100 MemoryUsage memory_usage() const noexcept {
101 MemoryUsage result;
102 if (!values_.empty()) {
103 const auto& tree = values_.impl();
104 const auto tail_offset = tree.tail_offset();
105 if (tail_offset != 0) {
106 count_tree(tree.root, tree.shift, tail_offset, result);
107 }
108 ++result.blocks;
109 add(result.block_bytes, Node::max_sizeof_leaf);
110 }
111 add(result.total_bytes, result.block_bytes);
112 add(result.total_bytes, result.node_bytes);
113 return result;
114 }
115
116 private:
117 template <std::ranges::input_range Range>
118 requires std::
119 constructible_from<T, std::ranges::range_rvalue_reference_t<Range>>
120 static ImmerPermutableSequence from_range_impl(Range&& range) {
121 auto transient = Vector{}.transient();
122 auto it = std::ranges::begin(range);
123 const auto end = std::ranges::end(range);
124 for (; it != end; ++it) {
125 if (transient.size() == Vector::max_size()) {
126 throw std::length_error("ImmerPermutableSequence: size limit");
127 }
128 transient.push_back(T(std::ranges::iter_move(it)));
129 }
131 result.values_ = std::move(transient).persistent();
132 return result;
133 }
134
135 std::size_t size_impl() const noexcept { return values_.size(); }
136 T value_at_impl(std::size_t position) const {
137 if (position >= values_.size()) {
138 throw std::out_of_range("ImmerPermutableSequence: read position");
139 }
140 return values_[position];
141 }
142 void insert_at_impl(std::size_t position, T value) {
143 if (position > values_.size()) {
144 throw std::out_of_range("ImmerPermutableSequence: insert position");
145 }
146 check_growth(1);
147 // Keep the original root for rollback; consume the temporary slices.
148 values_ = values_.insert(position, std::move(value));
149 }
150 void rotate_left_impl(std::size_t left,
151 std::size_t right,
152 std::size_t distance) {
153 if (left > right || right > values_.size()) {
154 throw std::out_of_range("ImmerPermutableSequence: rotation range");
155 }
156 if (left == right || (distance %= right - left) == 0) {
157 return;
158 }
159 auto prefix = values_.take(left);
160 auto first = values_.drop(left).take(distance);
161 auto second = values_.drop(left + distance).take(right - left - distance);
162 auto suffix = values_.drop(right);
163 auto result = std::move(prefix) + std::move(second) + std::move(first) +
164 std::move(suffix);
165 values_ = std::move(result);
166 }
167 void merge_impl(ImmerPermutableSequence& donor) {
168 if (this == &donor || donor.empty()) {
169 return;
170 }
171 check_growth(donor.size());
172 if (this->empty()) {
173 *this = std::move(donor);
174 return;
175 }
176 auto result = values_ + donor.values_;
177 values_ = std::move(result);
178 donor.values_ = Vector{};
179 }
180 std::size_t memory_usage_bytes_impl() const noexcept {
181 return memory_usage().total_bytes;
182 }
183 void check_growth(std::size_t count) const {
184 if (count > Vector::max_size() - values_.size()) {
185 throw std::length_error("ImmerPermutableSequence: size limit");
186 }
187 }
188 static void add(std::size_t& total, std::size_t bytes) noexcept {
189 const auto maximum = std::numeric_limits<std::size_t>::max();
190 total = bytes > maximum - total ? maximum : total + bytes;
191 }
192 static void count_tree(Node* node,
193 unsigned shift,
194 std::size_t size,
195 MemoryUsage& result) noexcept {
196 if (shift < Vector::bits_leaf) {
197 ++result.blocks;
198 add(result.block_bytes, Node::max_sizeof_leaf);
199 return;
200 }
201 ++result.nodes;
202 const auto* relaxed = node->relaxed();
203 add(result.node_bytes,
204 relaxed ? Node::max_sizeof_inner_r : Node::max_sizeof_inner);
205 if (relaxed && !Node::embed_relaxed) {
206 add(result.node_bytes, Node::max_sizeof_relaxed);
207 }
208 const auto child_capacity = std::size_t{1} << shift;
209 const auto count =
210 relaxed ? relaxed->d.count : 1 + (size - 1) / child_capacity;
211 std::size_t prefix = 0;
212 for (std::size_t i = 0; i < count; ++i) {
213 const auto end = relaxed ? relaxed->d.sizes[i]
214 : std::min(size, prefix + child_capacity);
215 if (shift == Vector::bits_leaf) {
216 ++result.blocks;
217 add(result.block_bytes, Node::max_sizeof_leaf);
218 } else {
219 count_tree(node->inner()[i], shift - Vector::bits, end - prefix,
220 result);
221 }
222 prefix = end;
223 }
224 }
225
226 Vector values_;
227};
228} // namespace pixie
Move-only, by-value adapter for immer::flex_vector<T>.
Definition immer_sequence.h:41
ImmerPermutableSequence & operator=(ImmerPermutableSequence &&other) noexcept
Transfer ownership; self-move is a no-op, other becomes empty.
Definition immer_sequence.h:70
static constexpr std::size_t leaf_capacity
Maximum number of values per leaf.
Definition immer_sequence.h:57
ImmerPermutableSequence() noexcept=default
Construct canonical empty without heap allocation.
static constexpr std::size_t fanout
Maximum number of child pointers per internal node.
Definition immer_sequence.h:55
MemoryUsage memory_usage() const noexcept
Account for live storage in linear time, without allocations.
Definition immer_sequence.h:100
CRTP contract for exclusively owned sequences with immutable reads.
Definition permutable_sequence.h:53
Lightweight immutable-read permutable-sequence CRTP contract.
Requested live tree bytes, excluding heap free lists and RSS.
Definition immer_sequence.h:78
std::size_t node_bytes
Internal node and relaxed-size-table allocation bytes.
Definition immer_sequence.h:86
std::size_t total_bytes
Facade plus block_bytes plus node_bytes; saturates at SIZE_MAX.
Definition immer_sequence.h:89
std::size_t block_bytes
Leaf allocation bytes including unused capacity.
Definition immer_sequence.h:84
std::size_t blocks
Number of live leaf allocations, including the tail.
Definition immer_sequence.h:80
std::size_t nodes
Number of live internal nodes.
Definition immer_sequence.h:82