From 764e5bee71386c9940ffd72c02d4c34f1519797f Mon Sep 17 00:00:00 2001 From: "DeepSeek-V4.1-Flash" <> Date: Wed, 4 Nov 2026 02:41:53 +0800 Subject: [PATCH] =?UTF-8?q?chore(docs):=20=E5=88=A0=E9=99=A4=E8=BF=87?= =?UTF-8?q?=E6=97=B6=E7=9A=84=20UI=20=E9=87=8D=E6=9E=84=E9=98=B6=E6=AE=B5?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Documents/UI_Refactor_Plan.md、UI_Refactor_Progress.md、UI_Refactor_Report.md 描述的是 2026-10-28 那一阶段的计划与结论,已被后续多轮实现推翻/覆盖, 且内容与当前代码不一致,继续保留会误导接手者。 删除后由 Documents/Code_Review_Plan.md / Code_Review_Progress.md / Code_Review_Report.md 承接(下一分支 Code_Review)。 --- Documents/UI_Refactor_Plan.md | 153 ---------------------- Documents/UI_Refactor_Progress.md | 202 ------------------------------ Documents/UI_Refactor_Report.md | 157 ----------------------- 3 files changed, 512 deletions(-) delete mode 100644 Documents/UI_Refactor_Plan.md delete mode 100644 Documents/UI_Refactor_Progress.md delete mode 100644 Documents/UI_Refactor_Report.md diff --git a/Documents/UI_Refactor_Plan.md b/Documents/UI_Refactor_Plan.md deleted file mode 100644 index 7d95ed6..0000000 --- a/Documents/UI_Refactor_Plan.md +++ /dev/null @@ -1,153 +0,0 @@ -# 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 重建 -``` diff --git a/Documents/UI_Refactor_Progress.md b/Documents/UI_Refactor_Progress.md deleted file mode 100644 index f34e4b2..0000000 --- a/Documents/UI_Refactor_Progress.md +++ /dev/null @@ -1,202 +0,0 @@ -# Just DataInspector UI 重构 —— 当前进度 - -> 计划见 `UI_Refactor_Plan.md`,结论与遗留见 `UI_Refactor_Report.md`。 -> 最后更新:2026-10-29(本轮:体积 L3 落地 + L1 几何产物层 + 审计) - ---- - -## 1. 总览 - -| 层 | 内容 | 状态 | -| --- | --- | --- | -| L0 | `UiComponent` 协议 + 有序组件表 + `UiWidget` 控件树 | ✅ 已完成(前几轮) | -| L1 | `UiBox` / `UiBoxTable` / `UiComponent::BuildBoxes` | ✅ **本轮完成** | -| L2 | `UiDamage` 脏矩形 + S4 独立叠加层 | ⏸ **评估后缓做**(理由见 Plan §5) | -| L3 | `-fno-exceptions` + `MemProbe` + `-ffunction-sections` + `--gc-sections` + LTO | ✅ **本轮完成** | - ---- - -## 2. L3 体积与异常策略 - -### 2.1 Makefile 开关 - -| 开关 | 默认 | 作用 | 备注 | -| --- | --- | --- | --- | -| `LAYOUT_BOUNDS` | `1` | 是否编入「显示布局边界」调试叠加层 | `make release LAYOUT_BOUNDS=0` 可剔除 | -| `NO_EXCEPTIONS` | `1` | 追加 `-fno-exceptions` | 必须配合 `MemProbe` 的预检验 | -| `LTO` | `1` | 追加 `-flto`(编译 + 链接) | **切换需 `make clean`** | - -派生变量: - -| 变量 | `LTO=1` | `LTO=0` | -| --- | --- | --- | -| `FUNC_SECTIONS` | 空 | `-ffunction-sections -fdata-sections` | -| `ASM_LISTING` / `ASM_LISTING_C` | 空 | `-Wa,-a,-ad,-ahlms=$(@:%.o=%.lst)` | -| `GC_SECTIONS` | `-Wl,--gc-sections` | 同左 | - -> `GC_SECTIONS` / `FUNC_SECTIONS` / `ASM_LISTING*` 都定义在 `LDFLAGS` 之后。 -> 它们是递归变量(`=` / `+=`),make 在展开 recipe 时才求值,所以顺序不影响生效; -> 之所以还是放在后面,是为了让"三个开关"集中在同一处,不散落在两个位置。 - -### 2.2 两个已复现的坑 - -**坑 1:切换 LTO 必须 `make clean`。** -LTO 与非 LTO 目标文件混链会让 GCC 15.2.0 在 `binds_to_current_def_p (symtab.cc:2598)` 处 ICE。 - -**坑 2:`-flto` 与 `-ffunction-sections -fdata-sections` 同时打开也会触发同一个 ICE。** -即使全部干净重编也必崩(`Utilities/Inc/DataModel.hpp:393` 的 `struct SignalData` 被指为出错点, -但那只是 COMDAT 解析的落点,不是原因)。 -**解决办法**:LTO 下 `FUNC_SECTIONS` 置空(LTO 自己就做跨单元分区与去重), -并追加 **`-flto-partition=one`**: -默认多分区 LTRANS(本项目 11 个分区)稳定复现,单分区从未复现。 -代价是 LTRANS 串行,全量构建慢约一倍。 - -> 结论:**`-flto-partition=one` 不是性能选项,是稳定性选项**,不要删。 - -### 2.3 实测体积 - -| 阶段 | 产物 | 体积 | 相对基线 | -| --- | --- | --- | --- | -| 基线 | `BaseCheck.exe` | 909,824 B | — | -| `-fno-exceptions` + `--gc-sections` + `MemProbe` | `Step1.exe` | 882,688 B | **−27,136 B** | -| 再叠加 LTO | `JustDataInspector.exe` | **788,992 B** | **−120,832 B(−13.3%)** | - -段构成(`objdump -h`,最终构建): - -| 段 | 基线 | `-fno-exc` + `gc-sections` | 最终(LTO) | -| --- | ---: | ---: | ---: | -| `.text` | 735,600 | 720,528 | **657,168** | -| `.rdata` | 49,184 | 45,072 | **35,016** | -| `.xdata` | 48,132 | 34,152 | **27,512** | -| `.pdata` | 35,424 | 34,524 | **28,524** | -| `.rsrc` | 20,656 | 20,656 | 20,656 | -| `.data` | 2,272 | 9,312 | 1,952 | -| `.bss` | 10,224 | 3,088 | 10,000 | -| `.idata` | 12,440 | 12,440 | 12,440 | -| `.tls` | 16 | 16 | 16 | -| `.reloc` | — | — | 1,700 | - -**体积溯源(超阈值时必须能回答这个问题)**: - -* `.text` 减少最多(−78 KB / −10.7%)—— 主要来自 LTO 的跨单元内联与重复代码合并。 -* `.xdata`+`.pdata`(异常展开表 + 函数表)减少 27.5 KB —— 来自 `-fno-exceptions` - 与 `--gc-sections` 丢弃未引用的函数。 -* `.rdata` 减少 14 KB —— 来自"未引用字符串/常量被 gc 掉"。 -* `.rsrc` 完全不变(图标/清单)。 - -> 预警阈值 1MB(1,048,576 B):当前 788,992 B,**余量 259,584 B(约 253 KiB)**。 - -### 2.4 `MemProbe`(预检验 + 兜底) - -| 函数 | 作用 | -| --- | --- | -| `MemAvailablePhysical()` | `GlobalMemoryStatusEx` + **200ms 缓存**(绘制路径每帧可能问多次) | -| `MemCanAllocate(bytes)` | 留出 1/8 可用内存作 headroom 后判断是否安全 | -| `MemReserveIfRoom(container, n)` | 仅供"按数据规模预留":含 `capacity()` 早退与乘法溢出保护 | -| `MemInstallTerminateHandler()` | `std::set_terminate` 装一次性模态提示后 `_Exit` | - -改动点: - -* `Utilities/Src/DataModel.cpp`:原先**唯一**的 `try/catch`(`extremes.reserve(count / 8)` - 捕 `std::bad_alloc`)→ `MemReserveIfRoom(extremes, count / 8)`。 -* `Components/PlotView/Src/PlotView.cpp`:`PlotView::AvailablePhysicalBytes()` 原有**一份完整的 - `GlobalMemoryStatusEx` + 200ms 缓存实现**(与 `MemProbe` 重复)→ 改为转发 `MemAvailablePhysical()`。 -* `Components/Application/Src/Application.cpp`:`Initialize()` 开头 `MemInstallTerminateHandler();`。 - -> 全仓已确认**无 `try/catch` 残留**(除 `MemProbe` 注释中对它的说明)。 - ---- - -## 3. L1 几何产物层 - -### 3.1 结构 - -``` -UiBox.hpp struct UiBox { rect, clip, depth, layer }; ← 纯值,无行为 - class UiBoxTable : UiTextRectSink ← 只累积几何 + 裁剪/层/遮挡 -UiDebugBounds.hpp UiDebugBoundsColor / UiDebugPaintBoundsClipped / UiDebugPaintBoxList - ↑ 唯一的消费者(调试描边),与几何解耦 -``` - -`UiComponent::BuildBoxes(UiBoxTable&)` 默认实现: - -```cpp -void UiComponent::BuildBoxes(UiBoxTable& boxes) { boxes.Add(Area(), 0); } -``` - -各组件覆盖情况: - -| 组件 | 上报内容 | -| --- | --- | -| `SignalPanel` | 表头 / 筛选框 / 列表 / 样式区 / 每个可见行 / 行内元素(复选框、色块、名称、GX 徽标、✕、折起三角、组计数)/ 滚动轨道与滑块 / 编辑框 | -| `PlotView` | 网格、GX 徽标、图例条目、**5 类浮动面板**(含逐字段)+ 面板遮挡与裁剪 | -| `Toolbar` | 区域、每个按钮、按钮内容(文本/图标) | -| `TabBar` | 区域、每个标签条、新建按钮、标签 ✕、控件树 | -| `PopupMenu` | 条目(裁到 `m_scrollEnabled ? m_scrollViewport : m_rect`)、可视区、轨道、滑块、二级子菜单 | -| `ColorPickerPopup` | 各控件 | - -### 3.2 与 `UiDebugBounds` 的边界 - -* 几何(矩形 / 层级 / 层号 / 裁剪 / 遮挡)→ `UiBox.hpp` -* 观感(配色 / 包角粗细 / 图元数量 / 抗锯齿开关)→ `UiDebugBounds.hpp` - -这样 `SignalPanel.cpp` / `PlotViewPaint.cpp` / `PopupMenu.cpp` 等**只包含 `UiBox.hpp`**, -不再把"调试描边"这个依赖拉进自己的编译单元;只有宿主 `ApplicationCommands.cpp` 同时包含两者。 - -### 3.3 零像素回归的证明 - -L1 落地前后 `JustDataInspector.exe` **字节数完全相同(788,992 B)**, -`rendercheck` 8 个场景哈希逐一相同 —— 因为这只是"把几何从调试绘制里搬出来", -没有动任何绘制顺序或图元。 - ---- - -## 4. 回归结果(最终构建上跑) - -| 探针 | 结果 | -| --- | --- | -| `Tools/uicheck` | **69 passed, 0 failed** | -| `.preflight/lbcheck` | **21 passed, 0 failed** | -| `.preflight/lbresid` | **5 passed, 0 failed** | -| `.preflight/lbzorder` | **4 passed, 0 failed** | -| `.preflight/lbcovr` | **7 passed, 0 failed** | -| `.preflight/lbpanel` | **10 passed, 0 failed** | - -`Tools/rendercheck`(逐像素哈希,必须与基线逐一相同): - -``` -A FAED59DF284153DB B E6443BFE4E3955F8 C 96A0AA8C402F1729 D E3D5DBD998E43720 -E DA0583CB13C26A6F F FFB822159F604D1D G 19F8DE8FE3A8DF18 H DBBDDE4CB2C97C92 -FUNC 7/7 PASS, exit=0 -``` - -构建:`make release` 零 error / 零 warning;`make debug` 零 warning。 - -> ⚠️ 探针 `build.ps1` 链接的是 `Build/Release/*.o`。**LTO 下这些是 LTO 字节码**, -> 因此 7 个探针脚本的链接参数里都补了 `-flto -flto-partition=one`。 -> 若把 `LTO` 关掉,这两个参数无害(可与普通目标文件混链)。 - ---- - -## 5. 本轮审计清单 - -| 项 | 处理 | -| --- | --- | -| `MemProbe.hpp` / `.cpp` 里"省 40~60KB"的估算 | 改为实测值 27,136 B(909,824 → 882,688) | -| `MemProbe.hpp` 的 `#include ` | 未使用,删除 | -| `MemProbe.cpp` 的 `#include ` | 未使用,删除 | -| `UiDebugPaintBounds(g, rect, depth)` | **零调用者**(唯一调用者是它自己),删除;只保留带 `clip` 的版本 | -| `TabBar::BuildBoxes` / `Toolbar::BuildBoxes` / `PlotView::BuildBoxes` / `SignalPanel::BuildBoxes` 的"(调试用)" | 改为"几何 box"(层级已变,它们不再关心调试) | -| `PlotView::SetForceFullRepaint` 文档 | 补写"为什么保留而不优化"(L2 缓做的理由) | -| 全仓旧名 `UiDebugBoxList` / `UiDebugBox` / `CollectDebugBoxes` | 源码零残留(仅 `Documents/agent_mem.md` 历史记录保留原样) | - ---- - -## 6. 已知问题 / 未做 - -1. **L2 未做**(见 Plan §5)。`SetForceFullRepaint` 仍在,布局边界打开时 `PlotView` 走整幅重绘。 -2. `Tools/` 与 `.preflight/` 下的探针脚本未被 git 跟踪(本地验证工具), - 本轮给它们补的 `-flto` 参数**不会进版本库**。若 LTO 长期默认开启, - 建议把探针脚本纳入版本管理,否则下次 `make clean` 后重建探针需要手工补参数。 -3. `.lst` 汇编清单在 `LTO=1` 时不再生成(`lto-wrapper` 会丢弃 `-Wa` 参数)。 - 需要看汇编时用 `make release LTO=0`。 diff --git a/Documents/UI_Refactor_Report.md b/Documents/UI_Refactor_Report.md deleted file mode 100644 index 12777d8..0000000 --- a/Documents/UI_Refactor_Report.md +++ /dev/null @@ -1,157 +0,0 @@ -# Just DataInspector UI 重构 —— 最终报告 - -> 计划见 `UI_Refactor_Plan.md`,过程与实测数据见 `UI_Refactor_Progress.md`。 -> 日期:2026-10-29 - ---- - -## 1. 一句话结论 - -在**不拆 DLL、不改优化等级、不改变任何绘制像素**的前提下, -把界面从"宿主点名各组件 + 各算一遍几何"改成"组件协议 + 几何产物层", -同时把产物从 **909,824 B 降到 788,992 B(−120,832 B,−13.3%)**, -并用 6 组、共 116 条断言 + 8 个逐像素哈希证明零回归。 - ---- - -## 2. 收益 - -### 2.1 结构 - -| 之前 | 之后 | -| --- | --- | -| 宿主 `if` 链逐个点名 4 个大组件,同一件事要写 5 遍 | 一张有序组件表,新增组件不改宿主 | -| 几何分散在 `Paint*` / `HitTest` / `CollectDebugBoxes` 三处,**三份实现** | `BuildBoxes()` 一处产出,绘制/命中/调试同源 | -| "调试"概念渗透进每个组件 | 组件只吐几何;"怎么画"收敛在 `UiDebugBounds` | -| 单个 `.cpp` 数千行 | 按职责分片(PlotView 5 片、SignalPanel 3 片…) | - -### 2.2 体积 - -| 项 | 数值 | -| --- | ---: | -| 基线 | 909,824 B | -| 最终 | **788,992 B** | -| 减少 | **120,832 B(−13.3%)** | -| 相对 1MB 预警阈值的余量 | 259,584 B(约 253 KiB) | - -构成变化的解释见 `UI_Refactor_Progress.md` §2.3(`.text` −78 KB 来自 LTO, -`.xdata`+`.pdata` −27.5 KB 来自关异常 + gc,`.rdata` −14 KB 来自 gc 掉未引用常量)。 - -### 2.3 健壮性 - -* `-fno-exceptions` 之后没有"静默消失":OOM 走 `MemInstallTerminateHandler()`, - 弹一次带建议的模态提示再干净退出。 -* 唯一一处 `try/catch` 改成**预检验**(`MemReserveIfRoom`),语义等价且不依赖异常。 -* `PlotView` 里重复实现的"可用物理内存查询"被去重到 `MemProbe`(单一实现、单一缓存)。 - ---- - -## 3. 关键决策与依据 - -### 3.1 拒绝"拆 DLL 换体积" - -用户的定位是**单文件轻量工具**。拆 DLL 只把字节从 `.exe` 挪到旁边的 `.dll`, -总交付物更大、部署更脆(版本错配、路径缺失),且 `-static` 换来的"拷一个文件就能跑" -这一核心体验会丢掉。**因此体积优化的边界是"编译期与链接期手段",不含产物拆分。** - -### 3.2 关异常 → 必须预检验,而不是"少写 try/catch" - -`-fno-exceptions` 的真实代价不是"少了个 try",而是**分配失败从"可恢复"变成"进程终止"**。 -所以必须成对交付: - -1. 所有"按数据规模预留"的路径先问内存(`MemReserveIfRoom`); -2. 遗漏路径有兜底提示(terminate handler)。 - -只做第 1 步是不够的 —— 事后新增的代码很容易忘记检查。 - -### 3.3 LTO 用 `-flto-partition=one` 换稳定性 - -`-flto` 单独用会在 COMDAT 解析处 ICE(`binds_to_current_def_p, symtab.cc:2598`), -根因是多分区 LTRANS;单分区稳定(实测 `--gc-sections` 开/关都得到 791,552 B, -说明这一档体积收益来自 LTO 本身而不是分区数)。 -代价是 LTRANS 串行 —— 这是**用构建时间换"构建能过"**,值得。 - -同时确认:`-flto` 与 `-ffunction-sections -fdata-sections` 不能同时开(同一个 ICE), -LTO 下把 `FUNC_SECTIONS` 置空。 - -### 3.4 L1 而不是"给所有元素加基类" - -`PlotView` 有 4 层自持位图缓存、`SignalPanel` 有行位图缓存,它们要求"像素由组件自己负责"。 -把几何塞进继承体系会和缓存失效体系打架(历史上"后台渲染覆盖用户平移""命中错位" -就是这类冲突)。`UiBox` 是**纯值类型**,因此可以被绘制、命中、将来的脏矩形共用, -却不需要虚函数与生命周期管理。 - -### 3.5 L2 缓做 - -见 `UI_Refactor_Plan.md` §5。一句话:**受益方只有调试开关打开时的帧率, -而正确实现要穿透 `PlotView` 全部四条快路径与两层缓存。** -在"用户反复报告过拖拽错位/贴图残留"的背景下,这笔风险不值得为一个调试特性去承担。 - ---- - -## 4. 验证方法与结果 - -### 4.1 为什么"逐像素哈希"是唯一可信的验收 - -"看起来一样"不可作为判据:这类重构最容易出的问题是**1px 偏移**与**边界条件** -(例如某个元素在某个 DPI 下多画了一条线)。`Tools/rendercheck` 对 8 个典型场景 -(空白 / 单网格 / 多网格 / 分组 / 面板展开 / 菜单 / 拾色器 / 样式区)渲染到内存位图后 -取 FNV 哈希,**与基线逐一比对**,因此任何像素级差异都会暴露。 - -结果:8 个哈希与改造前**逐一相同**,且 `JustDataInspector.exe` 在 L1 前后**字节数完全相同**。 - -### 4.2 断言矩阵 - -| 探针 | 断言 | 覆盖 | -| --- | ---: | --- | -| `uicheck` | 69 | 协议行为、命中、几何同源、浮层表面 | -| `lbcheck` | 21 | 边界收集 + **关闭后逐像素还原** | -| `lbresid` | 5 | 同一状态连续两帧逐像素相同(残留判据) | -| `lbzorder` | 4 | 浮层压住本体 | -| `lbcovr` | 7 | 覆盖完整性(时间轴/滚动条/胶囊/文本框/缓存行) | -| `lbpanel` | 10 | 面板遮挡、面板内容裁剪、滚动不闪烁、菜单条目裁剪 | -| 合计 | **116** | — | - -### 4.3 方法论教训(已写入 `agent_mem.md`) - -* **体积结论必须区分稳态与瞬时**;改编译开关后**必须 `make clean`**(混链会 ICE)。 -* **判断"残留"不能用"拖拽帧 vs 松开后一帧"**(预热进度不同会产生几十万像素合法差异), - 正确判据是**同一状态连续两帧逐像素相同**。 -* **`--gc-sections` 已经承担了内联去重**(`UiPointInRect` 等自有符号重复组数为 0), - 不必为了体积手工把 inline 实现搬进 `.cpp`。 -* **探针判"盒子越界"必须按绘制层过滤**,否则会把更上层浮层的合法越界算成错误。 - ---- - -## 5. 风险与缓解 - -| 风险 | 缓解 | -| --- | --- | -| LTO 构建偶发 ICE(编译器缺陷,非代码缺陷) | `-flto-partition=one`;`LTO=0` 可一键回退(体积回到 882,688 B,仍远低于阈值) | -| `-fno-exceptions` 下遗漏一处分配检查 | terminate handler 兜底 + 全仓已无 `try/catch` + `MemReserveIfRoom` 是唯一预留入口 | -| LTO 构建时间翻倍 | 日常开发可用 `make release LTO=0`;提交前用 `LTO=1` 校验体积 | -| 探针脚本未纳入版本库,重建时缺 `-flto` | 已写入 Progress §6 待办;脚本本身只是本地验证工具 | -| L2 未做,布局边界打开时帧率下降 | 该开关是调试特性,默认关闭;文档已记录取舍 | - ---- - -## 6. 遗留 - -1. **L2 `UiDamage` + S4 叠加层**:设计已定型(`kMaxRects=12`、超限退化为包围盒、 - 整数像素外扩 1px、只增不减;`PlotView::Paint(g, UiDamage&)` 契约;6 条路径各自 `damage.Add`), - 但未实施。`UiBoxTable` 的几何可以直接喂给它,不需要重新收集。 -2. **探针脚本入版本库**(`Tools/*/build.ps1`、`.preflight/*/build.ps1`)。 -3. **`.lst` 汇编清单在 LTO 下不可用**:需要时用 `make release LTO=0`,这一点已在 Makefile 注释中写明。 - ---- - -## 7. 交付确认 - -```powershell -git status --short # 干净 -make clean; make release TARGET_FILE_NAME=JustDataInspector -j16 -# → 零 error / 零 warning,788,992 B -``` - -唯一产物:`Build/Release/JustDataInspector.exe`(含图标、清单,`-static` 链 CRT)。 -**无任何新增 DLL 依赖。**