micro_games_kit/assets/
mod.rs1pub mod font;
2pub mod shader;
3pub mod sound;
4pub mod spine;
5pub mod texture;
6
7use crate::assets::{
8 font::FontAssetProtocol, shader::ShaderAssetProtocol, sound::SoundAssetProtocol,
9 spine::SpineAssetProtocol, texture::TextureAssetProtocol,
10};
11use keket::{
12 database::{path::AssetPath, AssetDatabase},
13 fetch::{
14 container::{ContainerAssetFetch, ContainerPartialFetch},
15 AssetFetch,
16 },
17 protocol::{bytes::BytesAssetProtocol, group::GroupAssetProtocol, text::TextAssetProtocol},
18};
19use serde::{Deserialize, Serialize};
20use std::{
21 collections::HashMap,
22 error::Error,
23 io::{Cursor, Read, Write},
24 ops::Range,
25 path::Path,
26};
27
28pub fn name_from_path<'a>(path: &'a AssetPath<'a>) -> &'a str {
29 path.meta_items()
30 .find(|(key, _)| *key == "as")
31 .map(|(_, value)| value)
32 .unwrap_or(path.path())
33}
34
35pub fn make_database(fetch: impl AssetFetch) -> AssetDatabase {
36 AssetDatabase::default()
37 .with_protocol(BytesAssetProtocol)
38 .with_protocol(TextAssetProtocol)
39 .with_protocol(GroupAssetProtocol)
40 .with_protocol(ShaderAssetProtocol)
41 .with_protocol(TextureAssetProtocol)
42 .with_protocol(FontAssetProtocol)
43 .with_protocol(SoundAssetProtocol)
44 .with_protocol(SpineAssetProtocol)
45 .with_fetch(fetch)
46}
47
48pub fn make_memory_database(package: &[u8]) -> Result<AssetDatabase, Box<dyn Error>> {
49 Ok(make_database(ContainerAssetFetch::new(
50 AssetPackage::decode(package)?,
51 )))
52}
53
54pub fn make_directory_database(
55 directory: impl AsRef<Path>,
56) -> Result<AssetDatabase, Box<dyn Error>> {
57 Ok(make_database(ContainerAssetFetch::new(
58 AssetPackage::from_directory(directory)?,
59 )))
60}
61
62#[derive(Debug, Default, Serialize, Deserialize)]
63struct AssetPackageRegistry {
64 mappings: HashMap<String, Range<usize>>,
65}
66
67#[derive(Default)]
68pub struct AssetPackage {
69 registry: AssetPackageRegistry,
70 content: Vec<u8>,
71}
72
73impl AssetPackage {
74 pub fn from_directory(directory: impl AsRef<Path>) -> Result<Self, Box<dyn Error>> {
75 fn visit_dirs(
76 dir: &Path,
77 root: &str,
78 registry: &mut AssetPackageRegistry,
79 content: &mut Cursor<Vec<u8>>,
80 ) -> std::io::Result<()> {
81 if dir.is_dir() {
82 for entry in std::fs::read_dir(dir)? {
83 let entry = entry?;
84 let path = entry.path();
85 let name = path.file_name().unwrap().to_str().unwrap();
86 let name = if root.is_empty() {
87 name.to_owned()
88 } else {
89 format!("{}/{}", root, name)
90 };
91 if path.is_dir() {
92 visit_dirs(&path, &name, registry, content)?;
93 } else {
94 let bytes = std::fs::read(path)?;
95 let start = content.position() as usize;
96 content.write_all(&bytes)?;
97 let end = content.position() as usize;
98 registry.mappings.insert(name, start..end);
99 }
100 }
101 }
102 Ok(())
103 }
104
105 let directory = directory.as_ref();
106 let mut registry = AssetPackageRegistry::default();
107 let mut content = Cursor::new(Vec::default());
108 visit_dirs(directory, "", &mut registry, &mut content)?;
109 Ok(AssetPackage {
110 registry,
111 content: content.into_inner(),
112 })
113 }
114
115 pub fn decode(bytes: &[u8]) -> Result<Self, Box<dyn Error>> {
116 let mut stream = Cursor::new(bytes);
117 let mut size = 0u32.to_be_bytes();
118 stream.read_exact(&mut size)?;
119 let size = u32::from_be_bytes(size) as usize;
120 let mut registry = vec![0u8; size];
121 stream.read_exact(&mut registry)?;
122 let registry = toml::from_str(std::str::from_utf8(®istry)?)?;
123 let mut content = Vec::default();
124 stream.read_to_end(&mut content)?;
125 Ok(Self { registry, content })
126 }
127
128 pub fn encode(&self) -> Result<Vec<u8>, Box<dyn Error>> {
129 let mut stream = Cursor::new(Vec::default());
130 let registry = toml::to_string(&self.registry)?;
131 let registry = registry.as_bytes();
132 stream.write_all(&(registry.len() as u32).to_be_bytes())?;
133 stream.write_all(registry)?;
134 stream.write_all(&self.content)?;
135 Ok(stream.into_inner())
136 }
137
138 pub fn paths(&self) -> impl Iterator<Item = &str> {
139 self.registry.mappings.keys().map(|key| key.as_str())
140 }
141}
142
143impl ContainerPartialFetch for AssetPackage {
144 fn load_bytes(&mut self, path: AssetPath) -> Result<Vec<u8>, Box<dyn Error>> {
145 if let Some(range) = self.registry.mappings.get(path.path()).cloned() {
146 if range.end <= self.content.len() {
147 Ok(self.content[range].to_owned())
148 } else {
149 Err(format!(
150 "Asset: `{}` out of content bounds! Bytes range: {:?}, content byte size: {}",
151 path,
152 range,
153 self.content.len()
154 )
155 .into())
156 }
157 } else {
158 Err(format!("Asset: `{}` not present in package!", path).into())
159 }
160 }
161}
162
163impl std::fmt::Debug for AssetPackage {
164 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165 f.debug_struct("AssetPackage")
166 .field("registry", &self.registry)
167 .finish_non_exhaustive()
168 }
169}