Files
DeepSeek V4.1 Flash 044b87383b 体积 L3(-fno-exceptions + MemProbe + LTO)与 L1 几何产物层;产物 909,824 -> 788,992 B
## 为什么

用户 2026-10-29 要求:按既定计划完整实现改进,但
① **不得把编译产物拆成 DLL**(项目定位是单文件轻量工具,最终只能有一个 .exe);
② 异常处理改用**预检验**而不是 try/catch(-fno-exceptions 省体积,且 try/catch 本身开销大);
③ 先删除 Documents 内三份 UI 重构文档再按"计划 / 当前进度 / 最终报告"重建;
④ 顺便审计代码,删除无用/过时注释,修复名实不符;
⑤ 提交到本地仓库,期间不要占用用户的鼠标/键盘/屏幕。

## 改了什么

### 1. 体积(909,824 B -> 788,992 B,-120,832 B,-13.3%)

Makefile 新增三个可覆盖开关(默认全为 1):

- `LAYOUT_BOUNDS`  编入/剔除「显示布局边界」叠加层
- `NO_EXCEPTIONS`  追加 `-fno-exceptions`
- `LTO`            追加 `-flto`(编译 + 链接)

派生:`FUNC_SECTIONS`(LTO 下为空)、`GC_SECTIONS`(`-Wl,--gc-sections`)、
`ASM_LISTING`/`ASM_LISTING_C`(LTO 下为空,因为 lto-wrapper 会丢弃 `-Wa` 清单)。

两个实测复现的坑(已写进 Makefile 注释):

- **切换 LTO 必须 `make clean`**:LTO 与非 LTO 目标文件混链会让 GCC 15.2.0 在
  `binds_to_current_def_p (symtab.cc:2598)` 处 ICE。
- **`-flto` 与 `-ffunction-sections -fdata-sections` 同时打开也 ICE**(干净重编也必崩,
  报错落点是 `DataModel.hpp:393`,但那是 COMDAT 解析落点、不是原因)⇒
  LTO 下 `FUNC_SECTIONS` 置空,并加 `-flto-partition=one`:默认 11 个分区的 LTRANS
  稳定复现,单分区从未复现。**这是稳定性选项,不是性能选项。**

### 2. 关异常必须成对交付:预检验 + 兜底

新增 `Utilities/Inc/MemProbe.hpp` + `Utilities/Src/MemProbe.cpp`:

- `MemAvailablePhysical()`:`GlobalMemoryStatusEx` + 200ms 缓存
- `MemCanAllocate(bytes)`:预留后仍须保有 1/8 可用内存
- `MemReserveIfRoom(container, n)`:含 `capacity()` 早退与乘法溢出保护
- `MemInstallTerminateHandler()`:OOM 时弹一次带建议的模态提示后 `_Exit`(而不是静默消失)

接入:

- `Utilities/Src/DataModel.cpp`:全仓**唯一**的 `try/catch`(`extremes.reserve` 捕
  `std::bad_alloc`)改为 `MemReserveIfRoom(extremes, count / 8)`
- `Components/PlotView/Src/PlotView.cpp`:`AvailablePhysicalBytes()` 原有一份**完整重复**
  的 `GlobalMemoryStatusEx` + 缓存实现 → 转发 `MemAvailablePhysical()`
- `Components/Application/Src/Application.cpp`:`Initialize()` 开头安装 terminate handler

### 3. L1 几何产物层

新增 `Components/Shared/Inc/UiBox.hpp` + `Src/UiBox.cpp`:

- `struct UiBox { rect, clip, depth, layer }` —— 纯值类型,不继承任何东西
  (继承会和 PlotView 4 层位图缓存 / SignalPanel 行位图缓存的失效体系打架)
- `class UiBoxTable : public UiTextRectSink` —— 只累计几何 + 裁剪/层/遮挡,**不含绘制代码**

