1use std::collections::HashMap;
14use std::sync::Arc;
15
16#[derive(Debug, Clone)]
24pub struct AssetHandle<T> {
25 inner: Arc<AssetInner<T>>,
26}
27
28#[derive(Debug)]
29struct AssetInner<T> {
30 data: T,
32 path: String,
34 #[allow(dead_code)]
36 modified: bool,
37}
38
39impl<T> AssetHandle<T> {
40 pub fn get(&self) -> &T {
42 &self.inner.data
43 }
44
45 pub fn path(&self) -> &str {
47 &self.inner.path
48 }
49
50 pub fn ref_count(&self) -> usize {
52 Arc::strong_count(&self.inner)
53 }
54}
55
56impl<T> std::ops::Deref for AssetHandle<T> {
57 type Target = T;
58 fn deref(&self) -> &Self::Target {
59 &self.inner.data
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
69pub enum AssetType {
70 Image,
72 Texture,
74 Font,
76 Sound,
78 Text,
80 Data,
82}
83
84#[derive(Debug, Clone, Copy, PartialEq)]
90pub enum LoadStatus {
91 NotLoaded,
93 Loading(f32),
95 Loaded,
97 Failed,
99}
100
101#[derive(Debug, Clone)]
107pub struct AssetConfig {
108 pub root: String,
110 pub hot_reload: bool,
112}
113
114impl Default for AssetConfig {
115 fn default() -> Self {
116 Self {
117 root: "assets".to_string(),
118 hot_reload: cfg!(debug_assertions),
119 }
120 }
121}
122
123pub struct AssetManager {
140 config: AssetConfig,
141 images: HashMap<String, AssetHandle<ImageAsset>>,
143 #[allow(dead_code)]
145 loading_queue: Vec<String>,
146 total_bytes: usize,
148}
149
150#[derive(Debug, Clone)]
152pub struct ImageAsset {
153 pub width: u32,
155 pub height: u32,
157 pub pixels: Vec<u8>,
159 pub format: PixelFormat,
161}
162
163#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165pub enum PixelFormat {
166 Rgba8,
168 Rgb8,
170 Grayscale8,
172}
173
174impl ImageAsset {
175 pub fn new(width: u32, height: u32) -> Self {
177 Self {
178 width,
179 height,
180 pixels: vec![0; (width * height * 4) as usize],
181 format: PixelFormat::Rgba8,
182 }
183 }
184
185 pub fn get_pixel(&self, x: u32, y: u32) -> (u8, u8, u8, u8) {
187 let idx = ((y * self.width + x) * 4) as usize;
188 if idx + 3 >= self.pixels.len() {
189 return (0, 0, 0, 0);
190 }
191 (self.pixels[idx], self.pixels[idx + 1], self.pixels[idx + 2], self.pixels[idx + 3])
192 }
193
194 pub fn set_pixel(&mut self, x: u32, y: u32, r: u8, g: u8, b: u8, a: u8) {
196 let idx = ((y * self.width + x) * 4) as usize;
197 if idx + 3 < self.pixels.len() {
198 self.pixels[idx] = r;
199 self.pixels[idx + 1] = g;
200 self.pixels[idx + 2] = b;
201 self.pixels[idx + 3] = a;
202 }
203 }
204
205 pub fn sub_image(&self, x: u32, y: u32, w: u32, h: u32) -> ImageAsset {
207 let mut sub = ImageAsset::new(w, h);
208 for py in 0..h {
209 for px in 0..w {
210 let (r, g, b, a) = self.get_pixel(x + px, y + py);
211 sub.set_pixel(px, py, r, g, b, a);
212 }
213 }
214 sub
215 }
216
217 pub fn flip_horizontal(&mut self) {
219 let row_bytes = self.width as usize * 4;
220 for y in 0..self.height {
221 let row_start = (y as usize) * row_bytes;
222 let row: &mut [u8] = &mut self.pixels[row_start..row_start + row_bytes];
223 row.chunks_exact_mut(4).for_each(|pixel| pixel.reverse());
224 }
225 }
226
227 pub fn flip_vertical(&mut self) {
229 let row_bytes = self.width as usize * 4;
230 let total_rows = self.height as usize;
231 for y in 0..total_rows / 2 {
232 let top = y * row_bytes;
233 let bottom = (total_rows - 1 - y) * row_bytes;
234 let (top_slice, bottom_slice) =
235 self.pixels.split_at_mut(bottom);
236 let top_row = &mut top_slice[top..top + row_bytes];
237 let bottom_row = &mut bottom_slice[..row_bytes];
238 top_row.swap_with_slice(bottom_row);
239 }
240 }
241}
242
243impl AssetManager {
244 pub fn new(config: AssetConfig) -> Self {
246 Self {
247 config,
248 images: HashMap::new(),
249 loading_queue: Vec::new(),
250 total_bytes: 0,
251 }
252 }
253
254 fn normalize_path(&self, path: &str) -> String {
256 let path = path.trim_start_matches('/').trim_start_matches('\\');
257 format!("{}/{}", self.config.root, path)
258 }
259
260 pub fn load_image(&mut self, path: &str) -> Result<AssetHandle<ImageAsset>, String> {
264 let normalized = self.normalize_path(path);
265
266 if let Some(handle) = self.images.get(&normalized) {
268 return Ok(handle.clone());
269 }
270
271 let asset = ImageAsset::new(1, 1); let bytes = asset.pixels.len();
275 self.total_bytes += bytes;
276
277 let handle = AssetHandle {
278 inner: Arc::new(AssetInner {
279 data: asset,
280 path: normalized.clone(),
281 modified: false,
282 }),
283 };
284
285 self.images.insert(normalized, handle.clone());
286 Ok(handle)
287 }
288
289 pub fn unload_image(&mut self, path: &str) {
291 let normalized = self.normalize_path(path);
292 if let Some(handle) = self.images.remove(&normalized) {
293 self.total_bytes -= handle.get().pixels.len();
294 }
295 }
296
297 pub fn check_hot_reload(&mut self) {
299 if !self.config.hot_reload {
300 return;
301 }
302 }
305
306 pub fn total_image_bytes(&self) -> usize {
308 self.total_bytes
309 }
310
311 pub fn image_count(&self) -> usize {
313 self.images.len()
314 }
315
316 pub fn clear(&mut self) {
318 self.images.clear();
319 self.total_bytes = 0;
320 }
321}
322
323#[derive(Debug, Clone)]
329pub struct SpriteSheet {
330 pub image: AssetHandle<ImageAsset>,
332 pub columns: u32,
334 pub rows: u32,
336 pub frame_width: f32,
338 pub frame_height: f32,
339}
340
341impl SpriteSheet {
342 pub fn new(image: AssetHandle<ImageAsset>, columns: u32, rows: u32) -> Self {
344 let frame_width = image.width as f32 / columns as f32;
345 let frame_height = image.height as f32 / rows as f32;
346 Self {
347 image,
348 columns,
349 rows,
350 frame_width,
351 frame_height,
352 }
353 }
354
355 pub fn frame_rect(&self, index: u32) -> crate::math::Rect {
357 let col = index % self.columns;
358 let row = index / self.columns;
359 crate::math::Rect::new(
360 col as f32 * self.frame_width,
361 row as f32 * self.frame_height,
362 self.frame_width,
363 self.frame_height,
364 )
365 }
366
367 pub fn cell_rect(&self, row: u32, col: u32) -> crate::math::Rect {
369 crate::math::Rect::new(
370 col as f32 * self.frame_width,
371 row as f32 * self.frame_height,
372 self.frame_width,
373 self.frame_height,
374 )
375 }
376
377 pub fn frame_count(&self) -> u32 {
379 self.columns * self.rows
380 }
381}