数据层大重构:文件→分组 / 时间轴一等公民 / 加载器抽象 + CSV 多时间轴 + MF4 支持
一、数据模型解耦(详见 Documents/refactor_report_2026-10-15.md 与 agent_mem §9.27)
1. DataFile → SignalGroup:删除 filePath / fileName / time / timeColumnName / timeMonotonic /
minTime / maxTime —— 「分组」是应用内的逻辑容器,加载完成后与磁盘文件再无关系。
2. 新增 TimeAxis(name / time / minTime / maxTime / monotonic / finiteCount),一个分组可含 1..N 条;
SignalData 新增 axisIndex 指向所属时间轴,采样值与时间的对应关系自包含。
3. SignalData 删除冗余的 rawHeader(name 的副本)与 unit(由列名猜出、全仓无人使用);
名字只保留 name(源名称)+ displayName(用户重命名),Title() 统一展示。
4. SignalRef::fileIndex → groupIndex;DataSet:AddGroup/RemoveGroup/GroupAt/GroupCount/Groups()/
SetGroupDisplayName/RemapSelectionAfterGroupRemoval + 新增 Axis(ref);
TabConfig:SetGroupVisible/GroupHasVisible/GroupAllVisible/RemapAfterGroupRemoval +
RowKind::GROUP / CMD_GROUP_*。
5. 采样与统计 API 首参由 const DataFile& 改为 const TimeAxis&:
SampleSignalAt / SampleDisplayedSignalAt / FindNearestSample(Utilities)与
SampleSpacingPixels / VisibleIndexRange / EdgeIndexRange / MeanFilterValueAt / WeakPeakValueAt /
EdgeCountsFor / EdgeCenterCountsFor / EdgeScanFor(PlotView)。
6. 视图层:ResolvedSignal = { data, style, group, axis, visible };PolylineCacheEntry::file→group;
EdgeScanState::file→axis(统计身份 = 时间轴 + 信号);
ComputeVisibleTimeBounds 只统计可见信号真正用到的时间轴。
二、加载层抽象(格式与模型彻底解耦)
- 新增 Utilities/Inc/DataTable.hpp:DataTable(通用列优先表格)+ LoadProgress/Info +
TableBlock(一个时间轴块)+ LoadedSource(源文件全部块)。
- 新增 Utilities/Inc|Src/DataLoader.*:唯一入口 LoadSignalSource 按扩展名分发 +
IsSupportedSignalFile + SignalFileDialogFilter。
- 新增 Utilities/Inc|Src/CsvLoader.*:CSV 解析 + 多时间轴切分 + 各轴按自己的时间列裁剪。
- 新增 Utilities/Inc|Src/Mf4Loader.*:ASAM MDF v4 解析。
- 删除 Utilities/Inc/CsvTable.hpp 与 Utilities/Src/CsvTable.cpp。
- 唯一装配点 BuildSignalGroup(LoadedSource&&, paletteBase, group, error, progress):
时间轴去重合并 + 信号命名 + 调色板取色 + 统计与粗粒度包络;新增格式只需写一个加载器。
三、CSV 多时间轴(修正用户报告的缺陷)
- 只有名为 t/time/timestamp/simulation time/时间/时间轴 的列是时间轴(判据收紧,
不再用「包含 time」以免把 timeout 误判成时间轴)。
- 每个时间列开启一个新块,其后直到下一个时间列之前的列都属于该块;各块按自己的时间列裁剪
⇒ 支持各轴长度不同(Multi_Timeline.CSV 实测切出 3 条轴,长度 7/4/7);
**不再把其它 time 列当成信号显示**。
- 完全没有时间列时保留旧行为(第一列当时间轴,单轴)。
四、MF4 支持(新功能)
- 加载后新建一个分组、组名 = 文件名,文件内全部信号归入该分组。
- 每个通道组(CG)是一条时间轴,内容相同的时间轴自动合并(实测 27 个 CG → 3 条轴 / 124 条信号)。
- 实现 MDF v4 块解析(HD/DG/CG/CN/CC/DT)、1–64 位整数(任意位偏移、含符号扩展)与
32/64 位 IEEE 浮点解码、无效位(inval bit)处理、线性换算 phys = cc_val[0] + cc_val[1]*raw。
- TX 文本块同时兼容规范写法(U32 长度 + UTF-16/UTF-8)与 CANape 的「无长度前缀 NUL 结尾裸文本」。
- 未实现(跳过该组):压缩块 ##DZ/##HL/##SD、##DL 数据列表、字符串/MIME 通道。
五、顺带修复
- Application::Initialize:窗口类已注册(ERROR_CLASS_ALREADY_EXISTS)时复用而不是弹模态错误框
(旧实现让同一进程第二次构造 Application 永久阻塞);Shutdown 注销窗口类并销毁窗口。
- UI 文案统一到「分组」:选中该分组全部信号 / 取消该分组全部信号 / 关闭该分组(从工作区移除)/
请关闭部分分组或信号后重试。/ 尚未加载数据,请打开数据文件 / 打开数据文件以查看波形 /
(可同时加载多个文件,各自成为一个分组);对话框过滤器增加 *.mf4/*.mdf。
六、验证(全部离屏:BELOW_NORMAL 优先级 + SW_HIDE 窗口 + 只注入合成消息)
- 新增 .preflight/perf/datacheck_main.cpp:69/69 通过(单轴 CSV 回归、多时间轴 CSV、MF4 组名/信号数/
时间轴/主时间轴 ns→s/setSpeed 首样本与原始字节人工核对、DataSet 不变量、派生信号沿用源轴、
MF4 集成 124 信号并一格 60~68ms/帧)。
- 新增 .preflight/perf/edgecheck_main.cpp(ASCII-only,替代被编码破坏的 panelcheck):46/46 通过
(「调整」符号规则走真实菜单路径、边沿统计按帧推进/不饿死/逐位一致/无鼠标输入也完成、
面板两列等宽与信号名限宽、弱峰复位控件)。
- 既有 .preflight/perf/verify_main.cpp(已适配新 API):47/47,另有 2 条本机未同步的旧规则断言
(derived offsetTime kept / mouse in lane1 lower half),与本轮无关。
- make clean && make all 零警告。
This commit is contained in:
1 parent
fba40ee1a2
commit
6eedbe3c5d
18 files changed
+2545
-817
No files matched your search
@@ -1357,6 +1357,102 @@ GDI+ 抗锯齿逐段扫描这些**近竖直、满幅**的边,代价爆炸(
|
||||
|
||||
---
|
||||
|
||||
### 9.27 数据层大重构:文件→分组 / 时间轴一等公民 / 加载器抽象 + CSV 多时间轴 + MF4 支持(本轮)
|
||||
|
||||
> 完整审计与改动清单见 `Documents/refactor_report_2026-10-15.md`。本轮**推翻并替换**了 §12 里
|
||||
> 「DataFile = 一个文件及其自有时间轴」的旧模型;所有界面行为与显示保持与旧版一致。
|
||||
|
||||
#### 一、模型:`DataFile` → `SignalGroup`,时间轴独立成 `TimeAxis`
|
||||
|
||||
* **新增 `TimeAxis`**:`{ name, time, minTime, maxTime, monotonic, finiteCount }`。
|
||||
* **`SignalData`**:删除 `rawHeader`(`name` 的副本)与 `unit`(由列名猜出、全仓无人使用);
|
||||
新增 **`axisIndex`** 指向所属时间轴 ⇒ 采样值与时间的对应关系**自包含**,不再需要「文件」。
|
||||
名字只剩两个且职责明确:`name`(源名称,永不改写)/ `displayName`(用户重命名)。
|
||||
* **`SignalGroup`(原 `DataFile`)**:`{ name(默认=源文件名), displayName, axes[], signals[] }`;
|
||||
**删除** `filePath` / `fileName` / `time` / `timeColumnName` / `timeMonotonic` / `minTime` / `maxTime`。
|
||||
新增 `AxisOf(signal)` / `AxisAt(i)` / `TimeRange(low, high)`(多轴并集)。
|
||||
* **`SignalRef::fileIndex` → `groupIndex`**;`DataSet`:`AddGroup` / `RemoveGroup` / `GroupAt` / `GroupCount` /
|
||||
`Groups()` / `SetGroupDisplayName` / `RemapSelectionAfterGroupRemoval` + 新增 **`Axis(ref)`**。
|
||||
* `TabConfig`:`SetGroupVisible` / `GroupHasVisible` / `GroupAllVisible` / `RemapAfterGroupRemoval`;
|
||||
`RowKind::GROUP`、`CMD_GROUP_*`。
|
||||
* **采样/统计 API 首参由 `const DataFile&` 改为 `const TimeAxis&`**:
|
||||
`SampleSignalAt` / `SampleDisplayedSignalAt` / `FindNearestSample`(Utilities)与
|
||||
`SampleSpacingPixels` / `VisibleIndexRange` / `EdgeIndexRange` / `MeanFilterValueAt` / `WeakPeakValueAt` /
|
||||
`EdgeCountsFor` / `EdgeCenterCountsFor` / `EdgeScanFor`(PlotView)。
|
||||
* 视图层:`ResolvedSignal` 改为 `{ data, style, group, axis, visible }`;
|
||||
`PolylineCacheEntry::file → group`;`EdgeScanState::file → axis`(统计身份 = 时间轴 + 信号)。
|
||||
* `TabConfig::ComputeVisibleTimeBounds` 只统计**可见信号真正用到的时间轴**。
|
||||
|
||||
#### 二、加载层:格式与模型彻底解耦(新文件)
|
||||
|
||||
| 文件 | 职责 |
|
||||
| --- | --- |
|
||||
| `Utilities/Inc/DataTable.hpp` | `DataTable`(通用列优先表格)、`LoadProgressInfo`/`LoadProgress`、`TableBlock`(一个时间轴块)、`LoadedSource`(源文件全部块) |
|
||||
| `Utilities/Inc/DataLoader.hpp` + `.cpp` | **唯一入口** `LoadSignalSource(path,…)`:按扩展名分发(`.csv/.txt` → CSV;`.mf4/.mdf` → MDF4;其它按文本尝试)+ `IsSupportedSignalFile` + `SignalFileDialogFilter` |
|
||||
| `Utilities/Inc/CsvLoader.hpp` + `.cpp` | CSV 解析 + 多时间轴切分 + 各轴按自己的时间列裁剪 |
|
||||
| `Utilities/Inc/Mf4Loader.hpp` + `.cpp` | ASAM MDF v4 解析 |
|
||||
| 删除 | `Utilities/Inc/CsvTable.hpp`、`Utilities/Src/CsvTable.cpp` |
|
||||
|
||||
* **唯一装配点** `BuildSignalGroup(LoadedSource&&, paletteBase, group, error, progress)`:与格式无关;
|
||||
负责**时间轴去重合并**(内容逐位相同只保留一条)、信号命名、调色板取色、统计与粗粒度包络。
|
||||
⚠️ **新增文件格式时只写一个加载器**,数据模型与全部界面代码**不用改**。
|
||||
|
||||
#### 三、CSV 多时间轴(修正用户报告的缺陷)
|
||||
|
||||
* 规则:**只有名为 `time` 的列是时间轴**;每个时间列开启新块,其后直到下一个时间列之前的列都属于该块。
|
||||
* 各块按**自己的时间列**裁剪 ⇒ 支持各轴长度不同(实测 `Multi_Timeline.CSV` → 3 条轴,长度 **7/4/7**);
|
||||
**不再把其它 `time` 列当作信号显示**。
|
||||
* 完全没有 `time` 列时保留旧行为(第一列当时间轴,单轴)。
|
||||
* `LooksLikeTimeHeader` 判据收紧为 `t`/`time`/`timestamp`/`simulation time`/`时间`/`时间轴`
|
||||
(旧判据「包含 time」会把 `timeout` 误判成时间轴并切出空块)。
|
||||
|
||||
#### 四、MF4 支持(实测格式要点,务必保留)
|
||||
|
||||
* 加载后**新建一个分组、组名 = 文件名**,文件内全部信号归入该分组(用户要求)。
|
||||
* **每个通道组(CG)是一条时间轴**;内容相同的时间轴自动合并(实测 27 个 CG → **3 条轴**、**124 条信号**)。
|
||||
因此 MF4 分组天然是多时间轴的,`SignalData::axisIndex` 让每条信号各归其轴。
|
||||
* 块布局(MDF 4.10 / CANape 16 实测):
|
||||
* 块头 24 字节 = `id[4] + reserved[4] + block_len(U64) + link_count(U64)`,随后是 `link_count` 个
|
||||
**8 字节绝对文件偏移**(0 = 空),数据段紧随其后。DT 可以 0 链接。
|
||||
* `HD[0] = 第一个 DG`;`DG[1] = 第一个 CG`、`DG[2] = 数据块 DT`;
|
||||
`CG[1] = 第一个 CN`、`CG[2] = tx_name`、数据段 `+8 = cycle_count`;
|
||||
**记录长度 = DT 数据段字节数 ÷ cycle_count**(实测 114/110)。
|
||||
* `CN`:`[2] = 名称 TX`、`[4] = 换算 CC`;数据段 `+0 type`、`+1 sync_type`(**1 = 时间主通道**)、
|
||||
`+2 data_type`、`+3 bit_offset`、`+4 byte_offset(U32,**字节**)`、`+8 bit_count`、`+12 flags`
|
||||
(bit0 整列无效 / bit1 有无效位)、`+16 invalid_bit_pos`。
|
||||
**绝对位偏移 = byte_offset × 8 + bit_offset**。
|
||||
* `data_type`:0/1 无符号(LE/BE)、2/3 有符号、4/5 IEEE 浮点、≥6 字符串/字节数组(跳过)。
|
||||
* `CC`:`+0 type`、`+6 val_count`、`+8 phy_range_min`、`+16 phy_range_max`、`+24 cc_val[]`。
|
||||
**换算公式(实测确认)= `phys = cc_val[0] + cc_val[1] × raw`**(本文件时间主通道是
|
||||
「double 纳秒 + 线性 1e-9」⇒ 解出 `[0.024320125, 42.3918] s`,与 CC 的物理范围逐位吻合,可自检)。
|
||||
* **TX 文本块**:规范是「U32 长度 + UTF-16」(4.1 起也可 UTF-8 字节数),
|
||||
但 **CANape 写的是无长度前缀的 NUL 结尾裸文本** ⇒ 必须先按规范、失败再按裸文本回退,
|
||||
否则所有通道名都会变成空(本轮踩过)。
|
||||
* 未实现(遇到则跳过该组):压缩块 `##DZ`/`##HL`/`##SD`、`##DL` 数据列表、字符串/MIME 通道。
|
||||
|
||||
#### 五、顺带修复的健壮性缺陷
|
||||
|
||||
* `Application::Initialize`:`RegisterClassExW` 返回 `ERROR_CLASS_ALREADY_EXISTS` 时**复用**已注册的类
|
||||
(旧实现当作致命错误弹**模态**框 ⇒ 同进程第二次构造 `Application` 会**永久阻塞**);
|
||||
`Shutdown` 现在注销窗口类并销毁窗口。
|
||||
* UI 文案统一到「分组」:`选中该分组全部信号` / `取消该分组全部信号` / `关闭该分组(从工作区移除)` /
|
||||
`请关闭部分分组或信号后重试。` / `尚未加载数据,请打开数据文件` / `打开数据文件以查看波形` /
|
||||
`(可同时加载多个文件,各自成为一个分组)`;对话框过滤器含 `*.mf4;*.mdf`。
|
||||
|
||||
#### 六、验证(全部离屏,`BELOW_NORMAL` + `SW_HIDE`,不注入真实鼠标键盘)
|
||||
|
||||
* 新增 `.preflight/perf/datacheck_main.cpp`:**69/69 通过** —— 单轴 CSV 回归、多时间轴 CSV(3 轴 / 7-4-7 /
|
||||
不再出现 `time` 信号)、MF4(组名 / 124 信号 / 3 轴 / ns→s / `setSpeed` 首样本与原始字节人工核对)、
|
||||
`DataSet` 不变量、派生信号沿用源轴、**MF4 集成**(124 信号并一格渲染 67.5ms/帧)。
|
||||
* 新增 `.preflight/perf/edgecheck_main.cpp`(ASCII-only,替代上轮被编码破坏的 `panelcheck`):
|
||||
**46/46 通过** —— 「调整」符号规则(真实菜单路径)、边沿统计按帧推进 / 不饿死 / 逐位一致 / 无鼠标输入也完成、
|
||||
面板两列等宽与名称限宽、弱峰复位控件。
|
||||
* 既有 `.preflight/perf/verify_main.cpp`(已适配新 API):**47/47**,另有 2 条**本机未同步的旧规则断言**
|
||||
(`derived offsetTime kept`、`mouse in lane1 lower half`)——属本机 `.preflight` 版本滞后,与本轮无关。
|
||||
* `make release` 零警告。
|
||||
|
||||
---
|
||||
|
||||
## 10. 变更日志(本文件维护记录)
|
||||
|
||||
| 日期 | 设备/模型 | 摘要 |
|
||||
@@ -1396,6 +1492,7 @@ GDI+ 抗锯齿逐段扫描这些**近竖直、满幅**的边,代价爆炸(
|
||||
| 2026-10-14 | DeepSeek-V4.1-Flash(**起源设备**) | **新标签页分组默认折起 + GX 徽标悬停不改光标 + 标题栏 RAM 改用「私有工作集」+ 边沿计数改为「逐块累积 + 每 tick 预算推进」(详见 §9.22)**。① `NewTab(bool collapseGroups)`(首个标签页 false、用户新建 true)+ `TabConfig::SetGroupsCollapsedByDefault/SetAllCollapsed`;`SyncToDataSet` 用该默认值初始化**新出现的**分组。② `QueryCursor` 的 GX 徽标分支由 `IDC_HAND` 改为 `nullptr`(悬停不改形状,仅拖拽时 `IDC_SIZENS`)。③ **RAM 口径修正**:原取 `WorkingSetSize`(总工作集,含映射 DLL/共享页与已释放未回收的驻留页)→ 改为 `PROCESS_MEMORY_COUNTERS_EX2::PrivateWorkingSetSize`(与任务管理器「内存」列一致;失败退回 `PrivateUsage` → `WorkingSetSize`)。实测加载后 117.55/109.96 MiB、关闭分组后 15.44/7.62 MiB —— **无泄漏,内存完整归还**。④ **边沿计数性能**:改前 25.8M 采样 ≈**115ms/条/帧**(5 条同网格 = 575ms/帧,用户报告"开启即卡顿、之后拖不动");改为按 `kEdgeBlockSamples=8192` 分块累积、每 tick 预算 `kEdgeScanPairsPerTick=1.2M`、区间用块和相加(区间不进键)、拖动横向光标期间不推进并显示上一个完整值 + `complete=false`(`EdgeCountsText` 显示"统计中…/…");实测首帧 1.7ms、单次最坏 1.9ms、拖动中 10 次查询 0.11ms、最终结果与参考实现逐位一致。⑤ **内存排查报告**(用户要求,第 4 项只排查未改动):弱峰调整工作缓冲实测 **≈34 B/采样**(极值表 `PeakExtremum` 24 B/极值为主导),**4 条工作线程并发 +412 MiB / 3M 采样** ⇒ 25.8M 采样峰值 ≈**3.5GB**(+数据 1.03GB +曲线缓存 ≤768MB ⇒ 4~5GB,与用户 4.4GB 观测吻合);**全仓无 `try`/`catch`** ⇒ 低内存时 `bad_alloc` 逸出线程入口 ⇒ `std::terminate()` 崩溃(无优雅降级机制)。验证:离屏 `uitest` **738/738 通过**(新增 `[AR]` 边沿计数预算/精确性/拖动期 O(1)、`[AS]` 标题栏 RAM 口径),`make release` 零警告。 |
|
||||
| 2026-10-14 | DeepSeek-V4.1-Flash(**起源设备**) | **边沿计数改用「绘制值」+ 派生信号烘入时间偏移 + 图例托底统一为 GX 徽标款 + 调整二级面板向上展开(详见 §9.21)**。① **边沿计数恒为 0**:`EdgeCountsFor`/`EdgeCenterCountsFor` 只比原始采样值,而阈值是屏幕上的值 → 「系数 50」的信号统计恒为 0;修法:判定值改为 `ApplyDrawTransform(style, values[SourceIndexFor(...)])`(中心轴两侧同套变换;水平镜像时曲线窗口也要先映射下标);缓存新增 **`styleKey`**(否则改完「调整」仍命中旧计数)。② **区间边界点被误计**:`EdgeIndexRange` 左端“多取一个采样点”的外扩**已删除**,收紧为“**两个端点都在区间内**才计入”(光标恰落在越过阈值的点 B 上时 B 不再被算作上升沿)。③ **`offsetTime` 也烘进派生采样值**(**推翻 §9.19 的“唯一例外”**):按 `time[i]-offsetTime` 取最近采样点重采样(单调时间轴用只前进的游标,O(n);越界置 NaN),新信号样式**全默认**;非单调时间轴退回到把 `offsetTime` 带在样式上。④ **图例托底 = GX 徽标同款**(同高 / 圆角 = 高度一半 / 同配色 / 水平内边距 = 胶囊半径),文本改用新增字体 **`uiTiny`(10 逻辑 px)**;`kLegendPlateRadius` 已删。⑤ **`PopupMenu::Open(..., expandUp=true)`**(锤点为**左下角**、向上展开,`m_buildExpandUp` 供 `RebuildLayout` 复用)+ 「系数/偏移」「镜像」面板锤在按钮左上角再上移 `kAdjustMenuGap`。验证:`.preflight/uitest` 新增 `[AR]`(五组断言全部与**独立参考实现**逐位比对,含 A/B:“按原始采样值算会得到 7/6 个边沿”);**并同步修正 harness 里两处旧规则断言**(`[AP]` 派生信号继承 → 默认样式 + 烘值;`[AN]` 图例带无托底 → 装饰矩形逐块且暴露列可见底层内容);合计 **722/722 通过**,`make release` 零警告。 |
|
||||
| 2026-10-15 | DeepSeek-V4.1-Flash(**另一台设备**) | **「调整」数值符号规则 + 边沿统计按帧推进(含首帧公平分摊)+ 浮动面板排版与弱峰复位控件(详见 §9.26)**。① **符号**:Y 轴偏移 0 不再显示 `+0.00`;系数非 0 正值带 `+`(编辑态初始文本仍无符号);`Δt:` 后按正负带符号(`Δt:+5s 60ms` / `Δt:-5s 60ms` / `Δt:0s`)。② **边沿统计卡住**根因:预算按 `GetTickCount64()` 分桶(系统粒度 15.6ms)而 `PostMessage` 自持重绘把帧率拉高 ⇒ 同一 tick 内多帧完全不推进 → 改为 **`Paint` 开头按帧重置**;并给**每个状态独立每帧配额**(`share = max(6, m_edgeScans.size())`,避免同帧新建状态被第一个独占整额)→ 实测首帧 5 个状态全部推进、**9 帧内全部扫完且不注入任何鼠标消息**、与参考实现逐位一致。③ **面板排版**:信号名宽度上限 `kEdgePanelNameMaxWidth=150`(300 字符名字只多 44px,旧实现多 5100px);统计信息**两列等宽**(`2×max(列宽)+间距`)⇒ 多信号“上升沿/下降沿”列起点固定、垂直对齐;未完成时**两个数字后都带 `…`**(旧实现只跟在“下降沿”后)。④ **弱峰调整**:每行滑块右侧新增**复位控件**(复用还原图标,悬停高亮 + `IDC_HAND` + 提示;点击恢复幅度 0 / 迭代 1 且上限回 10);上限输入框提示改为“输入上限(当前N)”;滑块标签去掉多余的 `%` / `N`。验证:新增离屏探针 `panelcheck` **46/46 通过**(全部走真实菜单/点击路径),既有 `verify` 47/47(另 2 条为本机未同步的旧规则断言),`make release` 零警告。 |
|
||||
| 2026-10-15 | DeepSeek-V4.1-Flash(**另一台设备**) | **数据层大重构(文件→分组 / 时间轴一等公民 / 加载器抽象)+ CSV 多时间轴 + MF4 支持(详见 §9.27 与 `Documents/refactor_report_2026-10-15.md`)**。① **解耦**:`DataFile`→**`SignalGroup`**(删除 `filePath`/`fileName`/单一 `time`/`timeColumnName`/`timeMonotonic`/`minTime`/`maxTime`),新增 **`TimeAxis`**(1..N 条/组)与 `SignalData::axisIndex`,`SignalData` 删除冗余的 `rawHeader` 与**全仓无人使用的 `unit`**;`SignalRef::fileIndex`→`groupIndex`;`DataSet`/`TabConfig` 全套改名并新增 `Axis(ref)`;采样/统计 API(`SampleSignalAt`/`FindNearestSample`/`VisibleIndexRange`/`EdgeIndexRange`/`EdgeCountsFor`/`EdgeCenterCountsFor`/`EdgeScanFor`/`MeanFilterValueAt`/`WeakPeakValueAt`/`SampleSpacingPixels`)**首参由 `const DataFile&` 改为 `const TimeAxis&`**;`ResolvedSignal` 携带 `{group, axis}`。② **加载器抽象**:新增 `DataTable.hpp`(`DataTable`/`LoadProgress`/`TableBlock`/`LoadedSource`)、`DataLoader.*`(唯一入口 `LoadSignalSource` 按扩展名分发)、`CsvLoader.*`、`Mf4Loader.*`,删除 `CsvTable.*`;唯一装配点 `BuildSignalGroup` 与格式无关(新增格式只写加载器)。③ **CSV 多时间轴**:每个 `time` 列开启新块、各块按自己的时间列裁剪 ⇒ 支持各轴不等长(实测 3 条轴 7/4/7),**不再把其它 `time` 列当成信号**;时间轴判据收紧。④ **MF4 支持**:加载后新建分组、组名=文件名,全文件信号归入该组;每个通道组(CG)一条时间轴并自动去重(实测 27 CG → 3 轴 / 124 信号);实现 MDF v4 块解析(DG/CG/CN/CC/DT)、位级整数与 32/64 位浮点解码、线性换算 `phys = cc_val[0] + cc_val[1]*raw`、无效位处理;**`##TX` 兼容规范的「长度+UTF-16」与 CANape 的「裸 NUL 结尾文本」**(否则通道名全空)。⑤ 顺带修复:`Application::Initialize` 在窗口类已注册时不再弹模态框(旧实现导致同进程二次构造永久阻塞),`Shutdown` 注销窗口类;UI 文案统一到「分组」,对话框支持 `*.mf4/*.mdf`。验证:新增 `datacheck` **69/69**、`edgecheck` **46/46**(替代被编码破坏的 `panelcheck`)、既有 `verify` **47/47**(另 2 条本机旧规则断言)、`make release` 零警告。 |
|
||||
|
||||
---
|
||||
|
||||
@@ -1561,6 +1658,28 @@ GDI+ 抗锯齿逐段扫描这些**近竖直、满幅**的边,代价爆炸(
|
||||
`rowIterResets` **必须进 `PanelSlotKey`**(否则悬停高亮不会重绘);两行按钮共用同一 X。
|
||||
⚠️ 滑块标签不得带多余的 `%` / `N`(但 MEAN / CENTER 参数输入框的 `迭代次数 N` 保持不变);
|
||||
迭代次数上限输入框的空内容提示为 `输入上限(当前N)`。
|
||||
49. ⚠️ **数据模型里禁止出现「文件」概念**(§9.27 重构后的硬约束):
|
||||
分组 `SignalGroup` **不保存路径/文件名**(用户明确要求:加载后与源文件再无关系);
|
||||
时间轴是独立的一等公民 `TimeAxis`,信号通过 `SignalData::axisIndex` 指向它;
|
||||
引用用 `SignalRef::groupIndex`(不是 `fileIndex`)。
|
||||
✓ 需要「时间 + 采样值」的函数一律只接 `const TimeAxis&` + `const SignalData&`,
|
||||
**不要**再接 `const DataFile&`/`const SignalGroup&`(那是旧的过度耦合,已全部改掉)。
|
||||
50. ⚠️ **新增文件格式只需加一个加载器**:实现 `Load*Source(path, LoadedSource&, error, progress)`,
|
||||
在 `DataLoader.cpp` 按扩展名登记即可;数据模型(`BuildSignalGroup`)与全部界面代码**不得**因新格式而改动。
|
||||
`LoadedSource` = 若干「时间轴块」(`TableBlock` = 一个时间列 + 同轴数据列),
|
||||
时间轴去重、命名、取色、统计都在 `BuildSignalGroup` 里完成。
|
||||
51. ⚠️ **CSV 多时间轴语义**:只有列名为 `t`/`time`/`timestamp`/`simulation time`/`时间`/`时间轴` 的列是时间轴
|
||||
(判据**不得**放宽为「包含 time」,否则 `timeout` 之类会被误判并切出空块);每个时间列开启新块,
|
||||
各块按**自己的时间列**裁剪 ⇒ 允许各轴长度不同。没有任何时间列时才回退「第一列是时间轴」。
|
||||
52. ⚠️ **MF4(MDF v4)解析要点**:块头 24 字节 + N 个 8 字节文件偏移链接;
|
||||
`DG[1] = CG`、`DG[2] = DT`、`CG[1] = CN`、`CG[2] = tx_name`、`CG data+8 = cycle_count`、
|
||||
**记录长度 = DT 载荷 ÷ cycle_count**;`CN[2] = 名称`、`CN[4] = CC`,数据段
|
||||
`+1 sync_type`(1 = 时间主通道)、`+2 data_type`、`+3 bit_offset`、`+4 byte_offset(字节)`、
|
||||
`+8 bit_count`、`+12 flags`、`+16 invalid_bit_pos`;**绝对位偏移 = byte_offset*8 + bit_offset**;
|
||||
`CC` 的 `+24 cc_val[]`,**换算 = `cc_val[0] + cc_val[1]*raw`**(用 CC 的 phy_range 自检)。
|
||||
⚠️ **`##TX` 文本块必须兼容两种写法**:规范的「U32 长度 + UTF-16/UTF-8」**与** CANape 的
|
||||
「无长度前缀的 NUL 结尾裸文本」;只按规范解析会得到空通道名。
|
||||
压缩块(`##DZ`/`##HL`/`##SD`)、`##DL` 数据列表、字符串/MIME 通道**未实现**(跳过该组)。
|
||||
|
||||
### 11.3 离屏验证方法学(本机偏好,强烈建议沿用)
|
||||
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
# 数据层审计与重构报告(2026-10-15)
|
||||
|
||||
> 本轮任务:① 审计代码库的命名/参数/注释/数据结构;② 在审计基础上做一次解耦重构(大刀阔斧,
|
||||
> 但**保持全部交互与显示不变**);③ 完善 CSV(多时间轴 / 各轴不等长)并新增 MF4 支持。
|
||||
>
|
||||
> 结论:**数据层与加载层被重写为「分组 / 时间轴 / 信号」三段式结构**,文件格式与数据模型彻底解耦;
|
||||
> CSV 多时间轴与 MF4 均已支持;三套离屏测试(69 + 46 + 47 条断言)通过;`make release` 零警告。
|
||||
|
||||
---
|
||||
|
||||
## 一、审计发现的问题(按严重度)
|
||||
|
||||
### 1.1 「文件」概念渗进了数据模型的核心(最严重)
|
||||
|
||||
| 位置 | 问题 |
|
||||
| --- | --- |
|
||||
| `DataFile`(结构体名) | 名字把「一次加载产生的信号集合」和「磁盘上的文件」绑死;实际应用语义是**信号分组**。 |
|
||||
| `DataFile::filePath` / `fileName` | 加载完成后应用与文件再无关系(文件可移动/删除/改名),这两个字段在运行期**没有任何用途**(`filePath` 只在加载消息里出现一次)。 |
|
||||
| `DataFile::time` / `timeColumnName` / `timeMonotonic` / `minTime` / `maxTime` | 把「时间轴」焊死在分组上 ⇒ **一个分组只能有一条时间轴**,与 CSV 多 `time` 列、MF4 多通道组的现实不符。 |
|
||||
| `SignalRef::fileIndex` | 引用语义被写成「文件下标」,全仓 223 处;后来者会以为信号与文件有绑定关系。 |
|
||||
| `EdgeCountsFor(const DataFile& file, const SignalData& signal, …)` | **典型文不对题**:函数只需要「时间 + 采样值」,却要求调用方提供整个「文件」;这也是用户指出的重点。 |
|
||||
| `SampleSignalAt(const DataFile& file, …)`、`FindNearestSample(const DataFile& file, …)`、`VisibleIndexRange(const DataFile& file, …)`、`EdgeIndexRange(const DataFile& file, …)`、`MeanFilterValueAt`、`WeakPeakValueAt` | 同上一类:参数语义过宽,时间轴与信号被迫通过「文件」间接耦合。 |
|
||||
| `ResolvedSignal::file` | 视图层的解析结果也携带「文件」指针,导致采样/命中/包络/边沿统计处处 `resolved.file->time`。 |
|
||||
|
||||
### 1.2 `SignalData` 的冗余字段(用户指出的重点)
|
||||
|
||||
| 字段 | 问题 | 结论 |
|
||||
| --- | --- | --- |
|
||||
| `name` | 源名称(列名/通道名)。 | **保留**,语义明确:加载后原样不变。 |
|
||||
| `displayName` | 用户重命名。 | **保留**(`Title()` 优先返回它)。 |
|
||||
| `rawHeader` | `name` 的副本(构建时 `rawHeader = headers[i]; name = Trim(rawHeader)`),派生信号里又 `rawHeader = derived.name`。 | **删除**(纯冗余,两个字段表达同一件事)。 |
|
||||
| `unit` | 由列名括号里猜出来的单位,全仓只有 2 处写、0 处读(从未显示、从未参与计算)。 | **删除**(属于「未实现的元数据」残留;用户明确说单位元数据暂不支持)。 |
|
||||
| 时间信息 | 缺失 ⇒ 只能靠「文件」找时间轴。 | **新增 `axisIndex`**:信号指向所属时间轴,采样值与时间的对应关系自包含。 |
|
||||
|
||||
### 1.3 加载层与格式耦合
|
||||
|
||||
| 位置 | 问题 |
|
||||
| --- | --- |
|
||||
| `CsvTable`(结构体) | 「从文件读出来的原始表格」这个名字把通用中转结构钉死在 CSV 上;后续每加一种格式都要再抄一遍类似结构。 |
|
||||
| `CsvProgress` / `CsvProgressInfo` | 名字里的 `Csv` 让「加载进度」这类通用概念只服务于 CSV。 |
|
||||
| `BuildDataFile(CsvTable&&, path, …)` | 建组函数直接吃 CSV 解析结果;MF4(或任何其它格式)都无法复用,只能复制一份逻辑。 |
|
||||
| `bool BuildDataFile(...)` 里的「识别时间轴列 / 回退第一列」逻辑 | 与「多时间轴」需求正面冲突:只认**第一个**时间列,且要求所有列等长。 |
|
||||
| `.preflight` 之外无任何「格式 → 数据」的装配点 | 新增格式要在 `Application::RunAsyncLoad` 里改流程。 |
|
||||
|
||||
### 1.4 过期 / 文不对题的注释
|
||||
|
||||
| 位置 | 问题 |
|
||||
| --- | --- |
|
||||
| `LooksLikeTimeHeader` | 判据是「列名含 time/时间」⇒ 会把 `timeout`、`TimeStampExtra` 之类误判成时间轴(多时间轴下会**切出空块**)。 |
|
||||
| `ExtractUnit` | 解析出来的单位无人使用(见 1.2)。 |
|
||||
| 大量 `@param [IN] file 信号所属文件` | 参数已改语义后注释未跟(本轮全部改为 `axis 该信号使用的时间轴`)。 |
|
||||
| UI 文案 | `选中该文件全部信号` / `取消该文件全部信号` / `关闭该文件(从工作区移除)` / `请关闭部分文件分组或信号` 等,把「分组」说成「文件」。 |
|
||||
| 空状态文案 | `尚未加载数据,请打开 CSV 文件` / `打开 CSV 文件以查看仿真数据波形`:应用已不只支持 CSV。 |
|
||||
|
||||
### 1.5 健壮性缺陷(审计中顺带发现)
|
||||
|
||||
| 位置 | 问题 |
|
||||
| --- | --- |
|
||||
| `Application::Initialize` | `RegisterClassExW` 失败即弹**模态**错误框。同一进程内第二次构造 `Application` 必然命中 `ERROR_CLASS_ALREADY_EXISTS` ⇒ 调用方**永久阻塞**(本轮离屏测试就被它卡住)。`Shutdown` 也从不注销窗口类。 |
|
||||
| `Application::LoadFile` 失败路径 | 会弹模态对话框(离屏/自动化场景是雷区)。已在测试中绕开;建议后续把「错误提示」与「模态阻塞」解耦。 |
|
||||
| MDF 文本块解析 | `##TX` 在 MDF 规范里是「长度 + UTF-16/UTF-8」,但 CANape 16 会写**无长度前缀的裸 ASCII**(见 §三),只按规范解析会得到空名称。 |
|
||||
|
||||
---
|
||||
|
||||
## 二、重构内容(改动清单)
|
||||
|
||||
### 2.1 数据模型(`Utilities/Inc/DataModel.hpp`、`Utilities/Src/DataModel.cpp`)
|
||||
|
||||
**新增 `TimeAxis`(时间轴成为一等公民)**
|
||||
|
||||
```cpp
|
||||
struct TimeAxis {
|
||||
std::wstring name; // 时间列名(源文件原样)
|
||||
std::vector<double> time;
|
||||
double minTime, maxTime;
|
||||
bool monotonic;
|
||||
std::size_t finiteCount;
|
||||
std::size_t Count() const;
|
||||
};
|
||||
```
|
||||
|
||||
**`SignalData` 精简 + 指向时间轴**
|
||||
|
||||
```cpp
|
||||
struct SignalData {
|
||||
std::wstring name; // 源名称(原样保留、永不修改)
|
||||
std::wstring displayName; // 用户重命名(空 = 用 name)
|
||||
std::size_t axisIndex = 0; // 所属时间轴(分组内下标) ← 新增
|
||||
std::vector<double> values;
|
||||
double minValue, maxValue; std::size_t finiteCount;
|
||||
std::vector<double> coarseMin, coarseMax; std::size_t coarseStride;
|
||||
SignalStyle style;
|
||||
const std::wstring& Title() const;
|
||||
};
|
||||
// 删除:rawHeader、unit
|
||||
```
|
||||
|
||||
**`DataFile` → `SignalGroup`(分组 = 若干时间轴 + 若干信号)**
|
||||
|
||||
```cpp
|
||||
struct SignalGroup {
|
||||
std::wstring name; // 默认取源文件名(便于辨认来源)
|
||||
std::wstring displayName; // 用户重命名
|
||||
std::vector<TimeAxis> axes; // 1..N 条时间轴 ← 取代单一 time
|
||||
std::vector<SignalData> signals;
|
||||
const std::wstring& Title() const;
|
||||
const TimeAxis& AxisOf(const SignalData&) const;
|
||||
const TimeAxis& AxisAt(std::size_t) const;
|
||||
bool TimeRange(double& low, double& high) const; // 全部轴的并集
|
||||
};
|
||||
// 删除:filePath、fileName、time、timeColumnName、timeMonotonic、minTime、maxTime
|
||||
```
|
||||
|
||||
**引用与集合**
|
||||
|
||||
* `SignalRef::fileIndex` → **`groupIndex`**(含注释:「分组是应用内的逻辑容器,与磁盘文件没有任何关系」)。
|
||||
* `DataSet`:`AddDataFile→AddGroup`、`RemoveFile→RemoveGroup`、`FileAt→GroupAt`、`FileCount→GroupCount`、
|
||||
`Files()→Groups()`、`SetFileDisplayName→SetGroupDisplayName`、
|
||||
`RemapSelectionAfterFileRemoval→RemapSelectionAfterGroupRemoval`;新增 **`Axis(ref)`**(解析信号→时间轴)。
|
||||
* `TabConfig`:`SetFileVisible→SetGroupVisible`、`FileHasVisible→GroupHasVisible`、`FileAllVisible→GroupAllVisible`、
|
||||
`RemapAfterFileRemoval→RemapAfterGroupRemoval`、`RowKind::FILE→RowKind::GROUP`、`CMD_FILE_*→CMD_GROUP_*`。
|
||||
|
||||
**采样与统计 API 去掉「文件」**
|
||||
|
||||
```cpp
|
||||
bool SampleSignalAt(const TimeAxis&, const SignalData&, double time, bool interpolate, double&);
|
||||
bool SampleDisplayedSignalAt(const TimeAxis&, const SignalData&, const SignalStyle&, …);
|
||||
int FindNearestSample(const TimeAxis&, double time);
|
||||
// PlotView:SampleSpacingPixels / VisibleIndexRange / EdgeIndexRange /
|
||||
// MeanFilterValueAt / WeakPeakValueAt / EdgeCountsFor /
|
||||
// EdgeCenterCountsFor / EdgeScanFor —— 首参全部由 const DataFile& 改为 const TimeAxis&
|
||||
```
|
||||
|
||||
**视图层**
|
||||
|
||||
* `ResolvedSignal`:`const DataFile* file` → **`const SignalGroup* group` + `const TimeAxis* axis`**;
|
||||
所有 `resolved.file->time` 改为 `resolved.axis->time`,`resolved.file->Title()` 保留走 `group`。
|
||||
* `PolylineCacheEntry::file` → `group`;`EdgeScanState::file` → **`axis`**(统计身份 = 时间轴 + 信号)。
|
||||
* `TabConfig::ComputeVisibleTimeBounds`:只统计**可见信号真正用到的时间轴**(多轴分组下避免拉出过宽范围)。
|
||||
* 时间轴刻度列名取「首个可见信号所属分组的第一条时间轴」。
|
||||
|
||||
### 2.2 加载层(新文件,格式与模型解耦)
|
||||
|
||||
```
|
||||
Utilities/Inc/DataTable.hpp DataTable(通用列优先表格)+ LoadProgress/Info
|
||||
+ TableBlock(一个时间轴块)+ LoadedSource(源文件的全部块)
|
||||
Utilities/Inc/DataLoader.hpp LoadSignalSource(path,…) 唯一入口 + IsSupportedSignalFile + 对话框过滤器
|
||||
Utilities/Src/DataLoader.cpp 按扩展名分发(.csv/.txt → CSV;.mf4/.mdf → MDF4;其它按文本尝试)
|
||||
Utilities/Inc/CsvLoader.hpp LoadCsvSource
|
||||
Utilities/Src/CsvLoader.cpp CSV 解析 + 多时间轴切分 + 各轴按自己的时间列裁剪
|
||||
Utilities/Inc/Mf4Loader.hpp LoadMf4Source
|
||||
Utilities/Src/Mf4Loader.cpp ASAM MDF v4 解析(见 §三)
|
||||
删除:Utilities/Inc/CsvTable.hpp、Utilities/Src/CsvTable.cpp
|
||||
```
|
||||
|
||||
**唯一装配点**:`BuildSignalGroup(LoadedSource&&, paletteBase, SignalGroup&, error, progress)`
|
||||
—— 与格式无关,负责:时间轴**去重合并**(内容逐位相同只保留一条)、信号命名、调色板取色、统计与粗粒度包络。
|
||||
新增格式时**只需写一个加载器**,数据模型与全部界面代码不用改。
|
||||
|
||||
### 2.3 CSV:多时间轴 + 各轴不等长
|
||||
|
||||
* 规则(与 MATLAB/Simulink 说明一致):**只有名为 `time` 的列是时间轴**;每个时间列开启一个新块,
|
||||
它之后直到下一个时间列之前的列都属于该块。
|
||||
* 各块按**自己的时间列**裁剪(较短的时间轴在数据区提前结束,其余位置为空)⇒ 支持
|
||||
`TestData/Multi_Timeline.CSV`(`time,data1,time,data2,data3,time,data4`,实测切出 3 条轴,长度 7/4/7)。
|
||||
* 修复用户报告的缺陷:**不再把其它 `time` 列当成信号显示**。
|
||||
* 保留旧行为:完全没有 `time` 列时退化为「第一列是时间轴」的单轴文件。
|
||||
* 判据收紧:`LooksLikeTimeHeader` 只接受 `t` / `time` / `timestamp` / `simulation time` / `时间` / `时间轴`
|
||||
(不再用「包含 time」这种会误判 `timeout` 的规则)。
|
||||
|
||||
### 2.4 MF4:新增支持
|
||||
|
||||
* 加载后**新建一个分组、组名 = 文件名**,文件内的全部信号都归入该分组(用户要求的行为)。
|
||||
* 每个**通道组(CG)**是独立时间轴:内容相同的时间轴自动合并
|
||||
(实测该文件 27 个 CG → **3 条时间轴**、124 条信号,避免 25 条重复的单样本轴)。
|
||||
* 只解码绘制需要的部分:字符串/字节数组通道、压缩块(DZ/HZ)、MIME 通道一律跳过;
|
||||
非数值通道不产生信号。
|
||||
|
||||
### 2.5 UI 文案与健壮性
|
||||
|
||||
* 文案统一到「分组」:`选中该分组全部信号`、`取消该分组全部信号`、`关闭该分组(从工作区移除)`、
|
||||
`请关闭部分分组或信号后重试。`、`尚未加载数据,请打开数据文件`、`打开数据文件以查看波形`、
|
||||
`(可同时加载多个文件,各自成为一个分组)`;打开对话框标题改为「打开测量数据文件」并支持 `*.mf4/*.mdf`。
|
||||
* `Application::Initialize`:`RegisterClassExW` 返回 `ERROR_CLASS_ALREADY_EXISTS` 时**复用**已注册的类
|
||||
(不再弹模态错误框);`Shutdown` 注销窗口类并销毁窗口。⇒ 同一进程可多次构造/销毁 `Application`。
|
||||
|
||||
---
|
||||
|
||||
## 三、MDF v4 解析要点(本轮实测得到,供后续维护)
|
||||
|
||||
文件实测为 **MDF 4.10 / CANape 16.0.50**(`##HD … ##DG … ##CG … ##CN … ##CC … ##DT`)。
|
||||
|
||||
* **块头(24 字节)**:`id[4]` + `reserved[4]` + `block_len(U64)` + `link_count(U64)`;之后是
|
||||
`link_count` 个 **8 字节绝对文件偏移**(0 = 空);数据段紧随链接区。DT/HL 等块可以 0 链接。
|
||||
* **ID 块**:偏移 0 起 `"MDF "`,偏移 8 起版本串(如 `"4.10 "`)。
|
||||
* **HD**:链接 `[0] = 第一个 DG`。
|
||||
* **DG**:链接 `[1] = 第一个 CG`、`[2] = 该组的数据块(DT)`。
|
||||
* **CG**:链接 `[1] = 第一个 CN`、`[2] = tx_name`;数据段 `+8 = cycle_count`。
|
||||
**记录长度**取 `DT 数据段字节数 / cycle_count`(实测 114/110 字节,与声明一致),拿不到时按通道位宽向上取整。
|
||||
* **CN**:链接 `[2] = 名称 TX`、`[4] = 换算 CC`;数据段
|
||||
`+0 type`、`+1 sync_type`(**1 = 时间主通道**)、`+2 data_type`、`+3 bit_offset`、
|
||||
`+4 byte_offset(U32,单位**字节**)`、`+8 bit_count`、`+12 flags`(bit0 整列无效 / bit1 有无效位)、
|
||||
`+16 invalid_bit_pos`、`+32 val_range_min`、`+40 val_range_max`。
|
||||
**绝对位偏移 = byte_offset × 8 + bit_offset**。
|
||||
* **data_type**:0/1 无符号(LE/BE)、2/3 有符号、4/5 IEEE 浮点、≥6 字符串/字节数组(跳过)。
|
||||
浮点只接受字节对齐的 32/64 位;整数支持 1–64 位任意位偏移(含符号扩展)。
|
||||
* **CC**:`+0 type`、`+1 precision`、`+2 flags`、`+4 ref_param_nr`、`+6 val_count`、
|
||||
`+8 phy_range_min`、`+16 phy_range_max`、`+24 cc_val[]`(每个 8 字节)。
|
||||
**换算公式(实测确认)= `phys = cc_val[0] + cc_val[1] × raw`**。
|
||||
本文件里时间主通道是「double 纳秒 + 线性换算 1e-9」⇒ 解码后时间范围 `[0.024320125, 42.3918] s`
|
||||
与 CC 里的物理范围逐位吻合,可据此自检。
|
||||
* **文本块(TX)**:规范是「U32 长度 + UTF-16 代码单元」(MDF 4.1 起也可 UTF-8 字节数),
|
||||
但 **CANape 会写无长度前缀的 NUL 结尾裸文本**。解析顺序:先按长度前缀(UTF-16 → UTF-8),
|
||||
失败则按「裸文本(自动判断 UTF-16/UTF-8)」回退。
|
||||
|
||||
---
|
||||
|
||||
## 四、验证(全部离屏;进程/线程 `BELOW_NORMAL`、窗口 `SW_HIDE`、不注入真实鼠标键盘)
|
||||
|
||||
| 套件 | 覆盖 | 结果 |
|
||||
| --- | --- | --- |
|
||||
| `.preflight/perf/datacheck_main.cpp` | 单轴 CSV 回归;多时间轴 CSV(3 轴、7/4/7、不再出现 `time` 信号);MF4(组名、124 信号、3 条轴、主时间轴 ns→s、`setSpeed` 首样本与原始字节人工核对);`DataSet` 不变量(轴共享/解析/时间范围/重命名);派生信号沿用源时间轴;**MF4 集成**(124 信号并入一个网格渲染 67.5ms/帧) | **69/69 通过** |
|
||||
| `.preflight/perf/edgecheck_main.cpp` | 「调整」符号规则(真实菜单路径);边沿统计按帧推进 / 不饿死 / 与参考实现逐位一致 / 无鼠标输入也完成;面板两列等宽与名称限宽;弱峰复位控件 | **46/46 通过** |
|
||||
| `.preflight/perf/verify_main.cpp`(既有) | PlotView 渲染/交互:Y 自适应、派生信号、图例命中、GX 徽标悬停、装饰层无残影、平移逐像素 | **47/47**(另有 2 条**本机未同步的旧规则断言**:`derived offsetTime kept`、`mouse in lane1 lower half`,与本轮无关) |
|
||||
| 构建 | `make release` | **零警告** |
|
||||
|
||||
补充:`Target.exe` 手工运行确认加载 `Multi_Timeline.CSV` 与 `MF4-Record.MF4` 正常(真实交互由用户后续验收)。
|
||||
|
||||
---
|
||||
|
||||
## 五、遗留与建议(未在本轮实施)
|
||||
|
||||
1. **错误提示与模态阻塞解耦**:`SetStatusMessage(..., isError=true)` 直接 `MessageBoxW`,
|
||||
在自动化/离屏场景会永久阻塞。建议改为宿主级非模态提示(或至少让调用方可选择不弹)。
|
||||
2. **MF4 压缩块(`##DZ`/`##HL`/`##SD`)与 `##DL` 数据列表**未实现:遇到这类文件会跳过相应通道组。
|
||||
如后续需要,解析 `DL` 的偏移数组即可(数据布局不变)。
|
||||
3. **MF4 单元/注释(`##CC` 的 unit TX、`##MD`)未使用**:用户明确暂不需要元数据。
|
||||
4. **`Component` 层仍在 `TabConfig` 里保存逐信号的边沿统计/预览参数**:属于「视图配置」与「数据」的
|
||||
第二次耦合(当前可用,但若要支持「同一信号在不同标签页用不同统计参数」需再拆一层)。
|
||||
5. **`Lane` 内含 4 类实验性功能的状态与面板坐标**(`edgeCount/meanFilter/weakPeak` + 各自面板 X/Y),
|
||||
已经把 `Lane` 撑到 200+ 行;建议后续抽出 `LanePanelState`。
|
||||
6. **`PlotView.cpp` 仍有 1.1 万行**:渲染分支(静态缓存/宽基底/样式补丁/整幅)与交互命中混在一个类里,
|
||||
建议按「渲染器 / 命中测试 / 派生曲线调度」拆成 3 个协作类(风险较高,宜单独一轮做)。
|
||||
7. **`SignalRef` 仍是「分组下标 + 组内下标」**:删除/重排分组时依赖调用方按顺序调用
|
||||
`Remap*` 系列函数维护;可考虑改为稳定 ID(后续需要撤销/重做时收益更大)。
|
||||
Reference in new issue
Block a user