24f0bf849d128b197293350ef7e2de0c680bcd32
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
24f0bf849d |
修复悬停高亮变长方形色块/联动失效 + 信号列表动画卡顿真因(行位图缓存)
一、悬停高亮恢复"整条曲线发光"(上一轮裁成鼠标附近一段 ⇒ 满幅方波上就是个长方形色块) 光晕的语义是"整条曲线一起发光",对编码器这种满幅方波,一列包络本来就跨满网格高, 裁成 144px 宽 × 满高的窗口在屏幕上就是一个像选区框的色块(用户 2026-10-18 第二轮报告)。 修法(PlotViewPaint.cpp): - PaintSignalHaloFor 恢复**整条**几何(包络带 FillPath / 折线 DrawLines); - 新增 PaintSignalHaloCached:光晕几何只取决于「信号 + 视界 + 网格几何 + 样式」,**与鼠标无关** ⇒ 渲染一次进离屏位图,之后每帧只做一次 AlphaBlend(源位图预乘 alpha,用 256×256 查表); - 缓存键与 AcquirePolyline 同源,**不含网格屏幕位置**(滚动网格只按新位置贴同一张位图)。 210ms/帧(整条描边)→ 一次性 15ms 渲染 + 每帧 ~2ms 贴图。 halocheck 校验:悬停必命中(60/60 位置)、光晕贴合带宽(outsideBand=0)、不越出网格、 bbox 覆盖整条曲线(不是中段方块)、列表行悬停联动仍生效。 二、信号列表动画卡顿的三个真因(探针 panelstutter:129 个可见网格 + 反复"展开→切组复选框→收起") 1) 面板每帧重画 ~36 行 × ~0.35ms(十来个 GDI+ 原语)⇒ panel.Paint 24~35ms; 2) 动画帧里仍在预热网格图层:每帧重建 1 个网格图层,2500 万采样信号 10~30ms ⇒ 帧被拉长; 3) 勾选分组会把上百个**离屏**网格排进待重画队列,随后每帧补 1 个 ⇒ 数秒持续掉帧。 修法: 1) 行位图缓存(SignalPanel::PaintRowCached):按"行的可见内容"做键,整行渲染进离屏 DIB, 命中时一次 BitBlt。panel.Paint 24~35ms → 5~6ms。 ⚠️ 必须 DIB + 内存 DC(gd::Bitmap 上 GDI 会关闭 ClearType,整列文字退化); ⚠️ 行内几何在 RowRect 里平移(GDI 文本不认 GDI+ 世界变换,否则文字画到小位图之外); ⚠️ 渲染到行位图时不按列表矩形裁剪,裁剪放到贴图时的 DC(GDI 不认 GDI+ 裁剪, 必须自己 SaveDC + IntersectClipRect,折起动画时行不许画到组名上); ⚠️ 缓存上限 192 行(≈10MB)。静态画面逐像素一致(面板区域 54/275200 不同)。 2) 新增 PlotView::SetAnimationBusy:UI 动画期间不预热网格图层(队列保留,动画结束后继续)。 3) 待重画队列只保留可视 + 预取带内的网格;RollWideBuffer 不再登记离屏网格。 实测:展开 p50 35ms → 13ms、收起 33ms → 13ms、动画帧内图层重建 56 次 → 0 次。 三、试过并放弃(避免以后重走) - ExtTextOutW 替代 DrawTextW:快 2~4 倍但光栅化不同(面板 10% 像素差异)⇒ 观感变化,放弃; - 用 gd::Bitmap 做行位图:文字丢失 ClearType,放弃。 四、验证(全部离屏,不占用鼠标/键盘/屏幕) - 新增 .preflight/perf/panelstutter_main.cpp(8/8)、halocheck_main.cpp(16/16); - 回归 verify 49/49、panelanim 158/158、hoverdrag 20/20、gridanim 93/93、lanescroll 23/23、 datacheck 69/69、edgecheck 46/46、errcheck 19/19、panflash 19/19、stress 6/6; - make release / make debug 零警告(TARGET_FILE_NAME=TargetCheck/TargetCheckD)。 文档:Documents/agent_mem.md 追加本轮小节(含 4 条新规则:高亮语义不得为性能让步、 GDI 文本的三个隐形陷阱、面板卡顿先量"每行成本×可见行数"与"本帧是否顺带做绘图区重活"、 动画帧内绝不做可延迟的整帧级工作)。 |
||
|
|
6b15575829 |
滚动动画根治(巨型信号不再影响滚动)+「N 项」计数点击语义 + 浮动面板首现右上角 + 开关/关闭面板卡顿
一、滚动过渡动画在巨型信号处卡顿 —— 真因是「退化成整幅重绘」 滚动到**从未构建过图层的区域**时,可见网格一个都贴不出图层像素 ⇒ BlitWideBuffer() 返回 false ⇒ 落到 Paint() 末尾的整幅重绘 ⇒ 把本帧全部可见网格的波形(含千万级采样的巨型信号)每帧重画一遍。 实测(MF4 124 网格 + VERY_BIG_DATA 5 网格,点选最后一个信号):单帧 20~35ms,且「可见的巨型信号 网格数」越多越慢(0 个 ≈20ms、5 个 ≈34ms);对照手动拖滚动条只要 ~6ms。 修法:BlitWideBuffer 只要还能画网格底衬(backing 非空)就返回 true, 只有 blits 与 backing 都为空时才交给整幅重绘 —— 这正是「没来得及画信号时就按只有网格叠底的状态继续滚动」。 实测:动画均值 23.0→11.2ms、最坏 35.6→13.1ms;含 5 个巨型信号网格的帧 33→10.7~12.3ms。 二、面板动画帧只重绘面板(额外 −2.7ms/帧) 新增 Application::m_panelDirty:滚动定时器在只有面板动画在跑时置位,Render 走 else if (panelOnly) 只重绘面板 + 分隔条(SignalPanel::Paint 会铺满自己的区域)。m_chromeDirty(真内容变化)仍走完整装饰重绘。 ⚠️ 回送仍是 if (fullFrame) {整窗} else {绘图区} 的无条件分支(遵守 §9.41)。 实测帧耗时拆解:plot 4.8ms / plot+panel 11.4ms / plot+全装饰 13.9ms。 三、分组行「N 项」计数:只折叠展开,绝不进入改名 新增 RowPart::GROUP_COUNT 与 SignalPanel::GroupStatsRect(row)(绘制与命中的唯一几何来源,命中矩形在 文本左右各外扩 3 逻辑像素)。RowHitPart 在名称区域之前判定计数区: - OnLButtonDown:折叠/展开;OnDoubleClick:只折叠/展开,任何点击频率都不进入改名; - DoubleClickIsPlainPress 返回 true ⇒ 快速连点每一击都生效。 ⚠️「快速双击进改名」的计时只对 RowPart::BODY 记录(否则“点计数、紧接着点名称”会被误判成双击)。 四、浮动面板首次出现的位置:右上角 旧默认位置 top = plotTop + 16 + laneIndex*28:上百个网格时 laneIndex 很大 ⇒ 被夹到右下角 (实测 plotH=1081 时 topMargin=964)。改为按「当前真正显示出来的面板中排在本面板之前的个数」错开 (固定遍历顺序:网格下标 → 面板种类),并把错开量夹到“面板下沿不超过绘图区 45% 高度”以内: 第一个面板错开 0 ⇒ 正好贴在上右角(实测 topMargin=24、rightMargin=24),6 个面板时最坏 topMargin=234。 ⚠️ 判定「面板是否显示」不能只看 *Panel.open —— LanePanelState::open 默认就是 true,必须同时看功能启用。 五、开关实验性功能 / 关闭面板卡顿 —— RebuildWideBuffer() 整批重建 四个 SetLane* 末尾都调 RebuildWideBuffer()(注释说是“预建宽缓冲基底”),而它同步重渲染全部网格的图层: 129 个网格(含 5 个千万级采样的巨型信号)整批重建要几百毫秒 ⇒ 点一下功能开关 / 关闭浮动面板就会卡住。 修法:新增 PlotView::EnsureLaneLayersPrefetched() —— 只有「图层数组与网格数量不符」(首次绘制 / 增删网格) 才同步整批建立;其余只 RollWideBuffer() 登记缺失/过期网格,由既有预算(每帧最多一个)逐帧补齐。 实测:几百毫秒 → ≤22ms;巨型信号网格上开关均值滤波同样 15ms 量级。 验证(全部离屏,不占用鼠标/键盘/屏幕) - 新增 .preflight/perf/lanescroll_main.cpp(lanescroll.exe)23/23:逐帧耗时 + 「可见巨型信号网格数」关联、 「N 项」计数热区与双击不进改名、面板首现位置与多面板错开、开着巨型信号的开关/关闭面板耗时。 - 既有回归:verify 49/49、panelanim 158/158、datacheck 69/69、edgecheck 46/46、errcheck 19/19、 gridanim 93/93。 - make release / make debug 零错误零警告(用 TARGET_FILE_NAME=TargetCheck/TargetCheckD 避免与运行中实例冲突)。 文档:Documents/agent_mem.md 新增 §9.44、§11.2 第 134–140 条与变更日志一行。 |
||
|
|
2904699eae |
修复五项交互缺陷:折起后滚动条 / 单点信号样式 / 线宽实时生效 / 网格菜单限高滚动 / 点选信号滚动绘图区
1) 分组折起后再展开,信号列表的滚动条消失(筛选态与常规态均可复现)
- 根因:AdvanceGroupAnimations 只在“折起完成”时重建行集合;而“展开完成”那一帧
m_contentHeight 从收起态跳回展开态,却没有任何路径重算滚动条可见性 ⇒ 滚动条停在隐藏。
- 修法:任一方向动画结束都 m_rowsDirty = true + BuildRows(),并每帧 RefreshScrollBar(false)。
⚠️ 动画期间不夹取滚动偏移,避免打断与滚动同步的折起动画(scrollSynced)。
2) 单点信号(finiteCount == 1)默认绘制方式由「点线」改为「散点」
- ApplySinglePointDefaultStyle → DrawMode::SCATTER(仍取最大线宽)。
- 新增 SignalPanel::ModeOptionEnabled:单点信号的「线段」「点线」置灰、不可点、
且不参与悬停高亮(避免“看着能点其实点不动”);样式区的绘制与命中都用它。
- PlotView::UpdateAutoScatterStyles 跳过单点信号,否则“放大自动点线”会把它又改回「点线」。
3) 拖动线宽滑块完全不生效(拖动时线宽不变、松手瞬间变了、再移动鼠标又变回去)
- 根因一:AcquirePolyline 的折线缓存键没有 lineWidth,而 BuildPolyline 把线宽烘进
包络带几何(每个像素列的上下包络各向外扩 max(1,线宽)*scale*0.5,「梳状」判定也依赖它)
⇒ 稠密信号(采样数 > 绘图区宽度)永远复用旧线宽的顶点与包络带。
- 根因二:样式补丁只更新“静态场景缓存 + 后备缓冲区”,而每网格图层(宽基底)仍是旧样式像素,
下一帧命中“宽基底快路径”时把新样式覆盖回旧像素。
- 修法:PolylineCacheEntry 增加 lineWidth 并进比较;
样式补丁之后 QueueLaneRebuild(受影响网格) + RenderPendingLaneRebuilds(1)(沿用每帧至多 1 个的预算)。
4) 「目标」行的 GX 标签复用「所属网格」菜单,并先给 PopupMenu 增加「限高 + 条目区滚动 + 尾部固定项」
- MenuLayoutConstraint::maxHeight(OpenLaneMenu 传客户区高度的 62%);
- 固定区 = 最后一个分隔线及其之后的条目(即「新建网格」)始终钉在菜单底部,其余条目可滚动;
- 滚轮滚动条目区(Application 在菜单内滚动时不再关闭菜单,其余情形保持既有行为);
- 键盘移动悬停项自动滚入可视区(EnsureItemVisible);ItemAtPoint 要求条目同时命中滚动视口;
- 滚动区文本用 UiTextClipped(GDI 文本不遵循 GDI+ SetClip),并跳过完全不可见的条目(性能);
- BuildLayout 不再无条件清零 m_scrollOffset(否则 RebuildLayout 会把滚轮位置吃掉);
- 「目标」行 GX 标签点击**不参与**联动滚动(悬停联动保留)。
5) 点选信号名 ⇒ 绘图区自动把目标网格滚动到可视区内(上百个独立网格时旧有高亮无法定位)
- SignalPanel::RequestPlotLaneScroll / TakePlotLaneScrollRequest;
Application::ApplyPendingPlotLaneScroll(WM_LBUTTONDOWN 的面板分支);
PlotView::ScrollLaneIntoView(在可视区上方就上滚、在下方就下滚、已完整可见则不动)。
- 只有“点击信号名”触发;悬停绝不改变视图;勾选框 / GX 徽标 / 移除 ✕ 也不触发。
验证(全部离屏执行,窗口 SW_HIDE + 只注入合成消息 + 进程与线程 BELOW_NORMAL,
不移动真实光标、不抢焦点、不截屏):
- 新增 .preflight/check(不入库)68/68 通过;
- A/B:五处修复全部回退 ⇒ 17 项如实失败(B 4 / C 4 / D 7 / E 2);“折起后滚动条”需要
逐帧重绘驱动动画才能复现,单独回退时 ⇒ 5 项如实失败;恢复后 68/68;
- make release / make debug 零警告。
其它:
- 按要求删除工作区 Tools/(structcheck 脚本)并移除 Makefile 的 structcheck 目标;
- Documents/agent_mem.md 同步:新增 §9.38、§11.2 第 97–103 条与变更日志一行。
|
||
|
|
e6b7bf66ec |
按组件目录结构把四个巨型实现文件拆分为多个协作文件(PlotView / SignalPanel / Application / DataModel)
承接上一条提交(0871eb0)—— 该提交只完成了 Components/Shared 与注释/命名清理,并**主动未做**
大文件拆分;本提交把大文件拆分补齐,至此“按功能拆分逻辑块到各组件自己的 Inc/Src”已落地。
## 一、拆分结果
| 原文件 | 拆分后 |
| --- | --- |
| Components/PlotView/Src/PlotView.cpp(13 695 行 / 544 KB) | PlotView.cpp(787) + PlotViewPaint.cpp(6 367) + PlotViewInteraction.cpp(2 715) + PlotViewFeatures.cpp(3 005) + PlotViewInternal.hpp(936) |
| Components/SignalPanel/Src/SignalPanel.cpp(4 184 行) | SignalPanel.cpp(1 141) + SignalPanelPaint.cpp(697) + SignalPanelInteraction.cpp(2 095) + SignalPanelInternal.hpp |
| Components/Application/Src/Application.cpp(3 135 行) | Application.cpp(1 427) + ApplicationCommands.cpp(1 184) + ApplicationLoading.cpp(270) + ApplicationInternal.hpp |
| Utilities/Src/DataModel.cpp(2 615 行) | DataModel.cpp(1 583,含自由函数区) + DataModelTab.cpp(732) + DataModelInternal.hpp |
分片职责(以 PlotView 为例):
core 视图核心:构造/析构、上下文、布局、坐标映射、视界与平移、网格结构与归属
paint 渲染与像素缓存:全部绘制、场景/装饰缓存、每网格图层、宽基底与滚动补条
interaction 交互与编辑:命中测试、鼠标/键盘、光标形状、内联输入框、浮动面板层序
features 视图特性与派生数据:光标读数/提示/缩放、自适应、吸附、单点、派生曲线线程池、
边沿统计、实验性功能参数
(SignalPanel 为 core/paint/interaction;Application 为 core/commands/loading;
DataModel 为 core/tab。)
## 二、关键手法(这是能安全拆开的前提)
原文件的辅助区都是**匿名命名空间**(PlotView 约 870 行常量与工具函数),被所有成员函数广泛引用。
直接拆文件会让各 .cpp 拿不到这些工具,因此:
1. 把该块整块搬进 `<组件>Internal.hpp`,从 `namespace { }` 改为**具名**命名空间
(pvdetail / spdetail / appdetail / dmdetail);
2. 块内**每个函数与常量都标 inline** ⇒ 多个翻译单元共用同一个定义:既不重复符号,
也不产生 N 份函数定义;
3. 头部末尾 `using namespace <ns>;` ⇒ 各 .cpp 函数体里原有的非限定调用(IntersectRect /
NiceStep / ClampDouble …)**一行都不用改**;
4. 成员定义按「方法名 → 分片」归类后**整块搬移** —— 全程**纯代码搬移**,没有改动任何一行函数体。
## 三、拆分过程中踩到并修正的四个坑(已固化到 .preflight/perf/split_component.ps1)
1. `[[maybe_unused]]` 等属性**必须排在 inline 之前**(`inline [[x]] T` 非法);
单独的 `{`/`}` 行、注释行、`struct`/`enum`/`using`/`template` 行不能加 inline。
2. 匿名命名空间的**结束行可能带注释**(`} // namespace`),只认 `^\t}$` 会一路匹配到后面
某个成员函数的结束括号,把大段成员定义误当成“辅助区”搬走。
3. **首个成员定义之前的所有行**必须跟着进核心文件:DataModel.cpp 在匿名命名空间之后、
首个成员定义之前还有约 390 行命名空间外的自由函数(SignalStats / ComputeStats /
CacheSparseSamples / ApplySinglePointDefaultStyle …),漏掉会被静默丢弃。
4. 内部头必须带上原文件真正用到的全部头:DataModel 需要 Theme.hpp / <cstring> / <cstdio> /
<deque> / <atomic> / <cwctype> / <new>(原文件靠这些头才编译得过)。
## 四、附带完成的命名审计
PlotView 中残留的 File 语义命名一并修正:LaneSpansMultipleFiles → LaneSpansMultipleGroups、
firstFile → firstGroup、multiFile → multiGroup、SceneCacheKey::haloFile → haloGroup。
## 五、验证(全部离屏:SW_HIDE + 不注入真实输入 + 进程/线程 BELOW_NORMAL)
- .preflight/perf/verify.exe(PlotView 渲染/交互,含逐像素断言)49/49
- .preflight/perf/panelanim.exe(信号面板:卷尺式折起/展开、筛选态、悬浮组名、目标行)156/156
- .preflight/perf/datacheck.exe(数据层:单轴/多时间轴 CSV、MF4、重命名语义)69/69
- .preflight/perf/edgecheck.exe(边沿统计与面板排版)46/46
- make release / make debug 零警告。
即:拆分后**行为与拆分前完全一致**(纯代码搬移,四套探针全绿)。
## 六、体积(诚实记录)
Target.exe: 754 176 → 764 416 B(+10 240 B,+1.4%)
因为 inline 工具函数会在每个引用它的翻译单元里各生成一份(PE/COFF 链接器没有 ICF 合并)。
这再次印证:拆分解决的是**可维护性**,不是体积(本轮的 −24.6% 全部来自 ICO 256×256 条目改 PNG)。
## 七、文档
Documents/agent_mem.md:§9.37 新增“七、多文件拆分(已完成)”与“八、仍未完成”(只剩
Lane → LanePanelState、模态提示解耦、MF4 压缩块三项);§11.2 新增第 91、92 条(多文件化的
唯一安全手法 + 体积代价)。Documents/refactor_report_2026-10-16.md 同步更新 §五/§七/§八。
|