Files
Just-DataInspector/Utilities/Inc/MatH5.hpp
T
DeepSeek-V4.1-Flash f44f8021e3 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 只在有字节时置真)。
2026-10-19 02:37:52 +08:00

160 lines
5.9 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.
#ifndef __MAT_H5_HPP__
#define __MAT_H5_HPP__
#include <cstdint>
#include <functional>
#include <string>
#include <utility>
#include <vector>
namespace jdi
{
/**
* @brief 只读的 HDF5 子集解析器(用于 MATLAB 7.3 的 `.mat` 文件)。
*
* MATLAB 7.3(`-v7.3`)的 `.mat` 其实是 **HDF5** 文件:数值矩阵是数据集,
* 结构体是分组(字段 = 成员),元信息放在属性里(如 `MATLAB_class`)。
* 本项目不引入 HDF5 库,因此这里实现一个**刚好够用**的只读子集:
*
* * 超级块 v0/v1/v2/v3;
* * 对象头 v1(含续块消息),v2 仅识别并跳过;
* * 分组成员:符号表(B-tree v1 + 本地堆)与 link 消息两种方式;
* * 数据空间 v1/v2、数据类型 v1(整数 / 浮点 / 定长字符串 / 引用 / 复合 / vlen);
* * 数据布局 v3(紧凑 / 连续 / 分块),分块数据集支持 deflate 压缩(zlib)。
*
* ⚠️ 不支持:外部数据文件、共享消息表、非 deflate 过滤器、极老的 v1 布局。
* 这些在 MATLAB 保存的文件里都不出现。
*/
class MatHdf5
{
public:
/** @brief 数据类型类别(对应 HDF5 的 class 编号)。 */
enum class TypeClass : int
{
Integer = 0,
Float = 1,
Time = 2,
String = 3,
Bitfield = 4,
Opaque = 5,
Compound = 6,
Reference = 7,
Enum = 8,
VariableLength = 9,
Array = 10
};
/** @brief 数据类型(只保留读取 MATLAB 数据所需的字段)。 */
struct Type
{
TypeClass cls = TypeClass::Opaque;
int version = 1;
std::size_t size = 0; /**< 单个元素占用的字节数。 */
bool isSigned = false; /**< 整数是否有符号。 */
bool bigEndian = false; /**< 字节序(MATLAB 一律小端)。 */
bool isVariableString = false; /**< vlen 字符串。 */
std::vector<std::pair<std::string, Type>> members; /**< 复合类型成员。 */
};
/** @brief 数据布局。 */
enum class LayoutClass
{
Compact = 0,
Contiguous = 1,
Chunked = 2
};
/** @brief 一个数据集的定义与数据。 */
struct Dataset
{
std::vector<std::size_t> dims; /**< 各维长度。 */
Type type; /**< 元素类型。 */
LayoutClass layout = LayoutClass::Compact;
std::vector<std::uint8_t> data; /**< 解码后的原始字节(行优先,按 `dims`)。 */
std::uint64_t chunkAddress = 0; /**< 分块数据集的 B-tree 地址。 */
std::vector<std::size_t> chunkDims;
bool deflate = false; /**< 是否使用了 deflate(zlib)过滤器。 */
bool ok = false;
};
/** @brief 读文件进度回调:`已读字节 / 总字节`。返回 false 表示请求取消。 */
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();
/** @brief 根分组地址。 */
std::uint64_t Root() const { return m_root; }
/** @brief 该对象是否为数据集(有数据空间消息)。 */
bool IsDataset(std::uint64_t address) const;
/** @brief 分组(或结构体)的成员:`名字 → 对象头地址`。 */
std::vector<std::pair<std::string, std::uint64_t>> Members(std::uint64_t address) const;
/** @brief 按名字取成员(不存在返回 0)。 */
std::uint64_t Member(std::uint64_t address, const std::string& name) const;
/** @brief 读取数据集定义(含解码后的原始数据)。 */
bool ReadDataset(std::uint64_t address, Dataset& out) const;
/** @brief 读取字符串属性(如 `MATLAB_class`、`MATLAB_fields`)。 */
std::string AttributeString(std::uint64_t address, const char* name) const;
/** @brief 读取 `char` 数据集为宽字符串(MATLAB 的 char 是 uint16)。 */
bool ReadText(std::uint64_t address, std::wstring& out) const;
/** @brief 读取数值数据集为 double 序列。 */
bool ReadNumbers(std::uint64_t address, std::vector<double>& out) const;
/** @brief 读取引用数据集(HDF5 对象引用 = 对象头地址,相对基地址)。 */
bool ReadReferences(std::uint64_t address, std::vector<std::uint64_t>& out) const;
private:
struct Message
{
std::uint16_t type = 0;
std::vector<std::uint8_t> data;
};
std::uint8_t U8(std::uint64_t offset) const;
std::uint16_t U16(std::uint64_t offset) const;
std::uint32_t U32(std::uint64_t offset) const;
std::uint64_t U64(std::uint64_t offset) const;
std::uint64_t Addr(std::uint64_t offset) const;
std::uint64_t Resolve(std::uint64_t address) const;
void ReadMessages(std::uint64_t address, std::vector<Message>& messages) const;
bool ParseType(const std::uint8_t* data, std::size_t size, std::size_t offset, Type& out,
std::size_t& consumed) const;
bool DecodeChunked(const Dataset& dataset, std::size_t elementSize,
std::vector<std::uint8_t>& out) const;
void WalkBtree(std::uint64_t node, std::uint64_t heapData,
std::vector<std::pair<std::string, std::uint64_t>>& out) const;
void WalkSymbolNode(std::uint64_t node, std::uint64_t heapData,
std::vector<std::pair<std::string, std::uint64_t>>& out) const;
std::vector<std::uint8_t> m_data;
std::uint64_t m_base = 0;
std::uint64_t m_root = 0;
int m_offsetSize = 8;
int m_lengthSize = 8;
mutable std::vector<std::pair<std::uint64_t, std::uint64_t>> m_heaps; /**< 堆地址 → 数据地址。 */
};
} // namespace jdi
#endif // __MAT_H5_HPP__