修复开启边沿计数后拖动时间光标卡顿(增量统计)
症状:VERY_BIG_DATA.CSV 全选信号后拖动时间光标,未开启实验性功能时约 13ms/帧很顺畅, 一旦对某个网格开启「边沿计数」或「边沿计数(中心轴)」,拖动就变卡(实测约 24ms / 34ms 每帧)。 根因:两个边沿统计函数的缓存键里包含时间区间(rangeMin/rangeMax)。拖动时间光标时区间 每帧都在变,于是每帧全部缓存失效,对网格内每条可见信号重扫整段区间(亿级采样即数十毫秒); 同时缓存是无上限的 vector,每个新区间都会追加一条,拖一会儿就积累数百条,查找也退化为线性扫描。 修复:缓存键改为「数据分组 + 信号 + 判定基准(横向光标幅值 / 平滑半径)+ 是否限定区间」, 时间区间不再进键;缓存项改为记录「上一次统计的采样下标区间 [begin, end) 与其计数」: - 区间完全一致 → 直接命中; - 与上一帧共享一个端点(拖动光标时必然如此)→ 只扫描两端差集并增量加减; - 两端都变 / 首次统计 / 时间轴非单调 → 整段重算。 另加缓存条目上限(48 条,超出淘汰最早一条),避免长时间拖动导致缓存无界增长。 新增的滑动区间基元 AccumulateEdgeCrossings(pairFirst, pairLast) 统计采样对 k ∈ [pairFirst, pairLast](每对为 (k-1, k)),起点对的连续性由其前一个采样决定;这样把区间切成互不相交的 若干段分别累加(或从总量中减去)与一次性扫描整段完全等价,是增量加减的正确性依据。 验证(VERY_BIG_DATA、5 信号、双光标、网格 1 开启对应功能、反复拖动时间光标): - 中心轴边沿计数 33.8ms → 13.3ms/帧;普通边沿计数 24.5ms → 11.3ms/帧; - 用临时探针把增量结果与「整段重算」逐位比对(两个功能都覆盖),零 MISMATCH; - 面板数值随拖动实时更新,单光标(无区间)走全信号统计并被缓存,横向光标与面板交互不变。
This commit is contained in:
1 parent
350538c634
commit
79c84edea8
2 files changed
+297
-153
No files matched your search
@@ -1235,17 +1235,26 @@ namespace jdi
|
||||
std::vector<gd::RectF> m_cursorLabelRects; /**< 本次绘制已占用的取值标注矩形(用于避让重叠)。 */
|
||||
std::vector<LaneVisual> m_lanes; /**< 当前网格绘制信息集合。 */
|
||||
|
||||
/** 边沿统计结果缓存项(按「文件 + 信号 + 横向光标幅值 + 时间区间」标识,任一变化即重算)。 */
|
||||
/**
|
||||
* @brief 边沿统计缓存项。
|
||||
*
|
||||
* 计数只由「采样下标区间 [begin, end) 与判定基准(横向光标幅值)」决定,
|
||||
* 因此**时间区间不进缓存键**:拖动时间光标时区间每帧都在变,若按时间区间做键就会
|
||||
* 每帧全量重扫整段区间(亿级采样下正是拖拽卡顿的主因,实测单帧数十毫秒)。
|
||||
* 改为记住「上一帧统计过的下标区间」:新区间与旧区间共享一个端点时(拖动时必然如此)
|
||||
* 只扫描两端差集并增量加减,单帧代价与光标位移成正比、与区间长度无关。
|
||||
*/
|
||||
struct EdgeCountEntry
|
||||
{
|
||||
const DataFile* file = nullptr;
|
||||
const SignalData* signal = nullptr;
|
||||
double edgeValue = 0.0;
|
||||
bool hasRange = false;
|
||||
double rangeMin = 0.0;
|
||||
double rangeMax = 0.0;
|
||||
std::size_t rising = 0;
|
||||
std::size_t falling = 0;
|
||||
const DataFile* file = nullptr; /**< 所属数据分组。 */
|
||||
const SignalData* signal = nullptr; /**< 目标信号。 */
|
||||
double edgeValue = 0.0; /**< 判定阈值(横向光标幅值)。 */
|
||||
bool hasRange = false; /**< 是否限定在时间区间内统计。 */
|
||||
bool reusable = false; /**< 计数是否完全由 [begin, end) 决定。 */
|
||||
std::size_t begin = 0; /**< 已统计的采样区间起点(含)。 */
|
||||
std::size_t end = 0; /**< 已统计的采样区间终点(不含)。 */
|
||||
std::size_t rising = 0; /**< 该区间内的上升沿数量。 */
|
||||
std::size_t falling = 0; /**< 该区间内的下降沿数量。 */
|
||||
};
|
||||
mutable std::vector<EdgeCountEntry> m_edgeCountCache; /**< 边沿统计缓存(纯缓存,逻辑上不影响对象状态)。 */
|
||||
|
||||
@@ -1259,17 +1268,18 @@ namespace jdi
|
||||
/** 中心平均值曲线缓存:**deque** 保证 push_back 后已有元素引用仍然有效。 */
|
||||
mutable std::deque<CenterCurveEntry> m_centerCurveCache;
|
||||
|
||||
/** 中心轴边沿统计缓存项。 */
|
||||
/** 中心轴边沿统计缓存项(增量统计机制与 `EdgeCountEntry` 完全一致)。 */
|
||||
struct EdgeCenterCountEntry
|
||||
{
|
||||
const DataFile* file = nullptr;
|
||||
const SignalData* signal = nullptr;
|
||||
int smooth = 0;
|
||||
bool hasRange = false;
|
||||
double rangeMin = 0.0;
|
||||
double rangeMax = 0.0;
|
||||
std::size_t rising = 0;
|
||||
std::size_t falling = 0;
|
||||
const DataFile* file = nullptr; /**< 所属数据分组。 */
|
||||
const SignalData* signal = nullptr; /**< 目标信号。 */
|
||||
int smooth = 0; /**< 中心平均值曲线的平滑半径 N。 */
|
||||
bool hasRange = false; /**< 是否限定在时间区间内统计。 */
|
||||
bool reusable = false; /**< 计数是否完全由 [begin, end) 决定。 */
|
||||
std::size_t begin = 0; /**< 已统计的采样区间起点(含)。 */
|
||||
std::size_t end = 0; /**< 已统计的采样区间终点(不含)。 */
|
||||
std::size_t rising = 0; /**< 该区间内的上升沿数量。 */
|
||||
std::size_t falling = 0; /**< 该区间内的下降沿数量。 */
|
||||
};
|
||||
mutable std::vector<EdgeCenterCountEntry> m_edgeCenterCountCache; /**< 中心轴边沿统计缓存。 */
|
||||
|
||||
|
||||
@@ -57,6 +57,110 @@ namespace jdi
|
||||
const float kEdgePanelEditFieldGap = 8.0f; /**< 编辑行说明文字与数值框之间的间隔。 */
|
||||
const float kEdgePanelButtonHeight = 30.0f; /**< 均值滤波面板底部按钮行高度。 */
|
||||
const wchar_t* const kMeanFilterBuildLabel = L"构建为新信号"; /**< 均值滤波面板底部按钮文案。 */
|
||||
const std::size_t kEdgeCountCacheLimit = 48; /**< 边沿统计缓存条目上限(超出时淘汰最早的一条)。 */
|
||||
|
||||
/**
|
||||
* @brief 统计相邻采样对 (k-1, k) 中穿越判定基准的边沿数量。
|
||||
*
|
||||
* `deltaAt(k)` 返回第 k 个采样相对判定基准的偏差;返回非有限值(NaN)表示该采样
|
||||
* 不参与判定,并**打断连续性**(与前一点之间不计边沿)——这正好覆盖
|
||||
* 「采样缺失」与「采样落在统计时间区间之外」两种情况。
|
||||
*
|
||||
* 统计的是采样对 `k ∈ [pairFirst, pairLast]`(闭区间),每对的「前一点」为 `k-1`,
|
||||
* 因此起点对的连续性由 `deltaAt(pairFirst - 1)` 决定(它无效时从下一个有效点重新起算)。
|
||||
*
|
||||
* 该函数是增量统计的基本单位:只要把区间切成互不相交的若干段分别累加(或从总量中减去),
|
||||
* 结果与一次性扫描整段完全一致,因此拖动光标时可以只扫描两端差集。
|
||||
*
|
||||
* @param [IN] pairFirst 起始采样对的下标 k(含),必须 ≥ 1。
|
||||
* @param [IN] pairLast 结束采样对的下标 k(含);小于起点时不做任何事。
|
||||
* @param [IN] deltaAt 偏差取值仿函数。
|
||||
* @param [IN,OUT] rising 累加的上升沿数量。
|
||||
* @param [IN,OUT] falling 累加的下降沿数量。
|
||||
*/
|
||||
template <typename DeltaAt>
|
||||
void AccumulateEdgeCrossings(std::size_t pairFirst, std::size_t pairLast, DeltaAt deltaAt,
|
||||
std::size_t& rising, std::size_t& falling)
|
||||
{
|
||||
if (pairFirst == 0 || pairFirst > pairLast)
|
||||
{
|
||||
return;
|
||||
}
|
||||
double previousDelta = deltaAt(pairFirst - 1);
|
||||
bool hasPrevious = std::isfinite(previousDelta);
|
||||
for (std::size_t index = pairFirst; index <= pairLast; ++index)
|
||||
{
|
||||
const double current = deltaAt(index);
|
||||
if (!std::isfinite(current))
|
||||
{
|
||||
hasPrevious = false;
|
||||
continue;
|
||||
}
|
||||
if (hasPrevious)
|
||||
{
|
||||
if (previousDelta < 0.0 && current >= 0.0)
|
||||
{
|
||||
++rising;
|
||||
}
|
||||
else if (previousDelta > 0.0 && current <= 0.0)
|
||||
{
|
||||
++falling;
|
||||
}
|
||||
}
|
||||
previousDelta = current;
|
||||
hasPrevious = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算统计区间对应的采样下标区间 [begin, end)。
|
||||
*
|
||||
* 时间轴单调时用二分定位,并在区间左侧多取一个采样点,
|
||||
* 使跨越区间边界的边沿也能被计入。
|
||||
*
|
||||
* @param [IN] file 所属数据分组。
|
||||
* @param [IN] count 采样点数量。
|
||||
* @param [IN] hasRange 是否限定时间区间。
|
||||
* @param [IN] rangeMin 区间左端点时间。
|
||||
* @param [IN] rangeMax 区间右端点时间。
|
||||
* @param [OUT] begin 输出区间起点(含)。
|
||||
* @param [OUT] end 输出区间终点(不含)。
|
||||
* @return 计数是否完全由该下标区间决定(时间轴单调时为真)。
|
||||
*/
|
||||
bool EdgeIndexRange(const DataFile& file, std::size_t count, bool hasRange, double rangeMin,
|
||||
double rangeMax, std::size_t& begin, std::size_t& end)
|
||||
{
|
||||
begin = 0;
|
||||
end = count;
|
||||
if (!hasRange)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!file.timeMonotonic)
|
||||
{
|
||||
// 时间轴非单调时无法用下标区间表达统计范围(需逐点按时间过滤)。
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::vector<double>& times = file.time;
|
||||
const std::size_t timeCount = std::min(times.size(), count);
|
||||
begin = static_cast<std::size_t>(
|
||||
std::lower_bound(times.begin(), times.begin() + static_cast<std::ptrdiff_t>(timeCount),
|
||||
rangeMin) -
|
||||
times.begin());
|
||||
end = static_cast<std::size_t>(
|
||||
std::upper_bound(times.begin(), times.begin() + static_cast<std::ptrdiff_t>(timeCount),
|
||||
rangeMax) -
|
||||
times.begin());
|
||||
if (begin > 0)
|
||||
{
|
||||
--begin;
|
||||
}
|
||||
end = std::min(end, timeCount);
|
||||
begin = std::min(begin, end);
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::uint64_t kHintHoldMs = 1300; /**< 提示浮层完全显示的停留时长(毫秒)。 */
|
||||
const std::uint64_t kHintFadeMs = 500; /**< 提示浮层的淡出时长(毫秒)。 */
|
||||
|
||||
@@ -1576,91 +1680,108 @@ namespace jdi
|
||||
rising = 0;
|
||||
falling = 0;
|
||||
|
||||
for (const EdgeCenterCountEntry& entry : m_edgeCenterCountCache)
|
||||
{
|
||||
if (entry.file == &file && entry.signal == &signal && entry.smooth == smooth &&
|
||||
entry.hasRange == hasRange && entry.rangeMin == rangeMin && entry.rangeMax == rangeMax)
|
||||
{
|
||||
rising = entry.rising;
|
||||
falling = entry.falling;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<float>& curve = CenterCurveFor(signal, smooth);
|
||||
const std::size_t count = std::min(curve.size(), signal.values.size());
|
||||
|
||||
std::size_t begin = 0;
|
||||
std::size_t end = count;
|
||||
if (hasRange && file.timeMonotonic)
|
||||
{
|
||||
const std::vector<double>& times = file.time;
|
||||
const std::size_t timeCount = std::min(times.size(), count);
|
||||
begin = static_cast<std::size_t>(
|
||||
std::lower_bound(times.begin(), times.begin() + static_cast<std::ptrdiff_t>(timeCount),
|
||||
rangeMin) -
|
||||
times.begin());
|
||||
end = static_cast<std::size_t>(
|
||||
std::upper_bound(times.begin(), times.begin() + static_cast<std::ptrdiff_t>(timeCount),
|
||||
rangeMax) -
|
||||
times.begin());
|
||||
if (begin > 0)
|
||||
{
|
||||
--begin;
|
||||
}
|
||||
end = std::min(end, timeCount);
|
||||
}
|
||||
begin = std::min(begin, end);
|
||||
std::size_t end = 0;
|
||||
const bool indexed = EdgeIndexRange(file, count, hasRange, rangeMin, rangeMax, begin, end);
|
||||
|
||||
// 边沿判定:比较信号本身与其中心平均值曲线。
|
||||
// 信号由下向上越过中心曲线 → 上升沿;由上向下穿过 → 下降沿。
|
||||
bool hasPrevious = false;
|
||||
double previousDelta = 0.0;
|
||||
for (std::size_t index = begin; index < end; ++index)
|
||||
// 判定基准:信号与中心平均值曲线之差(区间之外的采样返回 NaN)。
|
||||
const auto deltaAt = [&signal, &file, &curve, count, hasRange, rangeMin,
|
||||
rangeMax](std::size_t index) -> double
|
||||
{
|
||||
if (index >= count)
|
||||
{
|
||||
return std::numeric_limits<double>::quiet_NaN();
|
||||
}
|
||||
const double value = signal.values[index];
|
||||
const float center = curve[index];
|
||||
if (!std::isfinite(value) || !std::isfinite(center))
|
||||
{
|
||||
hasPrevious = false;
|
||||
continue;
|
||||
return std::numeric_limits<double>::quiet_NaN();
|
||||
}
|
||||
if (hasRange && !file.timeMonotonic && index < file.time.size())
|
||||
{
|
||||
const double time = file.time[index];
|
||||
if (time < rangeMin || time > rangeMax)
|
||||
{
|
||||
hasPrevious = false;
|
||||
continue;
|
||||
return std::numeric_limits<double>::quiet_NaN();
|
||||
}
|
||||
}
|
||||
return value - static_cast<double>(center);
|
||||
};
|
||||
|
||||
const double delta = value - static_cast<double>(center);
|
||||
if (hasPrevious)
|
||||
// 按「文件 + 信号 + 平滑半径 + 是否限定区间」定位缓存项(**时间区间不进缓存键**)。
|
||||
EdgeCenterCountEntry* entry = nullptr;
|
||||
for (EdgeCenterCountEntry& candidate : m_edgeCenterCountCache)
|
||||
{
|
||||
if (candidate.file == &file && candidate.signal == &signal &&
|
||||
candidate.hasRange == hasRange && candidate.smooth == smooth)
|
||||
{
|
||||
if (previousDelta < 0.0 && delta >= 0.0)
|
||||
{
|
||||
++rising;
|
||||
}
|
||||
else if (previousDelta > 0.0 && delta <= 0.0)
|
||||
{
|
||||
++falling;
|
||||
}
|
||||
entry = &candidate;
|
||||
break;
|
||||
}
|
||||
previousDelta = delta;
|
||||
hasPrevious = true;
|
||||
}
|
||||
if (entry == nullptr)
|
||||
{
|
||||
if (m_edgeCenterCountCache.size() >= kEdgeCountCacheLimit)
|
||||
{
|
||||
m_edgeCenterCountCache.erase(m_edgeCenterCountCache.begin());
|
||||
}
|
||||
EdgeCenterCountEntry created;
|
||||
created.file = &file;
|
||||
created.signal = &signal;
|
||||
created.smooth = smooth;
|
||||
created.hasRange = hasRange;
|
||||
m_edgeCenterCountCache.push_back(created);
|
||||
entry = &m_edgeCenterCountCache.back();
|
||||
}
|
||||
|
||||
EdgeCenterCountEntry entry;
|
||||
entry.file = &file;
|
||||
entry.signal = &signal;
|
||||
entry.smooth = smooth;
|
||||
entry.hasRange = hasRange;
|
||||
entry.rangeMin = rangeMin;
|
||||
entry.rangeMax = rangeMax;
|
||||
entry.rising = rising;
|
||||
entry.falling = falling;
|
||||
m_edgeCenterCountCache.push_back(entry);
|
||||
// 区间完全一致 → 直接命中。
|
||||
if (indexed && entry->reusable && entry->begin == begin && entry->end == end)
|
||||
{
|
||||
rising = entry->rising;
|
||||
falling = entry->falling;
|
||||
return;
|
||||
}
|
||||
|
||||
// 与上一帧共享一个端点(拖动光标时必然如此)→ 只扫描两端差集并增量加减。
|
||||
const bool shareBegin = indexed && entry->reusable && entry->begin == begin;
|
||||
const bool shareEnd = indexed && entry->reusable && entry->end == end;
|
||||
const std::size_t oldBegin = entry->begin;
|
||||
const std::size_t oldEnd = entry->end;
|
||||
|
||||
std::size_t deltaRising = 0;
|
||||
std::size_t deltaFalling = 0;
|
||||
if (shareBegin && !shareEnd)
|
||||
{
|
||||
const std::size_t low = std::min(oldEnd, end);
|
||||
const std::size_t high = std::max(oldEnd, end);
|
||||
AccumulateEdgeCrossings(std::max<std::size_t>(1, low), high - 1, deltaAt, deltaRising,
|
||||
deltaFalling);
|
||||
rising = (end > oldEnd) ? (entry->rising + deltaRising) : (entry->rising - deltaRising);
|
||||
falling = (end > oldEnd) ? (entry->falling + deltaFalling) : (entry->falling - deltaFalling);
|
||||
}
|
||||
else if (shareEnd && !shareBegin)
|
||||
{
|
||||
const std::size_t low = std::min(oldBegin, begin);
|
||||
const std::size_t high = std::max(oldBegin, begin);
|
||||
AccumulateEdgeCrossings(low + 1, high, deltaAt, deltaRising, deltaFalling);
|
||||
rising = (begin < oldBegin) ? (entry->rising + deltaRising) : (entry->rising - deltaRising);
|
||||
falling = (begin < oldBegin) ? (entry->falling + deltaFalling) : (entry->falling - deltaFalling);
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::size_t pairFirst = (end > begin) ? (begin + 1) : 1;
|
||||
const std::size_t pairLast = (end > begin) ? (end - 1) : 0;
|
||||
AccumulateEdgeCrossings(pairFirst, pairLast, deltaAt, rising, falling);
|
||||
}
|
||||
|
||||
entry->reusable = indexed;
|
||||
entry->begin = begin;
|
||||
entry->end = end;
|
||||
entry->rising = rising;
|
||||
entry->falling = falling;
|
||||
}
|
||||
|
||||
void PlotView::EdgeCountsFor(const DataFile& file, const SignalData& signal, double edgeValue,
|
||||
@@ -1670,96 +1791,109 @@ namespace jdi
|
||||
rising = 0;
|
||||
falling = 0;
|
||||
|
||||
// 缓存键必须包含横向光标幅值与时间区间:拖动光标或拖动时间光标都会改变统计结果。
|
||||
for (const EdgeCountEntry& entry : m_edgeCountCache)
|
||||
{
|
||||
if (entry.file == &file && entry.signal == &signal && entry.hasRange == hasRange &&
|
||||
entry.edgeValue == edgeValue && entry.rangeMin == rangeMin && entry.rangeMax == rangeMax)
|
||||
{
|
||||
rising = entry.rising;
|
||||
falling = entry.falling;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 采样范围:时间单调时二分定位,否则按区间过滤(与波形绘制的处理一致)。
|
||||
const std::vector<double>& values = signal.values;
|
||||
std::size_t begin = 0;
|
||||
std::size_t end = signal.values.size();
|
||||
if (hasRange && file.timeMonotonic)
|
||||
{
|
||||
const std::vector<double>& times = file.time;
|
||||
const std::size_t count = std::min(times.size(), signal.values.size());
|
||||
begin = static_cast<std::size_t>(
|
||||
std::lower_bound(times.begin(), times.begin() + static_cast<std::ptrdiff_t>(count),
|
||||
rangeMin) -
|
||||
times.begin());
|
||||
end = static_cast<std::size_t>(
|
||||
std::upper_bound(times.begin(), times.begin() + static_cast<std::ptrdiff_t>(count),
|
||||
rangeMax) -
|
||||
times.begin());
|
||||
if (begin > 0)
|
||||
{
|
||||
// 区间左侧补一个采样点:跨越区间边界的边沿也能被计入。
|
||||
--begin;
|
||||
}
|
||||
end = std::min(end, count);
|
||||
}
|
||||
else if (hasRange)
|
||||
{
|
||||
end = std::min(end, signal.values.size());
|
||||
}
|
||||
begin = std::min(begin, end);
|
||||
std::size_t end = 0;
|
||||
const bool indexed = EdgeIndexRange(file, values.size(), hasRange, rangeMin, rangeMax, begin, end);
|
||||
|
||||
// 边沿判定:以横向光标的幅值 edgeValue 为阈值。
|
||||
// 信号从下往上穿越 → 上升沿;从上往下穿越 → 下降沿。
|
||||
bool hasPrevious = false;
|
||||
double previous = 0.0;
|
||||
for (std::size_t index = begin; index < end; ++index)
|
||||
// 判定基准:采样值相对阈值的偏差。区间之外的采样返回 NaN(等价于原实现
|
||||
// 「区间外采样不参与判定且打断连续」的语义)。
|
||||
const auto deltaAt = [&values, &file, hasRange, rangeMin, rangeMax](std::size_t index) -> double
|
||||
{
|
||||
const double value = signal.values[index];
|
||||
const double value = values[index];
|
||||
if (!std::isfinite(value))
|
||||
{
|
||||
// NaN 视为断点:不参与边沿判定,也不与前一点相连。
|
||||
hasPrevious = false;
|
||||
continue;
|
||||
return std::numeric_limits<double>::quiet_NaN();
|
||||
}
|
||||
if (hasRange && !file.timeMonotonic && index < file.time.size())
|
||||
{
|
||||
const double time = file.time[index];
|
||||
if (time < rangeMin || time > rangeMax)
|
||||
{
|
||||
hasPrevious = false;
|
||||
continue;
|
||||
return std::numeric_limits<double>::quiet_NaN();
|
||||
}
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
if (hasPrevious)
|
||||
// 按「文件 + 信号 + 阈值 + 是否限定区间」定位缓存项(**时间区间不进缓存键**)。
|
||||
EdgeCountEntry* entry = nullptr;
|
||||
for (EdgeCountEntry& candidate : m_edgeCountCache)
|
||||
{
|
||||
if (candidate.file == &file && candidate.signal == &signal &&
|
||||
candidate.hasRange == hasRange && candidate.edgeValue == edgeValue)
|
||||
{
|
||||
const double before = previous - edgeValue;
|
||||
const double after = value - edgeValue;
|
||||
if (before < 0.0 && after >= 0.0)
|
||||
{
|
||||
++rising;
|
||||
}
|
||||
else if (before > 0.0 && after <= 0.0)
|
||||
{
|
||||
++falling;
|
||||
}
|
||||
entry = &candidate;
|
||||
break;
|
||||
}
|
||||
previous = value;
|
||||
hasPrevious = true;
|
||||
}
|
||||
if (entry == nullptr)
|
||||
{
|
||||
if (m_edgeCountCache.size() >= kEdgeCountCacheLimit)
|
||||
{
|
||||
m_edgeCountCache.erase(m_edgeCountCache.begin());
|
||||
}
|
||||
EdgeCountEntry created;
|
||||
created.file = &file;
|
||||
created.signal = &signal;
|
||||
created.edgeValue = edgeValue;
|
||||
created.hasRange = hasRange;
|
||||
m_edgeCountCache.push_back(created);
|
||||
entry = &m_edgeCountCache.back();
|
||||
}
|
||||
|
||||
EdgeCountEntry entry;
|
||||
entry.file = &file;
|
||||
entry.signal = &signal;
|
||||
entry.edgeValue = edgeValue;
|
||||
entry.hasRange = hasRange;
|
||||
entry.rangeMin = rangeMin;
|
||||
entry.rangeMax = rangeMax;
|
||||
entry.rising = rising;
|
||||
entry.falling = falling;
|
||||
m_edgeCountCache.push_back(entry);
|
||||
// 阈值参与判定,因此仿函数要在累加时一并带入。
|
||||
const auto crossingAt = [&deltaAt, edgeValue](std::size_t index) -> double
|
||||
{ return deltaAt(index) - edgeValue; };
|
||||
|
||||
// 区间完全一致 → 直接命中。
|
||||
if (indexed && entry->reusable && entry->begin == begin && entry->end == end)
|
||||
{
|
||||
rising = entry->rising;
|
||||
falling = entry->falling;
|
||||
return;
|
||||
}
|
||||
|
||||
// 与上一帧共享一个端点(拖动光标时必然如此)→ 只扫描两端差集并增量加减。
|
||||
const bool shareBegin = indexed && entry->reusable && entry->begin == begin;
|
||||
const bool shareEnd = indexed && entry->reusable && entry->end == end;
|
||||
const std::size_t oldBegin = entry->begin;
|
||||
const std::size_t oldEnd = entry->end;
|
||||
|
||||
std::size_t deltaRising = 0;
|
||||
std::size_t deltaFalling = 0;
|
||||
if (shareBegin && !shareEnd)
|
||||
{
|
||||
// 右端移动:变动的是采样对 k ∈ [min(oldEnd, end), max(oldEnd, end) - 1]。
|
||||
const std::size_t low = std::min(oldEnd, end);
|
||||
const std::size_t high = std::max(oldEnd, end);
|
||||
AccumulateEdgeCrossings(std::max<std::size_t>(1, low), high - 1, crossingAt, deltaRising,
|
||||
deltaFalling);
|
||||
rising = (end > oldEnd) ? (entry->rising + deltaRising) : (entry->rising - deltaRising);
|
||||
falling = (end > oldEnd) ? (entry->falling + deltaFalling) : (entry->falling - deltaFalling);
|
||||
}
|
||||
else if (shareEnd && !shareBegin)
|
||||
{
|
||||
// 左端移动:变动的是采样对 k ∈ [min(oldBegin, begin) + 1, max(oldBegin, begin)]。
|
||||
const std::size_t low = std::min(oldBegin, begin);
|
||||
const std::size_t high = std::max(oldBegin, begin);
|
||||
AccumulateEdgeCrossings(low + 1, high, crossingAt, deltaRising, deltaFalling);
|
||||
rising = (begin < oldBegin) ? (entry->rising + deltaRising) : (entry->rising - deltaRising);
|
||||
falling = (begin < oldBegin) ? (entry->falling + deltaFalling) : (entry->falling - deltaFalling);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 两端都变了(或首次统计):整段重算。
|
||||
const std::size_t pairFirst = (end > begin) ? (begin + 1) : 1;
|
||||
const std::size_t pairLast = (end > begin) ? (end - 1) : 0;
|
||||
AccumulateEdgeCrossings(pairFirst, pairLast, crossingAt, rising, falling);
|
||||
}
|
||||
|
||||
entry->reusable = indexed;
|
||||
entry->begin = begin;
|
||||
entry->end = end;
|
||||
entry->rising = rising;
|
||||
entry->falling = falling;
|
||||
}
|
||||
|
||||
float PlotView::EdgePanelWidth(int laneIndex, EdgePanelKind kind) const
|
||||
|
||||
Reference in new issue
Block a user