`UiComponent::CollectDebugBoxes` → **`virtual void BuildBoxes(UiBoxTable&)`**:
组件从"收集布局边界(调试用)"改为"上报几何产物",不再需要知道"调试"这个概念存在。
`UiDebugBounds.hpp/.cpp` 退化为唯一的消费者(配色 + 包角描边 + 遮挡挖洞)。

除宿主 `ApplicationCommands.cpp` 外,所有组件源码现在只包含 `UiBox.hpp`。

### 4. 审计

- `MemProbe` 里"省 40~60KB"的估算 → 改为实测 27,136 B
- `MemProbe.hpp` 的 `<new>`、`MemProbe.cpp` 的 `<cstdio>`:未使用,删除
- `UiDebugPaintBounds(g, rect, depth)`:零调用者,删除(只保留带 clip 的版本)
- `TabBar`/`Toolbar`/`PlotView`/`SignalPanel` 的 `BuildBoxes` 文档"(调试用)"→"几何 box"
- `PlotView::SetForceFullRepaint` 补写"为什么保留而不优化"

### 5. 文档

`Documents/UI_Refactor_Plan.md` / `_Progress.md` / `_Report.md` 先 `git rm` 再按
「计划 / 当前进度 / 最终报告」重建;`Documents/agent_mem.md` 追加第十三轮记录。

## 怎么验证

```
make clean
make release TARGET_FILE_NAME=JustDataInspector -j16   # 零 error / 零 warning,788,992 B
make debug   TARGET_FILE_NAME=JustDataInspectorD -j16  # 零 warning

uicheck 69/69   lbcheck 21/21  lbresid 5/5  lbzorder 4/4  lbcovr 7/7  lbpanel 10/10
rendercheck: 8 个场景哈希与改造前逐一相同 + FUNC 7/7 PASS, exit=0
  A FAED59DF284153DB  B E6443BFE4E3955F8  C 96A0AA8C402F1729  D E3D5DBD998E43720
  E DA0583CB13C26A6F  F FFB822159F604D1D  G 19F8DE8FE3A8DF18  H DBBDDE4CB2C97C92
```

体积溯源(`objdump -h`,基线 → 最终):
`.text` 735,600 → 657,168;`.rdata` 49,184 → 35,016;
`.xdata` 48,132 → 27,512;`.pdata` 35,424 → 28,524;`.rsrc` 20,656 不变。

## 没做什么(有意为之)

- **没有拆 DLL**(用户明确要求单文件)
- **没有改优化等级**(`-Oz` 保持)
- **L2(`UiDamage` 脏矩形 + 独立叠加层)评估后缓做**:受益方只有"调试开关打开时的帧率",
  而正确实现必须把叠加层旧矩形注入底层组件重绘范围,穿透 PlotView 四条快路径 + 两层缓存;
  在用户反复报过"拖拽错位 / 贴图残留"的背景下,风险与收益不成比例。理由写在
  `Documents/UI_Refactor_Plan.md` §5 与 `PlotView::SetForceFullRepaint` 的文档里。
2026-10-29 11:53:54 +08:00

