1use crate::font::Font;
2use crate::image::{ColorSpace, Image};
3use crate::mesh::Mesh;
4use crate::sky::Sky;
5use crate::source::Source;
6use sha2::{Digest, Sha256};
7use std::collections::BTreeMap;
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
10pub struct Handle(u32);
11
12#[derive(Clone, Debug)]
13pub enum Asset {
14 Mesh(Mesh),
15 Image(Image),
16 Sky(Sky),
17 Font(Font),
18}
19
20#[derive(Clone, Copy, PartialEq, Eq)]
21enum Kind {
22 Mesh,
23 Png,
24 Rgba16 {
25 width: u32,
26 height: u32,
27 space: ColorSpace,
28 },
29 Sky,
30 Font,
31}
32
33struct Entry {
34 hash: [u8; 32],
35 kind: Kind,
36 asset: Asset,
37}
38
39pub struct Cache {
40 index: BTreeMap<[u8; 32], u32>,
41 entries: Vec<Entry>,
42}
43
44impl Cache {
45 pub fn new() -> Self {
46 Cache {
47 index: BTreeMap::new(),
48 entries: Vec::new(),
49 }
50 }
51
52 pub fn len(&self) -> usize {
53 self.entries.len()
54 }
55
56 pub fn is_empty(&self) -> bool {
57 self.entries.is_empty()
58 }
59
60 pub fn get(&self, handle: Handle) -> Option<&Asset> {
61 self.entries
62 .get(handle.0 as usize)
63 .map(|entry| &entry.asset)
64 }
65
66 pub fn digest(&self, handle: Handle) -> Option<[u8; 32]> {
67 self.entries.get(handle.0 as usize).map(|entry| entry.hash)
68 }
69
70 pub fn load_mesh(&mut self, bytes: &[u8]) -> Result<Handle, String> {
71 self.insert(bytes, Kind::Mesh, || Ok(Asset::Mesh(Mesh::parse(bytes)?)))
72 }
73
74 pub fn load_mesh_from(&mut self, source: &Source, name: &str) -> Result<Handle, String> {
75 let bytes = source.read(name)?;
76 self.insert(&bytes, Kind::Mesh, || {
77 Ok(Asset::Mesh(Mesh::parse_with(&bytes, |uri| {
78 source.read_beside(name, uri)
79 })?))
80 })
81 }
82
83 pub fn load_png(&mut self, bytes: &[u8]) -> Result<Handle, String> {
84 self.insert(bytes, Kind::Png, || Ok(Asset::Image(Image::png(bytes)?)))
85 }
86
87 pub fn load_rgba16(
88 &mut self,
89 bytes: &[u8],
90 width: u32,
91 height: u32,
92 space: ColorSpace,
93 ) -> Result<Handle, String> {
94 self.insert(
95 bytes,
96 Kind::Rgba16 {
97 width,
98 height,
99 space,
100 },
101 || Ok(Asset::Image(Image::rgba16(bytes, width, height, space)?)),
102 )
103 }
104
105 pub fn load_sky(&mut self, bytes: &[u8]) -> Result<Handle, String> {
106 self.insert(bytes, Kind::Sky, || Ok(Asset::Sky(Sky::parse(bytes)?)))
107 }
108
109 pub fn load_font(&mut self, bytes: &[u8]) -> Result<Handle, String> {
110 self.insert(bytes, Kind::Font, || Ok(Asset::Font(Font::parse(bytes)?)))
111 }
112
113 fn insert(
114 &mut self,
115 bytes: &[u8],
116 kind: Kind,
117 build: impl FnOnce() -> Result<Asset, String>,
118 ) -> Result<Handle, String> {
119 let hash = sha256(bytes);
120 if let Some(&index) = self.index.get(&hash) {
121 return if self.entries[index as usize].kind == kind {
122 Ok(Handle(index))
123 } else {
124 Err("the same bytes are already cached as a different asset".into())
125 };
126 }
127 let asset = build()?;
128 let index =
129 u32::try_from(self.entries.len()).map_err(|_| "the cache is full".to_string())?;
130 self.index.insert(hash, index);
131 self.entries.push(Entry { hash, kind, asset });
132 Ok(Handle(index))
133 }
134}
135
136impl Default for Cache {
137 fn default() -> Self {
138 Self::new()
139 }
140}
141
142pub fn sha256(bytes: &[u8]) -> [u8; 32] {
143 let digest = Sha256::digest(bytes);
144 let mut hash = [0u8; 32];
145 hash.copy_from_slice(&digest);
146 hash
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152 use crate::Asset;
153 use std::path::Path;
154
155 const FILES: &[(&str, &[u8])] = &[
156 ("triangle.glb", include_bytes!("../tests/data/triangle.glb")),
157 (
158 "triangle.gltf",
159 include_bytes!("../tests/data/triangle.gltf"),
160 ),
161 ("swatch8.png", include_bytes!("../tests/data/swatch8.png")),
162 ("sky.hdr", include_bytes!("../tests/data/sky.hdr")),
163 ("family.ttf", include_bytes!("../tests/data/family.ttf")),
164 ];
165
166 #[test]
167 fn the_same_bytes_return_the_same_handle() {
168 let glb = include_bytes!("../tests/data/triangle.glb");
169 let mut cache = Cache::new();
170 assert!(cache.load_mesh(b"nope").is_err());
171 assert!(cache.is_empty());
172 let first = cache.load_mesh(glb).unwrap();
173 let second = cache.load_mesh(glb).unwrap();
174 assert_eq!(first, second);
175 assert_eq!(cache.len(), 1);
176 assert_eq!(cache.digest(first), Some(sha256(glb)));
177 match cache.get(first) {
178 Some(Asset::Mesh(mesh)) => assert_eq!(mesh.materials[0].name, "ink"),
179 other => panic!("mesh, got {other:?}"),
180 }
181
182 let png = include_bytes!("../tests/data/swatch8.png");
183 let image = cache.load_png(png).unwrap();
184 assert_ne!(image, first);
185 assert!(cache.load_mesh(png).is_err());
186 assert_eq!(cache.len(), 2);
187
188 let embedded = Source::embedded(FILES);
189 let folder = Source::folder(Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/data"));
190 let from_binary = cache.load_mesh_from(&embedded, "triangle.glb").unwrap();
191 let from_folder = cache.load_mesh_from(&folder, "triangle.glb").unwrap();
192 assert_eq!(from_binary, first);
193 assert_eq!(from_folder, first);
194
195 let sky = cache
196 .load_sky(include_bytes!("../tests/data/sky.hdr"))
197 .unwrap();
198 let font = cache
199 .load_font(include_bytes!("../tests/data/family.ttf"))
200 .unwrap();
201 let raw = cache
202 .load_rgba16(
203 include_bytes!("../tests/data/swatch.rgba16"),
204 2,
205 2,
206 ColorSpace::Linear,
207 )
208 .unwrap();
209 assert_eq!(
210 cache
211 .load_sky(include_bytes!("../tests/data/sky.hdr"))
212 .unwrap(),
213 sky
214 );
215 assert_eq!(
216 cache
217 .load_font(include_bytes!("../tests/data/family.ttf"))
218 .unwrap(),
219 font
220 );
221 assert_eq!(
222 cache
223 .load_rgba16(
224 include_bytes!("../tests/data/swatch.rgba16"),
225 2,
226 2,
227 ColorSpace::Linear
228 )
229 .unwrap(),
230 raw
231 );
232 assert!(
233 cache
234 .load_rgba16(
235 include_bytes!("../tests/data/swatch.rgba16"),
236 4,
237 1,
238 ColorSpace::Linear
239 )
240 .is_err()
241 );
242 let sidecar = cache.load_mesh_from(&folder, "triangle.gltf").unwrap();
243 match cache.get(sidecar) {
244 Some(Asset::Mesh(mesh)) => assert_eq!(mesh.groups[0].name, "sheet"),
245 other => panic!("mesh, got {other:?}"),
246 }
247 }
248}