#pragma once #include #include #include 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& 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& 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