micro_games_kit/assets/
mod.rs

1pub 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(&registry)?)?;
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}