Pixie
Loading...
Searching...
No Matches
sequence.h
1#pragma once
2
4#include <pixie/permutations/detail/element_storage.h>
5#include <pixie/permutations/detail/packed_value_block.h>
6#include <pixie/permutations/detail/payload_chunks.h>
7#include <pixie/permutations/detail/pointer_block.h>
8#include <pixie/permutations/detail/sequence_tree.h>
9
10#include <algorithm>
11#include <array>
12#include <climits>
13#include <concepts>
14#include <cstddef>
15#include <cstdint>
16#include <iterator>
17#include <limits>
18#include <memory>
19#include <new>
20#include <ranges>
21#include <span>
22#include <type_traits>
23#include <utility>
24
25namespace pixie {
26
58template <class T = std::uint64_t,
59 ElementStorage Storage = ElementStorage::automatic,
60 std::size_t StorageBits = 2048,
61 std::size_t Fanout = 8,
62 LengthLayout Layout = LengthLayout::cumulative,
63 std::size_t ChunkBytes = 4096>
66 PermutableSequence<T,
67 Storage,
68 StorageBits,
69 Fanout,
70 Layout,
71 ChunkBytes>,
72 T,
73 typename permutations::detail::ElementStorageTraits<T, Storage>::
74 const_reference> {
77 T,
78 typename Traits::const_reference>;
79 static constexpr bool packed = Traits::packed;
80 using Block =
81 std::conditional_t<packed,
82 permutations::detail::PackedValueBlock<T, StorageBits>,
83 permutations::detail::PointerBlock<T, StorageBits>>;
84 static_assert(sizeof(Block) * CHAR_BIT == StorageBits);
85 using Tree = permutations::detail::SequenceTree<Block, Fanout, Layout>;
87 using Chunk = typename Chunks::Chunk;
88 using Slot = typename Chunks::Slot;
89 static constexpr std::size_t chunk_capacity =
90 std::max(std::size_t{1}, ChunkBytes / sizeof(T));
91 struct NoChunks {};
92
93 public:
98 using value_type = T;
103 static constexpr ElementStorage storage = Traits::storage;
108 using const_reference = typename Traits::const_reference;
113 PermutableSequence() noexcept = default;
123 PermutableSequence& operator=(const PermutableSequence&) = delete;
130 PermutableSequence(PermutableSequence&& other) noexcept = default;
139 PermutableSequence& operator=(PermutableSequence&& other) noexcept {
140 if (this != &other) {
141 tree_ = std::move(other.tree_);
142 chunks_ = std::move(other.chunks_);
143 }
144 return *this;
145 }
146
160 template <class Pred>
161 std::size_t lower_bound(Pred pred) const {
162 if constexpr (packed) {
163 return tree_.lower_bound(pred);
164 } else {
165 return tree_.lower_bound([&](const T* p) { return pred(*p); });
166 }
167 }
168
180 struct MemoryUsage {
182 std::size_t facade_bytes = sizeof(PermutableSequence);
184 std::size_t blocks = 0;
186 std::size_t nodes = 0;
188 std::size_t tree_block_bytes = 0;
190 std::size_t tree_node_bytes = 0;
193 std::size_t order_bytes = 0;
196 std::size_t packed_capacity_bits = 0;
198 std::size_t chunks = 0;
200 std::size_t chunk_header_bytes = 0;
203 std::size_t vector_capacity_bytes = 0;
205 std::size_t vector_live_bytes = 0;
207 std::size_t vector_slack_bytes = 0;
210 std::size_t total_bytes = sizeof(PermutableSequence);
211 };
212
221 MemoryUsage memory_usage() const noexcept {
222 MemoryUsage result;
223 const auto tree = tree_.memory_usage();
224 result.blocks = tree.blocks;
225 result.nodes = tree.nodes;
226 result.tree_block_bytes = tree.block_bytes;
227 result.tree_node_bytes = tree.node_bytes;
228 const auto tree_bytes = add(tree.block_bytes, tree.node_bytes);
229 if constexpr (packed) {
230 result.packed_capacity_bits =
231 multiply(multiply(tree.blocks, Block::capacity),
232 std::numeric_limits<T>::digits);
233 } else {
234 result.order_bytes = tree_bytes;
235 for (auto* chunk = chunks_.head; chunk; chunk = chunk->next) {
236 ++result.chunks;
237 result.chunk_header_bytes =
238 add(result.chunk_header_bytes, sizeof(Chunk));
239 result.vector_capacity_bytes =
240 add(result.vector_capacity_bytes,
241 multiply(chunk->values.capacity(), sizeof(Slot)));
242 result.vector_live_bytes =
243 add(result.vector_live_bytes,
244 multiply(chunk->values.size(), sizeof(Slot)));
245 result.vector_slack_bytes =
246 add(result.vector_slack_bytes,
247 multiply(chunk->values.capacity() - chunk->values.size(),
248 sizeof(Slot)));
249 }
250 }
251 result.total_bytes =
252 add(sizeof(*this), add(tree_bytes, add(result.chunk_header_bytes,
253 result.vector_capacity_bytes)));
254 return result;
255 }
256#ifdef PIXIE_SEQUENCE_TREE_TESTING
261 using test_tree_type = Tree;
268 const Tree& test_tree() const noexcept { return tree_; }
278 static void test_fail_payload_after(std::ptrdiff_t n) noexcept {
279 payload_failure_ = n;
280 }
281#endif
282
283 private:
284#ifdef PIXIE_SEQUENCE_TREE_TESTING
285 inline static thread_local std::ptrdiff_t payload_failure_ = -1;
286#endif
287 static void payload_allocation() {
288#ifdef PIXIE_SEQUENCE_TREE_TESTING
289 if (payload_failure_ == 0) {
290 throw std::bad_alloc();
291 }
292 if (payload_failure_ > 0) {
293 --payload_failure_;
294 }
295#endif
296 }
297 static std::size_t add(std::size_t a, std::size_t b) noexcept {
298 const auto max = std::numeric_limits<std::size_t>::max();
299 return b > max - a ? max : a + b;
300 }
301 static std::size_t multiply(std::size_t a, std::size_t b) noexcept {
302 const auto max = std::numeric_limits<std::size_t>::max();
303 return b && a > max / b ? max : a * b;
304 }
305 // Stream blocks with bounded scratch and at most one unpublished chunk.
306 // Reserve each payload vector before publishing addresses; never grow it.
307 template <std::ranges::input_range Range>
308 requires std::
309 constructible_from<T, std::ranges::range_rvalue_reference_t<Range>>
310 static PermutableSequence from_range_impl(Range&& range) {
311 PermutableSequence result;
312 auto it = std::ranges::begin(range);
313 const auto end = std::ranges::end(range);
314 Chunk* current = nullptr;
315 std::size_t offset = 0;
316 auto has_input = [&] {
317 if constexpr (packed) {
318 return it != end;
319 } else {
320 return (current && offset < current->values.size()) || it != end;
321 }
322 };
323 auto next_block = [&] {
324 std::array<typename Block::value_type, Block::capacity> scratch;
325 std::size_t count = 0;
326 while (count < Block::capacity && has_input()) {
327 if constexpr (packed) {
328 scratch[count++] = T(std::ranges::iter_move(it));
329 ++it;
330 } else {
331 if (!current || offset == current->values.size()) {
332 payload_allocation();
333 auto chunk = std::make_unique<Chunk>();
334 payload_allocation();
335 chunk->values.reserve(chunk_capacity);
336 while (chunk->values.size() < chunk_capacity && it != end) {
337 if constexpr (std::same_as<T, bool>) {
338 chunk->values.emplace_back(bool(std::ranges::iter_move(it)));
339 } else {
340 chunk->values.emplace_back(std::ranges::iter_move(it));
341 }
342 ++it;
343 }
344 current = chunk.get();
345 result.chunks_.append(chunk.release());
346 offset = 0;
347 }
348 if constexpr (std::same_as<T, bool>) {
349 scratch[count++] = std::addressof(current->values[offset++].value);
350 } else {
351 scratch[count++] = std::addressof(current->values[offset++]);
352 }
353 }
354 }
355 return Block(
356 std::span<const typename Block::value_type>(scratch.data(), count));
357 };
358 // A genuine single-pass cursor: dereferencing never advances the input.
359 // A transform_view would incorrectly promise an equality-preserving map.
360 struct Blocks {
361 decltype(next_block)& next;
362 Block current;
363 struct Iterator {
364 using value_type [[maybe_unused]] = Block;
365 using difference_type [[maybe_unused]] = std::ptrdiff_t;
366 using iterator_concept [[maybe_unused]] = std::input_iterator_tag;
367 Blocks* source;
368 Block& operator*() const noexcept { return source->current; }
369 Iterator& operator++() {
370 source->current = source->next();
371 return *this;
372 }
373 void operator++(int) { ++*this; }
374 bool operator==(std::default_sentinel_t) const noexcept {
375 return source->current.size() == 0;
376 }
377 };
378 Iterator begin() {
379 current = next();
380 return {this};
381 }
382 std::default_sentinel_t end() const noexcept { return {}; }
383 } blocks{next_block, {}};
384 result.tree_ = Tree::from_blocks(blocks);
385 return result;
386 }
387 std::size_t size_impl() const noexcept { return tree_.size(); }
388 const_reference value_at_impl(std::size_t i) const {
389 if constexpr (packed) {
390 return tree_[i];
391 } else {
392 return *tree_[i];
393 }
394 }
395 // Stage a payload slot before inserting its pointer. Roll back the slot
396 // if tree insertion fails; publish a new chunk only after tree commit.
397 void insert_at_impl(std::size_t position, T value) {
398 if (position > tree_.size()) {
399 throw std::out_of_range("PermutableSequence: insert position");
400 }
401 if constexpr (packed) {
402 tree_.insert_at(position, std::move(value));
403 } else {
404 std::unique_ptr<Chunk> staged;
405 if (!chunks_.tail || chunks_.tail->values.size() >= chunk_capacity) {
406 payload_allocation();
407 staged = std::make_unique<Chunk>();
408 payload_allocation();
409 staged->values.reserve(chunk_capacity);
410 }
411 auto& slots = staged ? staged->values : chunks_.tail->values;
412 if constexpr (std::same_as<T, bool>) {
413 slots.emplace_back(bool(std::move(value)));
414 } else {
415 slots.emplace_back(std::move(value));
416 }
417 const T* ptr;
418 if constexpr (std::same_as<T, bool>) {
419 ptr = std::addressof(slots.back().value);
420 } else {
421 ptr = std::addressof(slots.back());
422 }
423 try {
424 tree_.insert_at(position, ptr);
425 } catch (...) {
426 slots.pop_back();
427 throw;
428 }
429 if (staged) {
430 chunks_.append(staged.release());
431 }
432 }
433 }
434 void rotate_left_impl(std::size_t left,
435 std::size_t right,
436 std::size_t distance) {
437 tree_.rotate_left(left, right, distance);
438 }
439 // Commit the order tree before the nonthrowing payload-chain splice.
440 void merge_impl(PermutableSequence& donor) {
441 if (this == &donor) {
442 return;
443 }
444 tree_.merge(donor.tree_);
445 if constexpr (!packed) {
446 chunks_.splice(donor.chunks_);
447 }
448 }
449 std::size_t memory_usage_bytes_impl() const noexcept {
450 return memory_usage().total_bytes;
451 }
452
453 // Declaration order destroys the pointer tree before the pointed-to objects.
454 [[no_unique_address]] std::conditional_t<packed, NoChunks, Chunks> chunks_;
455 Tree tree_;
456};
457
458} // namespace pixie
CRTP contract for exclusively owned sequences with immutable reads.
Definition permutable_sequence.h:53
~PermutableSequence()=default
Destroy order nodes and iteratively reclaim all owned chunks.
PermutableSequence() noexcept=default
Construct canonical empty.
T value_type
Stored element type.
Definition sequence.h:98
typename Traits::const_reference const_reference
Immutable indexed result type.
Definition sequence.h:108
std::size_t lower_bound(Pred pred) const
Find the first position where pred(value) is true.
Definition sequence.h:161
static constexpr ElementStorage storage
Definition sequence.h:103
MemoryUsage memory_usage() const noexcept
Enumerate tree allocations and chunks in linear time, without writes.
Definition sequence.h:221
Lightweight immutable-read permutable-sequence CRTP contract.
ElementStorage
Compile-time element storage selection.
Definition permutable_sequence.h:23
LengthLayout
Representation of tree child lengths.
Definition sequence_options.h:14
Explicit live requested-memory breakdown, excluding allocator/RSS.
Definition sequence.h:180
std::size_t tree_block_bytes
Leaf bytes.
Definition sequence.h:188
std::size_t chunks
Payload chunk count.
Definition sequence.h:198
std::size_t total_bytes
Total live bytes.
Definition sequence.h:210
std::size_t chunk_header_bytes
Chunk header bytes.
Definition sequence.h:200
std::size_t facade_bytes
Inline owner bytes.
Definition sequence.h:182
std::size_t packed_capacity_bits
Packed payload capacity in bits.
Definition sequence.h:196
std::size_t order_bytes
Indirect ordering bytes.
Definition sequence.h:193
std::size_t vector_live_bytes
Live payload slot bytes.
Definition sequence.h:205
std::size_t blocks
Leaf count.
Definition sequence.h:184
std::size_t vector_slack_bytes
Unused payload slot bytes.
Definition sequence.h:207
std::size_t vector_capacity_bytes
Payload allocation bytes.
Definition sequence.h:203
std::size_t tree_node_bytes
Node bytes.
Definition sequence.h:190
std::size_t nodes
Node count.
Definition sequence.h:186
Definition payload_chunks.h:14