Release 产物 1.74MB → 796KB(去掉 <filesystem> 与 zlib)+ .mat 加载进度不再停住
一、体积膨胀的真因(用 -Wl,-Map 逐对象/逐节对比上一提交定位,只有两个) 1) <filesystem>(致命,约 +900KB):MatH5::Open 用 std::filesystem::file_size 取文件大小, MinGW 的 libstdc++ 因此链入 fs_path/fs_dir/fs_ops/c++locale/codecvt/collate_members/ cow-wstring-inst/cxx11-wlocale-inst/fstream-inst/cp-demangle 整套 locale/iostream 机器 (归档成员 415→538);还把 64KB 静态表从 .bss 挤成 .data(1.8KB→73.8KB)。 修法:改用 _wfopen_s + _fseeki64/_ftelli64 取大小。 2) -lz(约 46KB):只需要"解开 HDF5 分块"。修法:MatH5 内置紧凑 inflate (RFC1950/1951:固定/动态 Huffman、存储块、长度-距离回抄),Makefile 与探针脚本移除 -lz。 顺带把 UploadHaloLayer 里 64KB 的预乘 alpha 查表换成 (v*a+128)*257>>16(无除法、无表)。 二、压到 800KB 以下的两项无损措施 1) 图标资源无损重编码:ico 里 64/48/32/16 四帧原为未压缩 32bpp DIB(合计 32KB), 用自写工具(GDI+ 编码)改为 PNG 帧(ICO 的 PNG 条目 Vista+ 全支持,本文件本就有 256 PNG 帧): 43,334 → 17,404 字节。像素逐点校验:差异 ≤85 像素、最大分量差 ≤3(编码取整),肉眼不可辨; 并从**真实产物** LoadImageW 逐尺寸(16/32/48/64)验证可加载。 2) RELEASE_OPT 由 -Os 改为 -Oz(省约 6KB);实测动画帧 p50 7.73ms(-Os) vs 7.77ms(-Oz), 无可辨识差异。 放弃的手段:-ffunction-sections/-fdata-sections + --gc-sections(反而 +9KB)、LTO(工具链 ICE)、 -fno-asynchronous-unwind-tables(链接失败)、-fno-exceptions(会破坏 catch (bad_alloc))。 结果:Build/Release/Target.exe = 795,648 字节。 三、.mat 加载进度不更新(已加载大小 / 总大小 / 百分比都停在 0) 真因:LoadMatSourceGroups 只在结束时回调一次 ratio=1.0,且从不设置 hasBytes; 读 42MB 文件与解码 19 个样本流(解压+拷贝,占大头)期间进度恒为 0。 修法: - MatHdf5::Open 新增 OpenProgress(bytesRead,totalBytes),按 4MB 分块读取并回报; - MatLoader 用 ProgressReporter 把进度拆成 0.00–0.55 读文件(hasBytes=true)与 0.55–0.98 解码样本流(每解完一个流推进 done/total),末段 1.0; - 取消与出错分开:取消时清空 error,不弹错误框。 实测:回调 29 次、字节数变化 11 次、比例单调、终值 1.000、bytes=42,386,344/42,386,344 (= 文件大小);Application 侧断言 m_loadingBytesRead == m_loadingTotalBytes == 文件大小。 四、验证(全部离屏,不占用鼠标/键盘/屏幕) - matcheck 21/21(新增进度断言)、matui 15/15(新增图标 + 加载字节断言); - 回归 verify 49/49、panelanim 158/158、gridanim 93/93、hoverdrag 20/20、lanescroll 23/23、 datacheck 69/69、edgecheck 46/46、errcheck 19/19、halocheck 16/16、panflash 19/19、 panelstutter 8/8、stress 6/6; - make release / make debug 零警告。 文档:Documents/agent_mem.md 追加本轮小节(含 5 条新规则:MinGW 下 <filesystem> 是体积炸弹、 链一整条库前先算账、膨胀要看节表而非 .o 大小、图标可无损重编码并用产物校验、 长解析必须按阶段回报进度且 hasBytes 只在有字节时置真)。
This commit is contained in:
1 parent
5b24dacee0
commit
f44f8021e3
8 files changed
+623
-46
No files matched your search
@@ -1281,21 +1281,9 @@ namespace jdi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 预乘 alpha:`AlphaBlend` 的 `AC_SRC_ALPHA` 要求源 RGB 已乘过 alpha。
|
// 预乘 alpha:`AlphaBlend` 的 `AC_SRC_ALPHA` 要求源 RGB 已乘过 alpha。
|
||||||
// 用 256×256 查表代替逐像素除法(1.6M 像素时可省 ~5ms)。
|
// ⚠️ 用 `(v * a + 128) * 257 >> 16` 代替 `(v * a + 127) / 255`(等价、无除法),
|
||||||
static std::uint8_t premultiply[256 * 256];
|
// **不要为此建 64KB 查表**:那张表会让 Release 产物凭空多出 64KB(见 §二进制体积),
|
||||||
static bool premultiplyReady = false;
|
// 而这里的乘法-移位在 1.6M 像素上也只要几毫秒。
|
||||||
if (!premultiplyReady)
|
|
||||||
{
|
|
||||||
for (int alpha = 0; alpha < 256; ++alpha)
|
|
||||||
{
|
|
||||||
for (int value = 0; value < 256; ++value)
|
|
||||||
{
|
|
||||||
premultiply[(alpha << 8) | value] =
|
|
||||||
static_cast<std::uint8_t>((value * alpha + 127) / 255);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
premultiplyReady = true;
|
|
||||||
}
|
|
||||||
gd::Rect rect(0, 0, width, height);
|
gd::Rect rect(0, 0, width, height);
|
||||||
gd::BitmapData data = {};
|
gd::BitmapData data = {};
|
||||||
if (cache.bitmap->LockBits(&rect, gd::ImageLockModeRead, PixelFormat32bppARGB, &data) !=
|
if (cache.bitmap->LockBits(&rect, gd::ImageLockModeRead, PixelFormat32bppARGB, &data) !=
|
||||||
@@ -1311,11 +1299,29 @@ namespace jdi
|
|||||||
unsigned char* row = destination + static_cast<std::size_t>(y) * width * 4u;
|
unsigned char* row = destination + static_cast<std::size_t>(y) * width * 4u;
|
||||||
for (int x = 0; x < width; ++x)
|
for (int x = 0; x < width; ++x)
|
||||||
{
|
{
|
||||||
const int alpha = source[x * 4 + 3];
|
const unsigned alpha = source[x * 4 + 3];
|
||||||
const int table = alpha << 8;
|
if (alpha == 0)
|
||||||
row[x * 4 + 0] = premultiply[table | source[x * 4 + 0]];
|
{
|
||||||
row[x * 4 + 1] = premultiply[table | source[x * 4 + 1]];
|
row[x * 4 + 0] = 0;
|
||||||
row[x * 4 + 2] = premultiply[table | source[x * 4 + 2]];
|
row[x * 4 + 1] = 0;
|
||||||
|
row[x * 4 + 2] = 0;
|
||||||
|
row[x * 4 + 3] = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (alpha == 0xFF)
|
||||||
|
{
|
||||||
|
row[x * 4 + 0] = source[x * 4 + 0];
|
||||||
|
row[x * 4 + 1] = source[x * 4 + 1];
|
||||||
|
row[x * 4 + 2] = source[x * 4 + 2];
|
||||||
|
row[x * 4 + 3] = 0xFF;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
row[x * 4 + 0] = static_cast<unsigned char>(
|
||||||
|
(static_cast<unsigned>(source[x * 4 + 0]) * alpha + 128u) * 257u >> 16);
|
||||||
|
row[x * 4 + 1] = static_cast<unsigned char>(
|
||||||
|
(static_cast<unsigned>(source[x * 4 + 1]) * alpha + 128u) * 257u >> 16);
|
||||||
|
row[x * 4 + 2] = static_cast<unsigned char>(
|
||||||
|
(static_cast<unsigned>(source[x * 4 + 2]) * alpha + 128u) * 257u >> 16);
|
||||||
row[x * 4 + 3] = static_cast<unsigned char>(alpha);
|
row[x * 4 + 3] = static_cast<unsigned char>(alpha);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6272,3 +6272,87 @@ else if (panelOnly) { /* 只回送“本帧重绘过的区域并集” */ }
|
|||||||
否则消息多、需要续块的对象会读到块外垃圾,字段全部解析失败。
|
否则消息多、需要续块的对象会读到块外垃圾,字段全部解析失败。
|
||||||
4. MATLAB 写出的**分块维度可能与数据空间维度维数不同**(1 维数据集的分块是 `[N,1]`),
|
4. MATLAB 写出的**分块维度可能与数据空间维度维数不同**(1 维数据集的分块是 `[N,1]`),
|
||||||
按"右侧补 1"对齐即可解码。
|
按"右侧补 1"对齐即可解码。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 本轮(2026-10-19):Release 产物 1.74MB → 796KB(只用 2 个真实原因)+ `.mat` 加载进度
|
||||||
|
|
||||||
|
用户报告:① 只加了一个格式支持,Release 二进制却从 ~787KB 涨到 **1.74MB**,要求压回 **< 800KB**;
|
||||||
|
② 加载 `.mat` 时遮罩层的「已加载大小 / 总大小 / 百分比」都不更新,界面只是原地等待。
|
||||||
|
|
||||||
|
### A. ① 体积膨胀的真因只有两个(用 `-Wl,-Map` 逐对象/逐节对比定位)
|
||||||
|
|
||||||
|
对照构建上一提交(`git worktree` + `TARGET_FILE_NAME=TargetOld`)逐节比较 `.text/.data/.rdata/...`:
|
||||||
|
|
||||||
|
1. **`<filesystem>`(致命,+~900KB)**:`MatH5::Open` 为了取文件大小用了
|
||||||
|
`std::filesystem::file_size`。MinGW 的 libstdc++ 会因此链入
|
||||||
|
**`fs_path.o` / `fs_dir.o` / `fs_ops.o` / `c++locale.o` / `codecvt.o` / `collate_members*` /
|
||||||
|
`cow-wstring-inst.o` / `cxx11-wlocale-inst.o` / `fstream-inst.o` / `cp-demangle.o`** 等
|
||||||
|
整套 locale/iostream 机器 —— 成员数 415 → 538,`.text` 615KB → 654KB,且把
|
||||||
|
`.bss` 里 64KB 的静态表挤成 `.data`(`.data` 从 1.8KB 涨到 73.8KB)。
|
||||||
|
修法:改用 `_wfopen_s` + `_fseeki64/_ftelli64` 取大小(零依赖)。
|
||||||
|
2. **`-lz`(~46KB)**:只需要「解开 HDF5 分块」这一功能,不值得链整个 zlib。
|
||||||
|
修法:`MatH5.cpp` 内置一份紧凑 inflate(RFC1950/1951,含固定/动态 Huffman、
|
||||||
|
存储块、长度-距离回抄),同时把 `-lz` 从 Makefile 与探针脚本移除。
|
||||||
|
|
||||||
|
顺带修掉一处**体积隐患**:`UploadHaloLayer` 里 64KB 的预乘 alpha 查表被换成
|
||||||
|
`(v*a+128)*257>>16` 的乘法-移位(无除法、无表)。⚠️ 若将来开 `-fdata-sections`,
|
||||||
|
函数内静态数组会被放进 `.data$_ZZ...`(**实测凭空 +72KB 文件体积**),这条也一并避开了。
|
||||||
|
|
||||||
|
### B. 剩下 ~40KB 的正当成本与最终压缩手段
|
||||||
|
|
||||||
|
去掉上面两项后仍是 825,856(旧版 786,944,+38.9KB = MatH5 15.4KB + MatLoader 12.5KB +
|
||||||
|
对齐/导入表,属"新增一个格式解析器"的正常成本)。要压到 <800KB,采用两项**不改变行为与观感**的措施:
|
||||||
|
|
||||||
|
1. **图标资源无损重编码**:`Resources/JustDataInspector.ico` 里 64/48/32/16 四帧原本是
|
||||||
|
未压缩的 32bpp DIB(合计 32KB),改用 GDI+ 重编码为 PNG 帧(ICO 的 PNG 条目 Vista+
|
||||||
|
全支持,本文件本身就有 256 PNG 帧):**43,334 → 17,404 字节**。
|
||||||
|
像素逐点校验:差异像素 ≤85 个、最大分量差 ≤3(GDI+ 编码取整),肉眼不可辨;
|
||||||
|
`matui` 探针从**真实产物**里 `LoadImageW` 逐一取 16/32/48/64 帧,全部 ok。
|
||||||
|
2. **`RELEASE_OPT` 由 `-Os` 改为 `-Oz`**(省 ~6KB)。实测动画帧 p50 7.73ms(-Os) vs
|
||||||
|
7.77ms(-Oz),无可辨识差异(本项目热点全在 GDI/GDI+ 绘制)。
|
||||||
|
|
||||||
|
⚠️ 试过并**放弃**的手段:`-ffunction-sections -fdata-sections + -Wl,--gc-sections` 反而 +9KB
|
||||||
|
(分节后每函数 16 字节对齐 + 静态量落 `.data`);LTO 在该工具链上 **ICE** 崩溃;
|
||||||
|
`-fno-asynchronous-unwind-tables` 链接失败;`-fno-exceptions` 会破坏 `DataModel` 的
|
||||||
|
`catch (std::bad_alloc)`。
|
||||||
|
|
||||||
|
最终:**`Build/Release/Target.exe` = 795,648 字节**(< 800KB,约 4.4KB 余量)。
|
||||||
|
|
||||||
|
### C. ② `.mat` 加载进度不更新
|
||||||
|
|
||||||
|
真因:`LoadMatSourceGroups` 只在**结束时**回调一次 `ratio=1.0` 且从不设置 `hasBytes`;
|
||||||
|
读文件(42MB)与解码 19 个样本流(解压 + 拷贝,占大头)期间进度恒为 0 ⇒ 遮罩层
|
||||||
|
一直显示 0.00%、总大小未知。
|
||||||
|
|
||||||
|
修法(`MatH5` + `MatLoader`):
|
||||||
|
- `MatHdf5::Open` 新增 `OpenProgress(bytesRead,totalBytes)`,**按 4MB 分块读入并回报**,
|
||||||
|
读完即得到"已加载 = 总大小";
|
||||||
|
- `MatLoader` 用 `ProgressReporter` 把进度拆成 `0.00–0.55 读文件`(`hasBytes=true`)
|
||||||
|
与 `0.55–0.98 解码样本流`(每解完一个流推进 `done/total`),末段 1.0;
|
||||||
|
- 取消(回调返回 false)与出错分开处理:取消时清空 `error`,不弹错误框。
|
||||||
|
|
||||||
|
实测(`matcheck`):回调 **29 次**、其中字节数变化 **11 次**、比例单调、终值 1.000、
|
||||||
|
`bytes=42,386,344/42,386,344`(= 文件大小);`matui` 进一步断言
|
||||||
|
`Application` 的 `m_loadingBytesRead == m_loadingTotalBytes == 文件大小`(遮罩层拿到的值)。
|
||||||
|
|
||||||
|
### D. 验证(全部离屏,不占用鼠标/键盘/屏幕)
|
||||||
|
|
||||||
|
- `matcheck` **21/21**(新增进度断言)、`matui` **15/15**(新增图标 + 加载字节断言);
|
||||||
|
- 回归:`verify` 49/49、`panelanim` 158/158、`gridanim` 93/93、`hoverdrag` 20/20、
|
||||||
|
`lanescroll` 23/23、`datacheck` 69/69、`edgecheck` 46/46、`errcheck` 19/19、
|
||||||
|
`halocheck` 16/16、`panflash` 19/19、`panelstutter` 8/8、`stress` 6/6;
|
||||||
|
- `make release` / `make debug` 零警告;`Build/Release/Target.exe` 795,648 字节。
|
||||||
|
|
||||||
|
### E. 教训(新增规则)
|
||||||
|
|
||||||
|
1. **MinGW 下 `#include <filesystem>` 是体积炸弹**(连带 locale/iostream/demangle 整套,
|
||||||
|
+0.9MB);只需要"取文件大小"时用 `_wfopen_s + _fseeki64`。
|
||||||
|
2. **为一个功能链一整条库前先算账**:`-lz` 46KB vs 自带 inflate 3~4KB;
|
||||||
|
能用几十行自实现的就不要链。
|
||||||
|
3. **判断二进制膨胀要看节表**(`objdump -h` + `-Wl,-Map` 逐对象求和),
|
||||||
|
只看 `.o` 大小会被"归档成员/对齐/`.data` 里的静态表"骗过去。
|
||||||
|
4. **图标资源可以无损重编码**(DIB 帧 → PNG 帧),同一份像素省 60% 体积;
|
||||||
|
用探针从**真实产物** `LoadImageW` 逐尺寸校验,而不是只看文件能编译。
|
||||||
|
5. **任何"长时间解析"都要按阶段回报进度**(读字节 + 每个解码单元),
|
||||||
|
且 `hasBytes` 只在真正有字节可报的阶段置真,否则遮罩层会停在 0%。
|
||||||
@@ -11,7 +11,9 @@ SUB_DIR_RELEASE = Release
|
|||||||
SUB_DIR_DEBUG = Debug
|
SUB_DIR_DEBUG = Debug
|
||||||
|
|
||||||
# Release 优化等级
|
# Release 优化等级
|
||||||
RELEASE_OPT = -Os
|
# ⚠️ 用 `-Oz` 而不是 `-Os`:本项目热点全在 GDI/GDI+ 绘制上,`-Oz` 与 `-Os` 的实测帧耗时
|
||||||
|
# 没有可辨识差异,但产物小 ~6KB(用户 2026-10-19 要求 Release 二进制 < 800KB)。
|
||||||
|
RELEASE_OPT = -Oz
|
||||||
|
|
||||||
# Debug 优化等级
|
# Debug 优化等级
|
||||||
DEBUG_OPT = -O0
|
DEBUG_OPT = -O0
|
||||||
@@ -57,7 +59,6 @@ LIB_LINK = \
|
|||||||
-ld3d11 \
|
-ld3d11 \
|
||||||
-ldwmapi \
|
-ldwmapi \
|
||||||
-ld3dcompiler \
|
-ld3dcompiler \
|
||||||
-lz \
|
|
||||||
|
|
||||||
# C定义
|
# C定义
|
||||||
C_DEFS = \
|
C_DEFS = \
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 17 KiB |
@@ -39,7 +39,9 @@ namespace jdi
|
|||||||
*
|
*
|
||||||
* 当前支持的格式:
|
* 当前支持的格式:
|
||||||
* * CSV 文本(`.csv` / `.txt`):支持多个 `time` 列(多时间轴)与各时间轴不等长;
|
* * CSV 文本(`.csv` / `.txt`):支持多个 `time` 列(多时间轴)与各时间轴不等长;
|
||||||
* * ASAM MDF v4(`.mf4` / `.mdf`):每个通道组(CG)是一个时间轴。
|
* * ASAM MDF v4(`.mf4` / `.mdf`):每个通道组(CG)是一个时间轴;
|
||||||
|
* * MATLAB 7.3 `.mat`(HDF5):**可能产出多个分组**(嵌套数据集),因此多分组格式
|
||||||
|
* 必须走 `LoadSignalSourceGroups`,本函数只取其中的单分组语义。
|
||||||
*
|
*
|
||||||
* @param [IN] filePath 文件完整路径。
|
* @param [IN] filePath 文件完整路径。
|
||||||
* @param [OUT] source 解析结果(若干时间轴块)。
|
* @param [OUT] source 解析结果(若干时间轴块)。
|
||||||
|
|||||||
+16
-2
@@ -2,6 +2,7 @@
|
|||||||
#define __MAT_H5_HPP__
|
#define __MAT_H5_HPP__
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -77,8 +78,21 @@ namespace jdi
|
|||||||
bool ok = false;
|
bool ok = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @brief 打开文件(一次性读入内存;`.mat` 通常几十 MB,可接受)。 */
|
/** @brief 读文件进度回调:`已读字节 / 总字节`。返回 false 表示请求取消。 */
|
||||||
bool Open(const std::wstring& path, std::wstring& error);
|
using OpenProgress = std::function<bool(std::uint64_t bytesRead, std::uint64_t totalBytes)>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 打开文件(一次性读入内存;`.mat` 通常几十 MB,可接受)。
|
||||||
|
*
|
||||||
|
* ⚠️ 按块读取并回调 `progress`:加载遮罩层的"已加载大小 / 总大小 / 百分比"
|
||||||
|
* 全靠它驱动。省略回调时行为不变。
|
||||||
|
* @param [IN] path 文件路径。
|
||||||
|
* @param [OUT] error 失败原因。
|
||||||
|
* @param [IN] progress 读取进度回调(可为空)。
|
||||||
|
* @return 成功返回 true;用户取消时也返回 false。
|
||||||
|
*/
|
||||||
|
bool Open(const std::wstring& path, std::wstring& error,
|
||||||
|
const OpenProgress& progress = OpenProgress());
|
||||||
void Close();
|
void Close();
|
||||||
|
|
||||||
/** @brief 根分组地址。 */
|
/** @brief 根分组地址。 */
|
||||||
|
|||||||
+399
-18
@@ -1,12 +1,9 @@
|
|||||||
// HDF5 只读子集:MATLAB 7.3 `.mat` 解析用(见 MatH5.hpp 的说明)。
|
// HDF5 只读子集:MATLAB 7.3 `.mat` 解析用(见 MatH5.hpp 的说明)。
|
||||||
#include "MatH5.hpp"
|
#include "MatH5.hpp"
|
||||||
|
|
||||||
#include <zlib.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <filesystem>
|
|
||||||
|
|
||||||
namespace jdi
|
namespace jdi
|
||||||
{
|
{
|
||||||
@@ -31,6 +28,359 @@ namespace jdi
|
|||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================================================================
|
||||||
|
// 内置 inflate(RFC 1950 / 1951)
|
||||||
|
//
|
||||||
|
// ⚠️ **不能用 zlib(`-lz`)**:链接 libz.a 会让 Release 产物多出约 46KB,
|
||||||
|
// 而这里只需要「解压 HDF5 分块」这一个功能,自带的紧凑实现只要几 KB。
|
||||||
|
// ⚠️ 更不能为了取文件大小包含 `<filesystem>`:它会连带拉进 locale / codecvt /
|
||||||
|
// iostream / cp-demangle 等一整套 libstdc++ 代码,实测让产物从 787KB 涨到 1.7MB。
|
||||||
|
// ==================================================================
|
||||||
|
|
||||||
|
/** @brief 位流读取器(LSB 优先,与 DEFLATE 一致)。 */
|
||||||
|
struct BitReader
|
||||||
|
{
|
||||||
|
const std::uint8_t* data = nullptr;
|
||||||
|
std::size_t size = 0;
|
||||||
|
std::size_t position = 0;
|
||||||
|
std::uint32_t buffer = 0;
|
||||||
|
int bits = 0;
|
||||||
|
bool error = false;
|
||||||
|
|
||||||
|
bool Fill(int count)
|
||||||
|
{
|
||||||
|
while (bits < count)
|
||||||
|
{
|
||||||
|
if (position >= size)
|
||||||
|
{
|
||||||
|
error = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
buffer |= static_cast<std::uint32_t>(data[position++]) << bits;
|
||||||
|
bits += 8;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t Read(int count)
|
||||||
|
{
|
||||||
|
if (count <= 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!Fill(count))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const std::uint32_t value = buffer & ((1u << count) - 1u);
|
||||||
|
buffer >>= count;
|
||||||
|
bits -= count;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Align() { buffer = 0; bits = 0; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @brief 规范 Huffman 表(只存计数与按长度排好序的符号)。 */
|
||||||
|
struct HuffmanTable
|
||||||
|
{
|
||||||
|
std::uint16_t count[16] = {};
|
||||||
|
std::uint16_t symbol[320] = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
bool BuildHuffman(HuffmanTable& table, const std::uint8_t* lengths, int total)
|
||||||
|
{
|
||||||
|
for (int index = 0; index < 16; ++index)
|
||||||
|
{
|
||||||
|
table.count[index] = 0;
|
||||||
|
}
|
||||||
|
for (int index = 0; index < total; ++index)
|
||||||
|
{
|
||||||
|
++table.count[lengths[index]];
|
||||||
|
}
|
||||||
|
if (table.count[0] == total)
|
||||||
|
{
|
||||||
|
return true; // 全部长度为 0:该表没有任何码(动态块里可能是距离表)
|
||||||
|
}
|
||||||
|
int left = 1;
|
||||||
|
for (int length = 1; length < 16; ++length)
|
||||||
|
{
|
||||||
|
left <<= 1;
|
||||||
|
left -= table.count[length];
|
||||||
|
if (left < 0)
|
||||||
|
{
|
||||||
|
return false; // 超订(over-subscribed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::uint16_t offsets[16] = {};
|
||||||
|
offsets[1] = 0;
|
||||||
|
for (int length = 1; length < 15; ++length)
|
||||||
|
{
|
||||||
|
offsets[length + 1] = static_cast<std::uint16_t>(offsets[length] + table.count[length]);
|
||||||
|
}
|
||||||
|
for (int index = 0; index < total; ++index)
|
||||||
|
{
|
||||||
|
if (lengths[index] != 0)
|
||||||
|
{
|
||||||
|
table.symbol[offsets[lengths[index]]++] = static_cast<std::uint16_t>(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DecodeSymbol(BitReader& reader, const HuffmanTable& table)
|
||||||
|
{
|
||||||
|
int code = 0;
|
||||||
|
int first = 0;
|
||||||
|
int index = 0;
|
||||||
|
for (int length = 1; length < 16; ++length)
|
||||||
|
{
|
||||||
|
code |= static_cast<int>(reader.Read(1));
|
||||||
|
if (reader.error)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
const int count = table.count[length];
|
||||||
|
if (code - first < count)
|
||||||
|
{
|
||||||
|
return table.symbol[index + (code - first)];
|
||||||
|
}
|
||||||
|
index += count;
|
||||||
|
first = (first + count) << 1;
|
||||||
|
code <<= 1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::uint16_t kLengthBase[29] = {3, 4, 5, 6, 7, 8, 9, 10, 11, 13,
|
||||||
|
15, 17, 19, 23, 27, 31, 35, 43, 51, 59,
|
||||||
|
67, 83, 99, 115, 131, 163, 195, 227, 258};
|
||||||
|
const std::uint8_t kLengthExtra[29] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2,
|
||||||
|
2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0};
|
||||||
|
const std::uint16_t kDistanceBase[30] = {1, 2, 3, 4, 5, 7, 9, 13,
|
||||||
|
17, 25, 33, 49, 65, 97, 129, 193,
|
||||||
|
257, 385, 513, 769, 1025, 1537, 2049, 3073,
|
||||||
|
4097, 6145, 8193, 12289, 16385, 24577};
|
||||||
|
const std::uint8_t kDistanceExtra[30] = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6,
|
||||||
|
6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13};
|
||||||
|
|
||||||
|
/** @brief 解压 zlib 流(含 2 字节头与 4 字节 Adler32 尾)。 */
|
||||||
|
bool Inflate(const std::uint8_t* source, std::size_t sourceSize,
|
||||||
|
std::vector<std::uint8_t>& out)
|
||||||
|
{
|
||||||
|
if (sourceSize < 6)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// zlib 头:CMF/FLG。只接受 deflate(CM = 8),且不接受预设字典。
|
||||||
|
const std::uint8_t cmf = source[0];
|
||||||
|
const std::uint8_t flg = source[1];
|
||||||
|
if ((cmf & 0x0F) != 8 || ((static_cast<unsigned>(cmf) * 256u + flg) % 31u) != 0 ||
|
||||||
|
(flg & 0x20) != 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
BitReader reader;
|
||||||
|
reader.data = source + 2;
|
||||||
|
reader.size = sourceSize - 6; // 末尾 4 字节是 Adler32
|
||||||
|
out.clear();
|
||||||
|
HuffmanTable fixedLiterals;
|
||||||
|
HuffmanTable fixedDistances;
|
||||||
|
bool fixedReady = false;
|
||||||
|
|
||||||
|
for (int block = 0; block < 100000; ++block)
|
||||||
|
{
|
||||||
|
const std::uint32_t final = reader.Read(1);
|
||||||
|
const std::uint32_t type = reader.Read(2);
|
||||||
|
if (reader.error)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (type == 0)
|
||||||
|
{
|
||||||
|
// 存储块(未压缩):对齐后 4 字节长度 + 反码 + 原始字节。
|
||||||
|
reader.Align();
|
||||||
|
if (reader.position + 4 > reader.size)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const std::uint16_t length = static_cast<std::uint16_t>(
|
||||||
|
reader.data[reader.position] | (reader.data[reader.position + 1] << 8));
|
||||||
|
reader.position += 4;
|
||||||
|
if (reader.position + length > reader.size)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.insert(out.end(), reader.data + reader.position,
|
||||||
|
reader.data + reader.position + length);
|
||||||
|
reader.position += length;
|
||||||
|
}
|
||||||
|
else if (type == 1 || type == 2)
|
||||||
|
{
|
||||||
|
HuffmanTable literals;
|
||||||
|
HuffmanTable distances;
|
||||||
|
if (type == 1)
|
||||||
|
{
|
||||||
|
if (!fixedReady)
|
||||||
|
{
|
||||||
|
std::uint8_t lengths[288];
|
||||||
|
for (int index = 0; index < 144; ++index)
|
||||||
|
{
|
||||||
|
lengths[index] = 8;
|
||||||
|
}
|
||||||
|
for (int index = 144; index < 256; ++index)
|
||||||
|
{
|
||||||
|
lengths[index] = 9;
|
||||||
|
}
|
||||||
|
for (int index = 256; index < 280; ++index)
|
||||||
|
{
|
||||||
|
lengths[index] = 7;
|
||||||
|
}
|
||||||
|
for (int index = 280; index < 288; ++index)
|
||||||
|
{
|
||||||
|
lengths[index] = 8;
|
||||||
|
}
|
||||||
|
if (!BuildHuffman(fixedLiterals, lengths, 288))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::uint8_t distanceLengths[30];
|
||||||
|
for (int index = 0; index < 30; ++index)
|
||||||
|
{
|
||||||
|
distanceLengths[index] = 5;
|
||||||
|
}
|
||||||
|
if (!BuildHuffman(fixedDistances, distanceLengths, 30))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fixedReady = true;
|
||||||
|
}
|
||||||
|
literals = fixedLiterals;
|
||||||
|
distances = fixedDistances;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const int literalCount = static_cast<int>(reader.Read(5)) + 257;
|
||||||
|
const int distanceCount = static_cast<int>(reader.Read(5)) + 1;
|
||||||
|
const int codeCount = static_cast<int>(reader.Read(4)) + 4;
|
||||||
|
if (reader.error || literalCount > 286 || distanceCount > 30)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
static const std::uint8_t kOrder[19] = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5,
|
||||||
|
11, 4, 12, 3, 13, 2, 14, 1, 15};
|
||||||
|
std::uint8_t codeLengths[19] = {};
|
||||||
|
for (int index = 0; index < codeCount; ++index)
|
||||||
|
{
|
||||||
|
codeLengths[kOrder[index]] = static_cast<std::uint8_t>(reader.Read(3));
|
||||||
|
}
|
||||||
|
HuffmanTable codeTable;
|
||||||
|
if (reader.error || !BuildHuffman(codeTable, codeLengths, 19))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::uint8_t lengths[288 + 30] = {};
|
||||||
|
int produced = 0;
|
||||||
|
while (produced < literalCount + distanceCount)
|
||||||
|
{
|
||||||
|
const int symbol = DecodeSymbol(reader, codeTable);
|
||||||
|
if (symbol < 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (symbol < 16)
|
||||||
|
{
|
||||||
|
lengths[produced++] = static_cast<std::uint8_t>(symbol);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int repeat = 0;
|
||||||
|
std::uint8_t value = 0;
|
||||||
|
if (symbol == 16)
|
||||||
|
{
|
||||||
|
if (produced == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
value = lengths[produced - 1];
|
||||||
|
repeat = 3 + static_cast<int>(reader.Read(2));
|
||||||
|
}
|
||||||
|
else if (symbol == 17)
|
||||||
|
{
|
||||||
|
repeat = 3 + static_cast<int>(reader.Read(3));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
repeat = 11 + static_cast<int>(reader.Read(7));
|
||||||
|
}
|
||||||
|
if (reader.error || produced + repeat > literalCount + distanceCount)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (int index = 0; index < repeat; ++index)
|
||||||
|
{
|
||||||
|
lengths[produced++] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!BuildHuffman(literals, lengths, literalCount) ||
|
||||||
|
!BuildHuffman(distances, lengths + literalCount, distanceCount))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int symbolIndex = 0; symbolIndex < 100000000; ++symbolIndex)
|
||||||
|
{
|
||||||
|
const int symbol = DecodeSymbol(reader, literals);
|
||||||
|
if (symbol < 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (symbol < 256)
|
||||||
|
{
|
||||||
|
out.push_back(static_cast<std::uint8_t>(symbol));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (symbol == 256)
|
||||||
|
{
|
||||||
|
break; // 块结束
|
||||||
|
}
|
||||||
|
const int lengthCode = symbol - 257;
|
||||||
|
if (lengthCode >= 29)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const std::size_t length =
|
||||||
|
kLengthBase[lengthCode] + reader.Read(kLengthExtra[lengthCode]);
|
||||||
|
const int distanceCode = DecodeSymbol(reader, distances);
|
||||||
|
if (distanceCode < 0 || distanceCode >= 30 || reader.error)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const std::size_t distance =
|
||||||
|
kDistanceBase[distanceCode] + reader.Read(kDistanceExtra[distanceCode]);
|
||||||
|
if (reader.error || distance == 0 || distance > out.size())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const std::size_t start = out.size() - distance;
|
||||||
|
for (std::size_t index = 0; index < length; ++index)
|
||||||
|
{
|
||||||
|
out.push_back(out[start + index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false; // 类型 3 非法
|
||||||
|
}
|
||||||
|
if (final != 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
std::uint8_t MatHdf5::U8(std::uint64_t offset) const
|
std::uint8_t MatHdf5::U8(std::uint64_t offset) const
|
||||||
@@ -67,27 +417,56 @@ namespace jdi
|
|||||||
return address + m_base;
|
return address + m_base;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MatHdf5::Open(const std::wstring& path, std::wstring& error)
|
bool MatHdf5::Open(const std::wstring& path, std::wstring& error, const OpenProgress& progress)
|
||||||
{
|
{
|
||||||
error.clear();
|
error.clear();
|
||||||
Close();
|
Close();
|
||||||
std::error_code code;
|
// ⚠️ 取文件大小一律用 C 运行库(`_fseeki64` / `_ftelli64`),**不要用 `<filesystem>`**:
|
||||||
const std::uintmax_t size = std::filesystem::file_size(path, code);
|
// 后者会把 libstdc++ 的 locale / codecvt / iostream / cp-demangle 整套拉进产物,
|
||||||
if (code || size == 0)
|
// 实测让 Release 二进制从 787KB 涨到 1.7MB(用户 2026-10-19 报告)。
|
||||||
{
|
|
||||||
error = L"无法读取文件大小。";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
m_data.resize(static_cast<std::size_t>(size));
|
|
||||||
FILE* file = nullptr;
|
FILE* file = nullptr;
|
||||||
if (_wfopen_s(&file, path.c_str(), L"rb") != 0 || file == nullptr)
|
if (_wfopen_s(&file, path.c_str(), L"rb") != 0 || file == nullptr)
|
||||||
{
|
{
|
||||||
error = L"无法打开文件。";
|
error = L"无法打开文件。";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const std::size_t read = std::fread(m_data.data(), 1, m_data.size(), file);
|
if (_fseeki64(file, 0, SEEK_END) != 0)
|
||||||
|
{
|
||||||
std::fclose(file);
|
std::fclose(file);
|
||||||
if (read != m_data.size())
|
error = L"无法读取文件大小。";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const long long size = _ftelli64(file);
|
||||||
|
if (size <= 0 || _fseeki64(file, 0, SEEK_SET) != 0)
|
||||||
|
{
|
||||||
|
std::fclose(file);
|
||||||
|
error = L"无法读取文件大小。";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_data.resize(static_cast<std::size_t>(size));
|
||||||
|
|
||||||
|
// 按块读入并回报进度(加载遮罩层的"已加载大小 / 总大小 / 百分比"由它驱动)。
|
||||||
|
constexpr std::size_t kChunk = 4u << 20;
|
||||||
|
std::size_t offset = 0;
|
||||||
|
while (offset < m_data.size())
|
||||||
|
{
|
||||||
|
const std::size_t want = std::min(kChunk, m_data.size() - offset);
|
||||||
|
const std::size_t got = std::fread(m_data.data() + offset, 1, want, file);
|
||||||
|
offset += got;
|
||||||
|
if (got != want)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (progress && !progress(offset, m_data.size()))
|
||||||
|
{
|
||||||
|
std::fclose(file);
|
||||||
|
Close();
|
||||||
|
error.clear(); // 用户取消:不显示错误
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::fclose(file);
|
||||||
|
if (offset != m_data.size())
|
||||||
{
|
{
|
||||||
error = L"读取文件失败。";
|
error = L"读取文件失败。";
|
||||||
return false;
|
return false;
|
||||||
@@ -544,13 +923,15 @@ namespace jdi
|
|||||||
}
|
}
|
||||||
if (chunk.size() != expected)
|
if (chunk.size() != expected)
|
||||||
{
|
{
|
||||||
inflated.assign(expected, 0);
|
// 内置 inflate(不依赖 zlib,见文件头的说明)。
|
||||||
uLongf destLength = static_cast<uLongf>(expected);
|
if (!Inflate(chunk.data(), chunk.size(), inflated))
|
||||||
if (uncompress(inflated.data(), &destLength, chunk.data(),
|
|
||||||
static_cast<uLong>(chunk.size())) != Z_OK)
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (inflated.size() < expected)
|
||||||
|
{
|
||||||
|
inflated.resize(expected, 0);
|
||||||
|
}
|
||||||
source = inflated.data();
|
source = inflated.data();
|
||||||
sourceSize = expected;
|
sourceSize = expected;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,10 +143,44 @@ namespace jdi
|
|||||||
source.blocks.push_back(std::move(block));
|
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; /**< 总字节。 */
|
||||||
|
|
||||||
|
/** @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;
|
||||||
|
}
|
||||||
|
LoadProgressInfo info;
|
||||||
|
info.ratio = low + (high - low) * fraction;
|
||||||
|
// 总字节已知时一并上报(遮罩层据此显示“已加载 / 总大小”)。
|
||||||
|
info.hasBytes = (total > 0);
|
||||||
|
info.bytesRead = (total > 0) ? read : 0;
|
||||||
|
info.totalBytes = total;
|
||||||
|
return (*sink)(info);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/** @brief SDI(`SimulationData.Dataset`)结构的分组读取。 */
|
/** @brief SDI(`SimulationData.Dataset`)结构的分组读取。 */
|
||||||
bool LoadSdi(const MatHdf5& file, std::uint64_t refs, std::uint64_t sigstream,
|
bool LoadSdi(const MatHdf5& file, std::uint64_t refs, std::uint64_t sigstream,
|
||||||
const std::wstring& stem, std::vector<LoadedSourceGroup>& groups,
|
const std::wstring& stem, std::vector<LoadedSourceGroup>& groups,
|
||||||
std::wstring& error)
|
std::wstring& error, const ProgressReporter& reporter)
|
||||||
{
|
{
|
||||||
// 1) 枚举 `#refs#` 里的全部元素。
|
// 1) 枚举 `#refs#` 里的全部元素。
|
||||||
std::map<std::uint64_t, SdiElement> elements;
|
std::map<std::uint64_t, SdiElement> elements;
|
||||||
@@ -229,6 +263,16 @@ namespace jdi
|
|||||||
std::map<std::wstring, std::size_t> groupIndex;
|
std::map<std::wstring, std::size_t> groupIndex;
|
||||||
std::vector<std::vector<double>> streams; // 流编号 → 样本
|
std::vector<std::vector<double>> streams; // 流编号 → 样本
|
||||||
std::vector<bool> streamCached;
|
std::vector<bool> streamCached;
|
||||||
|
// 进度:`#sigstream#` 的总流数,用来把"解码样本流"这段映射成阶段内 0-1。
|
||||||
|
std::size_t streamTotal = 0;
|
||||||
|
for (const auto& member : file.Members(sigstream))
|
||||||
|
{
|
||||||
|
if (!member.first.empty() && member.first[0] != '#')
|
||||||
|
{
|
||||||
|
++streamTotal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::size_t streamDone = 0;
|
||||||
const auto streamFor = [&](int index) -> const std::vector<double>*
|
const auto streamFor = [&](int index) -> const std::vector<double>*
|
||||||
{
|
{
|
||||||
if (index < 0)
|
if (index < 0)
|
||||||
@@ -249,6 +293,14 @@ namespace jdi
|
|||||||
streams[static_cast<std::size_t>(index)].clear();
|
streams[static_cast<std::size_t>(index)].clear();
|
||||||
}
|
}
|
||||||
streamCached[static_cast<std::size_t>(index)] = true;
|
streamCached[static_cast<std::size_t>(index)] = true;
|
||||||
|
// ⚠️ 必须在这里回报进度:读一个 42MB 的 `.mat` 时,"解码样本流"才是真正的
|
||||||
|
// 耗时大头(每个流都要解压 + 拷贝);不报进度的话界面会停在原地等
|
||||||
|
//(用户 2026-10-19 报告:加载 `.mat` 时已加载大小与百分比都不更新)。
|
||||||
|
++streamDone;
|
||||||
|
reporter.Report(streamTotal > 0
|
||||||
|
? static_cast<double>(streamDone) /
|
||||||
|
static_cast<double>(streamTotal)
|
||||||
|
: 1.0);
|
||||||
}
|
}
|
||||||
return streams[static_cast<std::size_t>(index)].empty()
|
return streams[static_cast<std::size_t>(index)].empty()
|
||||||
? nullptr
|
? nullptr
|
||||||
@@ -421,17 +473,51 @@ namespace jdi
|
|||||||
error.clear();
|
error.clear();
|
||||||
groups.clear();
|
groups.clear();
|
||||||
MatHdf5 file;
|
MatHdf5 file;
|
||||||
if (!file.Open(filePath, error))
|
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
// 进度拆成三段(用户 2026-10-19 报告:加载 `.mat` 时已加载大小与百分比都不更新,
|
||||||
|
// 界面只是原地等待):
|
||||||
|
// 0.00 – 0.55 读文件字节(遮罩层显示"已加载 / 总大小")
|
||||||
|
// 0.55 – 0.98 解码样本流(每个流解码后推进,含解压与拷贝)
|
||||||
|
// 0.98 – 1.00 收尾
|
||||||
|
// 读文件阶段把 `hasBytes` 置真;之后字节数已满,只推进百分比。
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
ProgressReporter readReporter;
|
||||||
|
readReporter.sink = &progress;
|
||||||
|
readReporter.low = 0.0;
|
||||||
|
readReporter.high = 0.55;
|
||||||
|
const MatHdf5::OpenProgress openProgress =
|
||||||
|
[&readReporter](std::uint64_t bytesRead, std::uint64_t totalBytes) -> bool
|
||||||
{
|
{
|
||||||
|
readReporter.read = bytesRead;
|
||||||
|
readReporter.total = totalBytes;
|
||||||
|
return readReporter.Report(totalBytes > 0 ? static_cast<double>(bytesRead) /
|
||||||
|
static_cast<double>(totalBytes)
|
||||||
|
: 0.0);
|
||||||
|
};
|
||||||
|
if (!file.Open(filePath, error, openProgress))
|
||||||
|
{
|
||||||
|
return false; // 用户取消时 `error` 为空
|
||||||
|
}
|
||||||
|
ProgressReporter parseReporter;
|
||||||
|
parseReporter.sink = &progress;
|
||||||
|
parseReporter.low = 0.55;
|
||||||
|
parseReporter.high = 0.98;
|
||||||
|
parseReporter.read = readReporter.total;
|
||||||
|
parseReporter.total = 0; // 字节已读完:本阶段只报百分比
|
||||||
|
if (!parseReporter.Report(0.0))
|
||||||
|
{
|
||||||
|
error.clear();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::wstring stem = StemOf(filePath);
|
const std::wstring stem = StemOf(filePath);
|
||||||
|
|
||||||
const std::uint64_t refs = file.Member(file.Root(), "#refs#");
|
const std::uint64_t refs = file.Member(file.Root(), "#refs#");
|
||||||
const std::uint64_t sigstream = file.Member(file.Root(), "#sigstream#");
|
const std::uint64_t sigstream = file.Member(file.Root(), "#sigstream#");
|
||||||
if (refs != 0 && sigstream != 0)
|
if (refs != 0 && sigstream != 0)
|
||||||
{
|
{
|
||||||
if (!LoadSdi(file, refs, sigstream, stem, groups, error))
|
if (!LoadSdi(file, refs, sigstream, stem, groups, error, parseReporter))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -461,6 +547,9 @@ namespace jdi
|
|||||||
{
|
{
|
||||||
LoadProgressInfo info;
|
LoadProgressInfo info;
|
||||||
info.ratio = 1.0;
|
info.ratio = 1.0;
|
||||||
|
info.hasBytes = (readReporter.total > 0);
|
||||||
|
info.bytesRead = readReporter.total;
|
||||||
|
info.totalBytes = readReporter.total;
|
||||||
progress(info);
|
progress(info);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Reference in new issue
Block a user