fix(formula,resample): 公式映射卡顿根治 + 语法/输入框完善 + 重采样面板三处修正
一、「公式映射」卡顿(用户报告:只是点进输入框就卡,编辑过公式后彻底没法用) 1. 真因:菜单内联编辑提交时无条件回写初值 ⇒ 点进输入框再点别处就把 formula = "f(t)" 写进样式;而 HasFormula() 只判 !empty(),于是 DrawTransformOrderPreserving() 变 false、ComputeLaneRange() 每帧逐点扫描 整条信号(2580 万采样 ≈ 960ms/帧)。 修法:新增 FormulaIsIdentity()(空串与 f(t) 等价);提交时归一化后恒等即清空; BuildLaneVisuals() 为「网格幅值范围」加指纹缓存(数据指针/长度/统计值 + 影响数值的样式字段),键不变直接复用(实测 0.0005ms)。 2. 真实公式仍然贵:新增 PlotView::AcquireFormulaEnvelope()(变换后的逐分组 包络,65536 组,按需算一次并缓存),BuildPolyline 的包络带改用它。 实测:静止 215→21ms、中键拖动 8ms、缩放最坏 843→81ms;极值仍逐点统计 (不会漏尖峰)。 3. AcquireProgram 只比 data() 指针 + 长度 ⇒ std::wstring 短串走 SSO 时, 等长改公式会一直复用旧程序(f(t)^2→f(t)^3 波形不更新)。改为按内容判定。 二、公式语法(Utilities/FormulaEval) - 新增 ^ 幂(右结合,2^3^2=512、2^-1=0.5、-2^2=-4),支持 2^f(t) 与 f(t)^2; - 常量 e / pi(大小写均接受,保存前统一小写); - 数据点只允许 X / x / f(t):单独的 f 不再接受(且不归一化,直接报错); - 数字扫描修正:2e 里的 e 不再被当成指数吞掉。 三、「公式映射」输入框(SignalPanelMenu) - Y= 画在输入框内部(非编辑态),进入编辑态只显示公式本身; - label 留空 ⇒ 输入框占满整行,与下方「系数调整」同宽。 四、「重采样」面板 - 修复点进间隔/频率输入框后输入框整块消失(paintOverlays 的 PaintEditField 名单漏了 RESAMPLE_INTERVAL / RESAMPLE_FREQUENCY); - 最大/最小数据间隔:3 位有效数字 + Δt 复合单位,末尾追加(频率)尾缀; - 间隔显示自适应单位、编辑态用秒;频率显示自适应单位、编辑态用 Hz(均不带单位); - 面板宽度按含(频率)尾缀的文本量宽。 五、十进制格式化(Utf8Util) - 新增 FormatDecimal(绝不产生科学计数法);FormatDurationCompound / FormatDurationSeconds / FormatFrequency 改用它,Δt/间隔/频率读数不再出现 1e-05。 验证:离屏探针 .preflight/perf/probe/formulacheck_main.cpp 75/75; make release / make debug 零 warning;Release 856,064 B。
This commit is contained in:
1 parent
955b233e63
commit
74456b9305
13 files changed
+682
-74
No files matched your search
@@ -1794,6 +1794,47 @@ namespace jdi
|
||||
std::vector<gd::PointF>& band, std::vector<std::size_t>& bandBreaks,
|
||||
bool& bandMode, bool& bandSpiky) const;
|
||||
|
||||
/**
|
||||
* @brief 「公式映射」信号的逐分组包络缓存条目(见 `AcquireFormulaEnvelope`)。
|
||||
*
|
||||
* 键 = 信号 + 采样数组 + 采样数 + 公式文本 + 影响数值的样式字段。
|
||||
* `minimums` / `maximums` 存的是**已经套用完整绘制变换**(公式 → 系数 → 绝对值 →
|
||||
* 镜像 → Y 偏移)之后、每 `stride` 个采样一个分组的极值(无有效值的分组为 NaN)。
|
||||
*/
|
||||
struct FormulaEnvelopeEntry
|
||||
{
|
||||
const void* signal = nullptr; /**< `resolved.data`(区分不同信号)。 */
|
||||
const void* values = nullptr; /**< `values.data()`(区分重新加载的数据)。 */
|
||||
std::size_t count = 0; /**< 参与统计的采样数。 */
|
||||
std::size_t stride = 0; /**< 每个分组覆盖的采样数。 */
|
||||
std::wstring formula; /**< 公式文本(空 = 不适用)。 */
|
||||
float gain = 1.0f;
|
||||
bool absolute = false;
|
||||
bool mirrorVertical = false;
|
||||
float offsetY = 0.0f;
|
||||
std::vector<double> minimums; /**< 每组的绘制值下限。 */
|
||||
std::vector<double> maximums; /**< 每组的绘制值上限。 */
|
||||
bool valid = false; /**< 是否已成功建立。 */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 取得(必要时建立)「公式映射」信号的逐分组包络。
|
||||
*
|
||||
* ⚠️⚠️ **为什么必须单独建一层**:`SignalData::coarseMin/coarseMax` 只存**原始采样**的
|
||||
* 分组极值,只有在数值变换**保序**时才能直接缩放使用(见 `DrawTransformOrderPreserving`)。
|
||||
* 公式(如 `f(t)^2`、`1/f(t)`)不保序,旧实现在这种情况会退化成**逐点扫描整条信号**:
|
||||
* `VERY_BIG_DATA` 单条 2580 万采样时,一次折线构建 ≈ 850ms(缩放一帧就卡一次)。
|
||||
* 这里把“变换后的分组极值”算**一次**缓存起来,之后每帧/每次缩放只按分组聚合
|
||||
* (几十微秒),且极值仍是逐点统计出来的 ⇒ **不会漏掉尖峰**。
|
||||
*
|
||||
* 只在公式信号上使用;无公式时返回 nullptr(继续走既有的 `coarseMin/Max` 快路径)。
|
||||
*
|
||||
* @param [IN] resolved 已解析的信号视图。
|
||||
* @return 缓存条目指针(内容有效);不适用或无法建立时返回 nullptr。
|
||||
*/
|
||||
const FormulaEnvelopeEntry* AcquireFormulaEnvelope(const ResolvedSignal& resolved) const;
|
||||
|
||||
|
||||
/**
|
||||
* @brief 绘制单个网格。
|
||||
* @param [IN,OUT] g 绘图上下文。
|
||||
@@ -3183,6 +3224,18 @@ namespace jdi
|
||||
std::vector<gd::RectF> m_cursorLabelRects; /**< 本次绘制已占用的取值标注矩形(用于避让重叠)。 */
|
||||
std::vector<LaneVisual> m_lanes; /**< 当前网格绘制信息集合。 */
|
||||
|
||||
/**
|
||||
* @brief 「网格幅值范围」缓存指纹(见 `BuildLaneVisuals`)。
|
||||
*
|
||||
* `ComputeLaneRange()` 对启用了「公式映射」的信号必须**逐点扫描整条信号**
|
||||
*(公式不保证单调,端点映射会算错)。而它在 `BuildLaneVisuals` 里**每帧**为
|
||||
* 每个网格各调一次 ⇒ 千万级采样时每帧几百毫秒。范围只依赖「可见信号 + 数值样式 +
|
||||
* 数据本身」,与视界无关,因此按指纹缓存:指纹一致就复用 `m_laneRangeYMin/Max`。
|
||||
*/
|
||||
std::uint64_t m_laneRangeKey = 0;
|
||||
std::vector<double> m_laneRangeYMin; /**< 各网格缓存的 yMin(与上述指纹配套)。 */
|
||||
std::vector<double> m_laneRangeYMax; /**< 各网格缓存的 yMax(与上述指纹配套)。 */
|
||||
|
||||
/**
|
||||
* @brief 「边沿统计」的累积状态(普通边沿计数与中心轴边沿计数**共用**)。
|
||||
*
|
||||
@@ -3519,6 +3572,13 @@ namespace jdi
|
||||
const LaneVisual& lane) const;
|
||||
|
||||
mutable std::deque<PolylineCacheEntry> m_polylineCache; /**< 折线顶点缓存(按几何键复用)。 */
|
||||
/**
|
||||
* @brief 「公式映射」信号的逐分组包络缓存(见 `AcquireFormulaEnvelope`)。
|
||||
*
|
||||
* 只在信号带公式时建立;条目键含公式文本与影响数值的样式字段,
|
||||
* 公式 / 系数 / 镜像等变化时自然失效。用 `deque` 保证追加时已有引用不失效。
|
||||
*/
|
||||
mutable std::deque<FormulaEnvelopeEntry> m_formulaEnvelopes;
|
||||
/**
|
||||
* @brief 折线顶点缓存的**容量上限**(条目数)。
|
||||
*
|
||||
|
||||
@@ -607,6 +607,18 @@ namespace jdi
|
||||
inline const double kResampleIntervalMin = 1.0e-9;
|
||||
inline const double kResampleIntervalMax = 1.0e6;
|
||||
|
||||
// ---- 「公式映射」的分组包络缓存(见 `PlotView::AcquireFormulaEnvelope`)----
|
||||
/**
|
||||
* @brief 每个信号的包络分组数上限(分组宽度按采样数反算)。
|
||||
*
|
||||
* 65536 组 × 2(min/max)× 4B ≈ 512KB/信号;对 2580 万采样 ⇒ 每组 ~394 个采样,
|
||||
* 而屏幕上一个像素列覆盖上万采样 ⇒ 分组粒度造成的包络误差远小于 1 像素。
|
||||
*/
|
||||
inline const std::size_t kFormulaEnvelopeBuckets = 65536;
|
||||
/** @brief 包络缓存最多保存的信号数(超出即整表清空,避免无界增长)。 */
|
||||
inline const std::size_t kFormulaEnvelopeCacheLimit = 16;
|
||||
|
||||
|
||||
// ---- 「均值滤波」面板的两行参数滑块(用户 2026-10-30 要求)----
|
||||
/**
|
||||
* 标签一律**不带 `X` / `N`**:滑块的量程本身已经说明是数值,符号只会让标签变长。
|
||||
@@ -1294,11 +1306,34 @@ namespace jdi
|
||||
return FormatValue(seconds) + L"s";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 把时间间隔四舍五入到 **3 位有效数字**(供「重采样」面板显示)。
|
||||
*
|
||||
* 计算出来的数据间隔常常带一长串浮点尾数(如 `5.0000158us`)。整串显示既没有意义,
|
||||
* 又会把面板撑得很宽(还要在后面追加「(频率)」尾缀,用户 2026-11-03 要求)。
|
||||
* 3 位有效数字足以区分相邻的采样间隔,且让显示稳定。
|
||||
*
|
||||
* @param [IN] seconds 时间间隔(秒)。
|
||||
* @return 舍入后的间隔;非法/非正输入原样返回。
|
||||
*/
|
||||
inline double RoundIntervalForDisplay(double seconds)
|
||||
{
|
||||
if (!std::isfinite(seconds) || seconds <= 0.0)
|
||||
{
|
||||
return seconds;
|
||||
}
|
||||
const double magnitude = std::floor(std::log10(seconds));
|
||||
const double scale = std::pow(10.0, 2.0 - magnitude);
|
||||
const double rounded = std::round(seconds * scale) / scale;
|
||||
return (rounded > 0.0) ? rounded : seconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 时间间隔的显示文本(**不带** "Δt:" 前缀)。
|
||||
*
|
||||
* 「重采样」面板的「最大 / 最小数据间隔」两行用它:单位随间隔长短自适应
|
||||
*(`FormatDurationCompound`:1h 1min 2s / 5s 60ms / 120us …)。
|
||||
*(`FormatDurationCompound`:1h 1min 2s / 5s 60ms / 120us …),
|
||||
* 且先舍入到 3 位有效数字(见 `RoundIntervalForDisplay`),**不出现科学计数法**。
|
||||
*
|
||||
* @param [IN] seconds 时间间隔(秒)。
|
||||
* @return 文本。
|
||||
@@ -1309,7 +1344,7 @@ namespace jdi
|
||||
{
|
||||
return L"—";
|
||||
}
|
||||
return FormatDurationCompound(seconds);
|
||||
return FormatDurationCompound(RoundIntervalForDisplay(seconds));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user