summaryrefslogtreecommitdiff
path: root/textures/src/lib.rs
blob: c1b56a8c041a8825bea089029f848c4362093fc5 (plain)
use std::collections::HashMap;
use std::path::Path;
use std::sync::Arc;

use exun::{RawUnexpected, ResultErrorExt};
use image::RgbaImage;
use packer::{RectanglePacker, TextureAtlas};

type ExunResult<T> = Result<T, RawUnexpected>;

pub struct TextureManager {
	textures: HashMap<Arc<str>, TextureMetadata>,
	loaded_textures: HashMap<Arc<str>, Arc<RgbaImage>>,
	packer: RectanglePacker,
	target_size: usize,
	current_size: usize,
}

struct TextureMetadata {
	path: Arc<Path>,
	size: usize,
}

impl TextureManager {
	pub fn new(target_size: usize) -> Self {
		Self {
			textures: HashMap::new(),
			loaded_textures: HashMap::new(),
			packer: RectanglePacker::new(),
			target_size,
			current_size: 0,
		}
	}

	pub fn add_texture(&mut self, name: Arc<str>, path: Arc<Path>, size: usize) {
		self.textures.insert(name, TextureMetadata { path, size });
	}

	pub fn try_preload(&mut self, name: Arc<str>) -> ExunResult<Option<bool>> {
		let Some(metadata) = self.textures.get(&name) else {
			return Ok(None);
		};

		if self.current_size + metadata.size < self.target_size {
			self.load(name)?;
			Ok(Some(true))
		} else {
			Ok(Some(false))
		}
	}

	pub fn load(&mut self, name: Arc<str>) -> ExunResult<Option<()>> {
		let Some(metadata) = self.textures.get(&name) else {
			return Ok(None);
		};

		self.current_size += metadata.size;
		let texture = image::open(&metadata.path).unexpect()?.to_rgba8();
		self.loaded_textures.insert(name, Arc::new(texture));

		Ok(Some(()))
	}

	pub fn unload(&mut self, name: Arc<str>) -> Option<()> {
		self.loaded_textures.remove(&name)?;
		Some(())
	}

	pub fn pack_texture(&mut self, name: Arc<str>) -> ExunResult<()> {
		if !self.loaded_textures.contains_key(&name) {
			self.load(name.clone())?;
		}

		self.packer.add_texture(
			name.clone(),
			self.loaded_textures
				.get(&name)
				.expect("we checked to make sure it was loaded")
				.clone(),
		);

		Ok(())
	}

	pub fn reset_packer(&mut self) {
		self.packer = RectanglePacker::new();
	}

	pub fn reload_texture_atlas(
		&mut self,
		min_width: u32,
		min_height: u32,
	) -> ExunResult<TextureAtlas> {
		self.packer.output(min_width, min_height)
	}
}