review(panels+size): 面板结构收敛收尾、审查清理、新增平行数组护栏
一、审查清理 * `PlotView::FreeMarkerValue` 的 `const Lane&` 形参从未使用(函数体里只有一句 `(void)lane;`)⇒ 删除形参与该语句,更新 2 个调用点。 * `EdgePanelLayoutFor` 里「选区截取是否就绪(双光标)」算了 3 遍 ⇒ 收敛为函数顶部 一次 `clipReady`。它同时决定面板高度 footer、条目行可见性、按钮几何, 三处各算一遍时只要有一处条件写错,就是"底部多一段空白"或"没双光标却能滚动"。 二、新增护栏(离屏检查) * `panels`:新增「平行数组长度一致」不变式 —— `EdgePanelLayout` 的 13 个按行平行 vector 必须与行数一致;4 个「重采样」专属数组允许为空、非空则必须对齐。 这是"某列读到别人矩形"这类错位缺陷的唯一低成本护栏(O(1))。 * `panelscroll`:同样加一份(大文件、566 行时才会暴露的问题)。 * `panels` 输出增加 rows 数,便于定位。 三、文档 * `Documents/Code_Review_Plan.md` 新增 §七(第 35 轮节点与硬约束)。 * `Documents/Code_Review_Progress.md` 新增第 35 轮(面板架构评估 + 三次重构记录)。 * `Documents/Code_Review_Report.md` 新增 §12(评估结论 + gdtoa 最终结论 + 护栏价值)。 * `Documents/agent_mem.md` 新增第 35 轮六条规则。 * `Tools/README.md`:补 `panelscroll`、`integer_replacement_matches_std`, 断言数更新为 260 / 108,最小集表补充面板相关条目。 验证: * `make release`(删 .o 全量)零 error / 零 warning,产物 **786,944 B**。 * `make -C Tools` 零 error / 零 warning。 * `UnitTests` → checks=260 failed=0 cases=19。 * `OffscreenChecks -all` → 6/6 检查 PASS,checks=108 failed=0。 * `panels -mode compare` → 7 个面板像素哈希与几何逐项不变。
This commit is contained in:
1 parent
34365b3c1c
commit
b58b427f62
8 files changed
+408
-15
No files matched your search
@@ -2377,7 +2377,7 @@ namespace jdi
|
||||
* @param [OUT] enumValue 输出采样原值(用于查枚举表)。
|
||||
* @return 取到有效值返回 true。
|
||||
*/
|
||||
bool FreeMarkerValue(const Lane& lane, const Lane::FreeMarkerItem& item, double& value,
|
||||
bool FreeMarkerValue(const Lane::FreeMarkerItem& item, double& value,
|
||||
double& enumValue) const;
|
||||
|
||||
/**
|
||||
|
||||
@@ -395,6 +395,9 @@ namespace jdi
|
||||
// 预留,而实际只画出并排的一行 ⇒ 内容区下方多出一整行“无意义的空白”
|
||||
// (用户 2026-11-03 连续三轮报告的那片空白,根因就在这一行)。
|
||||
// ⚠️ **未启用双光标**时两个按钮都不显示 ⇒ footer 归零(否则面板会留一大段空白)。
|
||||
// 「选区截取」的两个按钮 / 信号条目都只在**双光标**模式下存在;
|
||||
// 这个判定同时决定面板高度(footer)、条目行与按钮几何,过去在三个分支里各算一遍,
|
||||
// 任何一处写错就是「底部多一段空白」或「没双光标却能滚动」(用户 2026-11-03 报告)。
|
||||
const bool clipReady = clipPanel && (Tab().CursorMode() == PlotCursorMode::DOUBLE);
|
||||
const float footerHeight =
|
||||
clipPanel ? (clipReady ? (buttonHeight + pad) : 0.0f)
|
||||
@@ -432,7 +435,6 @@ namespace jdi
|
||||
// ⚠️ 「选区截取」在**未启用双光标**时没有任何可操作内容(只有一行提示文字),
|
||||
// 因此不按信号数累加行高、也不套用「至少 3 行」的下限
|
||||
// (用户 2026-11-03 报告:未启用双光标时仍显示信号条目、且能触发滚动)。
|
||||
const bool clipReady = clipPanel && (Tab().CursorMode() == PlotCursorMode::DOUBLE);
|
||||
float content = 0.0f;
|
||||
if (clipPanel && !clipReady)
|
||||
{
|
||||
@@ -781,8 +783,9 @@ namespace jdi
|
||||
const float groupRow = kEdgePanelMeanRowHeight * m_scale;
|
||||
const float sliderRow = kWeakPeakSliderRowHeight * m_scale;
|
||||
const float scrollW = kFreeMarkerScrollWidth * m_scale;
|
||||
const bool clipReadyHere =
|
||||
clipPanel && (Tab().CursorMode() == PlotCursorMode::DOUBLE);
|
||||
// ⚠️ 与高度算式共用**同一个** `clipReady`(见函数顶部):两处各算一遍时,
|
||||
// 只要有一处条件写错,面板高度与内容区高度就会不一致(底部多出空白)。
|
||||
const bool clipReadyHere = clipReady;
|
||||
// ⚠️ 两处(高度算式与这里)必须同为**一行按钮**高:两个按钮并排,不是上下两行。
|
||||
const float footerHeight =
|
||||
clipPanel ? (clipReadyHere ? (buttonRowHeight + pad) : 0.0f)
|
||||
@@ -1203,7 +1206,7 @@ namespace jdi
|
||||
return layout;
|
||||
}
|
||||
|
||||
bool PlotView::FreeMarkerValue(const Lane& lane, const Lane::FreeMarkerItem& item, double& value,
|
||||
bool PlotView::FreeMarkerValue(const Lane::FreeMarkerItem& item, double& value,
|
||||
double& enumValue) const
|
||||
{
|
||||
if (m_dataSet == nullptr)
|
||||
@@ -1226,7 +1229,6 @@ namespace jdi
|
||||
{
|
||||
return false;
|
||||
}
|
||||
(void)lane;
|
||||
enumValue = sampled; // 枚举按**原值**查
|
||||
value = ApplyDrawTransform(*resolved.style, sampled); // 读数与画出来的波形一致
|
||||
return std::isfinite(value);
|
||||
@@ -1262,7 +1264,7 @@ namespace jdi
|
||||
const Lane::FreeMarkerItem& previous = lane.freeMarkerItems[order[position - 1]];
|
||||
double previousValue = 0.0;
|
||||
double previousEnum = 0.0;
|
||||
if (!FreeMarkerValue(lane, previous, previousValue, previousEnum))
|
||||
if (!FreeMarkerValue(previous, previousValue, previousEnum))
|
||||
{
|
||||
return true; // 前一个点没有有效取值:不显示增量(避免编造一个数)
|
||||
}
|
||||
@@ -1354,7 +1356,7 @@ namespace jdi
|
||||
entry.itemIndex = itemIndex;
|
||||
double value = 0.0;
|
||||
double enumValue = 0.0;
|
||||
const bool valueOk = FreeMarkerValue(lane, item, value, enumValue);
|
||||
const bool valueOk = FreeMarkerValue(item, value, enumValue);
|
||||
bool hasPrev = false;
|
||||
double delta = 0.0;
|
||||
double timeDelta = 0.0;
|
||||
|
||||
@@ -203,3 +203,36 @@
|
||||
3. **"先量后改"**:任何以性能 / 体积为名的改动,必须在同一台机器、同一次会话内给出前后数字;
|
||||
收益低于噪声、或换来体积上涨的,一律回退,并把数字写进 `Code_Review_Report.md`。
|
||||
4. **像素基线只做本地 A/B**:哈希与 DPI/字体/数据文件绑定,**不入库、不当黄金图**。
|
||||
|
||||
---
|
||||
|
||||
## 七、第 35 轮追加(2026-11-04):浮动面板架构评估与收敛 + 整数格式化替换
|
||||
|
||||
> 触发:用户指出「7 个实验性功能面板观感/骨架高度一致,但每新增一种或改一次布局
|
||||
> 总会出意料之外的错误」,要求先**评估**(能否抽公共基类 / 公共组件,浮动面板、
|
||||
> 二级菜单、右键菜单是否算公共组件),再动手;同时把 §11.5 的
|
||||
> 「替换 `std::to_wstring` / `std::to_string`」做掉。
|
||||
|
||||
### 7.1 节点与验收标准
|
||||
|
||||
| 节点 | 内容 | 验收标准 |
|
||||
| --- | --- | --- |
|
||||
| **P20** | 面板「特征层」:唯一分类 / 标题 / 滚动几何来源 | 面板像素基线与几何**逐项不变** |
|
||||
| **P21** | 滚动设置收敛为唯一实现(7 个包装 + 2 条调用链) | `panelscroll` 新检查全绿 |
|
||||
| **P22** | 全仓替换整数格式化(76 + 2 处) | 面板像素不变 + 单元测试逐字符对照 |
|
||||
| **P23** | 其余审查清理(垃圾形参 / 重复判定) | 零 warning、像素不变 |
|
||||
|
||||
### 7.2 本轮新增硬约束
|
||||
|
||||
1. **新增一种实验性功能面板,只允许改三个地方**:
|
||||
`LanePanelScrollOf` / `PanelStyleOf` / `PanelTitle`(都在 `PlotViewInternal.hpp` /
|
||||
`PlotViewPanels.cpp`)。三者都是穷举 `switch` 且**不写 `default:`**,
|
||||
漏登记会被 `-Wswitch` 编译报警。
|
||||
**禁止**再在宽度 / 布局 / 绘制里各写一份 `kind == XXX` 组合判断。
|
||||
2. **底部"有按钮行"与"是构建按钮"是两个概念**(`PanelStyle::footerButtons()`
|
||||
vs `buildButton`)。混用会让面板高度与内容区高度不一致。
|
||||
3. **整数转文本一律用 `Utf8Util::FormatInteger*`**,不要用 `std::to_wstring` /
|
||||
`std::to_string`(它们把 MinGW printf 引擎拉进产物,且 libstdc++ 会强制
|
||||
`__USE_MINGW_ANSI_STDIO=1`,命令行关不掉 —— 见 Makefile 注释)。
|
||||
4. **改面板几何/绘制前后必须跑** `OffscreenChecks -run panels -mode record/compare`;
|
||||
本轮它当场抓到了"截取面板少了 37px 高度"的回归。
|
||||
@@ -241,3 +241,109 @@ Makefile 按目录通配收集源文件,**无需登记新文件**。
|
||||
| `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 项的前置护栏)—— 已记入待办。
|
||||
@@ -391,3 +391,92 @@ Tools/README.md 目录结构、作用、测试项、验收标准、A/B
|
||||
重写后 `-mode compare`;只要有一处像素/几何差异就说明改坏了。
|
||||
5. **不要重复这两次实验**:`EdgeScanFor` 哈希化(+5.6 KB / −5 μs 不划算)、
|
||||
仅切 `__USE_MINGW_ANSI_STDIO`(0 B)。数字见 §11.1 与 `Code_Review_Progress.md` 第 34 轮。
|
||||
|
||||
---
|
||||
|
||||
## 12. 第 35 轮(2026-11-04):浮动面板架构评估与收敛 + 整数格式化
|
||||
|
||||
> 计划见 `Code_Review_Plan.md` §七,逐节点记录见 `Code_Review_Progress.md` 第 35 轮。
|
||||
|
||||
### 12.1 一句话结论
|
||||
|
||||
「每个面板骨架一致、但每次改布局就出错」的机制**不是**"缺一个公共基类",
|
||||
而是**同一套"按种类分支"被抄了 6 份**(分类布尔量 ×3、滚动几何 ×3、滚动条绘制 ×3、
|
||||
滚动设置 ×7、按种类转发 ×2、标题 ×2)。本轮把这些抄本收敛成"唯一来源 + 编译期穷举检查",
|
||||
并补了两条离屏护栏(`panelscroll`、平行数组长度一致),
|
||||
用 786,944 B(−1,024)的产物体积换来"新增面板只改 3 个地方"。
|
||||
|
||||
### 12.2 评估:这些面板/菜单算不算"公共组件"?
|
||||
|
||||
| 对象 | 现状 | 结论 |
|
||||
| --- | --- | --- |
|
||||
| 右键菜单 / 二级菜单 | `Components/Shared/PopupMenu` + `Layout`/`Paint` 分片 | **已经是公共组件**(5 类菜单共用一份实现) |
|
||||
| 拾色器浮层 | `Components/Shared/ColorPickerPopup` | **已经是公共组件** |
|
||||
| 实验性功能浮动面板 | 全部在 `PlotView` 内,骨架一份、**按种类分支多份** | 骨架公共,分支需收敛(本轮已做) |
|
||||
|
||||
**为什么不把面板做成 `UiWidget` 树节点**(像 PopupMenu 那样):
|
||||
1. 面板像素要进 `m_panelDc` 图层缓存(`PanelGlobalKey ⊕ PanelSlotKey`),
|
||||
与 `UiWidget` 的 dirty/capture 体系叠加会互相失效(`SignalPanel` 行位图缓存
|
||||
与控件树就是刻意分开的);
|
||||
2. 面板几何**必须**由 `EdgePanelLayoutFor` 唯一给出(绘制 / 命中 / 光标 / 调试盒 /
|
||||
缓存键五处共用),控件树会拆掉这条"几何同源";
|
||||
3. 面板要往 `m_panelDc` `BitBlt` 抓像素、`AddOccluder` 挖洞、按 `m_panelOrder` 逐层设文本层号
|
||||
—— 全是 `PlotView` 内部协议。
|
||||
|
||||
⇒ 结论:**公共化方向是"派发点收敛",不是"搬进控件树"**。本轮照此执行。
|
||||
|
||||
### 12.3 做了什么(提交 `abcf3e6`、`34365b3`、`本提交`)
|
||||
|
||||
| 项 | 内容 | 效果 |
|
||||
| --- | --- | --- |
|
||||
| 面板特征层 | `pvdetail::PanelStyle` + `PanelStyleOf(kind)`、`PanelScrollMetricsFor(...)`、`PlotView::PanelTitle(kind)`;宽度 / 布局 / 绘制三处调用点全部改读 | 分类 3 份 → 1 份;滚动几何 3 份 → 1 份 |
|
||||
| 潜伏错误修复 | `EdgePanelWidth` 的标题链漏了 MARK/CLIP(按"边沿计数"量宽) | 标题与宽度从此同源 |
|
||||
| 滚动收敛 | `SetLanePanelScroll` 唯一实现 + 7 个一行包装;滚轮/拖滚动条的 2 条 `switch` 链改直接调用 | 7 份 → 1 份,读写同源 |
|
||||
| 滚动条绘制 | `paintPanelScrollBar(layout)`,3 处复用 | 3 份 → 1 份 |
|
||||
| 整数格式化 | `Utf8Util::FormatInteger*` 替代 76 处 `std::to_wstring` + 2 处 `std::to_string` | **−1,024 B** |
|
||||
| 审查清理 | 删 `FreeMarkerValue` 未使用形参与 `(void)lane;`;`clipReady` 3 处判定收敛为 1 处 | 减少分歧点 |
|
||||
|
||||
### 12.4 ⚠️ 一条建议的最终结论:gdtoa(≈33 KB)**去不掉**
|
||||
|
||||
* 只替换整数转换:**−1,024 B**。
|
||||
* 叠加 `-D__USE_MINGW_ANSI_STDIO=0`(`swprintf` 让给 UCRT):**0 字节**。
|
||||
* 根因定位到行:libstdc++ 的
|
||||
`.../include/c++/x86_64-w64-mingw32/bits/os_defines.h:48-51` **无条件**
|
||||
`#undef __USE_MINGW_ANSI_STDIO` + `#define … 1`("libstdc++ depends on
|
||||
POSIX-definitions of those functions")。`g++ -E -dM` 实测最终值恒为 1
|
||||
⇒ **任何 C++ 翻译单元**的 `swprintf` / `strtod` 都是 MinGW 版本,命令行无法覆盖。
|
||||
* ⇒ 要省这 33 KB 只能自己重写「浮点 → 十进制(正确舍入)」与浮点解析的特殊分支
|
||||
(等价于重写 gdtoa)。**判定:不做**,已在 `Makefile` 留下结论与证据,避免重复尝试。
|
||||
|
||||
### 12.5 新增的护栏(本轮最重要的产出之一)
|
||||
|
||||
1. **`panelscroll` 检查**:7 种面板逐一"滚到底 / 滚回顶",验证夹取、滑块端点、内容区不动、
|
||||
不可滚时无滚动条。用 `MF4-Record.MF4`(566 信号并入同一网格)实测 6/7 可滚
|
||||
(自由标记无固定点,不可滚是正确行为)。
|
||||
2. **平行数组长度不变式**(`panels` + `panelscroll` 各一份):`EdgePanelLayout` 里
|
||||
13 个按行平行的 `vector` 必须与行数一致;4 个「重采样」专属数组允许为空(非空则必须对齐)。
|
||||
这条检查成本 O(1),却直接守住"某列读到别人矩形"这一整类错位缺陷。
|
||||
3. **像素基线在真实事故中证明了价值**:本轮把 `hasButton` 映射到 `buildButton` 后,
|
||||
「选区截取」底部按钮行高归零 ⇒ 面板高度 124 → 87 px,`panels -mode compare`
|
||||
**当场报 MISMATCH**(另外 4 个面板的哈希也变了 —— 因为面板互相重叠,短掉的那块露出下层)。
|
||||
如果没有这条基线,这个回归只会在用户那里以"截取面板底部怪怪的"形式出现。
|
||||
|
||||
### 12.6 体积与验证
|
||||
|
||||
| 项 | 结果 |
|
||||
| --- | --- |
|
||||
| Release 产物 | **786,944 B**(第 34 轮末 788,480 → −1,536;其中 −1,024 来自整数格式化,−512 来自面板收敛) |
|
||||
| `make release` / `make -C Tools` | 零 error / 零 warning(全量重建) |
|
||||
| `UnitTests` | `checks=260 failed=0 cases=19` |
|
||||
| `OffscreenChecks -all` | 6 个检查全 PASS,`checks=108 failed=0` |
|
||||
| `panels -mode compare` | 7 个面板像素哈希 + 几何**逐项不变** |
|
||||
|
||||
### 12.7 后续建议(按性价比)
|
||||
|
||||
1. **`EdgePanelLayout` 的平行数组合并为 `std::vector<PanelRow>`**(314 处引用)。
|
||||
这是"改布局就出错"的**最后一块机制性隐患**:每个分支必须按同一顺序 push 恰好 13 项。
|
||||
本轮先补了长度一致检查作为护栏;真正的合并仍需一次专门的重构窗口。
|
||||
2. **`PaintEdgeCursor` 拆分**(约 1600 行):把三段内容绘制(表格 / 列表 / 自由标记)
|
||||
机械提取为 3 个成员函数,判据 = `panels -mode compare` 全绿。
|
||||
3. **`EdgeScanFor` 状态查找**:维持线性扫描(第 34 轮实测 198 状态 0.097 µs/次,
|
||||
哈希化需 +5,632 B,不划算)。
|
||||
@@ -10429,3 +10429,73 @@ release/debug **零警告**;Release `TargetCheck.exe` **890,880 B**(< 1 MB
|
||||
* 单元测试写错过的 5 处"我以为的语义"(都已按产品语义修正):
|
||||
`9.999999999999999`(>15 位有效数字允许 1 ULP)、`1..2` 合法、`FormulaIsIdentity` 不去空格、
|
||||
非法公式 `EvaluateFormula` **原样返回采样值**(不是 NaN)。
|
||||
|
||||
---
|
||||
|
||||
## 2026-11-04 · 第 35 轮(`Code_Review` 分支):浮动面板结构收敛 + 整数格式化替换
|
||||
|
||||
> 计划 `Documents/Code_Review_Plan.md` §七,逐节点 `Documents/Code_Review_Progress.md` 第 35 轮,
|
||||
> 结论 `Documents/Code_Review_Report.md` §12。提交:`abcf3e6`(面板)、`34365b3`(整数)、
|
||||
> 本提交(清理 + 文档 + 新检查)。
|
||||
|
||||
### 1. ⭐ 面板"改一次就出错"的真实机制(用户 2026-11-04 指出的问题)
|
||||
|
||||
**不是缺公共基类**,而是同一套"按种类分支"被抄了 6 份:
|
||||
|
||||
| 重复项 | 份数 |
|
||||
| --- | --- |
|
||||
| 分类布尔量(listStyle/countTable/hasButton…) | 3(宽度 / 布局 / 绘制) |
|
||||
| 滚动条几何 + 夹取 | 3(表格 / 列表 / 自由标记分支) |
|
||||
| 滚动条绘制 | 3 |
|
||||
| `SetLane*Scroll` 实现 | 7 |
|
||||
| 按种类转发滚动写入 | 2(滚轮 / 拖滚动条) |
|
||||
| 标题文本 | 2(**内容还不一致**) |
|
||||
|
||||
**新增硬规则:新增一种实验性功能面板,只允许改三个地方** ——
|
||||
`LanePanelScrollOf` / `PanelStyleOf` / `PanelTitle`。
|
||||
三者都是穷举 `switch` 且**不写 `default:`** ⇒ 漏登记会被 `-Wswitch` 编译报警。
|
||||
**禁止**再在宽度 / 布局 / 绘制里各写一份 `kind == XXX` 组合判断。
|
||||
|
||||
### 2. ⚠️ `PanelStyle` 里"有按钮行"≠"是构建按钮"
|
||||
|
||||
`footerButtons()`(有按钮行:buildButton || twoButtons)与 `buildButton`(是「构建为新信号」)
|
||||
必须分开:混用会让「选区截取」底部按钮行高归零 ⇒ 面板高度 124 → 87 px,
|
||||
而内容区仍按旧高度算。**这次是 `panels -mode compare` 当场抓到的**(5 个面板哈希同时变),
|
||||
它就是"改面板必须跑像素基线"的最好例证。
|
||||
|
||||
### 3. ⛔ gdtoa(≈33 KB)**在 C++ 里去不掉**(别再试)
|
||||
|
||||
* 替换 76 处 `std::to_wstring` + 2 处 `std::to_string` 为 `Utf8Util::FormatInteger*`:**−1,024 B**。
|
||||
* 再叠加 `-D__USE_MINGW_ANSI_STDIO=0`:**0 字节**。
|
||||
* 根因(精确到行):libstdc++ 的
|
||||
`.../include/c++/x86_64-w64-mingw32/bits/os_defines.h:48-51` **无条件**
|
||||
`#undef __USE_MINGW_ANSI_STDIO` + `#define … 1`
|
||||
("libstdc++ depends on POSIX-definitions of those functions")。
|
||||
`g++ -E -dM` 实测最终值恒为 1 ⇒ 任何 C++ TU 的 `swprintf`/`strtod` 都是 MinGW 版本。
|
||||
* ⇒ 要省它只能自己重写浮点↔十进制(等价 gdtoa)。**判定:不做**,`Makefile` 已留结论。
|
||||
|
||||
### 4. 面板"公共组件"评估结论(用户问的)
|
||||
|
||||
* **右键菜单 / 二级菜单**:`Components/Shared/PopupMenu` —— 已是公共组件(5 类菜单共用)。
|
||||
* **拾色器**:`Components/Shared/ColorPickerPopup` —— 已是公共组件。
|
||||
* **实验性功能面板**:骨架已是一份(标题栏 = 折起三角 + GX 徽标 + 标题 + ✕ / 滚动内容区 /
|
||||
底部按钮行),**不该**搬进 `UiWidget` 树:面板像素走 `m_panelDc` 图层缓存、
|
||||
几何必须由 `EdgePanelLayoutFor` 唯一给出、还要 `BitBlt` 抓像素 + `AddOccluder` 挖洞 +
|
||||
按 `m_panelOrder` 设文本层号 —— 全是 `PlotView` 内部协议。
|
||||
⇒ 正确方向是**派发点收敛**(本轮已做),不是控件树化。
|
||||
|
||||
### 5. 新增护栏(本轮最重要的产出)
|
||||
|
||||
1. **`panelscroll`**:7 种面板逐一滚到底/回顶,验证夹取、滑块端点、内容区不动、
|
||||
不可滚时无滚动条。`MF4-Record.MF4`(566 信号并入同一网格)实测 6/7 可滚。
|
||||
2. **平行数组长度不变式**(`panels` + `panelscroll`):`EdgePanelLayout` 的 13 个按行平行
|
||||
`vector` 必须等于行数;4 个「重采样」专属数组允许为空、非空则必须对齐。
|
||||
O(1) 成本,守住"某列读到别人矩形"这一整类错位。
|
||||
3. 工具断言总数:UnitTests **260**、OffscreenChecks **108**(-all)。
|
||||
|
||||
### 6. 遗留(下一步候选,按性价比)
|
||||
|
||||
* `EdgePanelLayout` 17 个平行数组合并为 `std::vector<PanelRow>` —— **314 处引用**,
|
||||
是"改布局就出错"的最后一块机制隐患;已先用长度检查兜住。
|
||||
* `PaintEdgeCursor`(约 1600 行)把三段内容绘制机械提取为 3 个成员函数,
|
||||
判据 = `panels -mode compare` 全绿。
|
||||
@@ -417,6 +417,69 @@ namespace jdi
|
||||
rect.y + rect.height <= plotRect.y + plotRect.height + 1;
|
||||
Expect(inside, std::string("panel inside plot area: ") + PanelKindName(kind));
|
||||
|
||||
// 不变式:`EdgePanelLayout` 的**平行数组长度必须全部等于行数**。
|
||||
// 这 13 个按行平行的 vector 每个分支都要按同一顺序 push 恰好 13 项,
|
||||
// 漏一项就会让某一列读到别的行的矩形("改布局就出错"的典型机制)。
|
||||
// ⚠️ 「重采样」专属的 4 个数组(最大/最小间隔、间隔/频率输入框)在**其它**
|
||||
// 面板里允许为空(没人读),但只要非空就必须同样与行数对齐。
|
||||
// 成本极低(O(1) 比长度),但能挡住整类错位缺陷。
|
||||
const std::size_t rowCount = layout.rowRefs.size();
|
||||
const std::size_t coreSizes[] = {
|
||||
layout.rowRefs.size(), layout.rowHeaders.size(),
|
||||
layout.rowNameRects.size(), layout.rowTriangles.size(),
|
||||
layout.rowChecks.size(), layout.rowPoints.size(),
|
||||
layout.rowIterations.size(), layout.rowStats.size(),
|
||||
layout.rowLevelTracks.size(), layout.rowLevelValues.size(),
|
||||
layout.rowLevelResets.size(), layout.rowIterTracks.size(),
|
||||
layout.rowIterValues.size(), layout.rowIterResets.size()};
|
||||
bool parallel = true;
|
||||
for (const std::size_t size : coreSizes)
|
||||
{
|
||||
if (size != rowCount)
|
||||
{
|
||||
parallel = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const std::size_t resampleSizes[] = {
|
||||
layout.rowInfoMaxInterval.size(), layout.rowInfoMinInterval.size(),
|
||||
layout.rowIntervalField.size(), layout.rowFrequencyField.size()};
|
||||
bool resampleParallel = true;
|
||||
for (const std::size_t size : resampleSizes)
|
||||
{
|
||||
if (size != 0 && size != rowCount)
|
||||
{
|
||||
resampleParallel = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!parallel || !resampleParallel)
|
||||
{
|
||||
EmitLine("[panels] %s row arrays: refs=%d hdr=%d name=%d tri=%d chk=%d pts=%d it=%d "
|
||||
"stat=%d lvl=%d/%d/%d iter=%d/%d/%d info=%d/%d fld=%d/%d\n",
|
||||
PanelKindName(kind), static_cast<int>(layout.rowRefs.size()),
|
||||
static_cast<int>(layout.rowHeaders.size()),
|
||||
static_cast<int>(layout.rowNameRects.size()),
|
||||
static_cast<int>(layout.rowTriangles.size()),
|
||||
static_cast<int>(layout.rowChecks.size()),
|
||||
static_cast<int>(layout.rowPoints.size()),
|
||||
static_cast<int>(layout.rowIterations.size()),
|
||||
static_cast<int>(layout.rowStats.size()),
|
||||
static_cast<int>(layout.rowLevelTracks.size()),
|
||||
static_cast<int>(layout.rowLevelValues.size()),
|
||||
static_cast<int>(layout.rowLevelResets.size()),
|
||||
static_cast<int>(layout.rowIterTracks.size()),
|
||||
static_cast<int>(layout.rowIterValues.size()),
|
||||
static_cast<int>(layout.rowIterResets.size()),
|
||||
static_cast<int>(layout.rowInfoMaxInterval.size()),
|
||||
static_cast<int>(layout.rowInfoMinInterval.size()),
|
||||
static_cast<int>(layout.rowIntervalField.size()),
|
||||
static_cast<int>(layout.rowFrequencyField.size()));
|
||||
}
|
||||
Expect(parallel, std::string("row arrays have identical lengths: ") + PanelKindName(kind));
|
||||
Expect(resampleParallel,
|
||||
std::string("resample row arrays empty-or-aligned: ") + PanelKindName(kind));
|
||||
|
||||
char buffer[160] = {};
|
||||
std::snprintf(buffer, sizeof(buffer), "%s=%016llX,%d,%d,%d,%d\n", PanelKindName(kind),
|
||||
static_cast<unsigned long long>(HashRegion(session.LastFrame(), rect)), rect.x,
|
||||
@@ -537,9 +600,36 @@ namespace jdi
|
||||
const PlotView::EdgePanelLayout initial = plot.EdgePanelLayoutFor(0, kind);
|
||||
const float maxScroll =
|
||||
std::max(0.0f, initial.markerContentHeight - initial.content.Height);
|
||||
EmitLine("[panelscroll] %-8s content=%.1f view=%.1f maxScroll=%.1f scrollable=%d\n", name,
|
||||
initial.markerContentHeight, initial.content.Height, maxScroll,
|
||||
initial.scrollTrack.Width > 0.0f ? 1 : 0);
|
||||
EmitLine("[panelscroll] %-8s content=%.1f view=%.1f maxScroll=%.1f scrollable=%d rows=%d\n",
|
||||
name, initial.markerContentHeight, initial.content.Height, maxScroll,
|
||||
initial.scrollTrack.Width > 0.0f ? 1 : 0,
|
||||
static_cast<int>(initial.rowRefs.size()));
|
||||
|
||||
// 多信号场景下额外守住"平行数组长度一致"(本轮新增,成本 O(1))。
|
||||
{
|
||||
const std::size_t rows = initial.rowRefs.size();
|
||||
const std::size_t sizes[] = {initial.rowHeaders.size(), initial.rowNameRects.size(),
|
||||
initial.rowTriangles.size(), initial.rowChecks.size(),
|
||||
initial.rowPoints.size(), initial.rowIterations.size(),
|
||||
initial.rowStats.size(), initial.rowLevelTracks.size(),
|
||||
initial.rowLevelValues.size(), initial.rowLevelResets.size(),
|
||||
initial.rowIterTracks.size(), initial.rowIterValues.size(),
|
||||
initial.rowIterResets.size(), initial.rowInfoMaxInterval.size(),
|
||||
initial.rowInfoMinInterval.size(),
|
||||
initial.rowIntervalField.size(),
|
||||
initial.rowFrequencyField.size()};
|
||||
bool aligned = true;
|
||||
for (const std::size_t size : sizes)
|
||||
{
|
||||
// 「重采样」专属数组在其它面板允许为空(见 `panels` 检查的说明)。
|
||||
if (size != rows && size != 0)
|
||||
{
|
||||
aligned = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Expect(aligned, std::string("row arrays aligned with rows: ") + name);
|
||||
}
|
||||
|
||||
if (maxScroll <= 1.0f)
|
||||
{
|
||||
|
||||
+7
-4
@@ -124,12 +124,13 @@ SUMMARY checks=<n> failed=<m> cases=<k> result=<PASS|FAIL>
|
||||
| `filter_moving_average_equivalence` | 1 阶 ≡ 单次、2 阶 ≡ 链式、常数信号不变 | 均值滤波多阶 |
|
||||
| `dataset_group_access` | `AddGroup` / `GroupAt` / `Style(ref)` 读写 | 数据层入口 |
|
||||
| `dataset_formula_transform_cache` | 值域 + 逐分组极值 vs 逐点参考**逐位相同**;命中同指针;样式变化失效 | 第 32 轮核心缓存的正确性 |
|
||||
| `stdio_mingw_vs_ucrt_formats` | MinGW ANSI stdio 与系统 UCRT 的 printf 在**本程序用到的全部格式**上逐字符比对(13.6 万次调用) | 评估“把 `swprintf` 交给 UCRT”是否可行(当前 0 差异);将来真做迁移就是验收标准 |
|
||||
| `stdio_mingw_vs_ucrt_formats` | MinGW ANSI stdio 与系统 UCRT 的 printf 在**本程序用到的全部格式**上逐字符比对(13.6 万次调用) | 评估“把 `swprintf` 交给 UCRT”是否可行(结论:0 字节,libstdc++ 强制开 MinGW 版本) |
|
||||
| `integer_replacement_matches_std` | `FormatInteger*` 与 `std::to_wstring` / `std::to_string` 逐字符对照(含 LLONG_MIN/MAX、ULLONG_MAX) | 全仓 78 处整数转换的语义护栏(它们已替代标准库,以去掉 printf 依赖) |
|
||||
|
||||
### 3.2 验收标准
|
||||
|
||||
* **零失败**(`result=PASS`)才算通过;
|
||||
* 断言数量只增不减(当前 **219** 项 / 18 个用例):删断言必须说明理由;
|
||||
* 断言数量只增不减(当前 **260** 项 / 19 个用例):删断言必须说明理由;
|
||||
* 任何"为了让它过"而放宽的断言,必须在代码注释里写清依据。
|
||||
|
||||
---
|
||||
@@ -162,7 +163,8 @@ Tools\OffscreenChecks\Build\OffscreenChecks.exe -list
|
||||
| --- | --- | --- |
|
||||
| `smoke` | 建隐藏窗口 → 加载 → 显示 3 条信号 → 渲染 → 抓帧 → 落盘 `smoke.png` | 客户区/绘图区尺寸合理、绘图区墨迹 > 1000 像素、**同一状态连续两帧逐像素相同**(渲染确定性) |
|
||||
| `frames` | 沿信号列表逐行移动"鼠标",每帧抓图 + 区域哈希 + 耗时 | 平均帧 < 500ms、帧序列**至少出现一次可见变化**;输出 `frame-XXX.png` 供事后分析 |
|
||||
| `panels` | 打开全部 7 个实验性面板(含双光标下的「选区截取」) | 每个面板:功能已启用、有尺寸、**必须被绘图区夹住**;`-mode record` 写 `panels-baseline.txt`,`-mode compare` 逐面板比对**像素哈希 + 几何** |
|
||||
| `panels` | 打开全部 7 个实验性面板(含双光标下的「选区截取」) | 每个面板:功能已启用、有尺寸、**必须被绘图区夹住**、**平行数组长度与行数一致**;`-mode record` 写 `panels-baseline.txt`,`-mode compare` 逐面板比对**像素哈希 + 几何** |
|
||||
| `panelscroll` | 全部信号合并到同一网格后,逐一滚动 7 种面板的内容区 | 可滚动时:滚到底/滚回顶、`scrollOffset` 夹到 `maxScroll`、滑块到达/返回轨道端点、内容区几何不动;不可滚动时不得出现滚动条。另守住平行数组一致性 |
|
||||
| `edgestats` | 3 个阈值下的边沿计数(普通计数)+ 中心轴计数 | 与 `Checks.cpp` 内的**独立参考实现**(朴素逐点)**逐位相等**;同参数重复查询结果稳定 |
|
||||
| `perf` | 整帧中位数、`EdgePanelLayoutFor` 单次耗时、198 个统计状态的整轮查询耗时 | 整帧 < 500ms、面板几何 < 200µs/次、统计查询 < 20µs/次;数字用于前后对比 |
|
||||
|
||||
@@ -199,9 +201,10 @@ OffscreenChecks.exe -run panels -mode compare -out ..\Temp\tools\panel-before
|
||||
| --- | --- |
|
||||
| 任何数据层 / 解析 / 公式 / 变换 | `UnitTests.exe` |
|
||||
| 绘图、面板、布局、缓存键 | `-run smoke -run panels`(+ 需要时 `-mode compare`) |
|
||||
| 面板内容滚动 / 面板几何 / 新增面板种类 | `-run panels -run panelscroll`(大文件加 `-data ..\TestData\MF4-Record.MF4`) |
|
||||
| 边沿统计 / 中心轴 / 扫描预算 | `-run edgestats -run perf` |
|
||||
| 性能相关 | `-run perf`(前后各跑一次,比 `us/query` 与 `median ms`) |
|
||||
| 完全不确定影响面 | `-run -all`(约几秒到几十秒)+ `UnitTests.exe` |
|
||||
| 完全不确定影响面 | `-all`(约几秒到几十秒)+ `UnitTests.exe` |
|
||||
|
||||
> 用户明确要求:**除非要求"全测一遍",否则只跑针对性检查**。
|
||||
|
||||
|
||||
Reference in new issue
Block a user