Files
Just-DataInspector/Utilities/Src/MatLoader.cpp
T
DeepSeek-V4.1-Flash 181f95cc3a 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 正在被用户运行的程序占用,链接步骤未覆盖它。)
2026-10-20 01:56:47 +08:00

677 lines
21 KiB
C++
Raw 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.
// MATLAB `.mat`(v7.3 / HDF5)加载器。
//
// 支持两种内容(详见 MatLoader.hpp 的说明):
// * Simulink SDI / `SimulationData.Dataset` 导出:`#refs#` 里的元素图 + `#sigstream#` 的样本流;
// * 普通 v7.3 变量:数值矩阵按"列 = 信号、行 = 采样"读取(时间轴 = 采样序号)。
#include "MatLoader.hpp"
#include "MatH5.hpp"
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <map>
#include <set>
#include <string>
#include <vector>
namespace jdi
{
namespace
{
/** @brief 一个 SDI 信号(或子数据集)的解析结果。 */
struct SdiElement
{
bool isSignal = false;
bool isDataset = false;
std::wstring name;
std::vector<std::uint64_t> children; /**< 子数据集元素的引用目标。 */
int dataStream = -1; /**< 数据流下标。 */
int timeStream = -1; /**< 时间流下标。 */
};
std::wstring TrimNumber(double value)
{
wchar_t buffer[64] = {};
if (value == static_cast<double>(static_cast<long long>(value)))
{
std::swprintf(buffer, 64, L"%lld", static_cast<long long>(value));
}
else
{
std::swprintf(buffer, 64, L"%g", value);
}
return buffer;
}
/** @brief 去掉文件名中的扩展名。 */
std::wstring StemOf(const std::wstring& path)
{
const std::size_t slash = path.find_last_of(L"\\/");
std::wstring name = (slash == std::wstring::npos) ? path : path.substr(slash + 1);
const std::size_t dot = name.find_last_of(L'.');
if (dot != std::wstring::npos && dot > 0)
{
name.resize(dot);
}
return name;
}
/** @brief 读取一个「流」的全部 double 样本(`#sigstream#/<n>/#data#`)。 */
bool ReadStream(const MatHdf5& file, std::uint64_t streamObject, std::vector<double>& out)
{
out.clear();
const std::uint64_t data = file.Member(streamObject, "#data#");
if (data == 0)
{
return false;
}
// ⚠️ SDI 的 `#data#` 是**字节流**(`uint8` 数据集,长度 = 采样数 × 8),
// 而不是 double 数据集:必须按 8 字节一组重新解释成 double。
// `ReadDoubleStream` 会逐 chunk 解压并**直接写入** `out`:
// 不生成“整个数据集的未压缩字节”,也不做第二次拷贝(GB 级文件的关键)。
return file.ReadDoubleStream(data, out) && !out.empty();
}
/** @brief 解析 `#refs#` 里的一个元素(信号 / 子数据集)。 */
void ParseElement(const MatHdf5& file, std::uint64_t address, SdiElement& element)
{
element = SdiElement();
std::wstring elementType;
file.ReadText(file.Member(address, "ElementType"), elementType);
file.ReadText(file.Member(address, "Name"), element.name);
const std::uint64_t values = file.Member(address, "Values");
if (elementType == L"dataset")
{
element.isDataset = true;
if (values != 0)
{
file.ReadReferences(values, element.children);
}
return;
}
element.isSignal = true;
if (values == 0)
{
return;
}
// 名字为空时退回 timeseries 自己的 Name。
if (element.name.empty())
{
file.ReadText(file.Member(values, "Name"), element.name);
}
std::vector<double> numbers;
if (file.ReadNumbers(file.Member(values, "DataR2"), numbers) && !numbers.empty())
{
element.dataStream = static_cast<int>(numbers.front());
}
numbers.clear();
if (file.ReadNumbers(file.Member(values, "TimeR2"), numbers) && !numbers.empty())
{
element.timeStream = static_cast<int>(numbers.front());
}
}
/** @brief 追加一条信号(时间 + 数值)到某个分组的时间轴块集合。 */
void AppendSignal(LoadedSource& source, const std::wstring& name, std::vector<double>&& time,
std::vector<double>&& values)
{
if (time.empty() || values.empty())
{
return;
}
TableBlock block;
block.timeHeader = L"time";
DataTable& table = block.table;
table.headers.push_back(L"time");
table.headers.push_back(name);
table.rowCount = std::min(time.size(), values.size());
time.resize(table.rowCount);
values.resize(table.rowCount);
table.columns.push_back(std::move(time));
table.columns.push_back(std::move(values));
source.blocks.push_back(std::move(block));
}
/** @brief 加载进度上报器(`.mat` 解析拆成“读文件”与“解码每个样本流”两段)。 */
struct ProgressReporter
{
const LoadProgress* sink = nullptr; /**< 目标回调(可空)。 */
double low = 0.0; /**< 本阶段占的进度区间下界。 */
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
{
if (sink == nullptr || !(*sink))
{
return true;
}
if (fraction < 0.0)
{
fraction = 0.0;
}
if (fraction > 1.0)
{
fraction = 1.0;
}
double ratio = low + (high - low) * fraction;
// 「已读字节」与「逐流解码」是两条独立进度来源:取较大值,避免进度条回退。
if (ratio < lastRatio)
{
ratio = lastRatio;
}
else
{
lastRatio = ratio;
}
LoadProgressInfo info;
info.ratio = ratio;
// 总字节已知时一并上报(遮罩层据此显示“已加载 / 总大小”)。
info.hasBytes = (total > 0);
info.bytesRead = (total > 0) ? std::min(read, total) : 0;
info.totalBytes = total;
return (*sink)(info);
}
};
/** @brief SDI(`SimulationData.Dataset`)结构的分组读取。 */
bool LoadSdi(const MatHdf5& file, std::uint64_t refs, std::uint64_t sigstream,
const std::wstring& stem, std::vector<LoadedSourceGroup>& groups,
std::wstring& error, const ProgressReporter& reporter)
{
// 1) 枚举 `#refs#` 里的全部元素。
std::map<std::uint64_t, SdiElement> elements;
for (const auto& member : file.Members(refs))
{
if (file.IsDataset(member.second))
{
continue;
}
SdiElement element;
ParseElement(file, member.second, element);
if (element.isSignal || element.isDataset)
{
elements[member.second] = std::move(element);
}
}
if (elements.empty())
{
error = L"文件中没有 Simulink 数据元素。";
return false;
}
// 2) 找出"根"数据集:没有被任何其它数据集当作子元素引用的那些。
std::set<std::uint64_t> childAddresses;
for (const auto& entry : elements)
{
for (const std::uint64_t child : entry.second.children)
{
childAddresses.insert(child);
}
}
std::vector<std::uint64_t> roots;
for (const auto& entry : elements)
{
if (!entry.second.isDataset)
{
continue;
}
if (childAddresses.count(entry.first) == 0)
{
roots.push_back(entry.first);
}
}
if (roots.empty())
{
// 没有任何子数据集关系:所有信号归入同一组。
for (const auto& entry : elements)
{
if (entry.second.isSignal)
{
roots.push_back(entry.first);
}
}
}
// 3) 深度优先遍历:路径上的数据集名字用 `-` 连接成分组名。
struct Pending
{
std::uint64_t address = 0;
std::vector<std::wstring> path;
int depth = 0;
};
std::vector<Pending> stack;
for (const std::uint64_t root : roots)
{
const auto found = elements.find(root);
if (found == elements.end())
{
continue;
}
Pending pending;
pending.address = root;
if (found->second.isDataset && !found->second.name.empty())
{
pending.path.push_back(found->second.name);
}
stack.push_back(std::move(pending));
}
// ⚠️ **先收集信号清单、再解码**(用户 2026-10-19 要求:边解析边填充):
// * 旧实现把**每条**信号的时间列和时间流各拷一份(`streams` 缓存 + `AppendSignal`
// 里的整份复制)⇒ GB 级文件会出现“文件 + 解压字节 + 每条信号两份 double”叠加;
// * 现在按「分组 + 时间流」合并成一个 `TableBlock`:时间列只存一份、信号列直接
// `std::move` 进数据模型,且每条数据流解码完立刻进入最终缓冲。
struct PendingSignal
{
std::wstring groupName;
std::wstring signalName;
int timeStream = -1;
int dataStream = -1;
};
std::vector<PendingSignal> signals;
int unnamedSignals = 0;
while (!stack.empty())
{
Pending pending = std::move(stack.back());
stack.pop_back();
const auto found = elements.find(pending.address);
if (found == elements.end())
{
continue;
}
const SdiElement& element = found->second;
if (element.isDataset)
{
for (const std::uint64_t child : element.children)
{
Pending next;
next.address = child;
next.path = pending.path;
const auto childElement = elements.find(child);
if (childElement != elements.end() && childElement->second.isDataset &&
!childElement->second.name.empty())
{
next.path.push_back(childElement->second.name);
}
stack.push_back(std::move(next));
}
continue;
}
if (!element.isSignal || element.dataStream < 0)
{
continue;
}
PendingSignal signal;
for (const std::wstring& piece : pending.path)
{
if (!signal.groupName.empty())
{
signal.groupName += L"-";
}
signal.groupName += piece;
}
if (signal.groupName.empty())
{
signal.groupName = stem;
}
signal.signalName = element.name;
if (signal.signalName.empty())
{
signal.signalName = L"信号" + std::to_wstring(++unnamedSignals);
}
signal.timeStream = element.timeStream;
signal.dataStream = element.dataStream;
signals.push_back(std::move(signal));
}
// 2) 解码:每个(分组,时间流)一个时间轴块;每条数据流只解码一次并直接进列。
std::map<std::wstring, std::size_t> groupIndex;
std::map<std::pair<std::wstring, int>, std::size_t> blockIndex;
std::size_t decoded = 0;
const auto reportStream = [&]()
{
++decoded;
const double fraction = (signals.empty())
? 1.0
: static_cast<double>(decoded) /
static_cast<double>(signals.size());
return reporter.Report(std::min(1.0, fraction));
};
for (PendingSignal& signal : signals)
{
// 分组(每个分组名对应一个 `LoadedSourceGroup`)。
std::size_t groupSlot = 0;
const auto existing = groupIndex.find(signal.groupName);
if (existing == groupIndex.end())
{
LoadedSourceGroup group;
group.name = signal.groupName;
group.source.sourceName = signal.groupName;
groups.push_back(std::move(group));
groupSlot = groups.size() - 1;
groupIndex.emplace(signal.groupName, groupSlot);
}
else
{
groupSlot = existing->second;
}
LoadedSource& source = groups[groupSlot].source;
// 数据列:直接解码到最终 vector(不再经过 `Dataset.data` 的整块字节)。
std::vector<double> values;
const std::uint64_t dataObject = file.Member(sigstream, std::to_string(signal.dataStream));
if (dataObject == 0 || !ReadStream(file, dataObject, values) || values.empty())
{
if (!reportStream())
{
return false;
}
continue;
}
// 找(或建)该信号所属的时间轴块。
const std::pair<std::wstring, int> key{signal.groupName, signal.timeStream};
std::size_t blockSlot = 0;
const auto blockFound = blockIndex.find(key);
if (blockFound == blockIndex.end())
{
TableBlock block;
block.timeHeader = L"time";
std::vector<double> time;
bool haveTime = false;
if (signal.timeStream >= 0)
{
const std::uint64_t timeObject =
file.Member(sigstream, std::to_string(signal.timeStream));
haveTime = timeObject != 0 && ReadStream(file, timeObject, time) &&
!time.empty();
}
if (!haveTime)
{
// 时间流缺失:退化为采样序号(秒),保证信号仍可显示(长度在下面裁剪)。
time.resize(values.size());
for (std::size_t index = 0; index < values.size(); ++index)
{
time[index] = static_cast<double>(index);
}
}
// ⚠️ **时间轴按内容去重**:SDI 里每条 timeseries 都有自己的时间流,
// 但内容通常完全一致。若同一分组里已经有一模一样的时间列,就直接复用它
//(否则每条信号各存一份时间轴:1.29GB / 5 信号的文件会多占 ~500MB)。
std::size_t reused = SIZE_MAX;
for (std::size_t index = 0; index < source.blocks.size(); ++index)
{
const std::vector<double>& existingTime = source.blocks[index].table.columns[0];
if (existingTime.size() != time.size())
{
continue;
}
if (time.empty() ||
std::memcmp(existingTime.data(), time.data(),
time.size() * sizeof(double)) == 0)
{
reused = index;
break;
}
}
if (reused != SIZE_MAX)
{
blockIndex.emplace(key, reused);
blockSlot = reused;
}
else
{
block.table.headers.push_back(block.timeHeader);
block.table.columns.push_back(std::move(time));
source.blocks.push_back(std::move(block));
blockSlot = source.blocks.size() - 1;
blockIndex.emplace(key, blockSlot);
}
}
else
{
blockSlot = blockFound->second;
}
TableBlock& block = source.blocks[blockSlot];
// 时间列长度与信号不一致(损坏 / 异构)⇒ 单独成块,用采样序号当时间,
// 保持“每条信号都能显示”的既有语义,而不是把数据截掉或错位。
if (block.table.columns[0].size() != values.size())
{
TableBlock own;
own.timeHeader = L"time";
std::vector<double> time(values.size());
for (std::size_t index = 0; index < values.size(); ++index)
{
time[index] = static_cast<double>(index);
}
own.table.headers.push_back(own.timeHeader);
own.table.columns.push_back(std::move(time));
own.table.headers.push_back(signal.signalName);
own.table.columns.push_back(std::move(values));
source.blocks.push_back(std::move(own));
if (!reportStream())
{
return false;
}
continue;
}
block.table.headers.push_back(signal.signalName);
block.table.columns.push_back(std::move(values));
if (!reportStream())
{
return false;
}
}
// 3) 统一行数:同一块内取各列最小长度(时间流与数据流长度不一致时按最小裁剪)。
for (LoadedSourceGroup& group : groups)
{
for (TableBlock& block : group.source.blocks)
{
std::size_t rows = SIZE_MAX;
for (const std::vector<double>& column : block.table.columns)
{
rows = std::min(rows, column.size());
}
if (rows == SIZE_MAX)
{
rows = 0;
}
for (std::vector<double>& column : block.table.columns)
{
if (column.size() > rows)
{
column.resize(rows);
}
}
block.table.rowCount = rows;
}
}
if (groups.empty())
{
error = L"文件中没有可用的信号数据。";
return false;
}
return true;
}
/** @brief 普通 v7.3 变量:数值矩阵 / 结构体。 */
void LoadPlainObject(const MatHdf5& file, std::uint64_t address, const std::wstring& name,
LoadedSource& source)
{
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;
}
std::size_t rows = 1;
std::size_t columns = 1;
if (dataset.dims.size() >= 2)
{
rows = dataset.dims[0];
columns = dataset.dims[1];
}
else if (dataset.dims.size() == 1)
{
rows = dataset.dims[0];
}
if (rows == 0 || columns == 0 || rows * columns != numbers.size())
{
return;
}
// 列 = 信号、行 = 采样(与 MATLAB 的列优先存储一致)。
for (std::size_t column = 0; column < columns; ++column)
{
std::vector<double> values(rows);
for (std::size_t row = 0; row < rows; ++row)
{
values[row] = numbers[column * rows + row];
}
std::vector<double> time(rows);
for (std::size_t row = 0; row < rows; ++row)
{
time[row] = static_cast<double>(row);
}
const std::wstring signalName =
(columns == 1) ? name : (name + L"(" + TrimNumber(static_cast<double>(column + 1)) + L")");
AppendSignal(source, signalName, std::move(time), std::move(values));
}
return;
}
// 结构体:字段各自作为信号(子结构体递归)。
for (const auto& member : file.Members(address))
{
if (member.first.empty())
{
continue;
}
std::wstring childName(member.first.begin(), member.first.end());
LoadPlainObject(file, member.second, childName, source);
}
}
} // namespace
bool LoadMatSourceGroups(const std::wstring& filePath, std::vector<LoadedSourceGroup>& groups,
std::wstring& error, const LoadProgress& progress)
{
error.clear();
groups.clear();
MatHdf5 file;
// ------------------------------------------------------------------
// 进度拆成三段:
// 0.00 – 0.02 打开文件(立刻知道总大小;遮罩层显示「0 / 总大小」)
// 0.02 – 0.98 随机读取 + 逐条解码样本流(进度按**实际已读字节**推进)
// 0.98 – 1.00 收尾
// ⚠️ 旧实现是内存映射:字节瞬间「全部可访问」⇒ 文件读取百分比立刻 100%,
// 而真正的解析还要跑很久(用户 2026-10-20 报告“这部分不太对劲”)。
// 现在每次读盘都累计到 `MatHdf5::BytesRead()`,百分比随真实 I/O 推进。
// ------------------------------------------------------------------
ProgressReporter parseReporter;
parseReporter.sink = &progress;
parseReporter.low = 0.02;
parseReporter.high = 0.98;
const MatHdf5::OpenProgress openProgress =
[&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 (!file.Open(filePath, error, openProgress))
{
return false; // 用户取消时 `error` 为空
}
// 流式解码期间的字节进度(每读一块回调一次);返回 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();
return false;
}
const std::wstring stem = StemOf(filePath);
const std::uint64_t refs = file.Member(file.Root(), "#refs#");
const std::uint64_t sigstream = file.Member(file.Root(), "#sigstream#");
if (refs != 0 && sigstream != 0)
{
if (!LoadSdi(file, refs, sigstream, stem, groups, error, parseReporter))
{
if (file.Cancelled())
{
error.clear(); // 用户取消:不显示错误
}
return false;
}
}
else
{
LoadedSourceGroup group;
group.source.sourceName = stem;
for (const auto& member : file.Members(file.Root()))
{
if (member.first.empty() || member.first[0] == '#')
{
continue;
}
std::wstring name(member.first.begin(), member.first.end());
LoadPlainObject(file, member.second, name, group.source);
}
if (!group.source.HasData())
{
error = L".mat 文件中没有可用的数值数据。";
return false;
}
groups.push_back(std::move(group));
}
if (progress)
{
LoadProgressInfo info;
info.ratio = 1.0;
info.hasBytes = (file.FileBytes() > 0);
info.bytesRead = file.FileBytes();
info.totalBytes = file.FileBytes();
progress(info);
}
return true;
}
} // namespace jdi