feat: a late packet backfills the hole it left, values only

This commit is contained in:
Martino Ferrari committed 2026-10-08 19:44:24 +02:00
1 parent fc81e661d0
commit 7c60d79b65
4 files changed
+143 -37

No files matched your search

+35 -26
View File
@@ -31,41 +31,50 @@ ChannelBuffer::ChannelBuffer(std::shared_ptr<const plot::data::RingBuffer<f32>>
void ChannelBuffer::acquire() noexcept {
const u64 produced = values_->produced();
if (produced <= published_) {
if (produced > published_) {
// A producer that lapped this ring -- a long trigger wait, or a store that was
// not being published for a while -- makes the incremental append meaningless:
// the samples between `published_` and `produced_` are no longer in the ring.
// Rebuilding from the retained window is O(capacity) once, and it is the only
// way the published window stays coherent (an empty or garbled window is what
// "the trace disappeared" looks like from outside).
// Nothing new; still allow the window to slide if another reader dropped it.
return;
if (produced - published_ > values_->capacity()) {
published_ = 0;
pyramid_.clear();
}
f32 chunk[kChunk];
for (u64 i = published_; i < produced;) {
const u64 take = std::min<u64>(kChunk, produced - i);
values_->copy_from(i, take, chunk);
for (u64 k = 0; k < take; ++k) pyramid_.append(chunk[k]);
i += take;
}
published_ = produced;
// The pyramid's window follows the ring's: sample index `base` is the oldest
// one still retained. drop_before() is monotonic and cheap.
pyramid_.drop_before(values_->base());
// And the window's first stamp with it, here and nowhere else (see the member's note): the
// window is pinned by the line above, so its origin is a fixed sample, and this is the one
// place in the object's life where that sample's slot is safe to read.
if (pyramid_.size() > 0) window_x0_ = time_of(absolute(0));
}
// A producer that lapped this ring -- a long trigger wait, or a store that was
// not being published for a while -- makes the incremental append meaningless:
// the samples between `published_` and `produced_` are no longer in the ring.
// Rebuilding from the retained window is O(capacity) once, and it is the only
// way the published window stays coherent (an empty or garbled window is what
// "the trace disappeared" looks like from outside).
if (produced - published_ > values_->capacity()) {
published_ = 0;
pyramid_.clear();
// Backfills, applied after the append pass above: an amend is only ever queued for a slot
// the value ring already produced, so the append has reached it and the pyramid's bounds
// test will accept it. Unconditional, outside the `produced > published_` guard -- a late
// packet can land in a frame interval that pushed nothing new, and the plan's placement
// behind the old early return would have left the pyramid stale until the next push.
if (!pending_amends_.empty()) {
for (const Amend& a : pending_amends_) pyramid_.amend(a.absolute, a.value);
pending_amends_.clear();
}
f32 chunk[kChunk];
for (u64 i = published_; i < produced;) {
const u64 take = std::min<u64>(kChunk, produced - i);
values_->copy_from(i, take, chunk);
for (u64 k = 0; k < take; ++k) pyramid_.append(chunk[k]);
i += take;
}
published_ = produced;
// The pyramid's window follows the ring's: sample index `base` is the oldest
// one still retained. drop_before() is monotonic and cheap.
pyramid_.drop_before(values_->base());
// And the window's first stamp with it, here and nowhere else (see the member's note): the
// window is pinned by the line above, so its origin is a fixed sample, and this is the one
// place in the object's life where that sample's slot is safe to read.
if (pyramid_.size() > 0) window_x0_ = time_of(absolute(0));
}
void ChannelBuffer::invalidate() noexcept {
published_ = 0;
window_x0_ = 0.0;
pyramid_.clear();
pending_amends_.clear();
}
f64 ChannelBuffer::time_of(u64 absolute_index) const noexcept {