1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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());
}
}
}
}
|