1mod assets;
6mod chunks;
7mod deserialize;
8mod file_reader;
9mod loader;
10mod serialize;
11
12use assets::{audio_clip::AudioClipAsset, sprite::SpriteAsset};
13use platform::{PixelFormat, Platform, AUDIO_CHANNELS};
14
15pub use assets::*;
16pub use chunks::{ChunkData, ChunkDescriptor, SpriteChunkData, SpriteChunkDescriptor};
17pub use deserialize::{deserialize, Deserialize};
18pub use file_reader::FileReader;
19pub use loader::ResourceLoader;
20pub use serialize::{serialize, Serialize};
21
22use crate::{
23 allocators::LinearAllocator,
24 collections::{FixedVec, SparseArray},
25};
26
27pub const RESOURCE_DB_MAGIC_NUMBER: u32 = 0xE97E6D00;
29pub const CHUNK_SIZE: u32 = 64 * 1024;
31pub const SPRITE_CHUNK_DIMENSIONS: (u16, u16) = (128, 128);
33pub const SPRITE_CHUNK_FORMAT: PixelFormat = PixelFormat::Rgba;
35
36pub const AUDIO_SAMPLES_PER_CHUNK: usize = CHUNK_SIZE as usize / size_of::<[i16; AUDIO_CHANNELS]>();
38
39#[derive(Clone, Copy)]
42pub struct ResourceDatabaseHeader {
43 pub chunks: u32,
45 pub sprite_chunks: u32,
47 pub sprites: u32,
49 pub audio_clips: u32,
51}
52
53impl ResourceDatabaseHeader {
54 pub const fn chunk_data_offset(&self) -> u64 {
59 use serialize::Serialize as Ser;
60 <ResourceDatabaseHeader as Ser>::SERIALIZED_SIZE as u64
61 + self.chunks as u64 * <ChunkDescriptor as Ser>::SERIALIZED_SIZE as u64
62 + self.sprite_chunks as u64 * <SpriteChunkDescriptor as Ser>::SERIALIZED_SIZE as u64
63 + self.sprites as u64 * <NamedAsset<SpriteAsset> as Ser>::SERIALIZED_SIZE as u64
64 + self.audio_clips as u64 * <NamedAsset<AudioClipAsset> as Ser>::SERIALIZED_SIZE as u64
65 }
66}
67
68pub struct ResourceDatabase {
73 sprites: FixedVec<'static, NamedAsset<SpriteAsset>>,
75 audio_clips: FixedVec<'static, NamedAsset<AudioClipAsset>>,
76 chunk_data_offset: u64,
78 chunk_descriptors: FixedVec<'static, ChunkDescriptor>,
79 sprite_chunk_descriptors: FixedVec<'static, SpriteChunkDescriptor>,
80 pub chunks: SparseArray<'static, ChunkData>,
84 pub sprite_chunks: SparseArray<'static, SpriteChunkData>,
87}
88
89impl ResourceDatabase {
90 pub(crate) fn new(
91 platform: &dyn Platform,
92 arena: &'static LinearAllocator,
93 file_reader: &mut FileReader,
94 max_loaded_chunks: u32,
95 max_loaded_sprite_chunks: u32,
96 ) -> Option<ResourceDatabase> {
97 profiling::function_scope!();
98 use Deserialize as De;
99 let header_size = <ResourceDatabaseHeader as De>::SERIALIZED_SIZE;
100
101 assert!(file_reader.push_read(0, header_size));
102 let header = file_reader
103 .pop_read(platform, true, |header_bytes| {
104 deserialize::<ResourceDatabaseHeader>(header_bytes, &mut 0)
105 })
106 .expect("resource database file should be readable");
107
108 let chunk_data_offset = header.chunk_data_offset();
109 let ResourceDatabaseHeader {
110 chunks,
111 sprite_chunks,
112 sprites,
113 audio_clips,
114 } = header;
115
116 let mut cursor = header_size;
117 let mut queue_read = |size: usize| {
118 assert!(file_reader.push_read(cursor as u64, size));
119 cursor += size;
120 };
121
122 queue_read(chunks as usize * <ChunkDescriptor as De>::SERIALIZED_SIZE);
123 queue_read(sprite_chunks as usize * <SpriteChunkDescriptor as De>::SERIALIZED_SIZE);
124 queue_read(sprites as usize * <NamedAsset<SpriteAsset> as De>::SERIALIZED_SIZE);
125 queue_read(audio_clips as usize * <NamedAsset<AudioClipAsset> as De>::SERIALIZED_SIZE);
126
127 let chunk_descriptors = deserialize_vec(arena, file_reader, platform)?;
129 let sprite_chunk_descriptors = deserialize_vec(arena, file_reader, platform)?;
130 let sprites = sorted(deserialize_vec(arena, file_reader, platform)?);
131 let audio_clips = sorted(deserialize_vec(arena, file_reader, platform)?);
132
133 Some(ResourceDatabase {
134 sprites,
135 audio_clips,
136 chunk_data_offset,
137 chunk_descriptors,
138 sprite_chunk_descriptors,
139 chunks: SparseArray::new(arena, chunks, max_loaded_chunks)?,
140 sprite_chunks: SparseArray::new(arena, sprite_chunks, max_loaded_sprite_chunks)?,
141 })
142 }
143
144 pub fn largest_chunk_source(&self) -> u64 {
148 let largest_chunk_source = (self.chunk_descriptors.iter())
149 .map(|chunk| chunk.source_bytes.end - chunk.source_bytes.start)
150 .max()
151 .unwrap_or(0);
152 let largest_sprite_chunk_source = (self.sprite_chunk_descriptors.iter())
153 .map(|chunk| chunk.source_bytes.end - chunk.source_bytes.start)
154 .max()
155 .unwrap_or(0);
156 largest_chunk_source.max(largest_sprite_chunk_source)
157 }
158}
159
160fn sorted<T: Ord>(mut input: FixedVec<'_, T>) -> FixedVec<'_, T> {
161 input.sort_unstable();
162 input
163}
164
165fn deserialize_vec<'a, D: Deserialize>(
166 alloc: &'a LinearAllocator,
167 file_reader: &mut FileReader,
168 platform: &dyn Platform,
169) -> Option<FixedVec<'a, D>> {
170 file_reader
171 .pop_read(platform, true, |src| {
172 let count = src.len() / D::SERIALIZED_SIZE;
173 let mut vec = FixedVec::new(alloc, count)?;
174 assert_eq!(0, vec.len() % D::SERIALIZED_SIZE);
175 for element_bytes in src.chunks_exact(D::SERIALIZED_SIZE) {
176 let Ok(_) = vec.push(D::deserialize(element_bytes)) else {
177 unreachable!()
178 };
179 }
180 Some(vec)
181 })
182 .expect("resource db file header should be readable")
183}
184
185pub use named_asset::{NamedAsset, ASSET_NAME_LENGTH};
186mod named_asset {
187 use core::cmp::Ordering;
188
189 use arrayvec::ArrayString;
190
191 pub const ASSET_NAME_LENGTH: usize = 27;
193
194 #[derive(Debug)]
202 pub struct NamedAsset<T> {
203 pub name: ArrayString<ASSET_NAME_LENGTH>,
205 pub asset: T,
207 }
208
209 impl<T> PartialEq for NamedAsset<T> {
210 fn eq(&self, other: &Self) -> bool {
211 self.name == other.name
212 }
213 }
214
215 impl<T> Eq for NamedAsset<T> {} impl<T> PartialOrd for NamedAsset<T> {
218 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
219 Some(self.cmp(other))
220 }
221 }
222
223 impl<T> Ord for NamedAsset<T> {
224 fn cmp(&self, other: &Self) -> Ordering {
225 self.name.cmp(&other.name)
226 }
227 }
228}