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
@@ -460,6 +460,76 @@ namespace jdi
|
||||
|
||||
const float scrollOffset = Tab().LaneScroll();
|
||||
|
||||
// ⚠️⚠️ **幅值范围(`ComputeLaneRange`)必须缓存**(用户 2026-11-03 报告“只是点进过
|
||||
// 公式输入框就卡死”):本函数**每帧**都会为**每个网格**调用一次 `ComputeLaneRange`,
|
||||
// 而启用了「公式映射」的信号在里面无法用 min/max 端点映射(公式不保证单调),
|
||||
// 只能**逐点扫描整条信号** —— `VERY_BIG_DATA` 单条 2580 万采样时单帧就是几百毫秒,
|
||||
// 拖拽时每帧都付一遍 ⇒ 完全没法用。
|
||||
//
|
||||
// 幅值范围只取决于「可见信号集合 + 影响数值的样式字段 + 信号数据本身」,
|
||||
// **与视界/缩放无关**,因此用指纹缓存住:指纹不变就直接复用上一帧的结果。
|
||||
// 指纹包含数据指针/长度/统计值与公式文本 ⇒ 重新加载数据、改样式、改公式都会自动失效。
|
||||
std::uint64_t rangeKey = 1469598103934665603ull;
|
||||
const auto mixRange = [&rangeKey](std::uint64_t value)
|
||||
{
|
||||
rangeKey ^= value;
|
||||
rangeKey *= 1099511628211ull;
|
||||
};
|
||||
const auto mixRangeFloat = [&mixRange](float value)
|
||||
{
|
||||
std::uint32_t bits = 0;
|
||||
std::memcpy(&bits, &value, sizeof(bits));
|
||||
mixRange(bits);
|
||||
};
|
||||
const auto mixRangeDouble = [&mixRange](double value)
|
||||
{
|
||||
std::uint64_t bits = 0;
|
||||
std::memcpy(&bits, &value, sizeof(bits));
|
||||
mixRange(bits);
|
||||
};
|
||||
mixRange(reinterpret_cast<std::uintptr_t>(m_dataSet));
|
||||
mixRange(static_cast<std::uint64_t>(count));
|
||||
for (const Lane& lane : lanes)
|
||||
{
|
||||
mixRange(lane.autoRange ? 1ull : 0ull);
|
||||
mixRangeDouble(lane.yMin);
|
||||
mixRangeDouble(lane.yMax);
|
||||
mixRange(static_cast<std::uint64_t>(lane.signals.size()));
|
||||
for (const SignalRef& ref : lane.signals)
|
||||
{
|
||||
mixRange(static_cast<std::uint64_t>(ref.groupIndex + 2));
|
||||
mixRange(static_cast<std::uint64_t>(ref.signalIndex + 2));
|
||||
mixRange(m_tab->IsVisible(ref) ? 1ull : 0ull);
|
||||
const SignalData* signal = m_dataSet->Resolve(ref);
|
||||
const SignalStyle* style = m_dataSet->Style(ref);
|
||||
mixRange(reinterpret_cast<std::uintptr_t>(signal));
|
||||
mixRange(reinterpret_cast<std::uintptr_t>(style));
|
||||
if (signal != nullptr)
|
||||
{
|
||||
mixRange(reinterpret_cast<std::uintptr_t>(signal->values.data()));
|
||||
mixRange(static_cast<std::uint64_t>(signal->values.size()));
|
||||
mixRange(static_cast<std::uint64_t>(signal->finiteCount));
|
||||
mixRangeDouble(signal->minValue);
|
||||
mixRangeDouble(signal->maxValue);
|
||||
}
|
||||
if (style != nullptr)
|
||||
{
|
||||
// 只有**影响数值范围**的字段入键(颜色/线宽等与范围无关)。
|
||||
mixRangeFloat(style->gain);
|
||||
mixRange(style->absolute ? 1ull : 0ull);
|
||||
mixRange(style->mirrorVertical ? 1ull : 0ull);
|
||||
mixRangeFloat(style->offsetY);
|
||||
for (const wchar_t character : style->formula)
|
||||
{
|
||||
mixRange(static_cast<std::uint64_t>(static_cast<std::uint32_t>(character)));
|
||||
}
|
||||
mixRange(0x2Bull); // 公式结尾标记(避免 "f(t)" 与 "f(t)+" 撞键)
|
||||
}
|
||||
}
|
||||
}
|
||||
const bool reuseRange = (rangeKey == m_laneRangeKey) &&
|
||||
(m_laneRangeYMin.size() == count) && (m_laneRangeYMax.size() == count);
|
||||
|
||||
m_lanes.resize(count);
|
||||
for (std::size_t index = 0; index < count; ++index)
|
||||
{
|
||||
@@ -468,13 +538,33 @@ namespace jdi
|
||||
m_plotRect.X,
|
||||
m_plotRect.Y + static_cast<float>(index) * (laneHeight + gap) - scrollOffset,
|
||||
m_plotRect.Width, laneHeight);
|
||||
m_tab->ComputeLaneRange(*m_dataSet, static_cast<int>(index), visual.yMin, visual.yMax);
|
||||
if (reuseRange)
|
||||
{
|
||||
visual.yMin = m_laneRangeYMin[index];
|
||||
visual.yMax = m_laneRangeYMax[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
m_tab->ComputeLaneRange(*m_dataSet, static_cast<int>(index), visual.yMin, visual.yMax);
|
||||
}
|
||||
if (!(visual.yMax > visual.yMin))
|
||||
{
|
||||
visual.yMin = 0.0;
|
||||
visual.yMax = 1.0;
|
||||
}
|
||||
}
|
||||
// 记录本帧的幅值范围,供下一帧命中(`ComputeLaneRange` 对公式信号是 O(采样数))。
|
||||
if (!reuseRange)
|
||||
{
|
||||
m_laneRangeYMin.resize(count);
|
||||
m_laneRangeYMax.resize(count);
|
||||
for (std::size_t index = 0; index < count; ++index)
|
||||
{
|
||||
m_laneRangeYMin[index] = m_lanes[index].yMin;
|
||||
m_laneRangeYMax[index] = m_lanes[index].yMax;
|
||||
}
|
||||
m_laneRangeKey = rangeKey;
|
||||
}
|
||||
}
|
||||
|
||||
float PlotView::TimeToX(double time) const
|
||||
|
||||
Reference in new issue
Block a user