PineForge unreleased
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
exit_scratch.hpp
Go to the documentation of this file.
1#pragma once
2
7
8#include <algorithm>
9#include <array>
10#include <cstdint>
11#include <cstring>
12#include <exception>
13#include <iterator>
14#include <limits>
15#include <optional>
16#include <stdexcept>
17#include <string>
18#include <type_traits>
19#include <utility>
20
21namespace pineforge::source {
24
25namespace detail {
26using pineforge::detail::ScratchBuffer;
27using pineforge::detail::ScratchByteSource;
31using pineforge::detail::ScratchKeyOrder;
32using pineforge::detail::ScratchReadback;
33using pineforge::detail::ScratchRecordInfo;
34using pineforge::detail::ScratchSpanSource;
36using pineforge::detail::CommandScratchStore;
37
38// CR-265: at most this many scope rows per teardown transaction. The final
39// partial batch commits once; scope population never sizes resident storage.
40inline constexpr std::size_t kExitScopeEraseBatch = 64;
41
42// Terminal diagnostic only, like CommandScratchStore::carried_failure().
43// Retain the first teardown failure across rollback and further unwinding;
44// it is neither logical scratch state nor a population-sized allocation.
46public:
47 bool pending() const noexcept { return status_ != ScratchStatus::ok; }
48 void remember(ScratchStatus status, const ScratchReadback& store) noexcept {
49 if (pending() || status == ScratchStatus::ok) return;
50 status_ = status;
51 if (status == ScratchStatus::io_error)
52 if (const auto* failure = store.carried_failure()) failure_ = *failure;
53 }
54 void check() const;
55private:
56 ScratchStatus status_ = ScratchStatus::ok;
57 std::optional<pineforge::detail::ReportScratchIoError> failure_;
58};
59static_assert(std::is_nothrow_copy_constructible_v<pineforge::detail::ReportScratchIoError>
60 && std::is_nothrow_copy_assignable_v<pineforge::detail::ReportScratchIoError>,
61 "teardown must retain the typed backing failure without throwing");
62
63// The source run owner provisions the CX-O1C store. The version owner must retain
64// every payload field externally until release_scope, including across nested
65// Applied callbacks; it may not keep an owning row/token table in RAM. Reads
66// reproduce the frozen value, not the current source row. These integration
67// declarations do not implement a second store.
69public:
70 virtual ~ExitScratchVersions() = default;
71 virtual std::uint64_t placement(std::uint64_t scope, const PlacementSnapshot&) = 0;
72 virtual PlacementSnapshot placement(std::uint64_t version) const = 0;
73 virtual std::uint64_t request(std::uint64_t scope, const native_order::Request&) = 0;
74 virtual native_order::Request request(std::uint64_t version) const = 0;
75 virtual std::uint64_t pending(std::uint64_t scope, const native_order::Request&,
76 const PlacementSnapshot&, const std::string& key) = 0;
77 virtual void pending(std::uint64_t version, native_order::Request&,
78 PlacementSnapshot&, std::string& key) const = 0;
79 // OPEN: native owners must retain this fixed membership beyond scratch
80 // scope destruction. A live BindCohort cannot implement this operation.
81 using OpeningReader = ScratchStatus (*)(void*, std::uint64_t,
84 std::int64_t cycle, std::uint64_t count, void*, OpeningReader) = 0;
85 virtual void release_scope(std::uint64_t scope) noexcept = 0;
86 void check_teardown_failure() const { teardown_failure_.check(); }
87 bool teardown_failed() const noexcept { return teardown_failure_.pending(); }
88protected:
90};
91
96 std::uint64_t next_scope = 0;
97 void* observation = nullptr;
98 void (*observe_rows)(void*, std::uint64_t scope, std::size_t rows) noexcept = nullptr;
99 void (*observe_lookup)(void*, std::size_t candidates) noexcept = nullptr;
100 ScratchStatus (*lots)(const NativeStrategyHost&, double, void*,
101 bool (*)(void*, const NativeOpenLot&)) = nullptr;
103 bool (*)(void*, const NativeWorkingRequest&)) = nullptr;
105};
106
107class ExitScratchError final : public coded<std::logic_error> {
108public:
110 ScratchStatus status() const noexcept { return status_; }
111private:
112 ScratchStatus status_;
113};
114
115// Defined in pine_adapter.cpp, including the unbound/error paths. project()
116// installs the run owner; bind_exit_scratch supplies an explicit borrowed override.
117ExitScratchContext& exit_scratch(const PineExecutionAdapter&);
118[[noreturn]] void exit_scratch_failure(ScratchStatus);
119
121 if (status != ScratchStatus::ok) exit_scratch_failure(status);
122}
123[[noreturn]] inline void exit_scratch_failure(ScratchStatus status, const ScratchReadback& store) {
124 if (status == ScratchStatus::io_error)
126 exit_scratch_failure(status);
127}
128inline void exit_scratch_check(ScratchStatus status, const ScratchReadback& store) {
129 if (status != ScratchStatus::ok) exit_scratch_failure(status, store);
130}
132 if (!pending()) return;
133 pineforge::detail::rethrow_carried_scratch_failure(failure_ ? &*failure_ : nullptr);
134 exit_scratch_failure(status_);
135}
137 if (context.versions) context.versions->check_teardown_failure();
138 context.teardown_failure.check();
139}
142 if (!context.versions) exit_scratch_failure(ScratchStatus::programming_error);
143 return *context.versions;
144}
145
146template<class Visit> void exit_scratch_lots(ExitScratchContext& scratch,
147 const NativeStrategyHost& host, double mark, Visit visit) {
149 if (!scratch.lots) exit_scratch_failure(ScratchStatus::programming_error);
150 const auto status = scratch.lots(host, mark, &visit,
151 [](void* context, const NativeOpenLot& lot) {
152 return (*static_cast<Visit*>(context))(lot);
153 });
154 if (status != ScratchStatus::cancelled) exit_scratch_check(status, scratch.store);
155}
156template<class Visit> void exit_scratch_working(ExitScratchContext& scratch,
157 const NativeStrategyHost& host, Visit visit) {
159 if (!scratch.working) exit_scratch_failure(ScratchStatus::programming_error);
160 const auto status = scratch.working(host, &visit,
161 [](void* context, const NativeWorkingRequest& request) {
162 return (*static_cast<Visit*>(context))(request);
163 });
164 if (status != ScratchStatus::cancelled) exit_scratch_check(status, scratch.store);
165}
166
167// A handle plus a borrowed readback, never a cache pointer or owning string.
168// It remains readable across eviction and nested scopes.
169class ExitScratchText final : public ScratchByteSource {
170public:
172 ScratchField field = ScratchField::value, std::uint64_t offset = 0)
173 : reader_(&reader), handle_(handle), field_(field), offset_(offset) {
174 ScratchRecordInfo info{};
175 exit_scratch_check(reader.point(handle, info), reader);
176 const auto bytes = field == ScratchField::key ? info.key_bytes : info.value_bytes;
177 if (offset > bytes) exit_scratch_failure(ScratchStatus::corrupt);
178 size_ = bytes - offset;
179 }
180 std::uint64_t size() const noexcept override { return size_; }
182 return reader_->carried_failure();
183 }
184 bool empty() const noexcept { return size_ == 0; }
185 ScratchStatus read(std::uint64_t offset, ScratchBuffer destination) const noexcept override {
186 if (offset > size_ || destination.size > size_ - offset)
187 return ScratchStatus::programming_error;
188 return reader_->read(handle_, field_, offset_ + offset, destination);
189 }
190private:
191 const ScratchReadback* reader_;
192 ScratchHandle handle_;
193 ScratchField field_;
194 std::uint64_t offset_;
195 std::uint64_t size_ = 0;
196};
197
198inline ScratchSpanSource exit_scratch_text(const std::string& text) noexcept {
199 return ScratchSpanSource({reinterpret_cast<const std::byte*>(text.data()), text.size()});
200}
201
202// The exact source_id + "\x1f" + from_entry bytes without a joined string.
204public:
205 ExitScratchPairText(const std::string& left, const std::string& right) noexcept
206 : parts_{{{reinterpret_cast<const std::byte*>(left.data()), left.size()},
207 {reinterpret_cast<const std::byte*>("\x1f"), 1},
208 {reinterpret_cast<const std::byte*>(right.data()), right.size()}}} {}
209 std::uint64_t size() const noexcept override {
210 return parts_[0].size + 1 + parts_[2].size;
211 }
212 ScratchStatus read(std::uint64_t offset, ScratchBuffer destination) const noexcept override {
213 if (offset > size() || destination.size > size() - offset
214 || (destination.size && !destination.data))
215 return ScratchStatus::programming_error;
216 for (const auto& part : parts_) {
217 if (offset >= part.size) { offset -= part.size; continue; }
218 const auto count = std::min<std::size_t>(destination.size, part.size - offset);
219 if (count) std::memcpy(destination.data, part.data + offset, count);
220 if (count) destination.data += count;
221 destination.size -= count;
222 offset = 0;
223 if (!destination.size) break;
224 }
225 return ScratchStatus::ok;
226 }
227private:
228 std::array<pineforge::detail::ScratchBytes, 3> parts_;
229};
230
232 std::uint64_t left_offset, const ScratchByteSource& right,
233 std::uint64_t right_offset, int& result) noexcept {
234 if (left_offset > left.size() || right_offset > right.size())
235 return ScratchStatus::programming_error;
236 const auto left_size = left.size() - left_offset;
237 const auto right_size = right.size() - right_offset;
238 const auto common = std::min(left_size, right_size);
239 std::array<std::byte, 128> a{}, b{};
240 for (std::uint64_t offset = 0; offset < common;) {
241 const auto count = static_cast<std::size_t>(std::min<std::uint64_t>(
242 a.size(), common - offset));
243 auto status = left.read(left_offset + offset, {a.data(), count});
244 if (status != ScratchStatus::ok) return status;
245 status = right.read(right_offset + offset, {b.data(), count});
246 if (status != ScratchStatus::ok) return status;
247 const int order = std::memcmp(a.data(), b.data(), count);
248 if (order != 0) { result = order; return ScratchStatus::ok; }
249 offset += count;
250 }
251 result = left_size < right_size ? -1 : (right_size < left_size ? 1 : 0);
252 return ScratchStatus::ok;
253}
254inline bool exit_scratch_equal(const ScratchByteSource& left,
255 const ScratchByteSource& right) {
256 if (left.size() != right.size()) return false;
257 int order = 0;
258 const auto status = exit_scratch_compare_text(left, 0, right, 0, order);
259 if (status == ScratchStatus::io_error) {
262 }
263 exit_scratch_check(status);
264 return order == 0;
265}
266inline bool operator==(const ExitScratchText& left, const std::string& right) {
267 return exit_scratch_equal(left, exit_scratch_text(right));
268}
269inline bool operator==(const std::string& left, const ExitScratchText& right) {
270 return right == left;
271}
272inline bool operator!=(const ExitScratchText& left, const std::string& right) {
273 return !(left == right);
274}
275inline bool operator!=(const std::string& left, const ExitScratchText& right) {
276 return !(right == left);
277}
278
279// All rows and secondary indexes use this ONE store. Order is scope, kind,
280// major, text bytes, ordinal. Ordinal breaks exact ties in original queue
281// order. Sources borrow input bytes; no put source reads this store.
282using ExitScratchKeyWords = std::array<std::uint64_t, 4>;
283inline constexpr std::size_t exit_scratch_key_bytes = sizeof(ExitScratchKeyWords);
284class ExitScratchKey final : public ScratchByteSource {
285public:
286 ExitScratchKey(std::uint64_t scope, std::uint64_t kind = 0,
287 std::uint64_t major = 0, std::uint64_t ordinal = 0,
288 const ScratchByteSource* text = nullptr) noexcept
289 : words_{scope, kind, major, ordinal}, text_(text) {}
290 std::uint64_t size() const noexcept override {
291 return exit_scratch_key_bytes + (text_ ? text_->size() : 0);
292 }
293 ScratchStatus read(std::uint64_t offset, ScratchBuffer destination) const noexcept override {
294 if (offset > size() || destination.size > size() - offset
295 || (destination.size && !destination.data))
296 return ScratchStatus::programming_error;
297 if (destination.size && offset < exit_scratch_key_bytes) {
298 const auto count = std::min<std::size_t>(destination.size,
299 exit_scratch_key_bytes - static_cast<std::size_t>(offset));
300 std::memcpy(destination.data,
301 reinterpret_cast<const std::byte*>(words_.data()) + offset, count);
302 destination.data += count;
303 destination.size -= count;
304 offset += count;
305 }
306 return destination.size
307 ? text_->read(offset - exit_scratch_key_bytes, destination) : ScratchStatus::ok;
308 }
309private:
310 ExitScratchKeyWords words_;
311 const ScratchByteSource* text_;
312};
313
314struct ExitScratchOrderStats { std::uint64_t comparisons = 0; };
316 return {0x5832355832370002ULL, stats,
317 [](void* context, const ScratchByteSource& left, const ScratchByteSource& right,
318 int& result) noexcept {
319 if (context) ++static_cast<ExitScratchOrderStats*>(context)->comparisons;
320 ExitScratchKeyWords a{}, b{};
321 if (left.size() < sizeof(a) || right.size() < sizeof(b))
322 return ScratchStatus::programming_error;
323 auto status = left.read(0, {reinterpret_cast<std::byte*>(a.data()), sizeof(a)});
324 if (status != ScratchStatus::ok) return status;
325 status = right.read(0, {reinterpret_cast<std::byte*>(b.data()), sizeof(b)});
326 if (status != ScratchStatus::ok) return status;
327 for (std::size_t i = 0; i < 3; ++i) {
328 if (a[i] == b[i]) continue;
329 result = a[i] < b[i] ? -1 : 1;
330 return ScratchStatus::ok;
331 }
332 status = exit_scratch_compare_text(left, sizeof(a), right, sizeof(b), result);
333 if (status != ScratchStatus::ok || result != 0) return status;
334 result = a[3] < b[3] ? -1 : (b[3] < a[3] ? 1 : 0);
335 return ScratchStatus::ok;
336 }};
337}
338
340 ScratchHandle handle) {
342 ExitScratchKeyWords words{};
343 exit_scratch_check(context.store.read(handle, ScratchField::key, 0,
344 {reinterpret_cast<std::byte*>(words.data()), sizeof(words)}), context.store);
345 return words;
346}
348 const ScratchByteSource& key) {
350 ScratchHandle handle = 0;
351 const auto status = context.store.lower_bound(key, handle);
352 if (status == ScratchStatus::end) return 0;
353 exit_scratch_check(status, context.store);
354 return handle;
355}
356template<class Edit> void exit_scratch_edit(ExitScratchContext& context, Edit operation) {
358 exit_scratch_check(context.store.begin(), context.store);
359 auto status = operation();
360 if (status == ScratchStatus::ok)
361 status = context.store.commit(ScratchDurability::buffered);
362 if (status != ScratchStatus::ok) {
363 // Rollback makes further backing calls. Preserve the first failure
364 // before cleanup can replace the backing's borrowed diagnostic.
365 std::optional<pineforge::detail::ReportScratchIoError> failure;
366 if (status == ScratchStatus::io_error)
367 if (const auto* carried = context.store.carried_failure()) failure = *carried;
368 const auto rollback_status = context.store.rollback();
369 pineforge::detail::rethrow_carried_scratch_failure(failure ? &*failure : nullptr);
370 exit_scratch_check(rollback_status, context.store);
371 exit_scratch_failure(status);
372 }
373}
374
375// Teardown uses only status-returning store operations. Snapshot the original
376// typed failure before rollback can replace the backing's borrowed diagnostic.
377// Reads include pending erases, so each lookup finds the next live scope row
378// without a resident handle list or a cursor invalidated by erase.
380 std::uint64_t scope, std::uint64_t first_kind, std::uint64_t last_kind,
381 ExitScratchTeardownFailure& failure) noexcept {
382 if (failure.pending()) return;
383 const ExitScratchKey key(scope, first_kind);
384 const auto next = [&](ScratchHandle& handle) noexcept {
385 auto status = store.lower_bound(key, handle);
386 if (status != ScratchStatus::ok) return status;
387 ExitScratchKeyWords words{};
388 status = store.read(handle, ScratchField::key, 0,
389 {reinterpret_cast<std::byte*>(words.data()), sizeof(words)});
390 if (status != ScratchStatus::ok) return status;
391 return words[0] != scope || words[1] > last_kind
392 ? ScratchStatus::end : ScratchStatus::ok;
393 };
394 for (;;) {
395 ScratchHandle handle = 0;
396 auto status = next(handle);
397 if (status == ScratchStatus::end) return;
398 if (status != ScratchStatus::ok) {
399 failure.remember(status, store);
400 return;
401 }
402 status = store.begin();
403 if (status != ScratchStatus::ok) {
404 failure.remember(status, store);
405 return;
406 }
407 for (std::size_t erased = 0; erased < kExitScopeEraseBatch; ++erased) {
408 status = store.erase(handle);
409 if (status != ScratchStatus::ok || erased + 1 == kExitScopeEraseBatch) break;
410 status = next(handle);
411 if (status == ScratchStatus::end) {
412 status = ScratchStatus::ok;
413 break;
414 }
415 if (status != ScratchStatus::ok) break;
416 }
417 if (status == ScratchStatus::ok)
418 status = store.commit(ScratchDurability::buffered);
419 if (status != ScratchStatus::ok) {
420 failure.remember(status, store);
421 const auto rollback_status = store.rollback();
422 failure.remember(rollback_status, store);
423 return;
424 }
425 }
426}
427
429public:
432 if (context.next_scope == std::numeric_limits<std::uint64_t>::max())
433 exit_scratch_failure(ScratchStatus::programming_error);
434 scope_ = ++context.next_scope;
435 }
439 : context_(std::exchange(other.context_, nullptr)), scope_(other.scope_) {}
440 ~ExitScratchScope() noexcept(false) {
441 if (!context_) return;
442 if (!context_->teardown_failure.pending()
443 && !(context_->versions && context_->versions->teardown_failed())) {
444 if (context_->versions) context_->versions->release_scope(scope_);
445 if (!(context_->versions && context_->versions->teardown_failed()))
446 exit_scratch_erase_scope_noexcept(context_->store, scope_, 0,
447 std::numeric_limits<std::uint64_t>::max(), context_->teardown_failure);
448 }
449 // Normal scope exit is a throwing boundary, even for the final scope
450 // in a callback. During unwinding preserve the active exception and
451 // retain the teardown fault for the next scratch/provider boundary.
452 if (std::uncaught_exceptions() == 0) exit_scratch_check_teardown(*context_);
453 }
454 std::uint64_t scope() const noexcept { return scope_; }
455 ExitScratchContext& context() const noexcept { return *context_; }
456 void clear() {
458 exit_scratch_erase_scope_noexcept(context_->store, scope_, 0,
459 std::numeric_limits<std::uint64_t>::max(), context_->teardown_failure);
461 }
462private:
463 ExitScratchContext* context_;
464 std::uint64_t scope_ = 0;
465};
466
467// An ordered index is records in the same store, not a resident side table.
468// Each lookup examines at most one candidate after lower_bound.
470public:
471 explicit ExitScratchIndex(ExitScratchContext& context) : scope_(context) {}
472 explicit ExitScratchIndex(const PineExecutionAdapter& owner) : scope_(exit_scratch(owner)) {}
473 void put(std::uint64_t major, std::uint64_t value, std::uint64_t ordinal = 0) {
474 const ExitScratchKey key(scope_.scope(), 0, major, ordinal);
475 put_key(key, value);
476 }
477 void put(const std::string& text, std::uint64_t value) {
478 const auto bytes = exit_scratch_text(text);
479 put(bytes, value);
480 }
481 void put(const ScratchByteSource& bytes, std::uint64_t value) {
482 const ExitScratchKey key(scope_.scope(), 0, 0, 0, &bytes);
483 put_key(key, value);
484 }
485 static constexpr std::uint64_t absent = std::numeric_limits<std::uint64_t>::max();
486 std::uint64_t find(std::uint64_t major) const {
487 const ExitScratchKey key(scope_.scope(), 0, major);
488 return find_key(key);
489 }
490 std::uint64_t find(const std::string& text) const {
491 const auto bytes = exit_scratch_text(text);
492 return find(bytes);
493 }
494 std::uint64_t find(const ScratchByteSource& bytes) const {
495 const ExitScratchKey key(scope_.scope(), 0, 0, 0, &bytes);
496 return find_key(key);
497 }
498 template<class Visit> void each(Visit visit) const { walk(0, false, visit); }
499 template<class Visit> void each(std::uint64_t major, Visit visit) const {
500 walk(major, true, visit);
501 }
502private:
503 void put_key(const ScratchByteSource& key, std::uint64_t value) {
504 const ScratchSpanSource payload({reinterpret_cast<const std::byte*>(&value), sizeof(value)});
505 auto& context = scope_.context();
506 exit_scratch_edit(context, [&] {
507 ScratchHandle handle = 0;
508 return context.store.put(key, payload, handle);
509 });
510 }
511 std::uint64_t find_key(const ScratchByteSource& key) const {
512 auto& context = scope_.context();
513 const auto handle = exit_scratch_seek(context, key);
514 if (context.observe_lookup)
515 context.observe_lookup(context.observation, handle ? 1 : 0);
516 if (!handle) return absent;
517 const ExitScratchText found(context.store, handle, ScratchField::key);
518 if (!exit_scratch_equal(key, found)) return absent;
519 std::uint64_t value = 0;
520 exit_scratch_check(context.store.read(handle, ScratchField::value, 0,
521 {reinterpret_cast<std::byte*>(&value), sizeof(value)}), context.store);
522 return value;
523 }
524 template<class Visit> void walk(std::uint64_t major, bool restricted, Visit visit) const {
525 auto& context = scope_.context();
526 const ExitScratchKey key(scope_.scope(), 0, major);
527 auto handle = exit_scratch_seek(context, key);
528 while (handle) {
529 const auto words = exit_scratch_words(context, handle);
530 if (words[0] != scope_.scope() || (restricted && words[2] != major)) break;
531 std::uint64_t value = 0;
532 exit_scratch_check(context.store.read(handle, ScratchField::value, 0,
533 {reinterpret_cast<std::byte*>(&value), sizeof(value)}), context.store);
534 if (!visit(value)) break;
535 const auto status = context.store.next(handle, handle);
536 if (status == ScratchStatus::end) break;
537 exit_scratch_check(status, context.store);
538 }
539 }
540 ExitScratchScope scope_;
541};
542
543template<class T> struct ExitScratchValue {
544 static_assert(std::is_trivially_copyable_v<T>,
545 "external payloads require a version, never an object/pointer spill");
546 static ScratchSpanSource source(const T& value) noexcept {
547 return ScratchSpanSource({reinterpret_cast<const std::byte*>(&value), sizeof(value)});
548 }
549 static T read(const ExitScratchContext& context, ScratchHandle handle) {
550 T value{};
551 exit_scratch_check(context.store.read(handle, ScratchField::value, 0,
552 {reinterpret_cast<std::byte*>(&value), sizeof(value)}), context.store);
553 return value;
554 }
555};
556template<> struct ExitScratchValue<std::string> {
557 static ScratchSpanSource source(const std::string& value) noexcept {
558 return exit_scratch_text(value);
559 }
560 static ExitScratchText read(const ExitScratchContext& context, ScratchHandle handle) {
561 return {context.store, handle};
562 }
563};
564
565// Every selected live handle belongs to this run. Keep its lifetime-managed
566// identity once, in the context, rather than spilling a SessionKey pointer.
567template<> struct ExitScratchValue<native_order::RequestHandle> {
569 return ScratchSpanSource({reinterpret_cast<const std::byte*>(&value.incarnation),
570 sizeof(value.incarnation)});
571 }
573 ScratchHandle handle) {
574 std::uint64_t incarnation = 0;
575 exit_scratch_check(context.store.read(handle, ScratchField::value, 0,
576 {reinterpret_cast<std::byte*>(&incarnation), sizeof(incarnation)}), context.store);
577 return {context.run, incarnation};
578 }
579};
580
581// No store visitor is active while a caller cancels, replaces, executes or
582// creates another scope. Each edit commits separately, so nested Applied
583// callbacks require neither nested transactions nor owning snapshots.
584template<class T> class ExitScratchRows {
585public:
587 : ExitScratchRows(exit_scratch(owner)) {}
592 : scope_(std::move(other.scope_)), count_(other.count_) {}
593 std::uint64_t scope() const noexcept { return scope_.scope(); }
594 ExitScratchContext& context() const noexcept { return scope_.context(); }
595 std::size_t size() const noexcept { return static_cast<std::size_t>(count_); }
596 bool empty() const noexcept { return count_ == 0; }
597
598 void push_back(const T& value) {
599 if constexpr (std::is_same_v<T, native_order::RequestHandle>) {
600 if (value.run != context().run)
601 exit_scratch_failure(ScratchStatus::programming_error);
602 }
603 put(count_, value);
604 ++count_;
605 observe();
606 }
607 void set(std::size_t index, const T& value) {
608 static_assert(!std::is_same_v<T, std::string>, "text selections are immutable");
609 put(index, value);
610 }
611 auto operator[](std::size_t index) const {
612 return ExitScratchValue<T>::read(context(), find(index));
613 }
614 auto front() const { return (*this)[0]; }
615 void clear() {
616 scope_.clear();
617 count_ = 0;
618 observe();
619 }
620 template<class Key, class Visit> void ordered(Key key, Visit visit) const {
621 ExitScratchIndex index(context());
622 for (std::size_t i = 0; i < size(); ++i) index.put(key((*this)[i]), i, i);
623 index.each([&](std::uint64_t ordinal) {
624 return visit((*this)[ordinal], static_cast<std::size_t>(ordinal));
625 });
626 }
627 bool contains(const std::string& text) const {
628 static_assert(std::is_same_v<T, std::string>, "text index only");
629 const auto bytes = exit_scratch_text(text);
630 const ExitScratchKey key(scope(), 1, 0, 0, &bytes);
631 const auto handle = exit_scratch_seek(context(), key);
632 if (context().observe_lookup)
633 context().observe_lookup(context().observation, handle ? 1 : 0);
634 if (!handle) return false;
635 const auto words = exit_scratch_words(context(), handle);
636 return words[0] == scope() && words[1] == 1
637 && exit_scratch_equal(bytes, ExitScratchText(context().store, handle,
638 ScratchField::key, exit_scratch_key_bytes));
639 }
640 template<class Visit> void ordered_text(Visit visit, bool unique = false) const {
641 static_assert(std::is_same_v<T, std::string>, "text index only");
642 const ExitScratchKey key(scope(), 1);
643 auto handle = exit_scratch_seek(context(), key);
644 ScratchHandle previous = 0;
645 while (handle) {
646 const auto words = exit_scratch_words(context(), handle);
647 if (words[0] != scope() || words[1] != 1) break;
648 const auto row = find(words[3]);
649 const ExitScratchText text(context().store, row);
650 if (!unique || !previous || !exit_scratch_equal(
651 text, ExitScratchText(context().store, previous))) {
652 if (!visit(text)) break;
653 }
654 previous = row;
655 const auto status = context().store.next(handle, handle);
656 if (status == ScratchStatus::end) break;
657 exit_scratch_check(status, context().store);
658 }
659 }
660
662 using iterator_category = std::input_iterator_tag;
664 std::declval<const ExitScratchContext&>(), ScratchHandle{}));
665 using difference_type = std::ptrdiff_t;
666 using pointer = void;
668 const ExitScratchRows* rows = nullptr;
669 std::size_t index = 0;
670 value_type operator*() const { return (*rows)[index]; }
671 const_iterator& operator++() { ++index; return *this; }
672 const_iterator operator++(int) { auto prior = *this; ++*this; return prior; }
673 bool operator==(const const_iterator& other) const {
674 return rows == other.rows && index == other.index;
675 }
676 bool operator!=(const const_iterator& other) const { return !(*this == other); }
677 };
678 const_iterator begin() const { return {this, 0}; }
679 const_iterator end() const { return {this, size()}; }
680
681private:
682 void observe() const noexcept {
683 if (context().observe_rows)
684 context().observe_rows(context().observation, scope(), size());
685 }
686 ScratchHandle find(std::uint64_t index) const {
687 const ExitScratchKey key(scope(), 0, 0, index);
688 const auto handle = exit_scratch_seek(context(), key);
689 if (!handle || exit_scratch_words(context(), handle)
690 != ExitScratchKeyWords{scope(), 0, 0, index})
691 exit_scratch_failure(ScratchStatus::programming_error);
692 return handle;
693 }
694 void put(std::uint64_t index, const T& value) {
695 const ExitScratchKey key(scope(), 0, 0, index);
696 const auto payload = ExitScratchValue<T>::source(value);
698 ScratchHandle handle = 0;
699 auto status = context().store.put(key, payload, handle);
700 if constexpr (std::is_same_v<T, std::string>) {
701 if (status != ScratchStatus::ok) return status;
702 const ExitScratchKey ordered(scope(), 1, 0, index, &payload);
703 const ScratchSpanSource empty({nullptr, 0});
704 status = context().store.put(ordered, empty, handle);
705 }
706 return status;
707 });
708 }
709 ExitScratchScope scope_;
710 std::uint64_t count_ = 0;
711};
712
713} // namespace detail
714} // namespace pineforge::source
coded(RunFailureCode code, const RunFailureArgs &args, const std::string &what)
const ReportScratchIoError * carried_failure() const noexcept override
ScratchStatus read(ScratchHandle, ScratchField, std::uint64_t offset, ScratchBuffer destination) const noexcept override
ScratchStatus commit(ScratchDurability durability) noexcept
ScratchStatus next(ScratchHandle current, ScratchHandle &result) const noexcept
ScratchStatus lower_bound(const ScratchByteSource &probe, ScratchHandle &result) const noexcept
ScratchStatus put(const ScratchByteSource &key, const ScratchByteSource &value, ScratchHandle &result) noexcept
virtual const ReportScratchIoError * carried_failure() const noexcept
virtual std::uint64_t size() const noexcept=0
virtual ScratchStatus point(ScratchHandle, ScratchRecordInfo &info) const noexcept=0
virtual const ReportScratchIoError * carried_failure() const noexcept
The public native host: an abstract subclass of BacktestEngine with no PineScript on it.
ScratchStatus status() const noexcept
void put(const std::string &text, std::uint64_t value)
void put(std::uint64_t major, std::uint64_t value, std::uint64_t ordinal=0)
std::uint64_t find(std::uint64_t major) const
ExitScratchIndex(const PineExecutionAdapter &owner)
static constexpr std::uint64_t absent
std::uint64_t find(const std::string &text) const
ExitScratchIndex(ExitScratchContext &context)
void each(std::uint64_t major, Visit visit) const
std::uint64_t find(const ScratchByteSource &bytes) const
void put(const ScratchByteSource &bytes, std::uint64_t value)
ExitScratchKey(std::uint64_t scope, std::uint64_t kind=0, std::uint64_t major=0, std::uint64_t ordinal=0, const ScratchByteSource *text=nullptr) noexcept
ScratchStatus read(std::uint64_t offset, ScratchBuffer destination) const noexcept override
std::uint64_t size() const noexcept override
std::uint64_t size() const noexcept override
ScratchStatus read(std::uint64_t offset, ScratchBuffer destination) const noexcept override
ExitScratchPairText(const std::string &left, const std::string &right) noexcept
ExitScratchRows(const PineExecutionAdapter &owner)
void ordered(Key key, Visit visit) const
void ordered_text(Visit visit, bool unique=false) const
ExitScratchRows(const ExitScratchRows &)=delete
bool contains(const std::string &text) const
ExitScratchRows(ExitScratchContext &context)
std::uint64_t scope() const noexcept
ExitScratchRows(ExitScratchRows &&other) noexcept
ExitScratchContext & context() const noexcept
auto operator[](std::size_t index) const
ExitScratchRows & operator=(const ExitScratchRows &)=delete
void set(std::size_t index, const T &value)
ExitScratchScope(const ExitScratchScope &)=delete
ExitScratchScope(ExitScratchScope &&other) noexcept
ExitScratchContext & context() const noexcept
ExitScratchScope(ExitScratchContext &context)
std::uint64_t scope() const noexcept
ExitScratchScope & operator=(const ExitScratchScope &)=delete
void remember(ScratchStatus status, const ScratchReadback &store) noexcept
const pineforge::detail::ReportScratchIoError * carried_failure() const noexcept override
ScratchStatus read(std::uint64_t offset, ScratchBuffer destination) const noexcept override
ExitScratchText(const ScratchReadback &reader, ScratchHandle handle, ScratchField field=ScratchField::value, std::uint64_t offset=0)
std::uint64_t size() const noexcept override
virtual std::uint64_t placement(std::uint64_t scope, const PlacementSnapshot &)=0
virtual std::uint64_t request(std::uint64_t scope, const native_order::Request &)=0
virtual std::uint64_t pending(std::uint64_t scope, const native_order::Request &, const PlacementSnapshot &, const std::string &key)=0
virtual void pending(std::uint64_t version, native_order::Request &, PlacementSnapshot &, std::string &key) const =0
virtual void release_scope(std::uint64_t scope) noexcept=0
virtual native_order::Request request(std::uint64_t version) const =0
virtual native_order::Owner freeze_openings(const native_order::RunIdentity &, std::int64_t cycle, std::uint64_t count, void *, OpeningReader)=0
virtual PlacementSnapshot placement(std::uint64_t version) const =0
ScratchStatus(*)(void *, std::uint64_t, native_order::RequestHandle &) OpeningReader
std::uint64_t ScratchHandle
void rethrow_carried_scratch_failure(const ReportScratchIoError *failure)
std::variant< Independent, WaitForApplied, BindOpening, BindOpenings, BindCohort > Owner
ScratchStatus exit_scratch_compare_text(const ScratchByteSource &left, std::uint64_t left_offset, const ScratchByteSource &right, std::uint64_t right_offset, int &result) noexcept
void exit_scratch_lots(ExitScratchContext &scratch, const NativeStrategyHost &host, double mark, Visit visit)
std::array< std::uint64_t, 4 > ExitScratchKeyWords
ExitScratchVersions & exit_scratch_versions(ExitScratchContext &context)
constexpr std::size_t kExitScopeEraseBatch
bool operator==(const ExitScratchText &left, const std::string &right)
ExitScratchContext & exit_scratch(const PineExecutionAdapter &)
void exit_scratch_check(ScratchStatus status)
bool exit_scratch_equal(const ScratchByteSource &left, const ScratchByteSource &right)
ScratchKeyOrder exit_scratch_key_order(ExitScratchOrderStats *stats=nullptr) noexcept
void exit_scratch_working(ExitScratchContext &scratch, const NativeStrategyHost &host, Visit visit)
ScratchSpanSource exit_scratch_text(const std::string &text) noexcept
void exit_scratch_erase_scope_noexcept(CommandScratchStore &store, std::uint64_t scope, std::uint64_t first_kind, std::uint64_t last_kind, ExitScratchTeardownFailure &failure) noexcept
constexpr std::size_t exit_scratch_key_bytes
ExitScratchKeyWords exit_scratch_words(ExitScratchContext &context, ScratchHandle handle)
ScratchHandle exit_scratch_seek(ExitScratchContext &context, const ScratchByteSource &key)
bool operator!=(const ExitScratchText &left, const std::string &right)
void exit_scratch_failure(ScratchStatus)
void exit_scratch_edit(ExitScratchContext &context, Edit operation)
void exit_scratch_check_teardown(const ExitScratchContext &context)
Owning value row for one open physical lot, copied at query time by native_open_lots(mark) — the lot-...
Owning value row for one live request, copied at query time.
Aggregate field order keeps market construction: Request{Transact{1.0}, "buy", "comment"}...
ScratchStatus(* lots)(const NativeStrategyHost &, double, void *, bool(*)(void *, const NativeOpenLot &))
void(* observe_lookup)(void *, std::size_t candidates) noexcept
void(* observe_rows)(void *, std::uint64_t scope, std::size_t rows) noexcept
ScratchStatus(* working)(const NativeStrategyHost &, void *, bool(*)(void *, const NativeWorkingRequest &))
bool operator!=(const const_iterator &other) const
bool operator==(const const_iterator &other) const
decltype(ExitScratchValue< T >::read( std::declval< const ExitScratchContext & >(), ScratchHandle{})) value_type
static ScratchSpanSource source(const native_order::RequestHandle &value) noexcept
static native_order::RequestHandle read(const ExitScratchContext &context, ScratchHandle handle)
static ScratchSpanSource source(const std::string &value) noexcept
static ExitScratchText read(const ExitScratchContext &context, ScratchHandle handle)
static ScratchSpanSource source(const T &value) noexcept
static T read(const ExitScratchContext &context, ScratchHandle handle)