ReliefMath 文件与持久化¶
.rmath v1 格式¶
.rmath 是显式编码的 little-endian 字节流;实现不会把 C++ 结构体直接写到磁盘。文件总长度必须与头部的 total_bytes 完全相等,不能包含尾随数据。文件上限为 16 MiB,解码只有在所有字段、UTF-8、记录数量、稳定 ID、样式限制和保留位均通过验证后才替换当前 Document。
顺序:头部; 字段:magic; 宽度:8 字节; v1 要求:
RMATH\r\n\0顺序:头部; 字段:version; 宽度:u32; v1 要求:
1顺序:头部; 字段:total_bytes; 宽度:u32; v1 要求:精确等于整个文件长度
顺序:头部; 字段:formula_count; 宽度:u32; v1 要求:等于后续记录数,最多 256
顺序:头部; 字段:title_bytes; 宽度:u32; v1 要求:UTF-8 标题长度,最多 256 字节
顺序:头部; 字段:next_id; 宽度:u64; v1 要求:下一个稳定公式 ID
顺序:头部; 字段:title; 宽度:
title_bytes; v1 要求:UTF-8 字节,不含 NUL顺序:每条记录; 字段:id; 宽度:u64; v1 要求:非零且在文件内唯一
顺序:每条记录; 字段:name_bytes; 宽度:u32; v1 要求:名称长度,最多 256 字节
顺序:每条记录; 字段:source_bytes; 宽度:u32; v1 要求:源码长度,最多 64 KiB
顺序:每条记录; 字段:rgba; 宽度:u32; v1 要求:
RRGGBBAA顺序:每条记录; 字段:point_size; 宽度:u16; v1 要求:按模型样式范围验证
顺序:每条记录; 字段:padding; 宽度:u16; v1 要求:按模型样式范围验证
顺序:每条记录; 字段:display; 宽度:u8; v1 要求:仅
0或1顺序:每条记录; 字段:reserved; 宽度:7 字节; v1 要求:必须全部为零
顺序:每条记录; 字段:name、source; 宽度:变长; v1 要求:分别紧随本记录头部,均为 UTF-8 且不含 NUL
头部固定为 32 字节,每条记录头固定为 32 字节。revision、运行期 generation、渲染缓存和路径不持久化。版本不受支持、长度不一致、截断、重复 ID、非法 UTF-8、非零保留位或越界字段都会显式失败;失败时目标 Document 和文件戳保持原值。
读取、冲突与原子保存¶
read_document(path, document, stamp, error, control) 在临时字节缓冲区和临时 Document 中完成读取、摘要与验证,成功后才提交输出。文件戳包含设备号、inode、大小、修改/状态变更时间及内容摘要。符号链接、非普通文件和越界文件会被拒绝。
atomic_save(path, document, expected, new_stamp, error, control) 先检查目标是否仍与 expected 一致,再在目标同目录以独占创建方式写临时文件。写入会处理 EINTR 和短写;文件数据同步后重新核对冲突,最后在 rename 前再次采样取消状态。成功 rename 后同步父目录。rename 前失败会删除临时文件并保留原目标;父目录同步失败则返回 Code::Io,错误明确说明目标已替换但目录持久化未确认,且 new_stamp 会报告已安装文件。
IoControl 可选地在调用线程中提供 cancelled(context) 和 progress(context, phase, completed, total)。读写按最多 64 KiB 分块并在分块间检查取消;原子保存另在 rename 前检查。rename 完成后不会响应取消,而会继续同步父目录并按实际结果报告。两个回调都可为 null,默认调用不需要控制对象。调用者应在后台工作线程使用这些同步 API;回调也在该线程执行,不会调用 FLTK。
冲突检查只能检测戳支持的变化。没有配合锁的外部写入者仍可能在最终检查与 rename 之间修改目标,因此此接口不声称跨程序 compare-and-swap;覆盖冲突应由上层显式处理。
独立 raw 字节保存 API atomic_save_bytes() 上限为 128 MiB,以容纳协议导出的单工件(当前 PNG 和 RGBA 上限各为 64 MiB,SVG 为 16 MiB)。此上限不改变 .rmath 的 16 MiB 编解码限制。
恢复快照¶
恢复目录使用绝对 XDG_STATE_HOME/reliefmath/recovery;未设置有效绝对 XDG_STATE_HOME 时使用 $HOME/.local/state/reliefmath/recovery。没有有效绝对 HOME 时返回明确错误。目录路径逐级拒绝符号链接,最终目录归当前用户所有并设为 0700;快照与实例锁为当前用户的 0600 普通文件。
目录初始化在每次创建子目录后先 fsync 其父目录,再打开下一级;已存在的目录项也同步,覆盖失败重试或并发创建后留下的未确认状态。最终目录设为 0700 后再次同步其元数据。同步遇到 EINTR 会重试,其他失败返回 Code::Io,不提交输出路径或已打开的 RecoveryStore;失败可能留下目录,后续重试仍须完成同步。
每个实例以独立 ID 和 flock 锁占有自己的快照。另一个仍持锁的实例不可被列为可恢复项或接管;进程崩溃后锁释放,遗留快照可被发现。快照封装版本、精确长度、仅作显示的原文件路径提示和一个完整 .rmath v1 文档,并受 128 MiB raw 文件上限约束;内嵌文档仍受 16 MiB 限制。
恢复只把快照加载为新 Document,路径提示不会触发原文件覆盖。取消恢复保留快照;只有显式 discard(id, error) 才丢弃指定项。实例正常关闭时可调用 discard_own(error) 清理自身快照;没有自身快照时该调用成功且不影响其他实例。删除后目录同步失败会报告快照已移除、持久化未确认。
RecoveryStore 由单一工作线程独占访问时可安全调用同步的 open、save、list、load、discard 和 discard_own。UI 应在对应 job 完成后读取 items();此容器本身不提供并发同步。恢复保存与加载接受同一可选 IoControl,供后台 job 取消及报告进度。