Files
Just-DataInspector/Documents/UI_Refactor_Plan.md
T
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

7.7 KiB
Raw Blame History

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 干净

统一验收命令

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   重建