#assets #executable #file #binary #filename

include_assets

在Rust可执行文件中包含压缩资源

1个稳定版本

1.0.0 2023年6月2日

#433压缩

Download history 46/week @ 2024-03-11 55/week @ 2024-03-18 60/week @ 2024-03-25 35/week @ 2024-04-01 48/week @ 2024-04-08 24/week @ 2024-04-15 42/week @ 2024-04-22 33/week @ 2024-04-29 47/week @ 2024-05-06 40/week @ 2024-05-13 23/week @ 2024-05-20 24/week @ 2024-05-27 6/week @ 2024-06-03 25/week @ 2024-06-10 48/week @ 2024-06-17 71/week @ 2024-06-24

每月下载 152
用于 asp_gui

LGPL-3.0-only

46KB
432 代码行

include_assets 在您的可执行文件中

include_assets 提供了将资源(任意文件)包含到Rust二进制文件中的便捷方式。资源被压缩,可以通过文件名或枚举的变体来查找。

按名称包含资源

这可能是最直接的方法。使用宏 include_dir!() 包含目录中的所有文件,在运行时使用 NamedArchive::load 加载(解压缩)资源。一旦加载,就可以像使用 NamedArchive 一样使用它们,基本上就像使用一个 HashMap<&str, &[u8]>.

请参阅文档examples/named/src/main.rs中的示例。

按枚举变体包含资源

这种方法可能有点不寻常。声明一个枚举,每个资源一个单位变体,并使用此crate提供的derive宏推导出 EnumAsset 特性。使用 EnumArchive::<MyEnum>::load()(将 MyEnum 替换为您为枚举选择的任何名称)加载未压缩的资源。然后通过索引(&archive[MyAsset::SomeVariant])查找资源数据 - 这是无法失败的!

这种方法有两个明显的优势

  • 您不能意外使用不在可执行文件中包含的任何资源:如果您尝试这样做,则会导致编译时错误。
  • 如果您在二进制文件中包含了一个资源但从未使用它(即从未构造相应的枚举变体),则会引起编译时警告。

一个缺点是无法遍历资源。此外,它们的名称在运行时会消失。

尽管缺乏迭代,但可以使用 AssetEnum::map 来映射资源数据。然后可以在生成的 EnumMap 中通过枚举变体查找资源。这对于同质资源可能很有用,例如解析模板、解码声音/图像文件等。请注意,同一个程序中可以有多个 EnumAsset;如果你有不同的枚举用于不同类型的资源,映射将更有用。

有关示例,请参阅文档examples/enums/src/main.rs

构建脚本

如果您想在资源更改时重新构建可执行文件,应使用类似于以下内容的 build.rs

fn main() {
    println!("cargo:rerun-if-changed=path/to/assets");
    println!("cargo:rerun-if-changed=more/assets");
}

许可证

此软件包根据LGPL v3许可。

压缩

目前支持:zstd、lz4、deflate、无压缩。

校验和

在编译时,为每个资源计算校验和。这些校验和包含在二进制文件中。在加载/解压缩资源时,将解压缩资源的校验和与编译时校验和进行比较,以防止数据损坏和(更重要的是)错误。

目前,使用blake2b进行此操作,但将来可能会更改。

限制

在运行时,主内存需要足够大,以便同时以压缩和解压缩形式存储所有资源。在编译时,主内存需要足够大,以便同时以压缩形式存储所有资源,并以解压缩形式存储两倍。

每个资源归档的总大小不能超过 u32::MAX(4 GiB)。每个资源归档最多可以包含 u32::MAX(大约4e9)个不同的资源。如果您的用例超过这些限制,请重新考虑这种方法是否真的合适。

需要 usize 至少32位宽。

Rust core 包含了 include_bytes! 宏,该宏允许包含单个文件(未压缩)。

有几个软件包允许包含压缩文件,甚至目录。

据我所知,此软件包是唯一一个将包含的文件作为一个整体压缩而不是单独压缩的软件包。这种方法有一个重大的缺点:为了解压缩单个文件,必须解压缩所有文件。然而,它导致了更好的压缩,因为压缩算法可以利用文件之间的相似性,而不仅仅是每个文件内部的相似性。

未来工作

  • 编译时间很糟糕。我不确定 include_bytes! 宏是如何工作的,但它 可能 不会仅仅将一个巨大的字节串放入AST。我非常愿意将数据写入 OUT_DIR,然后使用 include_bytes! 包含该blob,但这 在过程宏中不可用。作为一种替代方案,可能(更)有用的是从 build.rs 生成代码和压缩blob,然后从主代码中包含它。
  • 如果资产很大,解压缩可能会比较慢。调查 zstandard(以及 lz4)字典压缩可能值得。在编译时,可以通过分析每个资产来创建字典。然后,可以使用这个字典独立压缩每个资产。希望这不会导致文件比一起压缩后大得多。这种方法的优点是,运行时压缩可以使用多个线程并行执行。或者,可以根据需要解压缩每个文件,但这不是这个 crate 的当前目标。
  • 在压缩之前根据内容对资产进行去重可能很有用。压缩显然可以减小文件大小,但前提是冗余文件之间没有太大差距。这可以通过一层间接实现:资产名称映射到 blob id,blob id 映射到数据范围。目前,去重最好在应用程序代码中实现:archive.get(override_asset).unwrap_or_else(|| archive[fallback_asset])
  • 提供使用其他校验和算法的选项可能很有用。可能的选项:CRC 用于更小的哈希值,SHA256 用于(可能)更快的哈希(特殊的 CPU 指令!)[u8; 0] 以有效地禁用检查。选择最好通过功能标志来处理。
  • 宏的错误处理需要一些改进,但这被稳定 Rust 允许 proc 宏诊断所阻止。

贡献

错误报告非常受欢迎。功能请求也欢迎,但没有任何承诺。我目前不计划接受补丁。

依赖项

~1.1–9.5MB
~87K SLoC