use std::collections::HashMap;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct AssetHandle<T> {
inner: Arc<AssetInner<T>>,
}
#[derive(Debug)]
struct AssetInner<T> {
data: T,
path: String,
#[allow(dead_code)]
modified: bool,
}
impl<T> AssetHandle<T> {
pub fn get(&self) -> &T {
&self.inner.data
}
pub fn path(&self) -> &str {
&self.inner.path
}
pub fn ref_count(&self) -> usize {
Arc::strong_count(&self.inner)
}
}
impl<T> std::ops::Deref for AssetHandle<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner.data
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AssetType {
Image,
Texture,
Font,
Sound,
Text,
Data,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LoadStatus {
NotLoaded,
Loading(f32),
Loaded,
Failed,
}
#[derive(Debug, Clone)]
pub struct AssetConfig {
pub root: String,
pub hot_reload: bool,
}
impl Default for AssetConfig {
fn default() -> Self {
Self {
root: "assets".to_string(),
hot_reload: cfg!(debug_assertions),
}
}
}
pub struct AssetManager {
config: AssetConfig,
images: HashMap<String, AssetHandle<ImageAsset>>,
#[allow(dead_code)]
loading_queue: Vec<String>,
total_bytes: usize,
}
#[derive(Debug, Clone)]
pub struct ImageAsset {
pub width: u32,
pub height: u32,
pub pixels: Vec<u8>,
pub format: PixelFormat,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PixelFormat {
Rgba8,
Rgb8,
Grayscale8,
}
impl ImageAsset {
pub fn new(width: u32, height: u32) -> Self {
Self {
width,
height,
pixels: vec![0; (width * height * 4) as usize],
format: PixelFormat::Rgba8,
}
}
pub fn get_pixel(&self, x: u32, y: u32) -> (u8, u8, u8, u8) {
let idx = ((y * self.width + x) * 4) as usize;
if idx + 3 >= self.pixels.len() {
return (0, 0, 0, 0);
}
(self.pixels[idx], self.pixels[idx + 1], self.pixels[idx + 2], self.pixels[idx + 3])
}
pub fn set_pixel(&mut self, x: u32, y: u32, r: u8, g: u8, b: u8, a: u8) {
let idx = ((y * self.width + x) * 4) as usize;
if idx + 3 < self.pixels.len() {
self.pixels[idx] = r;
self.pixels[idx + 1] = g;
self.pixels[idx + 2] = b;
self.pixels[idx + 3] = a;
}
}
pub fn sub_image(&self, x: u32, y: u32, w: u32, h: u32) -> ImageAsset {
let mut sub = ImageAsset::new(w, h);
for py in 0..h {
for px in 0..w {
let (r, g, b, a) = self.get_pixel(x + px, y + py);
sub.set_pixel(px, py, r, g, b, a);
}
}
sub
}
pub fn flip_horizontal(&mut self) {
let row_bytes = self.width as usize * 4;
for y in 0..self.height {
let row_start = (y as usize) * row_bytes;
let row: &mut [u8] = &mut self.pixels[row_start..row_start + row_bytes];
row.chunks_exact_mut(4).for_each(|pixel| pixel.reverse());
}
}
pub fn flip_vertical(&mut self) {
let row_bytes = self.width as usize * 4;
let total_rows = self.height as usize;
for y in 0..total_rows / 2 {
let top = y * row_bytes;
let bottom = (total_rows - 1 - y) * row_bytes;
let (top_slice, bottom_slice) =
self.pixels.split_at_mut(bottom);
let top_row = &mut top_slice[top..top + row_bytes];
let bottom_row = &mut bottom_slice[..row_bytes];
top_row.swap_with_slice(bottom_row);
}
}
}
impl AssetManager {
pub fn new(config: AssetConfig) -> Self {
Self {
config,
images: HashMap::new(),
loading_queue: Vec::new(),
total_bytes: 0,
}
}
fn normalize_path(&self, path: &str) -> String {
let path = path.trim_start_matches('/').trim_start_matches('\\');
format!("{}/{}", self.config.root, path)
}
pub fn load_image(&mut self, path: &str) -> Result<AssetHandle<ImageAsset>, String> {
let normalized = self.normalize_path(path);
if let Some(handle) = self.images.get(&normalized) {
return Ok(handle.clone());
}
let asset = ImageAsset::new(1, 1); let bytes = asset.pixels.len();
self.total_bytes += bytes;
let handle = AssetHandle {
inner: Arc::new(AssetInner {
data: asset,
path: normalized.clone(),
modified: false,
}),
};
self.images.insert(normalized, handle.clone());
Ok(handle)
}
pub fn unload_image(&mut self, path: &str) {
let normalized = self.normalize_path(path);
if let Some(handle) = self.images.remove(&normalized) {
self.total_bytes -= handle.get().pixels.len();
}
}
pub fn check_hot_reload(&mut self) {
if !self.config.hot_reload {
return;
}
}
pub fn total_image_bytes(&self) -> usize {
self.total_bytes
}
pub fn image_count(&self) -> usize {
self.images.len()
}
pub fn clear(&mut self) {
self.images.clear();
self.total_bytes = 0;
}
}
#[derive(Debug, Clone)]
pub struct SpriteSheet {
pub image: AssetHandle<ImageAsset>,
pub columns: u32,
pub rows: u32,
pub frame_width: f32,
pub frame_height: f32,
}
impl SpriteSheet {
pub fn new(image: AssetHandle<ImageAsset>, columns: u32, rows: u32) -> Self {
let frame_width = image.width as f32 / columns as f32;
let frame_height = image.height as f32 / rows as f32;
Self {
image,
columns,
rows,
frame_width,
frame_height,
}
}
pub fn frame_rect(&self, index: u32) -> crate::math::Rect {
let col = index % self.columns;
let row = index / self.columns;
crate::math::Rect::new(
col as f32 * self.frame_width,
row as f32 * self.frame_height,
self.frame_width,
self.frame_height,
)
}
pub fn cell_rect(&self, row: u32, col: u32) -> crate::math::Rect {
crate::math::Rect::new(
col as f32 * self.frame_width,
row as f32 * self.frame_height,
self.frame_width,
self.frame_height,
)
}
pub fn frame_count(&self) -> u32 {
self.columns * self.rows
}
}