diff options
author | metamuffin <metamuffin@disroot.org> | 2025-02-20 14:50:39 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-02-20 14:50:39 +0100 |
commit | d4c9a59ed41c11bf3edccc37d531da46e4fcd218 (patch) | |
tree | 620e0cb9dd59b2b55682f84c0362723fba01a369 /client/src/skin_manager.rs | |
parent | d39a7d62e7bdc63b2055ad93bf511cdd1e40e69d (diff) | |
download | twclient-d4c9a59ed41c11bf3edccc37d531da46e4fcd218.tar twclient-d4c9a59ed41c11bf3edccc37d531da46e4fcd218.tar.bz2 twclient-d4c9a59ed41c11bf3edccc37d531da46e4fcd218.tar.zst |
can render map
Diffstat (limited to 'client/src/skin_manager.rs')
-rw-r--r-- | client/src/skin_manager.rs | 146 |
1 files changed, 146 insertions, 0 deletions
diff --git a/client/src/skin_manager.rs b/client/src/skin_manager.rs new file mode 100644 index 0000000..1f6624d --- /dev/null +++ b/client/src/skin_manager.rs @@ -0,0 +1,146 @@ +// Adapted from twgpu-tools +// AGPL-3.0-only, Copyright 2025 Patiga + +use anyhow::Result; +use image::{ImageFormat, RgbaImage}; +use std::{collections::HashSet, io::BufReader, sync::Arc}; +use twgpu::{ + blit::Blit, + sprites::{AtlasToken, SpriteTextures, TeeSprite}, +}; +use wgpu::{Device, Queue, Texture}; + +pub fn load_png(path: &str, version: twmap::Version) -> Result<RgbaImage> { + let file = twstorage::read_file(path, version.into())?; + let reader = image::io::Reader::with_format(BufReader::new(file), ImageFormat::Png); + Ok(reader.decode()?.into_rgba8()) +} + +pub fn init_sprite_textures( + textures: &mut SpriteTextures, + version: twmap::Version, + device: &Device, + queue: &Queue, +) -> Result<()> { + println!("Loading sprite textures from filesystem"); + let game = load_png("game.png", version)?; + let particles = load_png("particles.png", version)?; + let emoticons = load_png("emoticons.png", version)?; + let extras = load_png("extras.png", version)?; + textures.game_skin = textures.register_atlas_image(&game, device, queue); + textures.particles = textures.register_atlas_image(&particles, device, queue); + textures.emoticons = textures.register_atlas_image(&emoticons, device, queue); + textures.extras = textures.register_atlas_image(&extras, device, queue); + Ok(()) +} + +type SkinResult = Result<(String, Vec<Texture>), (String, String)>; + +pub struct SkinManager { + known: HashSet<String>, + handles: Vec<std::thread::JoinHandle<SkinResult>>, + blit: Arc<Blit>, + device: Arc<Device>, + queue: Arc<Queue>, +} + +fn load_skin(name: &str, blit: &Blit, device: &Device, queue: &Queue) -> Result<Vec<Texture>> { + // TODO check filename + let path = format!("skins/{name}.png"); + let path2 = format!("downloadedskins/{name}.png"); + let image = match load_png(&path, twmap::Version::DDNet06) { + Err(_) => load_png(&path2, twmap::Version::DDNet06)?, + Ok(img) => img, + }; + + let texture = blit.upload_mipmapped_atlas::<TeeSprite>(&image, device, queue); + Ok(texture) +} + +impl SkinManager { + pub fn new( + blit: Arc<Blit>, + textures: &mut SpriteTextures, + device: Arc<Device>, + queue: Arc<Queue>, + ) -> Self { + let mut manager = Self { + known: HashSet::new(), + handles: Vec::new(), + blit, + device, + queue, + }; + if let Some(default) = manager.load_skin("default", textures) { + textures.default_skin = default; + } + if let Some(ninja) = manager.load_skin("x_ninja", textures) { + textures.ninja_skin = ninja; + } + manager + } + + pub fn load_skin( + &mut self, + name: &str, + textures: &mut SpriteTextures, + ) -> Option<AtlasToken<TeeSprite>> { + let texture = match load_skin(name, &self.blit, &self.device, &self.queue) { + Ok(texture) => texture, + Err(err) => { + println!("Error loading skin '{name}': {err}"); + return None; + } + }; + self.known.insert(name.to_string()); + let token = textures.register_skin_texture(texture, name.to_string(), &self.device); + Some(token) + } + + pub fn queue_load_skin(&mut self, name: String) { + let device = self.device.clone(); + let queue = self.queue.clone(); + let blit = self.blit.clone(); + let handle = std::thread::spawn(move || match load_skin(&name, &blit, &device, &queue) { + Err(err) => Err((name, err.to_string())), + Ok(texture) => Ok((name, texture)), + }); + self.handles.push(handle); + } + + pub fn wait_for_queued(&mut self, textures: &mut SpriteTextures) { + while let Some(handle) = self.handles.pop() { + match handle.join().unwrap() { + Ok((name, texture)) => { + textures.register_skin_texture(texture, name, &self.device); + } + Err((name, err)) => println!("Error with skin '{name}': {err}"), + } + } + } + + pub fn poll_queued(&mut self, textures: &mut SpriteTextures) { + while !self.handles.is_empty() { + if self.handles[0].is_finished() { + match self.handles.remove(0).join().unwrap() { + Ok((name, texture)) => { + textures.register_skin_texture(texture, name, &self.device); + } + Err((name, err)) => println!("Error with skin '{name}': {err}"), + } + } else { + return; + } + } + } + + pub fn queue_snap_skins(&mut self, snap: &twsnap::Snap) { + for player in snap.players.values() { + if !self.known.contains(player.skin.as_str()) { + println!("New skin: '{}'", player.skin); + self.known.insert(player.skin.to_string()); + self.queue_load_skin(player.skin.to_string()); + } + } + } +} |