feat: relative mode keeps the stream's absolute clock for the export
This commit is contained in:
1 parent
d11e7d48c4
commit
57e2f6aa8b
6 files changed
+87
-9
No files matched your search
@@ -135,6 +135,7 @@ std::uint32_t Acquisition::build_stamps(const udps_frame_t* frame, int time_sign
|
|||||||
n = std::max(n, udps_signal_num_elements(&frame->signals[i]));
|
n = std::max(n, udps_signal_num_elements(&frame->signals[i]));
|
||||||
}
|
}
|
||||||
stamps_.assign(n, 0.0);
|
stamps_.assign(n, 0.0);
|
||||||
|
abs_stamps_.assign(n, 0.0);
|
||||||
|
|
||||||
// Relative mode: the stamp is a pure function of position and the *declared* rate. Nothing
|
// Relative mode: the stamp is a pure function of position and the *declared* rate. Nothing
|
||||||
// below this block runs -- not the arrival-cadence override, not the degeneracy guard, not
|
// below this block runs -- not the arrival-cadence override, not the degeneracy guard, not
|
||||||
@@ -168,6 +169,21 @@ std::uint32_t Acquisition::build_stamps(const udps_frame_t* frame, int time_sign
|
|||||||
stamps_[k] = double(slot_base_ + k) * dt;
|
stamps_[k] = double(slot_base_ + k) * dt;
|
||||||
}
|
}
|
||||||
slot_base_ += n;
|
slot_base_ += n;
|
||||||
|
// The absolute clock is still read, for export only. When the stream carries no usable
|
||||||
|
// time signal there is no absolute clock to record and the relative stamp is the honest
|
||||||
|
// answer -- an export then says what the counter says, which is all anyone had.
|
||||||
|
if (time_signal >= 0 && time_signal < static_cast<int>(frame->num_signals)) {
|
||||||
|
const udps_signal_t& ts = frame->signals[time_signal];
|
||||||
|
const bool scalar_time = udps_signal_num_elements(&ts) == 1;
|
||||||
|
for (std::uint32_t k = 0; k < n; ++k) {
|
||||||
|
abs_stamps_[k] =
|
||||||
|
scalar_time
|
||||||
|
? udps_frame_value(frame, static_cast<std::uint32_t>(time_signal), k, 0)
|
||||||
|
: udps_frame_value(frame, static_cast<std::uint32_t>(time_signal), 0, k);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (std::uint32_t k = 0; k < n; ++k) abs_stamps_[k] = stamps_[k];
|
||||||
|
}
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,6 +429,10 @@ std::uint32_t Acquisition::build_stamps(const udps_frame_t* frame, int time_sign
|
|||||||
for (std::uint32_t k = 0; k < n; ++k) stamps_[k] *= unit_scale;
|
for (std::uint32_t k = 0; k < n; ++k) stamps_[k] *= unit_scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stream mode: `time()` already holds the absolute clock, so the parallel ring repeats it
|
||||||
|
// rather than carrying a second timeline. One rule for the export either way.
|
||||||
|
for (std::uint32_t k = 0; k < n; ++k) abs_stamps_[k] = stamps_[k];
|
||||||
|
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,6 +496,7 @@ void Acquisition::on_data(const udps_frame_t* frame) {
|
|||||||
value_acc_n_.assign(channels.size(), 0);
|
value_acc_n_.assign(channels.size(), 0);
|
||||||
math_sample_.assign(channels.size(), std::nanf(""));
|
math_sample_.assign(channels.size(), std::nanf(""));
|
||||||
time_acc_ = 0.0;
|
time_acc_ = 0.0;
|
||||||
|
abs_time_acc_ = 0.0;
|
||||||
time_acc_n_ = 0;
|
time_acc_n_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -595,10 +616,17 @@ void Acquisition::on_data(const udps_frame_t* frame) {
|
|||||||
// boxcar's group delay puts the averaged value, and stamping it at the end of the
|
// boxcar's group delay puts the averaged value, and stamping it at the end of the
|
||||||
// group would shift every trace half a stored sample late.
|
// group would shift every trace half a stored sample late.
|
||||||
time_acc_ += rel;
|
time_acc_ += rel;
|
||||||
|
abs_time_acc_ += abs_stamps_[k] - epoch;
|
||||||
++time_acc_n_;
|
++time_acc_n_;
|
||||||
if (tick % g == g - 1) {
|
if (tick % g == g - 1) {
|
||||||
store.time().push(time_acc_ / static_cast<double>(time_acc_n_));
|
store.time().push(time_acc_ / static_cast<double>(time_acc_n_));
|
||||||
time_acc_ = 0.0;
|
time_acc_ = 0.0;
|
||||||
|
// The absolute clock on the same slot and under the same group mean, so an export can
|
||||||
|
// pair value, relative stamp and absolute stamp by index with no interpolation.
|
||||||
|
if (plot::data::RingBuffer<f64>* abs = store.abs_time(); abs != nullptr) {
|
||||||
|
abs->push(abs_time_acc_ / static_cast<double>(time_acc_n_));
|
||||||
|
}
|
||||||
|
abs_time_acc_ = 0.0;
|
||||||
time_acc_n_ = 0;
|
time_acc_n_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ class Acquisition {
|
|||||||
/// Latches the one warning about a frame whose length disagrees with the stream's first.
|
/// Latches the one warning about a frame whose length disagrees with the stream's first.
|
||||||
bool warned_batch_ = false;
|
bool warned_batch_ = false;
|
||||||
std::vector<double> stamps_;
|
std::vector<double> stamps_;
|
||||||
|
std::vector<double> abs_stamps_;
|
||||||
/// The decimation filter's state: a running sum per channel (and one for the shared
|
/// The decimation filter's state: a running sum per channel (and one for the shared
|
||||||
/// clock) over the group of base samples that becomes one stored sample. A boxcar
|
/// clock) over the group of base samples that becomes one stored sample. A boxcar
|
||||||
/// average, which is what turns decimation into a *resampling* rather than a stride:
|
/// average, which is what turns decimation into a *resampling* rather than a stride:
|
||||||
@@ -114,6 +115,7 @@ class Acquisition {
|
|||||||
std::vector<f32> math_sample_;
|
std::vector<f32> math_sample_;
|
||||||
std::vector<std::uint64_t> value_acc_n_;
|
std::vector<std::uint64_t> value_acc_n_;
|
||||||
double time_acc_ = 0.0;
|
double time_acc_ = 0.0;
|
||||||
|
double abs_time_acc_ = 0.0;
|
||||||
std::uint64_t time_acc_n_ = 0;
|
std::uint64_t time_acc_n_ = 0;
|
||||||
/// The partition the accumulators were sized for, so a re-plan resets them rather than
|
/// The partition the accumulators were sized for, so a re-plan resets them rather than
|
||||||
/// averaging across the boundary.
|
/// averaging across the boundary.
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ struct EventSnapshot {
|
|||||||
/// (see `BufferStore::time()`), which also makes the cut in `publish_event()`/
|
/// (see `BufferStore::time()`), which also makes the cut in `publish_event()`/
|
||||||
/// `publish_pause_view()` a straight copy.
|
/// `publish_pause_view()` a straight copy.
|
||||||
std::vector<f64> stamps;
|
std::vector<f64> stamps;
|
||||||
|
/// Absolute stream seconds on the same indices as `stamps`, or empty when the store had no
|
||||||
|
/// absolute ring. **Not** rebased by `x_offset` or by the trigger edge: a snapshot outlives
|
||||||
|
/// the epoch that produced it, so it stores the absolute value outright.
|
||||||
|
std::vector<f64> abs_stamps;
|
||||||
std::vector<std::vector<f32>> values; ///< one array per channel, same length
|
std::vector<std::vector<f32>> values; ///< one array per channel, same length
|
||||||
/// The stream time of this snapshot's x = 0, so `x_offset + stamps[i]` is the clock the
|
/// The stream time of this snapshot's x = 0, so `x_offset + stamps[i]` is the clock the
|
||||||
/// data arrived on: the edge for a trigger event, the newest sample at the moment of the
|
/// data arrived on: the edge for a trigger event, the newest sample at the moment of the
|
||||||
|
|||||||
+11
-2
@@ -127,7 +127,9 @@ ExportTable collect_export(ScopeModel& model, RowSet& rows) {
|
|||||||
table.abs_seconds.resize(n);
|
table.abs_seconds.resize(n);
|
||||||
for (std::size_t i = 0; i < n; ++i) {
|
for (std::size_t i = 0; i < n; ++i) {
|
||||||
table.time[i] = st[first + i];
|
table.time[i] = st[first + i];
|
||||||
table.abs_seconds[i] = abs_base + table.time[i];
|
table.abs_seconds[i] = snap->abs_stamps.empty()
|
||||||
|
? abs_base + table.time[i]
|
||||||
|
: snap->abs_stamps[first + i];
|
||||||
}
|
}
|
||||||
for (const int c : chans) {
|
for (const int c : chans) {
|
||||||
const std::size_t ch = static_cast<std::size_t>(c);
|
const std::size_t ch = static_cast<std::size_t>(c);
|
||||||
@@ -159,9 +161,16 @@ ExportTable collect_export(ScopeModel& model, RowSet& rows) {
|
|||||||
store.time().copy_from(first, n, stamps.data());
|
store.time().copy_from(first, n, stamps.data());
|
||||||
table.time.resize(static_cast<std::size_t>(n));
|
table.time.resize(static_cast<std::size_t>(n));
|
||||||
table.abs_seconds.resize(static_cast<std::size_t>(n));
|
table.abs_seconds.resize(static_cast<std::size_t>(n));
|
||||||
|
std::vector<f64> abs_stamps;
|
||||||
|
if (const plot::data::RingBuffer<f64>* abs_ring = store.abs_time(); abs_ring != nullptr) {
|
||||||
|
abs_stamps.resize(static_cast<std::size_t>(n));
|
||||||
|
abs_ring->copy_from(first, n, abs_stamps.data());
|
||||||
|
}
|
||||||
for (std::size_t i = 0; i < stamps.size(); ++i) {
|
for (std::size_t i = 0; i < stamps.size(); ++i) {
|
||||||
table.time[i] = stamps[i] - s0;
|
table.time[i] = stamps[i] - s0;
|
||||||
table.abs_seconds[i] = abs_base + table.time[i];
|
table.abs_seconds[i] = abs_stamps.empty()
|
||||||
|
? abs_base + table.time[i]
|
||||||
|
: model.epoch() + abs_stamps[i];
|
||||||
}
|
}
|
||||||
for (const int c : chans) {
|
for (const int c : chans) {
|
||||||
const std::size_t ch = static_cast<std::size_t>(c);
|
const std::size_t ch = static_cast<std::size_t>(c);
|
||||||
|
|||||||
+24
-5
@@ -43,7 +43,7 @@ constexpr std::uint64_t kMathCostSample = 1024;
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Partition plan_partition(const std::vector<double>& channel_dt, double buffer_bytes,
|
Partition plan_partition(const std::vector<double>& channel_dt, double buffer_bytes,
|
||||||
double target_span_s) {
|
double target_span_s, bool with_abs_time) {
|
||||||
Partition p;
|
Partition p;
|
||||||
const std::size_t n = channel_dt.size();
|
const std::size_t n = channel_dt.size();
|
||||||
p.stride.assign(n, 1);
|
p.stride.assign(n, 1);
|
||||||
@@ -69,12 +69,14 @@ Partition plan_partition(const std::vector<double>& channel_dt, double buffer_by
|
|||||||
// affects every channel equally and goes through the ingest filter.
|
// affects every channel equally and goes through the ingest filter.
|
||||||
for (std::size_t i = 0; i < n; ++i) p.stride[i] = 1;
|
for (std::size_t i = 0; i < n; ++i) p.stride[i] = 1;
|
||||||
|
|
||||||
|
p.abs_time = with_abs_time;
|
||||||
|
|
||||||
const u64 budget_bytes = static_cast<u64>(std::max(1.0, buffer_bytes));
|
const u64 budget_bytes = static_cast<u64>(std::max(1.0, buffer_bytes));
|
||||||
// Weighted in *bytes*, not in slots: the shared clock is f64 and the values are f32
|
// Weighted in *bytes*, not in slots: the shared clock is f64 and the values are f32
|
||||||
// (see `BufferStore::time()`), so a time slot costs twice a value sample and the two
|
// (see `BufferStore::time()`), so a time slot costs twice a value sample and the two
|
||||||
// slices are not interchangeable. Counting both as one f32 is what would make
|
// slices are not interchangeable. Counting both as one f32 is what would make
|
||||||
// `--buffer-mb` quietly understate the time ring by its own size.
|
// `--buffer-mb` quietly understate the time ring by its own size.
|
||||||
double weight = static_cast<double>(sizeof(f64)); // the time slice itself
|
double weight = static_cast<double>(sizeof(f64)) * (with_abs_time ? 2.0 : 1.0); // the time slice(s)
|
||||||
for (u64 s : p.stride) weight += static_cast<double>(sizeof(f32)) / static_cast<double>(s);
|
for (u64 s : p.stride) weight += static_cast<double>(sizeof(f32)) / static_cast<double>(s);
|
||||||
|
|
||||||
const u64 slots = static_cast<u64>(static_cast<double>(budget_bytes) / weight);
|
const u64 slots = static_cast<u64>(static_cast<double>(budget_bytes) / weight);
|
||||||
@@ -99,7 +101,7 @@ Partition plan_partition(const std::vector<double>& channel_dt, double buffer_by
|
|||||||
return std::max<u64>(pow2_floor(ticks / stride), 1);
|
return std::max<u64>(pow2_floor(ticks / stride), 1);
|
||||||
};
|
};
|
||||||
const auto bytes_for = [&](u64 ticks) {
|
const auto bytes_for = [&](u64 ticks) {
|
||||||
u64 total = ticks * sizeof(f64);
|
u64 total = ticks * sizeof(f64) * (with_abs_time ? 2u : 1u);
|
||||||
for (u64 s : p.stride) total += capacity_for(ticks, s) * sizeof(f32);
|
for (u64 s : p.stride) total += capacity_for(ticks, s) * sizeof(f32);
|
||||||
return total;
|
return total;
|
||||||
};
|
};
|
||||||
@@ -139,6 +141,11 @@ BufferStore::BufferStore(const Partition& part, f64 span_s)
|
|||||||
: part_(part),
|
: part_(part),
|
||||||
time_(std::make_shared<plot::data::RingBuffer<f64>>(part.time_capacity)) {
|
time_(std::make_shared<plot::data::RingBuffer<f64>>(part.time_capacity)) {
|
||||||
(void)span_s;
|
(void)span_s;
|
||||||
|
// Same capacity as the clock it parallels: the two are written on the same slots, so a
|
||||||
|
// smaller ring would make an export's stamps run out before its values do.
|
||||||
|
if (part.abs_time) {
|
||||||
|
abs_time_ = std::make_shared<plot::data::RingBuffer<f64>>(part.time_capacity);
|
||||||
|
}
|
||||||
const std::size_t n = part.value_capacity.size();
|
const std::size_t n = part.value_capacity.size();
|
||||||
values_.reserve(n);
|
values_.reserve(n);
|
||||||
channels_.reserve(n);
|
channels_.reserve(n);
|
||||||
@@ -340,7 +347,7 @@ void ScopeModel::rebuild_locked() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
part_ = plan_partition(dts, cfg_.buffer_mb * 1024.0 * 1024.0,
|
part_ = plan_partition(dts, cfg_.buffer_mb * 1024.0 * 1024.0,
|
||||||
target_span_.load(std::memory_order_relaxed));
|
target_span_.load(std::memory_order_relaxed), cfg_.relative_time);
|
||||||
store_ = std::make_unique<BufferStore>(part_, part_.span_s);
|
store_ = std::make_unique<BufferStore>(part_, part_.span_s);
|
||||||
|
|
||||||
if (trigger_.channel >= static_cast<int>(channels_.size())) trigger_.channel = -1;
|
if (trigger_.channel >= static_cast<int>(channels_.size())) trigger_.channel = -1;
|
||||||
@@ -358,7 +365,7 @@ bool ScopeModel::set_target_span(double seconds) {
|
|||||||
std::vector<double> dts;
|
std::vector<double> dts;
|
||||||
dts.reserve(channels_.size());
|
dts.reserve(channels_.size());
|
||||||
for (const ChannelInfo& c : channels_) dts.push_back(c.dt);
|
for (const ChannelInfo& c : channels_) dts.push_back(c.dt);
|
||||||
const Partition want = plan_partition(dts, cfg_.buffer_mb * 1024.0 * 1024.0, seconds);
|
const Partition want = plan_partition(dts, cfg_.buffer_mb * 1024.0 * 1024.0, seconds, cfg_.relative_time);
|
||||||
if (want.time_stride == part_.time_stride) return false;
|
if (want.time_stride == part_.time_stride) return false;
|
||||||
std::fprintf(stderr,
|
std::fprintf(stderr,
|
||||||
"[udpscope] window %.4f s -> %.4f s: decimation %llu -> %llu, stored rate "
|
"[udpscope] window %.4f s -> %.4f s: decimation %llu -> %llu, stored rate "
|
||||||
@@ -524,6 +531,12 @@ void ScopeModel::publish_pause_view(double lo_s, double hi_s, double x_offset) {
|
|||||||
src.time().copy_from(first, n, snap->stamps.data());
|
src.time().copy_from(first, n, snap->stamps.data());
|
||||||
for (f64& t : snap->stamps) t -= x_offset;
|
for (f64& t : snap->stamps) t -= x_offset;
|
||||||
}
|
}
|
||||||
|
if (const plot::data::RingBuffer<f64>* abs = src.abs_time(); abs != nullptr) {
|
||||||
|
snap->abs_stamps.resize(static_cast<std::size_t>(n));
|
||||||
|
abs->copy_from(first, n, snap->abs_stamps.data());
|
||||||
|
// Absolute seconds outright -- deliberately not rebased like `stamps`.
|
||||||
|
for (f64& t : snap->abs_stamps) t += epoch_;
|
||||||
|
}
|
||||||
snap->values.resize(channels_.size());
|
snap->values.resize(channels_.size());
|
||||||
snap->y.resize(channels_.size(), plot::Range{0.0, 0.0});
|
snap->y.resize(channels_.size(), plot::Range{0.0, 0.0});
|
||||||
snap->pyramids.resize(channels_.size());
|
snap->pyramids.resize(channels_.size());
|
||||||
@@ -588,6 +601,12 @@ void ScopeModel::publish_event(double pre_s, double post_s, double edge) {
|
|||||||
// disagree with.
|
// disagree with.
|
||||||
for (auto& t : snap->stamps) t -= edge;
|
for (auto& t : snap->stamps) t -= edge;
|
||||||
}
|
}
|
||||||
|
if (const plot::data::RingBuffer<f64>* abs = src.abs_time(); abs != nullptr) {
|
||||||
|
snap->abs_stamps.resize(static_cast<std::size_t>(n));
|
||||||
|
abs->copy_from(first, n, snap->abs_stamps.data());
|
||||||
|
// Absolute seconds outright -- deliberately not rebased like `stamps`.
|
||||||
|
for (f64& t : snap->abs_stamps) t += epoch_;
|
||||||
|
}
|
||||||
|
|
||||||
snap->values.resize(channels_.size());
|
snap->values.resize(channels_.size());
|
||||||
snap->y.resize(channels_.size(), plot::Range{0.0, 0.0});
|
snap->y.resize(channels_.size(), plot::Range{0.0, 0.0});
|
||||||
|
|||||||
+18
-2
@@ -93,6 +93,11 @@ struct Partition {
|
|||||||
double base_dt = 1.0; ///< seconds per base tick
|
double base_dt = 1.0; ///< seconds per base tick
|
||||||
double span_s = 0.0; ///< stream seconds one buffer covers
|
double span_s = 0.0; ///< stream seconds one buffer covers
|
||||||
|
|
||||||
|
/// True when a second f64 ring parallel to the clock was planned and paid for in the
|
||||||
|
/// byte budget. Relative mode only -- in stream mode `time()` already *is* the absolute
|
||||||
|
/// clock and a copy would cost ~22% of the user's `--buffer-mb` for nothing.
|
||||||
|
bool abs_time = false;
|
||||||
|
|
||||||
/// Seconds between two time-base slots (the sample interval of every channel).
|
/// Seconds between two time-base slots (the sample interval of every channel).
|
||||||
double slot_dt() const noexcept { return base_dt * static_cast<double>(time_stride); }
|
double slot_dt() const noexcept { return base_dt * static_cast<double>(time_stride); }
|
||||||
/// The rate samples are actually stored at, in samples per second.
|
/// The rate samples are actually stored at, in samples per second.
|
||||||
@@ -109,9 +114,10 @@ struct Partition {
|
|||||||
/// Plans a partition for `channel_dt` cadences within `buffer_bytes`, covering at least
|
/// Plans a partition for `channel_dt` cadences within `buffer_bytes`, covering at least
|
||||||
/// `target_span_s` seconds of stream -- decimating (with the ingest filter) by whatever
|
/// `target_span_s` seconds of stream -- decimating (with the ingest filter) by whatever
|
||||||
/// power of two that takes. Throws std::runtime_error when the budget cannot hold even
|
/// power of two that takes. Throws std::runtime_error when the budget cannot hold even
|
||||||
/// one sample per channel.
|
/// one sample per channel. `with_abs_time` adds a second f64 per time slot to the budget
|
||||||
|
/// and sets `Partition::abs_time`.
|
||||||
Partition plan_partition(const std::vector<double>& channel_dt, double buffer_bytes,
|
Partition plan_partition(const std::vector<double>& channel_dt, double buffer_bytes,
|
||||||
double target_span_s);
|
double target_span_s, bool with_abs_time = false);
|
||||||
|
|
||||||
/// One of the two identical sample buffers: a shared time-base ring plus one
|
/// One of the two identical sample buffers: a shared time-base ring plus one
|
||||||
/// value ring and one `ChannelBuffer` per channel.
|
/// value ring and one `ChannelBuffer` per channel.
|
||||||
@@ -132,6 +138,15 @@ class BufferStore {
|
|||||||
/// bits is plenty; a *timestamp's* is a property of how long the scope has been running.
|
/// bits is plenty; a *timestamp's* is a property of how long the scope has been running.
|
||||||
plot::data::RingBuffer<f64>& time() noexcept { return *time_; }
|
plot::data::RingBuffer<f64>& time() noexcept { return *time_; }
|
||||||
const plot::data::RingBuffer<f64>& time() const noexcept { return *time_; }
|
const plot::data::RingBuffer<f64>& time() const noexcept { return *time_; }
|
||||||
|
/// The stream's **absolute** clock, parallel to `time()` and on the same slots, or
|
||||||
|
/// `nullptr` when the store was planned without it.
|
||||||
|
///
|
||||||
|
/// There is exactly one time ring per store, shared by every channel, and relative mode
|
||||||
|
/// fills it with counter-derived stamps -- so without this ring, selecting relative mode
|
||||||
|
/// would silently drop absolute time from an export, the one output `todo.md` item 2 says
|
||||||
|
/// must keep it. Allocated in relative mode only; read only by the export path.
|
||||||
|
plot::data::RingBuffer<f64>* abs_time() noexcept { return abs_time_.get(); }
|
||||||
|
const plot::data::RingBuffer<f64>* abs_time() const noexcept { return abs_time_.get(); }
|
||||||
plot::data::RingBuffer<f32>& values(std::size_t channel) { return *values_[channel]; }
|
plot::data::RingBuffer<f32>& values(std::size_t channel) { return *values_[channel]; }
|
||||||
ChannelBuffer& channel(std::size_t channel) { return *channels_[channel]; }
|
ChannelBuffer& channel(std::size_t channel) { return *channels_[channel]; }
|
||||||
/// Shared handle to a channel view: the caller keeps the storage alive even if
|
/// Shared handle to a channel view: the caller keeps the storage alive even if
|
||||||
@@ -147,6 +162,7 @@ class BufferStore {
|
|||||||
private:
|
private:
|
||||||
Partition part_;
|
Partition part_;
|
||||||
std::shared_ptr<plot::data::RingBuffer<f64>> time_;
|
std::shared_ptr<plot::data::RingBuffer<f64>> time_;
|
||||||
|
std::shared_ptr<plot::data::RingBuffer<f64>> abs_time_;
|
||||||
std::vector<std::shared_ptr<plot::data::RingBuffer<f32>>> values_;
|
std::vector<std::shared_ptr<plot::data::RingBuffer<f32>>> values_;
|
||||||
std::vector<std::shared_ptr<ChannelBuffer>> channels_;
|
std::vector<std::shared_ptr<ChannelBuffer>> channels_;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in new issue
Block a user