PineForge unreleased
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
scratch_sequence.hpp
Go to the documentation of this file.
1#pragma once
2
4
5#include <algorithm>
6#include <array>
7#include <cstddef>
8#include <cstdint>
9#include <iterator>
10#include <limits>
11#include <stdexcept>
12#include <type_traits>
13
14namespace pineforge::detail {
15
16// A numeric sequence over ScratchFile, with one fixed 64 KiB write-back
17// window. Prefix retirement changes an offset, never moves rows or builds
18// an in-memory directory. These are exact bytes, not a hash summary.
19template<class T>
21 static_assert(std::is_trivially_copyable_v<T>);
22 static_assert(sizeof(T) <= 64 * 1024);
23 static constexpr std::size_t kValues = 64 * 1024 / sizeof(T);
24public:
25 ScratchSequence() { file_.bound_cache(); }
27 public:
28 using iterator_category = std::input_iterator_tag;
29 using value_type = T;
30 using difference_type = std::ptrdiff_t;
31 using reference = T;
32 using pointer = void;
33 const_iterator() = default;
34 T operator*() const { return owner_->at(index_); }
35 const_iterator& operator++() { ++index_; return *this; }
36 const_iterator operator++(int) { auto old = *this; ++*this; return old; }
37 const_iterator operator+(std::size_t n) const { return {owner_, index_ + n}; }
38 bool operator==(const const_iterator& o) const {
39 return owner_ == o.owner_ && index_ == o.index_;
40 }
41 bool operator!=(const const_iterator& o) const { return !(*this == o); }
42 private:
43 friend class ScratchSequence;
44 const_iterator(const ScratchSequence* owner, std::size_t index)
45 : owner_(owner), index_(index) {}
46 const ScratchSequence* owner_ = nullptr;
47 std::size_t index_ = 0;
48 };
49
50 void set_directory(const std::string& directory) {
51 file_.set_directory(directory);
52 directory_ = directory;
53 }
54 std::size_t size() const noexcept { return end_ - base_; }
55 bool empty() const noexcept { return size() == 0; }
56 static constexpr std::size_t resident_capacity() noexcept { return kValues; }
57 void clear() noexcept {
58 file_.clear();
59 base_ = end_ = page_ = loaded_ = 0;
60 dirty_ = false;
61 }
62 T at(std::size_t index) const {
63 if (index >= size()) throw std::out_of_range("scratch sequence index");
64 load(base_ + index);
65 return window_[(base_ + index) % kValues];
66 }
67 T operator[](std::size_t index) const { return at(index); }
68 void set(std::size_t index, T value) {
69 if (index >= size()) throw std::out_of_range("scratch sequence index");
70 load(base_ + index);
71 window_[(base_ + index) % kValues] = value;
72 dirty_ = true;
73 }
74 void push_back(T value) {
75 if (end_ == std::numeric_limits<std::size_t>::max())
76 throw ScratchIoError(EFBIG, "scratch sequence extent");
77 ScratchFile::check_extent(byte_offset(end_), sizeof(T));
78 load(end_);
79 window_[end_ % kValues] = value;
80 loaded_ = std::max(loaded_, end_ % kValues + 1);
81 dirty_ = true;
82 ++end_;
83 }
84 void resize(std::size_t count, T value = T{}) {
85 while (size() < count) push_back(value);
86 if (count == size()) return;
87 flush();
88 end_ = base_ + count;
89 loaded_ = 0;
90 }
91 const_iterator begin() const { return {this, 0}; }
92 const_iterator end() const { return {this, size()}; }
94 if (first.owner_ != this || last.owner_ != this
95 || first.index_ != 0 || last.index_ > size())
96 throw std::logic_error("scratch sequence erase is not a prefix");
97 base_ += last.index_;
98 }
99 template<class Compare>
100 void stable_sort(Compare before) {
101 ScratchSequence scratch;
102 scratch.set_directory(directory_);
103 for (std::size_t width = 1; width < size();) {
104 scratch.clear();
105 PageReader left_page(*this), right_page(*this);
106 for (std::size_t start = 0; start < size();) {
107 const auto middle = start + std::min(width, size() - start);
108 const auto stop = middle + std::min(width, size() - middle);
109 auto left = start, right = middle;
110 while (left < middle || right < stop) {
111 if (right == stop || (left < middle
112 && !before(right_page.at(right), left_page.at(left))))
113 scratch.push_back(left_page.at(left++));
114 else
115 scratch.push_back(right_page.at(right++));
116 }
117 start = stop;
118 }
119 using std::swap;
120 swap(*this, scratch);
121 if (width > size() / 2) break;
122 width *= 2;
123 }
124 }
125private:
126 class PageReader {
127 public:
128 explicit PageReader(const ScratchSequence& source) : source_(source) {}
129 T at(std::size_t index) {
130 if (index < first_ || index - first_ >= count_) {
131 source_.flush();
132 const auto first = index - index % kValues;
133 const auto count = std::min(kValues, source_.size() - first);
134 count_ = 0;
135 source_.file_.read(byte_offset(source_.base_ + first),
136 values_.data(), count * sizeof(T));
137 first_ = first;
138 count_ = count;
139 }
140 return values_[index - first_];
141 }
142 private:
143 const ScratchSequence& source_;
144 std::array<T, kValues> values_{};
145 std::size_t first_ = 0, count_ = 0;
146 };
147 static std::uint64_t byte_offset(std::size_t index) {
148 if (index > std::numeric_limits<std::uint64_t>::max() / sizeof(T))
149 throw ScratchIoError(EFBIG, "scratch sequence offset");
150 return static_cast<std::uint64_t>(index) * sizeof(T);
151 }
152 void flush() const {
153 if (!dirty_) return;
154 file_.open("pineforge-sequence");
155 file_.write(byte_offset(page_), window_.data(), loaded_ * sizeof(T));
156 dirty_ = false;
157 }
158 void load(std::size_t global) const {
159 const std::size_t next = global - global % kValues;
160 if (loaded_ != 0 && page_ == next) return;
161 flush();
162 const auto count = next < end_ ? std::min(kValues, end_ - next) : 0;
163 loaded_ = 0;
164 if (count != 0)
165 file_.read(byte_offset(next), window_.data(), count * sizeof(T));
166 page_ = next;
167 loaded_ = count;
168 }
169 mutable ScratchFile file_;
170 std::string directory_;
171 mutable std::array<T, kValues> window_{};
172 std::size_t base_ = 0, end_ = 0;
173 mutable std::size_t page_ = 0, loaded_ = 0;
174 mutable bool dirty_ = false;
175};
176} // namespace pineforge::detail
static void check_extent(std::uint64_t offset, std::size_t length, bool arena=false, IoRaiser raiser=IoRaiser::report_sink)
void read(std::uint64_t offset, void *data, std::size_t length) const
bool operator!=(const const_iterator &o) const
bool operator==(const const_iterator &o) const
const_iterator operator+(std::size_t n) const
T operator[](std::size_t index) const
void set(std::size_t index, T value)
void set_directory(const std::string &directory)
void erase(const_iterator first, const_iterator last)
void resize(std::size_t count, T value=T{})
std::size_t size() const noexcept
static constexpr std::size_t resident_capacity() noexcept
ReportScratchIoError ScratchIoError