154 lines
7.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Just DataInspector UI 重构 —— 计划
> 本文档只描述**要做什么、为什么这么做、怎么验收**。
> 已完成情况见 `UI_Refactor_Progress.md`,结论与遗留见 `UI_Refactor_Report.md`。
> 最后更新:2026-10-29
---
## 1. 目标与非目标
### 1.1 目标
| # | 目标 | 判据 |
| --- | --- | --- |
| G1 | 收敛"宿主点名每个组件"的散乱结构 | 宿主只遍历一张组件表,不再 `if (component == plot)` 式分支 |
| G2 | 几何只有**一处**来源 | "画出来的形状"与"能点到的区域"由同一批函数给出,不再各算一遍 |
| G3 | 源码按**职责**分片,单片可读 | 单个 `.cpp` 不超过约 1200 行;文件名与内容一致 |
| G4 | 减小产物体积,且**仍然只有一个 `.exe`** | 体积比基线小,无新增依赖 DLL |
| G5 | 关闭 C++ 异常后**不牺牲健壮性** | 所有大块分配前有预检验;OOM 有可见提示 |
| G6 | 重构**不得改变任何绘制像素** | 8 个场景的逐像素哈希与基线逐一相同 |
### 1.2 非目标(明确不做)
* **不把编译产物拆成 DLL**:本项目的定位是"单文件、拷过去就能跑的轻量工具"。
任何以拆 DLL 换体积的方案一律否决。
* **不改优化等级**(`-Oz` 保持不变)。
* **不把"波形"与"网格线"拆成两层位图**:像素级平移不变量会被破坏。
* **不为体积牺牲可维护性**(例如放弃分片、放弃几何同源)。
---
## 2. 硬约束
1. **单一可执行文件**:不得引入额外的 `.dll` / 运行时依赖。
2. **体积预警阈值 = 1MB(1,048,576 B)**。这不是硬上限:超过仍可发布,
但必须主动提醒用户,并说明"超在哪一段(`.text` / `.rdata` / `.xdata`+`.pdata` / `.rsrc`)、
由哪个改动贡献"。旧的 900KB / 800KB 红线**已作废**。
3. **异常处理用预检验,不用 try/catch**:`-fno-exceptions` 下 `try/catch` 不会被编译,
因此所有"按数据规模预留大块内存"的地方必须先问 `MemReserveIfRoom()`。
4. **像素级零回归**:验收以 `Tools/rendercheck` 的 8 个场景哈希为准。
5. **中文注释按 UTF-8 无 BOM 保存**;构建参数用 `-finput-charset=UTF-8 -fexec-charset=GBK`。
---
## 3. 分层架构(L0–L3)
```
L3 体积与异常策略 -fno-exceptions / MemProbe / LTO / gc-sections
▲
L2 重绘范围控制 UiDamage(脏矩形) ← 本轮评估后**缓做**,见 §5
▲
L1 几何产物层 UiBox / UiBoxTable / UiComponent::BuildBoxes
▲
L0 组件协议 UiComponent + 组件表 + UiWidget 控件树
```
### L0 组件协议(前几轮已完成)
* `UiComponent`:`SetScale / Layout / Area / Paint / HitTest / OnMouseLeave / IsEditing / QueryCursor`。
* 宿主持有一张**有序组件表**,逐个遍历;新增组件不必改宿主的 5 处消息分支。
* `UiWidget` 与 `UiComponent` 是**兄弟**而非父子:前者管"一块区域内部的布局/命中/绘制",
后者管"自己这一整块地盘:区域、缓存、局部重绘策略"。
`Toolbar` / `TabBar` 同时是两者。
### L1 几何产物层(本轮完成)
* 新文件 `Components/Shared/Inc/UiBox.hpp` + `Src/UiBox.cpp`。
* `struct UiBox { rect, clip, depth, layer }`:**纯值类型**,不继承任何东西。
* `class UiBoxTable`:几何收集器,只维护"矩形 / 层级 / 绘制层 / 裁剪 / 遮挡"这些**几何事实**,
**不含任何绘制代码**。
* `UiComponent::BuildBoxes(UiBoxTable&)`:组件上报自己的几何产物(默认只登记 `Area()`)。
* `UiDebugBounds` 退化为**消费者**:`UiDebugPaintBoxList(g, table)` 把几何画成彩色包角线。
⇒ 组件不再需要知道"调试"这个概念存在。
### L2 重绘范围控制(缓做 —— 见 §5)
* 设计意图:`UiDamage`(最多 12 个矩形的脏区集合,超限退化为包围盒)替代
"全窗重绘 / 绘图区重绘"的二值分支;`PlotView::Paint(g, UiDamage&)` 逐路径累加脏区。
* 收益:去掉 `SetForceFullRepaint`(布局边界开关打开时的整幅重绘)。
### L3 体积与异常策略(本轮完成)
见 `UI_Refactor_Progress.md` 的实测表。
---
## 4. 阶段与验收标准
| 阶段 | 内容 | 验收 |
| --- | --- | --- |
| P1–P6 | 文件按职责拆分、`UiComponent` 协议、组件表、布局边界功能 | 编译 + `uicheck` |
| P7 | 布局边界"几何同源"(`RowRect` 前缀和、文本墨迹矩形、浮层层级/遮挡/裁剪) | `lbcovr` / `lbzorder` / `lbresid` / `lbpanel` |
| P8 | 体积:`-fno-exceptions` + `MemProbe` + `-ffunction-sections` + `--gc-sections` | 体积下降且 8 哈希不变 |
| P9 | 体积:LTO(含 `-flto-partition=one` 稳定性约束) | 同上,且可重复构建 |
| P10 | L1 几何产物层(`UiBox` / `UiBoxTable` / `BuildBoxes`) | 同上,且源码零残留旧名 |
| P11 | 代码审计(过时注释 / 名实不符 / 死代码) | 零 warning + 全量回归 |
| P12 | 文档重建 + 本地提交 | `git status` 干净 |
**统一验收命令**
```powershell
make clean
make release TARGET_FILE_NAME=JustDataInspector -j16 # 期望零 error / 零 warning
make debug TARGET_FILE_NAME=JustDataInspectorD -j16 # 期望零 warning
objdump -h Build\Release\JustDataInspector.exe # 段构成,用于体积溯源
Tools\uicheck\uicheck.exe # 69/69
.preflight\lbcheck\lbcheck.exe # 21/21
.preflight\lbresid\lbresid.exe # 5/5
.preflight\lbzorder\lbzorder.exe # 4/4
.preflight\lbcovr\lbcovr.exe # 7/7
.preflight\lbpanel\lbpanel.exe # 10/10
Tools\rendercheck\rendercheck.exe # 8 哈希不变 + FUNC 7/7
```
---
## 5. 明确缓做的部分及理由
### L2 `UiDamage` + 独立叠加层(S4)
**为什么缓做**
1. 受益方只有"**调试开关打开时**的帧率"。`SetForceFullRepaint` 只在
「显示布局边界」开启时被置位(`Application::Render` 里 `m_plot.SetForceFullRepaint(m_showLayoutBounds)`),
普通用户路径上**本来就是快路径**。
2. 正确实现无法只改 `PlotView` 内部:宿主用的是**持久后备缓冲区**,
上一帧叠加层画上去的边框像素必须在下一帧被"底层内容重画"覆盖掉。
若改成"独立叠加位图 + 只混合脏区",就必须把叠加层的旧矩形**注入**到底层组件的重绘范围里 ——
这要穿透 `PlotView` 的全部四条快路径(静态场景贴图 / 宽基底贴图 / 样式补丁 / 网格层重建)
与两层缓存。
3. 风险与收益不成比例:用户已在 2026-10-28 反复报告过"拖拽错位 / 贴图残留"一类事故,
而这类事故正是从"某条快路径漏了某块像素"开始的。
**保留了什么**:`SetForceFullRepaint` 及其四处守卫、`UiBoxTable` 的几何产物。
将来若真要做,只需把 `UiBoxTable` 的 `rect` 直接喂给 `UiDamage`,不需要重新收集几何。
---
## 6. 交付物清单
```
Components/Shared/Inc/UiBox.hpp 新增(几何产物层)
Components/Shared/Src/UiBox.cpp 新增
Components/Shared/Inc/UiDebugBounds.hpp 精简为"只画"(消费者)
Components/Shared/Src/UiDebugBounds.cpp 精简为"只画"
Components/Shared/Inc/UiComponent.hpp CollectDebugBoxes → BuildBoxes
Utilities/Inc/MemProbe.hpp 新增(分配预检验 + terminate 兜底)
Utilities/Src/MemProbe.cpp 新增
Makefile LAYOUT_BOUNDS / NO_EXCEPTIONS / LTO 三个开关
Documents/UI_Refactor_{Plan,Progress,Report}.md 重建
```