feat(data): SPSC ring buffer, incremental min/max LOD pyramid, series types
This commit is contained in:
1 parent
c93c98002b
commit
138fa478c3
15 files changed
+1182
-8
No files matched your search
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
|
||||
#include <plot/core/types.hpp>
|
||||
#include <plot/data/minmax_pyramid.hpp>
|
||||
|
||||
namespace plot::data {
|
||||
|
||||
/// A uniform-grid series of y samples: sample i lives at x = x0() + i*dx().
|
||||
///
|
||||
/// Every concrete series answers range queries exactly, and never by scanning the
|
||||
/// whole range: the bucket-aligned interior comes from the pyramid (O(log n)
|
||||
/// bucket aggregates) and at most two partial edge buckets are read through
|
||||
/// copy_raw() and scanned. Cost is independent of series length and no query
|
||||
/// allocates.
|
||||
///
|
||||
/// Indexing is over the series' own window: index 0 is the oldest sample the
|
||||
/// series currently holds (for a ring-backed series that is the oldest retained
|
||||
/// one), so reduce(start, end) and gather() are in the same coordinates as
|
||||
/// size(), x0() and pyramid().
|
||||
class Series {
|
||||
public:
|
||||
virtual ~Series() = default;
|
||||
|
||||
virtual u64 size() const = 0;
|
||||
/// x of sample 0, i.e. of the oldest sample currently held.
|
||||
virtual f64 x0() const = 0;
|
||||
/// Uniform x spacing between consecutive samples.
|
||||
virtual f64 dx() const = 0;
|
||||
virtual Range x_bounds() const = 0;
|
||||
virtual Range y_bounds() const = 0;
|
||||
virtual const MinMaxPyramid& pyramid() const = 0;
|
||||
|
||||
/// Appends interleaved (x, y) pairs in data coordinates for bucket-aligned
|
||||
/// level `level`; level 0 emits raw samples. begin/end are expected to be
|
||||
/// bucket-aligned at that level; an unaligned end is widened to the bucket that
|
||||
/// holds it (so passing end == size() includes the trailing partial bucket).
|
||||
/// Pairs are appended to out_xy, which is never cleared.
|
||||
virtual void gather(u32 level, u64 begin, u64 end, std::vector<f32>& out_xy) const = 0;
|
||||
|
||||
/// Exact min/max, sum and sum-of-squares over [begin, end).
|
||||
/// Preconditions: begin < end, end <= size(); a degenerate range yields a
|
||||
/// zero-span Range and zero sums. Implemented once, non-virtually, over
|
||||
/// copy_raw() in src/data/series.cpp.
|
||||
Range reduce(u64 begin, u64 end) const;
|
||||
f64 sum(u64 begin, u64 end) const;
|
||||
f64 sumsq(u64 begin, u64 end) const;
|
||||
|
||||
protected:
|
||||
/// Copies raw y samples for [begin, end) into `out`, which the caller sized to
|
||||
/// (end - begin). Handles ring wraparound. Only ever called with the small edge
|
||||
/// spans of the queries above, never on a per-sample path. Out-of-range spans
|
||||
/// are clamped and the unwritten tail is zero-filled.
|
||||
virtual void copy_raw(u64 begin, u64 end, f32* out) const = 0;
|
||||
|
||||
/// The shared body of the concrete gather() overrides: raw samples at level 0,
|
||||
/// bucket min/max envelope above it.
|
||||
void gather_impl(u32 level, u64 begin, u64 end, std::vector<f32>& out_xy) const;
|
||||
|
||||
private:
|
||||
/// [begin, end) split into the bucket-aligned interior the pyramid can answer
|
||||
/// exactly, plus the at most two partial edge spans it cannot.
|
||||
struct QueryParts {
|
||||
u64 interior_begin = 0, interior_end = 0; // whole level-0 buckets, may be empty
|
||||
u64 left_begin = 0, left_end = 0; // partial head bucket
|
||||
u64 right_begin = 0, right_end = 0; // partial tail bucket
|
||||
};
|
||||
static QueryParts split_query(u64 begin, u64 end);
|
||||
/// Reads both edge spans through copy_raw() into `out` (which must hold at
|
||||
/// least 2*kBucket samples) and returns how many were written.
|
||||
u64 copy_edges(const QueryParts& parts, f32* out) const;
|
||||
};
|
||||
|
||||
} // namespace plot::data
|
||||
Reference in new issue
Block a user