MF4 补 ##SD/##SI/VLSD + 信号枚举文本 enumText + .mat 改随机读(流式进度)
1) 修复上轮记录的两个已知问题
- MatLoader: LoadPlainObject 不再为拿 dims 调 ReadDataset(整块解码第二次),
改用 ReadDatasetInfo;普通 v7.3 大矩阵的时间/内存不再翻倍。
- MdfFile::TextBlock 注释与实现对齐:真的支持 ##MD(UTF-8 XML,无长度前缀)。
2) 信号枚举信息(以 TestData/NEW-MF4.mf4 为目标)
- 新增 SignalEnumText(DataTable.hpp):values[] + texts[] + Empty()/Find(),
命名用 enumText —— 它描述"取值代表哪个状态",不是单位。
- DataTable::columnEnumText 与 columns 同下标;SignalData::enumText 由
BuildSignalGroup 移动赋值;其它格式(CSV/.mat)保持为空。
- MF4 cc_type = 7:cc_val[i](F64,数据段 +24)↔ cc_ref[4 + i](##TX/##MD 文本),
最后一个引用是默认文本,不使用。
- 界面只在**光标取值标注**追加 " = <枚举文本>"(等号两侧各一个空格、无尖括号),
查表用采样原值(ApplyDrawTransform 之前);常规状态显示一个像素都不变。
- 实测:NEW-MF4.mf4 8 信号 / 4 条带枚举(32+5+32+5),MF4-Record.MF4 另有 2 种(12/32)。
3) MF4 支持范围
- ##SI 采样索引:只有索引 ≥2 条且全部 < 周期数才生效(逐记录有效性位图)。
实测部分工具给每个通道挂只含常量 513/4 的 ##SI,必须忽略,否则整列变 NaN。
- ##SD + VLSD(cn_type == 1):##SD 数据段是「UINT32 长度 + 字节」样本序列,
记录里放 64 位偏移;AppendSignalData 递归展开 SD/DZ/DL/HL(单通道上限 512MB),
DecodeVlsdValue 先按直接偏移、失败再按 >>1 回退,取样本第一个元素。
- CN 的 cn_data / md_unit / md_comment 下标按链接总数反推:8 链接(MDF 4.0 风格)
= 换算[4]/数据[5]/单位[6]/注释[7],9 链接(4.1)= [5]/[6]/[7]/[8]。
实测 Simulink 导出的“4.10”仍写 8 链接,不能写死 Link(cn,4/5)。
- 主通道:cn_sync_type 1/2/3(时间/角度/距离)→ 名字 t/time/timestamp/zeit/时间
(含 t[...])→ 第一个数值通道兜底;cg_flags 第 2 位(远程主通道)跳过 sync 判定。
- 记录长度优先 cg_data_bytes(CG 数据段 +24);旧实现把 U8(cgData) 当
cg_record_id_len,其实是 cg_flags 低字节(总线事件位)。
- 进度:先走 DG 链得到真实组数(不再写死 64),hasBytes = true,bytesRead 取
MdfFile::ReadAt 累计(夹到文件大小);4MB 分块读取检查取消。
- ##DZ:数据段前有 28 字节头(original_type/zip_type/param/original_size/zip_size),
旧实现整段当 zlib 流是错的;只支持纯 deflate(zip_type = 0)。
- 一个信号都没有时,错误信息附带「布局无法解析 / 缺少时间通道 / 空数据组」计数。
4) .mat:内存映射 → 随机读 + 元数据块缓存(消除 SEH 风险)
- MatHdf5::Open 改为 CreateFileW(FILE_FLAG_SEQUENTIAL_SCAN) + ReadFile;
删除 MappedBytes,改为 ReadAt / Cached(16 × 64KB LRU + 上次命中槽)/ Tag /
ReadData / ReadCString。I/O 失败只会变成 false,不再触发页访问异常。
- 数据读取:连续布局按 4MB 切片;分块布局按需 + 64KB 对齐。
固定 4MB 预读在交错存放的分块数据集上会把读盘量放大到 2.2 倍(2874MB/1293MB),
改后 1301MB。
- 关键提速:dims=[N]、chunk=[32768,1] 归一化后 rank=2、每行 1 字节,逐行算坐标
是 12.9 亿次迭代;新增「rank == 2 且 chunkDims[1] == dims[1]」快路径(整块一次
sink)。BIG_MAT.mat(1.29GB / 5 × 16,142,471):21.5s → 7.9s,私有内存 +741MB。
- 进度:Open 立刻上报「0 / 总大小」,之后每读一块回调 ReadProgress(bytesRead,total)
→ MatLoader 映射到 0.02–0.98 并单调夹紧(字节进度与逐流解码取较大值),收尾 1.0。
遮罩层百分比不再一上来就 100%;取消会中止(StreamDataset 返回 false),不留 NaN 空洞。
- 连续布局数据不足(文件被截断)现在明确失败,不再静默截短。
验证(全部离屏:无可见窗口、不注入鼠标键盘屏幕)
- 新增 .preflight/perf/loadercheck(数据层)32/32:
NEW-MF4 8 信号 / 4 条枚举 / 枚举文本逐条核对 / 9396 与 94 行;
MF4-Record 124 信号 / 3 轴 / 4 条枚举;data-sample.mat 4 组 8 信号 / 行数;
BIG_MAT.mat 5 信号 × 16,142,471 + 进度(total 已知、ratio 单调、bytes 递增、
起点 ≤0.05、终值 1.0、更新 ≥10 次)。
- 新增 .preflight/perf/enumui(真实 Application + SW_HIDE)12/12:
只显示 gVR_WinOpenReq[0],光标落在枚举采样点上 —— 标签宽度 265px;
临时清空 enumText 后 25px;差值 240px 与 " = WinOpenReqAtDrvr_Idle" 的
UiMeasureTextWidth 完全一致;恢复后宽度回到 265px。
- 回归:datacheck 69/69、matcheck 21/21、matui 15/15(loading bytes 达到总大小)、
verify 49/49。
- make release / make debug 零警告。Build/Release/TargetCheck.exe = 820,224 字节
(上轮 785,920,+34KB)。(Target.exe 正在被用户运行的程序占用,链接步骤未覆盖它。)
This commit is contained in:
1 parent
75f0fafd41
commit
181f95cc3a
10 files changed
+1328
-291
No files matched your search
+53
-25
@@ -142,6 +142,7 @@ namespace jdi
|
||||
double high = 1.0; /**< 本阶段占的进度区间上界。 */
|
||||
std::uint64_t read = 0; /**< 已读字节。 */
|
||||
std::uint64_t total = 0; /**< 总字节。 */
|
||||
mutable double lastRatio = -1.0; /**< 已上报的最大比例(保证单调不回退)。 */
|
||||
|
||||
/** @brief 上报一个 0-1 的阶段内进度(向宿主传递字节数与百分比)。 */
|
||||
bool Report(double fraction) const
|
||||
@@ -158,11 +159,21 @@ namespace jdi
|
||||
{
|
||||
fraction = 1.0;
|
||||
}
|
||||
double ratio = low + (high - low) * fraction;
|
||||
// 「已读字节」与「逐流解码」是两条独立进度来源:取较大值,避免进度条回退。
|
||||
if (ratio < lastRatio)
|
||||
{
|
||||
ratio = lastRatio;
|
||||
}
|
||||
else
|
||||
{
|
||||
lastRatio = ratio;
|
||||
}
|
||||
LoadProgressInfo info;
|
||||
info.ratio = low + (high - low) * fraction;
|
||||
info.ratio = ratio;
|
||||
// 总字节已知时一并上报(遮罩层据此显示“已加载 / 总大小”)。
|
||||
info.hasBytes = (total > 0);
|
||||
info.bytesRead = (total > 0) ? read : 0;
|
||||
info.bytesRead = (total > 0) ? std::min(read, total) : 0;
|
||||
info.totalBytes = total;
|
||||
return (*sink)(info);
|
||||
}
|
||||
@@ -503,13 +514,19 @@ namespace jdi
|
||||
{
|
||||
if (file.IsDataset(address))
|
||||
{
|
||||
// ⚠️ 只解析元信息(维度),**不要**用 `ReadDataset`:那会把整块数据再解码一遍。
|
||||
// 旧实现先 `ReadNumbers` 解码一次、再 `ReadDataset` 只为拿 `dims`,
|
||||
// 大矩阵的时间与内存都翻倍。
|
||||
MatHdf5::Dataset dataset;
|
||||
if (!file.ReadDatasetInfo(address, dataset) || dataset.dims.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::vector<double> numbers;
|
||||
if (!file.ReadNumbers(address, numbers) || numbers.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
MatHdf5::Dataset dataset;
|
||||
file.ReadDataset(address, dataset);
|
||||
std::size_t rows = 1;
|
||||
std::size_t columns = 1;
|
||||
if (dataset.dims.size() >= 2)
|
||||
@@ -566,22 +583,24 @@ namespace jdi
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 进度拆成三段:
|
||||
// 0.00 – 0.02 映射文件(内存映射,几乎瞬时;遮罩层仍能看到“已加载 = 总大小”)
|
||||
// 0.02 – 0.98 逐条解码样本流(每个流解压后直接填进数据模型,含解压开销)
|
||||
// 0.00 – 0.02 打开文件(立刻知道总大小;遮罩层显示「0 / 总大小」)
|
||||
// 0.02 – 0.98 随机读取 + 逐条解码样本流(进度按**实际已读字节**推进)
|
||||
// 0.98 – 1.00 收尾
|
||||
// ⚠️ 旧实现把 0–0.55 全给了“读文件字节”(因为旧实现真的整文件读入);
|
||||
// 现在改成内存映射后那一段几乎没有耗时,若不调整权重,进度条会“瞬间跳到 55% 再慢慢走”。
|
||||
// ⚠️ 旧实现是内存映射:字节瞬间「全部可访问」⇒ 文件读取百分比立刻 100%,
|
||||
// 而真正的解析还要跑很久(用户 2026-10-20 报告“这部分不太对劲”)。
|
||||
// 现在每次读盘都累计到 `MatHdf5::BytesRead()`,百分比随真实 I/O 推进。
|
||||
// ------------------------------------------------------------------
|
||||
ProgressReporter readReporter;
|
||||
readReporter.sink = &progress;
|
||||
readReporter.low = 0.0;
|
||||
readReporter.high = 0.02;
|
||||
ProgressReporter parseReporter;
|
||||
parseReporter.sink = &progress;
|
||||
parseReporter.low = 0.02;
|
||||
parseReporter.high = 0.98;
|
||||
|
||||
const MatHdf5::OpenProgress openProgress =
|
||||
[&readReporter](std::uint64_t bytesRead, std::uint64_t totalBytes) -> bool
|
||||
[&parseReporter](std::uint64_t bytesRead, std::uint64_t totalBytes) -> bool
|
||||
{
|
||||
readReporter.read = bytesRead;
|
||||
readReporter.total = totalBytes;
|
||||
return readReporter.Report(totalBytes > 0 ? static_cast<double>(bytesRead) /
|
||||
parseReporter.read = bytesRead;
|
||||
parseReporter.total = totalBytes;
|
||||
return parseReporter.Report(totalBytes > 0 ? static_cast<double>(bytesRead) /
|
||||
static_cast<double>(totalBytes)
|
||||
: 0.0);
|
||||
};
|
||||
@@ -589,12 +608,17 @@ namespace jdi
|
||||
{
|
||||
return false; // 用户取消时 `error` 为空
|
||||
}
|
||||
ProgressReporter parseReporter;
|
||||
parseReporter.sink = &progress;
|
||||
parseReporter.low = 0.02;
|
||||
parseReporter.high = 0.98;
|
||||
parseReporter.read = readReporter.total;
|
||||
parseReporter.total = 0; // 字节已读完:本阶段只报百分比
|
||||
|
||||
// 流式解码期间的字节进度(每读一块回调一次);返回 false 即取消。
|
||||
file.SetReadProgress(
|
||||
[&parseReporter](std::uint64_t bytesRead, std::uint64_t totalBytes) -> bool
|
||||
{
|
||||
parseReporter.read = bytesRead;
|
||||
parseReporter.total = totalBytes;
|
||||
return parseReporter.Report(totalBytes > 0 ? static_cast<double>(bytesRead) /
|
||||
static_cast<double>(totalBytes)
|
||||
: 0.0);
|
||||
});
|
||||
if (!parseReporter.Report(0.0))
|
||||
{
|
||||
error.clear();
|
||||
@@ -609,6 +633,10 @@ namespace jdi
|
||||
{
|
||||
if (!LoadSdi(file, refs, sigstream, stem, groups, error, parseReporter))
|
||||
{
|
||||
if (file.Cancelled())
|
||||
{
|
||||
error.clear(); // 用户取消:不显示错误
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -637,9 +665,9 @@ namespace jdi
|
||||
{
|
||||
LoadProgressInfo info;
|
||||
info.ratio = 1.0;
|
||||
info.hasBytes = (readReporter.total > 0);
|
||||
info.bytesRead = readReporter.total;
|
||||
info.totalBytes = readReporter.total;
|
||||
info.hasBytes = (file.FileBytes() > 0);
|
||||
info.bytesRead = file.FileBytes();
|
||||
info.totalBytes = file.FileBytes();
|
||||
progress(info);
|
||||
}
|
||||
return true;
|
||||
|
||||
Reference in new issue
Block a user