一、体积膨胀的真因(用 -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 只在有字节时置真)。
160 lines
5.9 KiB
C++
160 lines
5.9 KiB
C++
#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__
|