- 新增 pvdetail::PanelKindTraits + PanelTraitsOf(唯一 switch):标题 / 排版类别 / 滚动字段 / 面板状态 / 功能开关同表登记;新增面板只改「枚举 + 一行」, -Wswitch 仍在唯一登记点生效。PanelTitle 改为「前缀 + rangeSuffix 标志」, 不再依赖 switch(标题降级为数据,不再需要函数指针)。 - PaintPanelMarkerGroups(约 340 行)拆成 6 个具名函数,主函数只保留绘制顺序 (普通分组栏 -> 条目 -> 表头 -> 吸顶栏 -> 滚动条 -> 透明度行);纯搬移、0 字节变化。 - 顺手修正该函数体遗留的多余缩进。 验证:release/debug/tools 零 warning;UnitTests 260/19;OffscreenChecks -all 7/133; panels -mode compare 7 面板像素哈希逐项不变;rowgeom compare 965+7 / 1268+7 键 0 差异。 产物 781,824 -> 782,336 B(+512,全部来自登记表)。
791 lines
46 KiB
Markdown
791 lines
46 KiB
Markdown
# Code Review 进度(分支 `Code_Review`)
|
||
|
||
> 每个节点完成后追加:节点号、改动摘要、验证方式、实测数字。
|
||
> 计划见 `Documents/Code_Review_Plan.md`;最终报告见 `Documents/Code_Review_Report.md`。
|
||
|
||
## 基线(起点 `764e5be`)
|
||
|
||
| 项 | 数值 |
|
||
| --- | --- |
|
||
| Release 产物 | **890,880 B** |
|
||
| `.text` | 753,136 B |
|
||
| `.rdata` | 36,936 B |
|
||
| `.pdata` | 30,420 B |
|
||
| `.xdata` | 29,496 B |
|
||
| `.rsrc` | 20,656 B |
|
||
| `.idata` | 12,856 B |
|
||
| Debug 产物 | 1,801,617 B |
|
||
| 源码规模 | 约 5.6 万行 |
|
||
|
||
关键发现(详见计划 §一):
|
||
|
||
* `dynamic_cast`(`Toolbar.cpp:370`)使最小复现程序从 39,936 B 涨到 115,200 B
|
||
⇒ **+75,264 B**;map 中 `cp-demangle.o` 占 **56,320 B**。
|
||
* 每个 `EdgePanelKind` 在源码中出现 21–26 次(6 种面板 ⇒ 约 140 处手写分支)。
|
||
|
||
---
|
||
|
||
## 节点记录
|
||
|
||
(下面按完成顺序追加)
|
||
|
||
### P1 移除 `dynamic_cast` + 关闭 RTTI —— ✅ 完成(提交 `5de3763`)
|
||
|
||
**改动**
|
||
|
||
1. `Toolbar::BuildBoxes` 里唯一的 `dynamic_cast` 是**多余强转**(`Button::widget` 本来就是
|
||
`UiButtonWidget*`)。改为直接遍历 `m_buttons`(与 `m_root->Children()` 同源同序)。
|
||
2. 仅关 RTTI **不够**:`cp-demangle.o` 仍在链接里。真凶是 libstdc++ 的 `vterminate.o`
|
||
(引用 `__cxa_demangle`),它被 `eh_terminate.o` 的 `std::terminate` 拉入。
|
||
⇒ 在 `Utilities/Src/MemProbe.cpp` 里**自行定义** `__gnu_cxx::__verbose_terminate_handler()`,
|
||
归档成员 `vterminate.o` 不再被拉入,demangler 整体消失。
|
||
应用启动后本来就会 `std::set_terminate` 换成自己的处理函数,行为等效。
|
||
3. `Makefile` 新增 `NO_RTTI=1`(默认开)→ `CXXFLAGS += -fno-rtti`。
|
||
|
||
**实测**
|
||
|
||
| 指标 | 改前 | 改后 | 差 |
|
||
| --- | --- | --- | --- |
|
||
| Release | 890,880 B | **829,440 B** | **−61,440 B** |
|
||
| `.text` | 753,136 B | 703,616 B | −49,520 B |
|
||
| `.rdata` | 36,936 B | 26,616 B | −10,320 B |
|
||
| Debug | 1,801,617 B | 1,734,578 B | −67,039 B |
|
||
|
||
**验证**:release + debug **全量重建**(删 `*.o`/`*.d`)零 error 零 warning;
|
||
map 中 `cp-demangle.o` 已无 `.text` 段分配。
|
||
|
||
### P2 Release 关闭异步展开表 + 修正 Debug 构建参数 —— ✅ 完成(提交 `94fc1c4`)
|
||
|
||
**改动**
|
||
|
||
1. 新增 `RELEASE_ONLY_OPT = -fno-asynchronous-unwind-tables`,**只**加在 Release 的两条
|
||
编译规则上。`-fno-exceptions` 下绝大多数函数不再需要 `.pdata`/`.xdata`。
|
||
* 代价(已评估接受):Release 崩溃时 WER 拿不到可靠调用栈;不打印栈回溯的程序
|
||
功能不受影响,Debug 构建保留完整展开表。
|
||
* `-fno-unwind-tables`(全关)会链接失败,不采用。
|
||
2. **修正真实缺陷**:`DEBUG_OPT = -O0` 定义了却**从未被使用** —— Debug 的两条编译规则
|
||
误用 `$(RELEASE_OPT)`(`-Oz`),且全程没有 `-g`。所谓 "Debug 构建" 其实只是
|
||
"不 strip 的 Release",根本无法调试。现改用 `$(DEBUG_OPT) -g`,链接也加 `-g`。
|
||
|
||
**实测**
|
||
|
||
| 指标 | 改前 | 改后 |
|
||
| --- | --- | --- |
|
||
| Release | 829,440 B | **793,088 B**(−36,352) |
|
||
| `.pdata` | 30,420 B | 12,684 B |
|
||
| `.xdata` | 28,904 B | 10,156 B |
|
||
| Debug | 1,734,578 B | 5,992,064 B(真实 `-O0 -g`,属预期) |
|
||
|
||
**验证**:release 与 debug 均全量重建,零 error 零 warning(`-O0` 下也没有新警告)。
|
||
|
||
### P7 面板状态/滚动量收敛为唯一派发点 —— ✅ 完成(提交 `7a3fefb`)
|
||
|
||
**问题(屎山核心之一)**:`Lane` 里 7 种实验性功能各有 1 份 `LanePanelState` + 1 个滚动偏移
|
||
(14 个字段)。旧实现在**每一处**“按种类取用”的地方各写一遍嵌套三元式或 `switch`:
|
||
`EdgePanelLayoutFor` 5 条 7 层嵌套三元式;标题栏拖拽、✕ 关闭、滚轮、滚动条、错开序数各写一遍。
|
||
新增面板要改 20+ 处,漏一处就是「拖不动 / 关不掉 / 滚轮滚不到底」
|
||
(`MARK`、`RESAMPLE`、`CLIP` 均真实踩过)。
|
||
|
||
**改动**:`PlotViewInternal.hpp` 新增唯一派发点
|
||
`LanePanelEnabled` / `LanePanelStateOf` / `LanePanelScrollOf`(可写 + 只读)。
|
||
这些 `switch` **穷举 7 种且不写 `default:`** ⇒ `-Wswitch` 在漏登记时编译报警
|
||
(已用最小程序验证 `-Wswitch` 确实会报)。
|
||
|
||
调用点:布局 5 条三元式 → 3 行;标题栏拖拽 7 分支 switch → 1 次写入;
|
||
滚轮/滚动条/✕ 关闭/折叠切换全部改为穷举无 `default`。
|
||
|
||
**验证**:release + debug 零 warning;产物 **793,088 B 不变**(等价变换,说明只是结构收敛)。
|
||
|
||
**未做(受验证条件限制)**:尚未把 `EdgePanelLayoutFor` 的 957 行分支整体改成
|
||
“每面板一行元数据表”。原因:没有可用的离屏像素对比工具(`.preflight` 已删除,
|
||
且崩溃会弹模态框),纯逻辑重写无法自证“像素完全一致”。已通过上面的派发收敛
|
||
消除最容易出错的一类重复,剩余重复属于“几何算式本身”,风险高于收益。
|
||
|
||
### P11 拆分 `ApplicationCommands.cpp` —— ✅ 完成(提交 `8437100`)
|
||
|
||
原文件 **2,165 行**,混杂六类职责。按职责**纯搬移**(零逻辑改动):
|
||
|
||
| 新文件 | 行数 | 内容 |
|
||
| --- | --- | --- |
|
||
| `ApplicationTabs.cpp` | 355 | 标签页新建/关闭/切换/重排、视图同步、窗口标题、标签栏回调、标签页右键菜单 |
|
||
| `ApplicationDerive.cpp` | 786 | 派生烘焙、重采样、选区截取(新建/就地)、克隆(含带调整) |
|
||
| `ApplicationMenus.cpp` | 409 | 绘图区右键菜单、菜单数值回写、主题切换、设置菜单、菜单结果派发 |
|
||
| `ApplicationCommands.cpp` | 642 | 分组/信号增删、移动与重排、文件对话框、工具栏动作、键盘鼠标分发、布局边界绘制 |
|
||
|
||
Makefile 按目录通配收集源文件,**无需登记新文件**。
|
||
|
||
**验证**:release 零 error 零 warning;产物 793,088 → 794,112 B(+1,024,LTO 内联边界
|
||
变化,< 0.13%)。
|
||
|
||
### P15 注释/文档校正 —— ✅ 完成(提交 `f139440`)
|
||
|
||
* `PlotView.hpp`:删除指向已删除代码的 `FormulaEnvelopeEntry` 注释块。
|
||
* `agent_mem.md` §2:`-Os` → `-Oz`;补体积开关(`NO_EXCEPTIONS` / `NO_RTTI` / `LTO` /
|
||
`RELEASE_ONLY_OPT`)与「改 Makefile 必须删 `.o` 全量重编」提醒;补 `msimg32`;
|
||
忽略列表补 `Temp/`、`.preflight/`、`Tools/`。
|
||
* `agent_mem.md` §3:目录结构按现状重写(删除不存在的 `CsvTable/`、`DataModel/`、
|
||
`PaintUtil/` 等旧条目;写明 `Components/Shared` 与 `Utilities` 的真实职责和依赖方向)。
|
||
* `agent_mem.md` 末尾新增「第 33 轮」记录。
|
||
|
||
### P16 最终验证与报告 —— ✅ 完成
|
||
|
||
* release(全量)+ debug(全量 `-O0 -g`)**零 error 零 warning**。
|
||
* 最终产物 **794,112 B**(起点 890,880 B,−96,768,−10.9%)。
|
||
* `cp-demangle.o` 已无 `.text` 段分配;`__gnu_cxx::__verbose_terminate_handler`
|
||
定义在本产物内(`0x140057ed1`);导入 DLL 无新增。
|
||
* 报告:`Documents/Code_Review_Report.md`。
|
||
|
||
### 未完成/搁置的节点(均已在报告中说明原因)
|
||
|
||
* **P8** `SceneContentHash` 混入收敛:无逐像素验证手段,合并会改变哈希值,风险不划算。
|
||
* **P9** `EdgePanelLayoutFor` 表驱动化:同上(957 行几何分支,无法自证像素一致)。
|
||
* **P10** 缓存五件套抽象:同上,且热路径复杂。
|
||
* **P12** `PlotView.hpp` 拆分:嵌套类型外移需改 70+ 处引用,收益仅为可读性。
|
||
* **P13/P14** UI 基础层与绘制边界核对:已做**依赖方向与引用统计**,未发现重叠或反向依赖,
|
||
因此不再拆分。
|
||
* **性能项**:未发现新的可复现热点;已记录 3 个"还能再快一点"的点(见报告 §6.2)。
|
||
|
||
---
|
||
|
||
## 第 34 轮(2026-11-04):验证基础设施 + §10 后续建议落地
|
||
|
||
### T1 验证工具入库 —— ✅ 完成(提交 `97e9163`)
|
||
|
||
**改动**
|
||
|
||
1. `.gitignore`:删除整目录忽略的 `Tools/`,改为 `Tools/**/Build/`(只忽略各工具产物)。
|
||
2. 新增 `Tools/Makefile`:`make` / `make UnitTests` / `make OffscreenChecks` / `make clean` / `make help`;
|
||
工具链接本体 `../Build/Release/*.o`(排除 `Main.o`),`-MMD -MP` 跟踪头文件依赖。
|
||
3. 新增 `Tools/UnitTests`(`Inc` + `Src` + `Build`)与 `Tools/OffscreenChecks`(同构)与 `Tools/README.md`。
|
||
|
||
**踩到并修掉的两个构建陷阱**
|
||
|
||
* 工具源码也加 `-flto` 会让 GCC 15.2.0 在链接阶段 `choose_baseaddr`(i386.cc:7447)**ICE**
|
||
⇒ 工具源码不带 `-flto`,只在链接参数里带 `-flto -flto-partition=one`。
|
||
* `$(foreach)` 生成的工具目标早于 `all` 出现 ⇒ make 把**第一个**显式目标当默认目标,
|
||
直接 `make` 只会构建 `UnitTests` ⇒ 显式 `.DEFAULT_GOAL := all`。
|
||
|
||
**实测**:`make -C Tools` 零 warning;两个产物分别在 `Tools/<Tool>/Build/<Tool>.exe`。
|
||
|
||
### T2 单元测试 —— ✅ 完成(219 条断言 / 18 个用例)
|
||
|
||
覆盖:`TryParseDouble` 与 libc `strtod` **逐位一致**(6 万条随机十进制串)、
|
||
输入接受性(尾随字符 / 十六进制浮点 / `inf` / `nan`)、`FormatDecimal*` 永不科学计数法、
|
||
`NiceStep` / `BuildTicks`、公式语法与语义、`FormulaProgram` 与 `EvaluateFormula` 逐位一致、
|
||
`ApplyDrawTransform` 顺序、`TransformValueRange`、滑动平均多阶等价性、
|
||
`DataSet::FormulaTransformFor` 值域与分组极值(逐点参考实现比对、命中同指针、样式变化失效)、
|
||
以及新增的 `stdio_mingw_vs_ucrt_formats`(见 T6)。
|
||
|
||
**过程中修正了 5 处"我以为的语义"**(都以源码为准,测试跟着产品改):
|
||
`9.999999999999999` 属 >15 位有效数字(允许 1 ULP);`1..2` 其实合法;
|
||
`FormulaIsIdentity` 不去空格;非法公式求值时 `EvaluateFormula` **原样返回采样值**(不是 NaN)。
|
||
|
||
### T3 离屏验证 —— ✅ 完成(5 个检查 / 71 条断言)
|
||
|
||
* `smoke`:初始化 + 加载 + 渲染 + 落盘;**同一状态两帧逐像素相同**(实测差异 0 像素)。
|
||
* `frames`:连续帧抓取(PNG + 区域 FNV 哈希 + 耗时),帧序列至少出现一次可见变化。
|
||
* `panels`:7 个面板全部展开后的几何不变式(必须被绘图区夹住)+ 像素基线 `-mode record/compare`。
|
||
* `edgestats`:边沿统计与工具内**独立朴素实现**逐位比对(阈值 2.4875/4.975/7.4625 →
|
||
20/19、20/19、20/19 完全一致;中心轴 1200/1179 也能完成)。
|
||
* `perf`:整帧中位数、`EdgePanelLayoutFor` 单次、198 个统计状态的整轮查询耗时。
|
||
|
||
**全程离屏**:窗口 `SW_HIDE`、"鼠标"用 `Application::HandleMouseMove()` 合成注入、
|
||
像素从后备缓冲区读取;进程用 `std::_Exit()` 收尾(避免单例析构晚于 `GdiplusShutdown`)。
|
||
|
||
### T4 `EdgeScanFor` 状态查找哈希化 —— ⛔ **实测后回退**(建议 #2)
|
||
|
||
* **改动前**:`mutable std::vector<EdgeScanState>` + 逐字段线性扫描。
|
||
* **改法**:容器改 `std::deque`(指针稳定)+ `mutable std::unordered_multimap<uint64_t, EdgeScanState*>`
|
||
按身份哈希索引,桶内仍逐字段比对(碰撞安全);新建 / 淘汰 / 定向作废 / 清空四处同步索引。
|
||
* **正确性**:`edgestats` 全绿(真实数据的计数与独立参考实现逐位相同),`panels` 像素基线不变。
|
||
* **性能(198 个状态,`MF4-Record.MF4`)**:整轮查询 `0.097 → 0.073 us/query`
|
||
(一轮 198 次查询省 ≈4.8 μs)。
|
||
* **体积代价**:`788,480 → 794,112 B`(**+5,632 B**)。
|
||
* **结论:回退**。5.6 KB 的体积换 ≈5 μs/帧(约占 5 ms 整帧的 0.1%)不成立 ——
|
||
这与 §6.2 原本的判断一致,本轮把它从"估计"变成了**实测**。
|
||
⚠️ 同时修正了 §6.2 的量级:报告原估"288 信号时 ≈0.3 ms/帧",实测 198 状态整轮 **≈20 μs/帧**,
|
||
差一个数量级(线性扫描的常数很小)。
|
||
|
||
### T5 `EdgePanelLayoutFor` 表驱动化 —— ⏸ 只交付"可验证手段",**未重写**
|
||
|
||
* 第 33 轮已把**按种类分派**收敛成唯一派发点(`LanePanelEnabled` / `LanePanelStateOf` /
|
||
`LanePanelScrollOf`,且 `switch` 不写 `default:` 让 `-Wswitch` 兜底)。
|
||
* 剩余的重复是 957 行**几何算式本身**(每个面板的内容行高、表头、按钮、滚动区各不相同),
|
||
无法用"每面板一行元数据"表达。
|
||
* 本轮实测该函数 **1.9~2.5 μs/次**(2000 轮 × 7 种),按每帧约 14 次调用计约 **30 μs/帧**,
|
||
不是热点。
|
||
* ⇒ 重写的收益是"可读性",代价是 957 行几何公式的可回归风险。
|
||
**本轮交付的是让这类重写变得可验证的工具**:`-run panels -mode record/compare`
|
||
会逐面板比对**几何 + 像素哈希**(7 个面板、含内容区/滚动条/按钮)。
|
||
|
||
### T6 `wcstod` 自写替身(省 gdtoa)—— ⛔ **实测 0 字节收益**(建议 #4)
|
||
|
||
* 报告 §1.3 的估计是"≈20 KB"。本轮先做**最小实验**而不是先写代码:
|
||
在 `Makefile` 里加 `-D__USE_MINGW_ANSI_STDIO=0`(把 `swprintf` 交给系统 UCRT),全量重编。
|
||
* **实测:产物仍然 788,480 B,一个字节都没变**;map 里 `mingw_wpformat.o` 依旧被链接。
|
||
* **原因(用 3 行最小程序确认)**:预编译的 **libstdc++ 自己引用 `__mingw_swprintf`** ——
|
||
只要工程里还调用 `std::to_wstring` / `std::to_string`(全仓约 80 处,其中若干处实参是浮点),
|
||
链接器就必须把 MinGW 的 printf 引擎(含 gdtoa)拉进来。
|
||
* ⇒ 真正的 33 KB 收益要以"替换全部 80 处 `std::to_*string`(含浮点格式语义)"为代价,
|
||
风险与收益不匹配,**不实施**。该实验已回退,只在 `Makefile` 里留下一段说明防止重复尝试。
|
||
* **顺带产出可复用的验收测试** `stdio_mingw_vs_ucrt_formats`:
|
||
同一批真实格式(`%.*f` / `%.6g` / `%g` / `%.4e` / `%.2f %ls` / `%llu` / `%lld` / `%02X` / `%.2f%%`)
|
||
× 13.6 万次调用做**逐字符比对**,**0 处差异**。将来若真做迁移,这条测试就是验收标准。
|
||
|
||
### 本轮验证汇总
|
||
|
||
| 项 | 结果 |
|
||
| --- | --- |
|
||
| `make release`(删 `.o` 全量) | 零 error、零 warning |
|
||
| `make -C Tools` | 零 error、零 warning |
|
||
| `Tools/UnitTests/Build/UnitTests.exe` | `checks=219 failed=0 cases=18` |
|
||
| `Tools/OffscreenChecks/Build/OffscreenChecks.exe -all` | 5 个检查全 PASS,`checks=71 failed=0` |
|
||
| Release 产物 | **788,480 B**(与第 33 轮末一致;T4/T6 均回退) |
|
||
|
||
---
|
||
|
||
## 第 35 轮(2026-11-04):浮动面板架构评估 + 收敛 + 整数格式化
|
||
|
||
### 评估:浮动面板 / 二级菜单 / 右键菜单算不算"公共组件"?
|
||
|
||
先回答用户的问题(结论来自读码,不是印象):
|
||
|
||
| 对象 | 现状 | 是否已是"公共组件" |
|
||
| --- | --- | --- |
|
||
| **右键菜单 / 二级菜单** | `Components/Shared/PopupMenu`(+ `Layout` / `Paint` 两分片) | **是**。同一份实现服务绘图区网格菜单、信号菜单、分组菜单、标签页菜单、工具栏设置菜单;新增菜单只需描述条目数组 |
|
||
| **拾色器** | `Components/Shared/ColorPickerPopup` | **是**。独立的浮层组件,自带编辑框/滑块/色域/命中 |
|
||
| **实验室功能浮动面板** | 全部在 `PlotView` 内部(`EdgePanelLayoutFor` 算几何 + `PaintEdgeCursor` 画 + `OnLButtonDown/Move/Up` 命中),**共用一份"面板骨架"**(标题栏 = 折起三角 + GX 徽标 + 标题 + ✕、可滚动内容区 + 滚动条、底部按钮行) | **骨架是公共的,但"按种类分支"的代码被抄了多份** |
|
||
|
||
**为什么不把面板做成 `UiWidget` 树节点(像 PopupMenu 那样)?**
|
||
三条硬约束决定了成本远大于收益:
|
||
1. 面板的**像素**要进 `m_panelDc` 图层缓存(键 = `PanelGlobalKey ⊕ PanelSlotKey`),
|
||
而 `UiWidget` 有自己的 dirty/capture 体系,两套缓存叠加会互相失效(历史教训:
|
||
`SignalPanel` 行位图缓存与控件树就被刻意分开维护)。
|
||
2. 面板几何**必须**由 `EdgePanelLayoutFor` 唯一给出(绘制 / 命中 / 光标 / 调试盒 / 缓存键
|
||
五处共用),改成控件树就要把这套"几何同源"拆掉。
|
||
3. 面板绘制要往 `m_panelDc` 里 `BitBlt` 抓像素、要 `AddOccluder` 挖洞、要按 `m_panelOrder`
|
||
逐层设置文本层号 —— 这些都是 `PlotView` 的内部协议。
|
||
|
||
⇒ **正确的公共化方向是把"按种类分支"收敛成唯一派发点,而不是把面板搬进控件树。**
|
||
|
||
### P20 面板「特征层」(提交 `abcf3e6`)
|
||
|
||
审计出同一套"按种类分支"被抄了多份:
|
||
|
||
| 重复项 | 位置 | 份数 |
|
||
| --- | --- | --- |
|
||
| 分类布尔量(`listStyle` / `countTable` / `hasButton` …) | `EdgePanelWidth` / `EdgePanelLayoutFor` / `PaintEdgeCursor` | 3 |
|
||
| 滚动条几何 + 滚动量夹取 | 布局里表格 / 列表 / 自由标记三分支 | 3 |
|
||
| 滚动条绘制(6 行) | 绘制里同样三分支 | 3 |
|
||
| `SetLane*Scroll` 实现 | `PlotViewFeatures.cpp` | 7 |
|
||
| 按种类转发滚动写入 | 滚轮 / 拖滚动条 | 2 |
|
||
| 标题文本 | `EdgePanelWidth` / `PaintEdgeCursor` | 2(**内容还不一致**) |
|
||
|
||
新增 `pvdetail::PanelStyle` + `PanelStyleOf(kind)`、`PanelScrollMetricsFor(...)`、
|
||
`PlotView::PanelTitle(kind)`,并让上面全部调用点改成读它们。
|
||
**顺带修掉一处潜伏错误**:`EdgePanelWidth` 的标题链漏了 MARK/CLIP(按"边沿计数"量宽)。
|
||
|
||
⚠️ 过程中踩到并修掉一个真实回归:把 `hasButton` 直接映射到 `PanelStyle::buildButton` 后,
|
||
「选区截取」的底部按钮行高归零 ⇒ 面板高度 124 → 87 px。像素基线**当场报 MISMATCH**。
|
||
修正方式:区分 `footerButtons()`(有按钮行)与 `buildButton`(是"构建为新信号")。
|
||
|
||
### P21 滚动收敛(提交 `abcf3e6`)
|
||
|
||
* 7 个 `SetLane*Scroll` → 一行转发到新的 `SetLanePanelScroll(laneIndex, kind, scroll)`。
|
||
* 滚轮 / 拖滚动条两条 `switch`/`if` 链 → 直接调它(**读与写从此同一个入口**)。
|
||
* `EdgePanelLayoutFor` 里 5 层滚动量三元式 → `LanePanelScrollOf(lane, kind)`
|
||
(该派发点第 33 轮就存在,布局一直没用它)。
|
||
|
||
新增离屏检查 `panelscroll`:7 种面板逐一验证"滚到底 / 滚回顶 / 夹取 / 内容区不动 /
|
||
不可滚动时无滚动条"。用 `MF4-Record.MF4`(566 信号并入同一网格)实测 **6/7 可滚**
|
||
(自由标记无固定点,不可滚是正确行为)。
|
||
|
||
### P22 整数格式化替换(提交 `34365b3`)
|
||
|
||
* 新增 `Utf8Util::FormatInteger` / `FormatIntegerUnsigned` / `FormatIntegerNarrow`
|
||
(手写十进制展开,不经 printf)。
|
||
* 全仓替换 **76 处** `std::to_wstring` + **2 处** `std::to_string`(20 个文件);
|
||
`MatLoader::TrimNumber` 的整数分支也改(它曾是最后一个引用 MinGW printf 的点)。
|
||
* **实测:−1,024 B**(787,968 → 786,944)。
|
||
* ⚠️ **gdtoa 那条 ≈33 KB 的链仍然去不掉**,原因已定位到具体文件行:
|
||
libstdc++ 的 `x86_64-w64-mingw32/bits/os_defines.h` **无条件**
|
||
`#undef __USE_MINGW_ANSI_STDIO` + `#define … 1`("libstdc++ depends on POSIX-definitions"),
|
||
`g++ -E -dM` 实测最终值恒为 1 ⇒ 任何 C++ TU 的 `swprintf`/`strtod` 必然是 MinGW 版本。
|
||
叠加 `-D__USE_MINGW_ANSI_STDIO=0` 实测 **0 字节**收益,已回退并在 Makefile 留下结论。
|
||
要真省这 33 KB 只能自己重写「浮点 → 十进制(正确舍入)」与浮点解析的特殊分支 ⇒ **不做**。
|
||
|
||
### P23 其余审查清理
|
||
|
||
* `PlotView::FreeMarkerValue` 的 `const Lane&` 形参从未使用(函数体里只有一句 `(void)lane;`)
|
||
⇒ 删除形参与该语句,更新 2 个调用点。
|
||
* 「选区截取是否就绪(双光标)」在布局里算了 3 遍 ⇒ 收敛到函数顶部一次
|
||
(它同时决定面板高度 footer、条目行、按钮几何,写错任一处就是"底部多一段空白"或"没双光标却能滚动")。
|
||
|
||
### 本轮验证
|
||
|
||
| 项 | 结果 |
|
||
| --- | --- |
|
||
| `make release`(删 `.o` 全量) | 零 error / 零 warning;产物 **786,944 B**(起点 787,968,−1,024) |
|
||
| `make -C Tools` | 零 error / 零 warning |
|
||
| `UnitTests` | `checks=260 failed=0 cases=19`(新增 `integer_replacement_matches_std`,41 条断言) |
|
||
| `OffscreenChecks -all` | 6 个检查全 PASS,`checks=87 failed=0`(新增 `panelscroll`) |
|
||
| `panels -mode compare` | 7 个面板**像素哈希 + 几何逐项不变**(三次重构全程守住) |
|
||
| `panelscroll`(MF4,566 信号) | 6/7 面板可滚,夹取与滑块位置全部符合预期 |
|
||
|
||
### 下一步建议(面板方向)
|
||
|
||
1. **`EdgePanelLayout` 的平行数组**:`rowHeaders` / `rowNameRects` / `rowTriangles` /
|
||
`rowChecks` / `rowPoints` / `rowIterations` / `rowStats` / `rowLevel*` / `rowIter*` /
|
||
`rowInfo*` / `rowIntervalField` / `rowFrequencyField` 共 17 个按行平行的 `vector`,
|
||
全仓引用 **314 处**。每加一种参数行都必须在每个分支里按**同一顺序** push 恰好 17 项,
|
||
漏一项 = 某列读到别人的矩形(这正是"改布局就出错"的机制)。
|
||
建议合并为 `struct PanelRow { … }` + `std::vector<PanelRow> rows`。
|
||
**本轮未做**:314 处引用、风险高,需要先补"逐行几何基线"检查。
|
||
2. **`PaintEdgeCursor` 拆分**:该函数约 1600 行,其中三段"内容绘制"(表格 / 列表 / 自由标记)
|
||
各 90–450 行,可机械提取为 3 个成员函数(`PaintPanelCountTableRows` /
|
||
`PaintPanelListRows` / `PaintPanelMarkerGroups`)。判据:`panels -mode compare` 全绿。
|
||
**本轮未做**:与 P22 抢时间,且属纯可读性收益。
|
||
3. **给平行数组补检查**:在 `panels` 里增加"每个 row 的 17 项长度一致"断言
|
||
(成本极低,可作为第 1 项的前置护栏)—— 已记入待办。
|
||
|
||
---
|
||
|
||
## 第 36 轮(2026-11-04):§12.7 两项建议落地
|
||
|
||
### P26 拆分 `PaintEdgeCursor` —— ✅ 完成(提交 `38fbd4b`)
|
||
|
||
* 新增 `Components/PlotView/Src/PlotViewPanelPaint.cpp`(1016 行,**机械搬移、零逻辑改动**):
|
||
* `PaintPanelCountTableRows` —— 边沿计数表格(固定表头 + 可滚动行);
|
||
* `PaintPanelListRows` —— 列表式(均值/中心轴/弱峰/重采样/截取)条目栏 + 参数行;
|
||
* `PaintPanelMarkerGroups` —— 自由标记(分组栏 + 表头 + 条目 + 吸顶)。
|
||
每个函数只接收它真正需要的参数(`layout` / `tabLane` / `kind` / `laneIndex`),
|
||
不再"整个函数都看得见"。
|
||
* `PlotViewPaint.cpp`:6554 → 5575 行(`PaintEdgeCursor` 由约 1900 行降到约 900 行);
|
||
三处块替换为一行调用,骨架(边框/标题栏/折起三角/GX 徽标/✕)与底部按钮仍在一处。
|
||
* 滚动条绘制抽为 `pvdetail::PaintPanelScrollBar(g, layout, theme)`。
|
||
* **顺带补上第 35 轮承诺未落地的一处**:`PaintEdgeCursor` 的标题仍是 7 层三元式
|
||
(与 `EdgePanelWidth` 里另一条链并存,后者漏了 MARK/CLIP)⇒ 统一为 `PanelTitle(kind)`。
|
||
|
||
**过程中"机械搬移"踩到的三个真实问题**(都已修)
|
||
1. 提取块里引用的 `paintCounts` / `paintPanelScrollBar` 是**局部 lambda** ⇒
|
||
前者改为新函数内的局部 lambda(只服务于该段)、后者提升为 pvdetail 内联函数。
|
||
2. 「标记透明度」滑块用到的 `panel` / `pad` 是外层局部变量 ⇒ 改为从 `layout.panel` 派生。
|
||
3. `tabLanes` 在 marker 段里是**被写**的(回写各列实测宽度)⇒ 参数必须非 const。
|
||
|
||
### P27 平行数组错位隐患 —— ✅ 用 `AppendRow()` 消除(提交 `85aaed5`)
|
||
|
||
审计发现 `EdgePanelLayout` 每行对应 **18 个平行 vector**,而**四个**布局分支各自手写 push:
|
||
* 边沿计数表格:13 次 push;
|
||
* 列表式基行:13 次 push;
|
||
* 「未勾选/收起」分支:又补 6(+重采样 4)次空 push;
|
||
* 「重采样展开」/「普通展开」分支:各自再 push 6(+4)次。
|
||
任何一处漏推 ⇒ 该列之后所有行读到**别人的**矩形。
|
||
|
||
改法:
|
||
* 新增 `EdgePanelLayout::AppendRow(ref, header, nameRect, triangle, check)` —— **唯一入口**,
|
||
18 个数组一次补齐(其余列为空矩形)。
|
||
* 三个 push 块 → 各 **1 次 `AppendRow`**;参数行/重采样行改为 `rowXxx.back() = …` **赋值**;
|
||
「未勾选/收起」分支的 6+4 次补空 push **整段删除**(AppendRow 已补过)。
|
||
* ⇒ "新增一列"只需改 `AppendRow` 一处;"新增一行"必须走它(结构上不会漏列)。
|
||
|
||
⚠️ **没做完整结构合并**(`std::vector<PanelRow>` + 列视图),理由:
|
||
列视图需要 `EdgePanelLayout` 自定义拷贝构造/赋值来重绑视图,而该类型是
|
||
`EdgePanelLayoutFor()` 的返回值、又被存进 `std::vector` —— 拷贝是热路径,
|
||
拷贝语义写错会产生**比原缺陷更隐蔽的悬垂视图**(原缺陷至少会被长度不变式抓到)。
|
||
先消除真实隐患,结构合并留待"逐行几何基线"检查就绪。
|
||
|
||
### P28 收紧不变式
|
||
|
||
`panels` / `panelscroll` 里的长度检查从"13 个必须等长 + 4 个可空"改为
|
||
**18 个全部必须等于行数** —— `AppendRow()` 成为唯一入口后,"某列短了"
|
||
就是"有人绕过它手写 push"的直接证据。
|
||
|
||
### 本轮验证
|
||
|
||
| 项 | 结果 |
|
||
| --- | --- |
|
||
| `make release`(全量) | 零 error / 零 warning;产物 786,432 → **785,408 B** |
|
||
| `make -C Tools` | 零 error / 零 warning |
|
||
| `UnitTests` | `checks=260 failed=0 cases=19` |
|
||
| `OffscreenChecks -all` | 6/6 PASS,`checks=101 failed=0`(断言合并后略减) |
|
||
| `panels -mode compare` | 7 个面板像素哈希 + 几何**逐项不变**(两次重构全程守住) |
|
||
| `panelscroll`(MF4,566 行) | 18 个行数组长度全部 = 566,行为不变 |
|
||
|
||
---
|
||
|
||
## 第 37 轮(2026-11-04):逐行几何基线(§13.6 第 1 项)
|
||
|
||
### P29 新增 `rowgeom` 检查 —— ✅ 完成
|
||
|
||
`Tools/OffscreenChecks/Src/Checks.cpp` 新增注册键 `rowgeom`,做法:
|
||
|
||
1. **场景 A(细节基线)**:切双光标 → 前 6 条信号 → `MERGE_LANES` → 7 个面板全开、
|
||
第 2 条信号勾选(同一面板里同时有"展开的参数行"与"未勾选的行")→ 加 3 个真实固定点
|
||
→ 导出 7 个面板 **展开态**全字段 → 全部折起再导一份 → 复原。
|
||
2. **场景 B(规模基线)**:全部信号合并成 1 个网格(默认数据 3 行;`-data MF4-Record.MF4` = 566 行)
|
||
→ 每面板一个 FNV-1a 摘要 + `rows` / `markerRows` 计数。
|
||
3. **导出格式**(纯文本、逐行 `键=值`,便于 `git diff` 与人工定位):
|
||
|
||
```text
|
||
mean.rowRefs.count=3
|
||
mean.rowNameRects[001]=1155.75,221.25,512.50,25.00
|
||
mark.markerGroupCount=2
|
||
mark.markerGroup[000].header=1155.75,171.25,512.50,27.50
|
||
mark.markerRow[000].item=0/1
|
||
mark.markerRowCount=3
|
||
```
|
||
|
||
⚠️ 因为 `rowRefs` 是**重载**的(见下),导出器按**每一列自己的长度**遍历,
|
||
不能"取 `rowRefs.size()` 当行数统一遍历全部 18 列"。
|
||
4. **`-mode record` / `-mode compare`**:比对双向(缺键 = MISSING,多键 = ADDED,值变 = CHANGED),
|
||
最多打印 20 条差异;差异数为 0 才算 PASS。
|
||
|
||
### 过程中两次崩溃(都已定位,结论写进 `agent_mem.md`)
|
||
|
||
| # | 现象 | 根因 | 处置 |
|
||
| --- | --- | --- | --- |
|
||
| 1 | `LaneCount()` 断言失败 | `SetMode(MERGED)` 只重建 `TabConfig` 的网格配置,`PlotView::m_lanes` 要等下一次布局/绘制才刷新 | `MERGE_LANES` 之后补 `SynchronizeView(true)` + `RenderFrame()` 再断言 |
|
||
| 2 | 0xC0000005(导出时越界) | 假设"18 个行数组等长",但 **`MARK` 面板复用 `rowRefs` 表示信号分组**,只填 `rowNameRects` / `rowNameCloses`,其余 14 列为空 ⇒ 按 `rowRefs.size()` 索引其它列直接越界 | 导出器改为**逐列按自身长度**遍历 |
|
||
|
||
### P30 长度不变式的判据修正 —— ✅ 完成
|
||
|
||
`panels` / `panelscroll` 里第 36 轮定的"18 列必须全部等于行数"对 `MARK` 面板**是错的**
|
||
(它对新结构是误判,只是既有场景恰好没触发)。改为按 `PanelStyleOf(kind).markerGroups` 分流:
|
||
|
||
* `markerGroups == true`:`rowNameRects` / `rowNameCloses` 等于分组数,
|
||
且 `rowHeaders` / `rowChecks` / `rowLevelTracks` / `rowIterTracks` **必须为空**;
|
||
* 其余面板:17 列(`rowRefs` + `rowHeaders` … `rowFrequencyField`)全部等于行数。
|
||
|
||
失败时打印 15 个计数字段,便于一眼看出是哪一列错了。
|
||
|
||
### 基线是否真的能抓回归(负向自检)
|
||
|
||
把基线副本里 `mark.markerRowCount=3` 改成 `2` 再跑 `-mode compare`:
|
||
|
||
```text
|
||
[rowgeom detail] CHANGED mark.markerRowCount: baseline=2 current=3
|
||
[FAIL] rowgeom detail geometry is unchanged
|
||
SUMMARY checks=32 failed=1 groups=1 result=FAIL
|
||
```
|
||
|
||
⇒ 检测器**不是空转**的。(正式基线所在目录始终是 `record` 生成的,未被改动。)
|
||
|
||
### 一个"顺手查清"的行为(不是缺陷)
|
||
|
||
`rowgeom` 第一次跑出 `mark.hash … rows=0`:场景 B 里固定点没了。追查结论:
|
||
`Session::ShowSignals()` 先 `SetAllVisible(false)`,而"全部不可见 ⇒ 丢弃网格"
|
||
是 `RebuildLanes` 的**既有设计**(MERGED 分支的 `lanes.clear()`),
|
||
网格级状态(固定点 / 面板开关 / 滚动位置)随之清空 —— 与 `CLEAR_ALL` 行为一致,**不是新缺陷**。
|
||
因此场景 B 改为**重新播**固定点(`addFreeMarkers(2)` lambda),
|
||
场景 B 的 MARK 面板现在是 `rows=2 markerRows=3`,规模覆盖也补上了。
|
||
|
||
### 本轮验证
|
||
|
||
| 项 | 结果 |
|
||
| --- | --- |
|
||
| `make release`(全量) | 零 error / 零 warning;产物 **785,408 B 不变**(本轮只动工具与检查) |
|
||
| `make -C Tools` | 零 error / 零 warning |
|
||
| `UnitTests` | `checks=260 failed=0 cases=19` |
|
||
| `OffscreenChecks -all` | **7/7 PASS**,`checks=127 failed=0`(第 36 轮为 6/6、101) |
|
||
| `rowgeom -mode compare`(默认数据) | `detail 983 键 + scale 7 键`,0 差异 |
|
||
| `rowgeom -mode compare -data MF4-Record.MF4` | `detail 1304 键 + scale 7 键`(566 行),0 差异 |
|
||
| `panels -mode compare` | 7 个面板像素哈希 + 几何逐项不变 |
|
||
|
||
---
|
||
|
||
## 第 38 轮(2026-11-04):删死列 + 拆重采样参数行(§14.5 第 1 / 第 3 项)
|
||
|
||
### P31 删除 `rowPoints` / `rowIterations` —— ✅ 完成
|
||
|
||
审计(§14.5 第 1 项的前提)在三处确认它们是**死列**:
|
||
|
||
| 位置 | 原用途 | 现状 |
|
||
| --- | --- | --- |
|
||
| 写入 | `AppendRow` 里推两个空矩形 | 这是**唯一**写入点,从未被赋过非空矩形 |
|
||
| 读取 | `PlotViewPaint.cpp` 调试框 `AddAll`、`PanelSlotKey` 的 mix 循环 | 都是"遍历即忘",不产生任何可见效果 |
|
||
| 绘制 | 无 | 旧式"点数 / 迭代次数"输入框早被滑块版(`rowLevelTracks` / `rowIterTracks`)取代 |
|
||
|
||
改动:删两个 `std::vector<gd::RectF>`(`EdgePanelLayout` 少 48 字节)、`AppendRow` 少两次 `push_back`、
|
||
调试框与缓存键各少两个循环、`rowgeom` 导出与两处长度不变式各少两项(16 列)。
|
||
|
||
**等价性被基线证明**(这是本轮最值得记的一条方法论):
|
||
|
||
```text
|
||
old lines = 983, new lines = 919
|
||
only in OLD = 64, only in NEW = 0
|
||
only-in-OLD lines NOT belonging to rowPoints/rowIterations = 0
|
||
```
|
||
|
||
* 983 − 919 = 64,正好是两列在 7 个面板上的键数(`count` 键 + 每行键);
|
||
* 其余 919 条键**逐字节相同** ⇒ "这两列从未有非空值"的判断不是靠眼睛,而是靠基线证伪。
|
||
* 随后用新代码重新 `record` 正式基线(`rowgeom-baseline.txt`),再次 `compare` 归零。
|
||
|
||
### P32 提取 `PaintPanelResampleRows` —— ✅ 完成
|
||
|
||
`PaintPanelListRows` 里 `if (resamplePanel) { …四行… continue; }` 这 **132 行**整块提取为
|
||
`PlotView::PaintPanelResampleRows(g, layout, rowIndex, laneIndex, ref, item)`:
|
||
|
||
* 两个局部 lambda(`paintInfoRow` 只读统计行 / `paintInputRow` 联动输入框)随之迁移;
|
||
* `maxInterval` / `minInterval` 两个"拷贝一份再说"的局部量直接用 `item.maxInterval` /
|
||
`item.minInterval`(原来是纯拷贝,无语义差别);
|
||
* 调用点只剩 3 行(含注释),`PaintPanelListRows` 现在只有"条目栏 + 通用参数行"两段。
|
||
|
||
### §14.5 第 3 项的完成度(诚实记录)
|
||
|
||
| 段落 | 状态 | 理由 |
|
||
| --- | --- | --- |
|
||
| 重采样行 | ✅ 已提取 | 排版与其它面板完全不同、入口/出口干净(`continue` 收尾) |
|
||
| 通用参数行(中心轴统计 + 两行滑块) | ⏸ 未提取 | 依赖 3 个 kind 标志 + 2 套状态(`m_dragMode`/`m_editTarget`)+ 2 个滑块 lambda,签名要约 9 个参数,可读性收益低于参数表成本 |
|
||
| 条目栏 | 保留 | 三角 + 名称 + 复选框 + 悬停是三种排版的公共前缀 |
|
||
|
||
### 本轮验证
|
||
|
||
| 项 | 结果 |
|
||
| --- | --- |
|
||
| `make release`(全量) | 零 error / 零 warning;产物 785,408 → **784,896 B**(−512) |
|
||
| `make -C Tools` | 零 error / 零 warning |
|
||
| `UnitTests` | `checks=260 failed=0 cases=19` |
|
||
| `OffscreenChecks -all` | 7/7 PASS,`checks=127 failed=0` |
|
||
| `panels -mode compare` | 7 个面板像素哈希 + 几何**逐项不变**(含 `resample=2BFC74B3D9B3239C`) |
|
||
| `rowgeom -mode compare` | 919 + 7 键,0 差异 |
|
||
| 行数 | `PlotViewPaint.cpp` 5575 → 5565;`PlotViewPanelPaint.cpp` 1016 → 1020(净增 4 行:函数头 + 文档) |
|
||
|
||
---
|
||
|
||
## 第 39 轮(2026-11-04):平行数组合并为 `std::vector<PanelRow>`(§15.6 第 1 项)
|
||
|
||
> 这是"面板改一次就出错"的**最后一个机制性隐患**:旧的 16 个平行数组靠"每个布局分支
|
||
> 按同一顺序 push 恰好 N 项"维持对齐,任何一处漏推都是静默错位。
|
||
|
||
### P33 合并成 `std::vector<PanelRow>`
|
||
|
||
```cpp
|
||
struct PanelRow
|
||
{
|
||
SignalRef ref; // 该行的信号(MARK = 分组的代表信号)
|
||
gd::RectF header; // 信号色叠底条目栏 / 分组栏
|
||
gd::RectF nameRect; // 名称区
|
||
gd::RectF nameClose; // 名称右侧 ✕(仅 MARK)
|
||
gd::RectF triangle; // 展开/收起三角
|
||
gd::RectF check; // 右端复选框
|
||
gd::RectF stats; // 「中心轴」上升/下降沿统计行
|
||
gd::RectF levelTrack / levelValue / levelReset; // 滑块 1
|
||
gd::RectF iterTrack / iterValue / iterReset; // 滑块 2
|
||
gd::RectF infoMaxInterval / infoMinInterval; // 「重采样」两行只读统计
|
||
gd::RectF intervalField / frequencyField; // 「重采样」两行输入框
|
||
};
|
||
std::vector<PanelRow> rows; // 唯一的行容器
|
||
```
|
||
|
||
* 唯一入口:`AppendRow(ref, header, nameRect, triangle, check)`(返回新行引用)
|
||
与 `AppendMarkerGroupRow(ref, nameRect, nameClose)`(「自由标记」分组行)。
|
||
两者都**没有**"补齐其余列"这一步 —— 因为字段天生就在同一行里。
|
||
* 参数行 / 重采样行从 `layout.rowXxx.back() = …` 变成 `layout.rows.back().xxx = …`。
|
||
* 原来的 `int rows`(可见信号数)与新容器同名 ⇒ 改名 **`signalCount`**,
|
||
并注明它与 `rows.size()` 在 MARK 面板里**不相等**(只有有固定点的信号才有行)。
|
||
|
||
### P34 顺带消失的两类问题
|
||
|
||
| 旧问题 | 现在 |
|
||
| --- | --- |
|
||
| 某分支漏推一列 ⇒ 该列之后所有行读到别人的矩形 | `rows.push_back(row)` 一次成型,**不可能**错位 |
|
||
| `rowRefs` 字段重载(MARK 借它表示"分组",其余 12 列必须为空,长度不变式要按类别分流判定) | 每行自带 `ref`,语义由"填哪些字段"表达;不变式简化为"每行都要有名称区 + MARK 行不得带参数行字段" |
|
||
|
||
### P35 清理恒真判空
|
||
|
||
合并后 `rowIndex < layout.rowXxx.size()` 一律变成 `rowIndex < layout.rows.size()`,
|
||
而它们全都在 `for (… rowIndex < layout.rows.size(); …)` 循环体内 ⇒ **恒真**。
|
||
用正则批量删除 50+ 处(含 `a.size() != rows` 的 15 元组长度数组、`? x : gd::RectF()` 三元判空、
|
||
`if (rowIndex < …) { extendRow(...); }` 包裹),并逐文件编译验证。
|
||
|
||
⚠️ 中途有一次误操作:把"均值滤波双滑块"那段替换成了"弱峰双滑块",
|
||
编译器报 brace 不平衡 + 大面积类型错误 ⇒ 用备份 `Temp/PanelPaint.pre-cleanup.cpp` 回滚后重做。
|
||
**教训**:这类"整段替换"必须先用 `read_file` 精确读出 5 行上下文,不要凭记忆拼 oldString。
|
||
|
||
### 等价性证明(本轮最关键的一步)
|
||
|
||
```
|
||
old keys = 919, new keys = 965
|
||
仅存在于旧 = 0 ← 没有任何键消失
|
||
已有键的值变化 = 20 ← 全部是 ".count":0 -> N
|
||
仅存在于新 = 46 ← 全部是 <column>[NNN],值全为 0.00,0.00,0.00,0.00
|
||
```
|
||
|
||
⇒ 旧结构里"某列没被 push"的那些行,现在每行都拥有该字段但值为空矩形;
|
||
**逐行逐字段的真实几何一位都没变**,7 个面板的像素哈希(含 `resample=2BFC74B3D9B3239C`)
|
||
也逐项未变。之后按新结构重录正式基线(965 条键)。
|
||
|
||
### 本轮验证
|
||
|
||
| 项 | 结果 |
|
||
| --- | --- |
|
||
| `make release`(全量) | 零 error / 零 warning;产物 784,896 → **782,336 B**(−2,560) |
|
||
| `make debug` / `make -C Tools` | 零 error / 零 warning |
|
||
| `UnitTests` | `checks=260 failed=0 cases=19` |
|
||
| `OffscreenChecks -all` | 7/7 PASS,`checks=133 failed=0`(上轮 127:新增行结构断言) |
|
||
| `panels -mode compare` | 7 个面板像素哈希**逐项不变** |
|
||
| `rowgeom -mode compare` | 965 + 7 键,0 差异(默认数据);1268 + 7 键(MF4 566 行),0 差异 |
|
||
| 读点数 | 旧的 243 处 `layout.rowXxx[…]` 全部消失;现在 95 处 `layout.rows[…]`(84 在命中判定、11 在绘制) |
|
||
| 行数 | `PlotView.hpp` 4795;`PlotViewInteraction.cpp` 4399;`PlotViewPanelPaint.cpp` 935;`PlotViewPaint.cpp` 5534;`PlotViewPanels.cpp` 1729 |
|
||
|
||
---
|
||
|
||
## 第 40 轮(2026-11-04):几何算式重写 + 参数行提取(§16.7 第 1 / 第 2 项)
|
||
|
||
> 上一轮已完成"结构合并 + 双基线",本轮把**算式**本身收敛成单一来源。
|
||
|
||
### P36 宽度算式与列几何共用一份度量
|
||
|
||
改前:`EdgePanelWidth`(决定面板宽)与 `EdgePanelLayoutFor`(决定列边界)**各自写了一份**
|
||
同名同形的 `UiMeasureTextWidth` 链(标签列 / 数值列 / 重采样列),注释写着
|
||
"与 `EdgePanelWidth` 同一组算式 ⇒ 不会漂移"。那是靠人守的约定。
|
||
|
||
改后:
|
||
|
||
```cpp
|
||
struct PanelColumnMetrics // 唯一的列宽度量
|
||
{
|
||
std::size_t signalCount; // 可见信号数
|
||
float nameColumn; // 名称列(已按上限夹取)
|
||
float statRising, statFalling; // 「边沿计数」/「中心轴」统计列
|
||
float sliderLabel, resampleLabel;// 参数行标签列(两种,互不合并)
|
||
float valueColumn; // 数值列(同时是绘制侧的裁剪宽度)
|
||
float resampleValueColumn; // 「重采样」只读统计的数值列
|
||
};
|
||
PanelColumnMetrics MeasurePanelColumns(const Lane&, EdgePanelKind) const;
|
||
float EdgePanelWidthFor(const Lane&, EdgePanelKind, const PanelColumnMetrics&) const;
|
||
```
|
||
|
||
`EdgePanelWidth(laneIndex, kind)` 退化为"量一次 + 算一次";`EdgePanelLayoutFor` 量一次后
|
||
**同时**用于宽度与列几何。
|
||
|
||
⚠️ 顺手验明一处"看起来重复其实必须分开"的量:`valueColumn` 在宽度算式里按
|
||
`max("100000", "-400%")` 量、在列几何里按 `max("100000", "1000")` 量 —— 两者结果相同
|
||
(`100000` 是最宽的),因此可以合并;而 **`sliderLabel` 与 `resampleLabel` 不能合并**
|
||
(「重采样」标签更长,并进来会让均值/弱峰面板整体变宽 = 像素变化)。
|
||
|
||
### P37 行高累加与行几何共用一份行计划
|
||
|
||
改前:`EdgePanelLayoutFor` 里**两趟独立遍历**同一批信号 ——
|
||
第一趟只累加 `meanContentHeight`,第二趟才生成 `rows` 并累加 `rowY`。
|
||
两趟的判定(可见性 / 勾选 / 收起 / 是否要统计行 / 参数行数)必须逐字一致。
|
||
|
||
改后:
|
||
|
||
```cpp
|
||
struct PanelRowPlan { SignalRef ref; bool checked, collapsed, hasStats; int sliderRows; };
|
||
std::vector<PanelRowPlan> PlanListPanelRows(const Lane&, EdgePanelKind) const;
|
||
```
|
||
|
||
高度 = `groupRow + (hasStats ? groupRow : 0) + sliderRows * sliderRow`;
|
||
几何按同一个 `plan.sliderRows / hasStats` 派发 ⇒ **结构上不可能不一致**。
|
||
「选区截取」未启用双光标时整份计划作废(面板只显示一行提示),与改前行为一致。
|
||
|
||
### P38 参数行绘制提取
|
||
|
||
`PaintPanelListRows` 里的「中心轴统计行 + 两条滑块分支(均值 / 弱峰)」
|
||
**整块提取**为 `PaintPanelParamRows(g, layout, row, kind, PanelParamState{…})`,
|
||
`paintCounts` / `paintMeanSlider` / `paintPeakSlider` 三个 lambda 与 `valueColumn` 随之迁入。
|
||
`PaintPanelListRows` 现在只负责"条目栏(叠底 + 悬停 + 三角 + 名称 + 复选框)" +
|
||
"按 `kind` 分派到重采样/参数行"。
|
||
|
||
### 过程中的两次返工(记录,避免重犯)
|
||
|
||
1. 用"整段替换"改宽度块时,锚点 `// 两行滑块` 在 `EdgePanelWidthFor` 里**也有一处**
|
||
⇒ 误删了宽度函数尾部。改用更长的锚点(带"的列宽")后重做。
|
||
2. 行发射循环的锚点 `float rowY = layout.content.Y - scrollMetrics.scroll;`
|
||
在**表格分支**里先出现一次 ⇒ 误删了「边沿计数」的行发射。改为"先定位**第二个**
|
||
`else if (listStyle)`,再从那里找 `rowY`"。
|
||
|
||
⇒ **教训:整段替换的锚点必须唯一;同一文件里同名语句出现多次时,先定位所属区块再取偏移。**
|
||
|
||
### 本轮验证
|
||
|
||
| 项 | 结果 |
|
||
| --- | --- |
|
||
| `make release` / `make debug` / `make -C Tools` | 零 error / 零 warning |
|
||
| 产物 | 782,336 → **781,824 B**(−512) |
|
||
| `UnitTests` | `checks=260 failed=0 cases=19` |
|
||
| `OffscreenChecks -all` | 7/7 PASS,`checks=133 failed=0` |
|
||
| `panels -mode compare` | 7 个面板像素哈希**逐项不变**(含 `resample=2BFC74B3D9B3239C`) |
|
||
| `rowgeom -mode compare` | 965 + 7 键 0 差异;`-data MF4-Record.MF4` 1268 + 7 键 0 差异 |
|
||
| 行数 | `PlotViewPanelPaint.cpp` 935 → 939;`PlotViewPanels.cpp` 1729 → 1766(新增两个几何函数,净增 37) |
|
||
| 样式 | 顺手把机械搬移遗留的"整个函数体多缩进 2 个 tab"修正 |
|
||
|
||
---
|
||
|
||
## 第 41 轮(2026-11-04):面板登记表 + 标记面板绘制分段(§17.6 第 1 / 第 2 项,收尾)
|
||
|
||
> 本轮之后**代码审查进入收尾**:所有"已知问题"要么已修、要么有实测数字支撑地**明确不做**
|
||
>(体积、`EdgeScanFor` 哈希化、`EdgePanelLayoutFor` 表驱动化),详见报告 §18。
|
||
|
||
### P39 面板的五个穷举 `switch` 合成一张 `constexpr` 登记表 ✅
|
||
|
||
改前新增一种面板要改三处(`PanelStyleOf` / `PanelTitle` / `LanePanelScrollOf`),
|
||
实际连 `LanePanelStateOf` / `LanePanelEnabled` 一共是**五处穷举 switch**
|
||
(§17.6 只点了三处,另外两处更早就有)。
|
||
|
||
改后 `PlotViewInternal.hpp` 里只有**一个**登记点:
|
||
|
||
```cpp
|
||
struct PanelKindTraits
|
||
{
|
||
PanelStyle style; // 排版类别 + 底部控件
|
||
const wchar_t* title = L""; // 标题前缀
|
||
bool rangeSuffix = false; // 是否追加 EdgeRangeSuffix()
|
||
float Lane::*scroll = nullptr; // 内容滚动偏移
|
||
LanePanelState Lane::*panel = nullptr; // 面板 UI 状态
|
||
bool Lane::*enabled = nullptr; // 功能开关
|
||
};
|
||
inline constexpr PanelKindTraits PanelTraitsOf(EdgePanelKind kind); // 唯一 switch
|
||
```
|
||
|
||
* 五个消费者全部退化成一行取值:`return lane.*(PanelTraitsOf(kind).scroll);`
|
||
* **`PanelTitle` 不再是 switch**:标题是数据(前缀字符串 + 一个 `rangeSuffix` 标志),
|
||
运行时后缀只对两种「边沿计数」追加 ⇒ §17.6 里"`PanelTitle` 依赖运行时字符串所以先解决
|
||
'标题是函数'的问题"就此解决(不需要函数指针,也不需要 `std::wstring` 进表)。
|
||
* ⚠️ `PanelStyle` 的三个具名构造函数(`ListPanelStyle` / `CountTablePanelStyle` /
|
||
`MarkerPanelStyle`)是刻意加的:表里若写 6 个裸 bool,聚合顺序错一位就变成
|
||
"表格面板带按钮"这种没人看得出来的错误。
|
||
* 体积代价:**+512 B**(7 份 traits 记录进 `.rdata` + `PanelTitle` 多一个分支)。
|
||
用 512 B 换"新增面板只改两处 + 编译期强制登记",判定值得。
|
||
|
||
### P40 `PaintPanelMarkerGroups` 拆成 6 个具名函数 ✅
|
||
|
||
| 函数 | 职责 |
|
||
| --- | --- |
|
||
| `MeasureMarkerColumnWidths` | 六列实测文本宽度 → 写回 `Lane::freeMarkerColumnWidths` |
|
||
| `PaintMarkerGroupBar` | 一个信号分组栏(三角 / 名称 / `N 标记点` / ✕) |
|
||
| `PaintMarkerColumnSeparators` | 列分隔竖线(条目行与表头共用) |
|
||
| `PaintMarkerRow` | 一个固定点条目(六列 + 分隔线 + 行尾 ✕) |
|
||
| `PaintMarkerTableHeader` | 一个展开分组的表头(不透明底衬 + 六列标题) |
|
||
| `PaintMarkerOpacityRow` | 底部「标记透明度」滑块行 |
|
||
| `PaintPanelMarkerGroups` | **只负责顺序**:普通分组栏 → 条目 → 表头 → 吸顶栏 → 滚动条 → 透明度行 |
|
||
|
||
* 原实现把这条顺序约束埋在一个 340 行函数的循环中间("为什么表头要在这里画"),
|
||
现在它是主函数里唯一的逻辑,注释也只剩一句"后两段必须压在上面"。
|
||
* 提取时顺带把机械搬移遗留的"函数体多缩进 2 个 tab"修正(整个函数重新缩进)。
|
||
* **零体积极限验证**:P39 之后产物已经是 782,336 B,P40 之后**完全相同** ⇒ 纯搬移。
|
||
|
||
### 过程中的一个判断(值得记)
|
||
|
||
`contents`(固定点内容缓存)原来在"画完普通分组栏之后"才取,现在提前到最前面。
|
||
两者唯一的区别是缓存重建的时机,而 `FreeMarkerContents` **不改任何绘制状态**
|
||
(只读采样 + 拼字符串 + 写 `m_freeMarkerContent`)⇒ 对像素无影响,双基线也证实了。
|
||
|
||
### 本轮验证
|
||
|
||
| 项 | 结果 |
|
||
| --- | --- |
|
||
| `make release` / `make debug` / `make -C Tools` | 零 error / 零 warning |
|
||
| 产物 | 781,824 → **782,336 B**(+512,全部来自 P39 的登记表;P40 为 0) |
|
||
| `UnitTests` | `checks=260 failed=0 cases=19` |
|
||
| `OffscreenChecks -all` | 7/7 PASS,`checks=133 failed=0` |
|
||
| `panels -mode compare` | 7 个面板像素哈希**逐项不变**(含 `resample=2BFC74B3D9B3239C`) |
|
||
| `rowgeom -mode compare` | 965 + 7 键 0 差异;`-data MF4-Record.MF4` 1268 + 7 键 0 差异 |
|
||
| 行数 | `PlotViewPanelPaint.cpp` 939 → 977(6 个函数头 + 说明);`PlotViewPanels.cpp` 1766 → 1758(`PanelTitle` 变短) |
|
||
| 读点 | `PanelStyleOf` / `PanelTitle` / `LanePanelScrollOf` / `LanePanelStateOf` / `LanePanelEnabled` 的调用点**一处未改**(签名不变) |
|
||
|
||
|
||
|