include_assets_decode/named.rs
1use crate::checksum;
2/// Named asset archives provide maps from path name to asset content.
3/// This crate contains functionality specific to this kind of asset archives.
4use crate::codec::Codec;
5
6use crate::common::{decompress_names, decompress_ranges, u32_to_usize, u32_to_usize_range};
7
8/// Compressed named archive
9///
10/// Contains the compressed asset data and all information required to uncompress it.
11///
12/// Users should only create these archives via the `include_dir!` macro and only read or access them via [`NamedArchive::load`].
13#[derive(Clone, Copy)]
14pub struct CompressedNamedArchive<C: Codec> {
15 /// Compression codec with which the data was compressed
16 pub codec: C,
17
18 /// Raw compressed data
19 pub data: &'static [u8],
20
21 /// Size of the data after decompression.
22 /// Limited to at most 4 GiB.
23 pub uncompressed_data_size: u32,
24
25 /// Names of the assets in some order, separated by null bytes (U+0000)
26 ///
27 /// The order needs to match the order of blobs in the uncompressed archive data.
28 /// The final name is _not_ null-terminated.
29 ///
30 /// Names are currently sorted such that all files in a directory are sorted.
31 /// This is for two reasons:
32 /// - It likely leads to better compression if all names with the same (path) prefix are close together, and
33 /// - It makes reproducible builds easier since we don't rely on file system iteration order.
34 pub compressed_names: &'static [u8],
35
36 /// Lengths of the uncompressed names (including separating null bytes)
37 pub uncompressed_names_size: u32,
38
39 /// List of asset checksums in the same order as [`CompressedNamedArchive::compressed_names`].
40 pub checksums: &'static [checksum::Checksum],
41
42 /// Compressed data sizes of the assets.
43 ///
44 /// Once uncompressed, these will be `u32`s (little endian) in the same order as [`CompressedNamedArchive::compressed_names`].
45 pub compressed_sizes: &'static [u8],
46}
47
48/// Unpacked archive of named assets
49///
50/// Can be used to look up assets by name (i.e. path).
51pub struct NamedArchive {
52 data: std::vec::Vec<u8>,
53 ranges: std::collections::HashMap<smartstring::SmartString<smartstring::LazyCompact>, std::ops::Range<u32>>,
54}
55
56impl NamedArchive {
57 /// Load (decompress) compressed asset archive at runtime
58 ///
59 /// # Panics
60 ///
61 /// Panics if loading fails.
62 /// This is only possible in the case of internal bugs, assuming that the compressed asset were created with the `include_dir!` macro.
63 pub fn load<C: Codec>(compressed: CompressedNamedArchive<C>) -> Self {
64 let CompressedNamedArchive {
65 codec,
66 data: compressed_data,
67 uncompressed_data_size,
68 compressed_names,
69 uncompressed_names_size,
70 checksums,
71 compressed_sizes,
72 } = compressed;
73
74 // decompress data
75 let data = codec.decompress_with_length(compressed_data, u32_to_usize(uncompressed_data_size));
76
77 // decompress names and data ranges
78 let names = decompress_names(&codec, compressed_names, uncompressed_names_size);
79 let ranges = decompress_ranges(&codec, compressed_sizes, checksums.len());
80 assert_eq!(names.len(), ranges.len(), "number of asset names should equal number of asset data ranges");
81
82 // Data ranges were constructed in decompress_ranges.
83 // We know that they are all non-overlapping, increasing, and don't leave any space.
84 // We know the first range starts at 0.
85 // The final range should end where the data ends.
86 assert_eq!(ranges.last().map(|range| range.end).unwrap_or(0), uncompressed_data_size);
87
88 let ranges: std::collections::HashMap<_, _> = names.into_iter().zip(ranges.into_iter()).collect();
89
90 Self { data, ranges }
91 }
92
93 /// Get the content of the asset with the given `name`.
94 ///
95 /// Returns `None` if the archive does not contain an asset with this `name`.
96 pub fn get<'a>(&'a self, name: &str) -> Option<&'a [u8]> {
97 self.ranges.get(name).map(|range| &self.data[u32_to_usize_range(range)])
98 }
99
100 /// Returns the number of assets included in the archive.
101 pub fn number_of_assets(&self) -> usize {
102 self.ranges.len()
103 }
104
105 /// Returns an iterator of all asset names and contents in unspecified order.
106 pub fn assets(&self) -> impl Iterator<Item = (&str, &[u8])> + ExactSizeIterator + '_ {
107 self.ranges.iter().map(|(name, range)| (name.as_ref(), &self.data[u32_to_usize_range(range)]))
108 }
109
110 /// Returns true if an asset with the given `name` is included in the archive.
111 pub fn contains(&self, name: &str) -> bool {
112 self.get(name).is_some()
113 }
114
115 /// Returns an iterator of all asset names in unspecified order.
116 pub fn names(&self) -> impl Iterator<Item = &str> + ExactSizeIterator + '_ {
117 self.ranges.keys().map(|s| s.as_ref())
118 }
119}
120
121impl<S: AsRef<str>> core::ops::Index<S> for NamedArchive {
122 type Output = [u8];
123
124 /// Return the contents of the asset with the given name.
125 /// Panics it the asset is not present.
126 fn index(&self, s: S) -> &[u8] {
127 match self.get(s.as_ref()) {
128 Some(data) => data,
129 None => panic!("asset '{}' not found", s.as_ref()),
130 }
131 }
132}