diff --git a/src/entity/player.rs b/src/entity/player.rs index 5dd9e44..a9b4d98 100644 --- a/src/entity/player.rs +++ b/src/entity/player.rs @@ -48,6 +48,8 @@ pub fn create_local(m: &mut ecs::Manager) -> ecs::Entity { pub struct PlayerModel { model: Option, + skin_url: Option, + dirty: bool, has_head: bool, has_name_tag: bool, @@ -64,6 +66,8 @@ impl PlayerModel { pub fn new(has_head: bool, has_name_tag: bool, first_person: bool) -> PlayerModel { PlayerModel { model: None, + skin_url: None, + dirty: false, has_head: has_head, has_name_tag: has_name_tag, @@ -76,6 +80,11 @@ impl PlayerModel { arm_time: 0.0, } } + + pub fn set_skin(&mut self, skin: Option) { + self.skin_url = skin; + self.dirty = true; + } } struct PlayerRenderer { @@ -122,7 +131,7 @@ impl ecs::System for PlayerRenderer { &self.filter } - fn update(&mut self, m: &mut ecs::Manager, _: &mut world::World, renderer: &mut render::Renderer) { + fn update(&mut self, m: &mut ecs::Manager, world: &mut world::World, renderer: &mut render::Renderer) { use std::f32::consts::PI; use std::f64::consts::PI as PI64; let world_entity = m.get_world(); @@ -132,6 +141,11 @@ impl ecs::System for PlayerRenderer { let position = m.get_component_mut(e, self.position).unwrap(); let rotation = m.get_component_mut(e, self.rotation).unwrap(); + if player_model.dirty { + self.entity_removed(m, e, world, renderer); + self.entity_added(m, e, world, renderer); + } + if let Some(pmodel) = player_model.model { let mdl = renderer.model.get_model(pmodel).unwrap(); let offset = if player_model.first_person { @@ -242,7 +256,13 @@ impl ecs::System for PlayerRenderer { fn entity_added(&mut self, m: &mut ecs::Manager, e: ecs::Entity, _: &mut world::World, renderer: &mut render::Renderer) { let player_model = m.get_component_mut(e, self.player_model).unwrap(); - let skin = render::Renderer::get_texture(renderer.get_textures_ref(), "entity/steve"); + player_model.dirty = false; + + let skin = if let Some(url) = player_model.skin_url.as_ref() { + renderer.get_skin(renderer.get_textures_ref(), &url) + } else { + render::Renderer::get_texture(renderer.get_textures_ref(), "entity/steve") + }; macro_rules! srel { ($x:expr, $y:expr, $w:expr, $h:expr) => ( @@ -335,6 +355,9 @@ impl ecs::System for PlayerRenderer { if let Some(model) = player_model.model.take() { renderer.model.remove_model(model); } + if let Some(url) = player_model.skin_url.take() { + renderer.get_textures_ref().read().unwrap().release_skin(&url); + } } } diff --git a/src/render/mod.rs b/src/render/mod.rs index ca54b0c..a058558 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -35,6 +35,9 @@ use collision; use std::hash::BuildHasherDefault; use types::hash::FNVHash; +use std::sync::atomic::{AtomicIsize, Ordering}; +use std::thread; +use std::sync::mpsc; const ATLAS_SIZE: usize = 1024; @@ -82,6 +85,8 @@ pub struct Renderer { // Light renderering pub light_level: f32, pub sky_offset: f32, + skin_request: mpsc::Sender, + skin_reply: mpsc::Receiver<(String, Option)>, } pub struct ChunkBuffer { @@ -170,7 +175,8 @@ impl Renderer { tex.set_parameter(gl::TEXTURE_2D_ARRAY, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE); tex.set_parameter(gl::TEXTURE_2D_ARRAY, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE); - let textures = Arc::new(RwLock::new(TextureManager::new(res.clone()))); + let (textures, skin_req, skin_reply) = TextureManager::new(res.clone()); + let textures = Arc::new(RwLock::new(textures)); let mut greg = glsl::Registry::new(); shaders::add_shaders(&mut greg); @@ -230,6 +236,8 @@ impl Renderer { light_level: 0.8, sky_offset: 1.0, + skin_request: skin_req, + skin_reply: skin_reply, } } @@ -529,6 +537,27 @@ impl Renderer { } fn update_textures(&mut self, delta: f64) { + { + let mut tex = self.textures.write().unwrap(); + while let Ok((hash, img)) = self.skin_reply.try_recv() { + if let Some(img) = img { + debug!("Got completed skin: {:?}", hash); + tex.update_skin(hash, img); + } else { + debug!("Failed to get skin for {:?}", hash); + } + } + let mut old_skins = vec![]; + for (skin, refcount) in &tex.skins { + if refcount.load(Ordering::Relaxed) == 0 { + old_skins.push(skin.clone()); + } + } + for skin in old_skins { + tex.skins.remove(&skin); + tex.remove_dynamic(&format!("skin-{}", skin)); + } + } self.gl_texture.bind(gl::TEXTURE_2D_ARRAY); self.do_pending_textures(); @@ -603,6 +632,26 @@ impl Renderer { } } } + + pub fn get_skin(&self, textures: &RwLock, url: &str) -> Texture { + let tex = { + textures.read().unwrap().get_skin(url) + }; + match tex { + Some(val) => val, + None => { + let mut t = textures.write().unwrap(); + // Make sure it hasn't already been loaded since we switched + // locks. + if let Some(val) = t.get_skin(url) { + val + } else { + t.load_skin(self, url); + t.get_skin(url).unwrap() + } + } + } + } } struct TransInfo { @@ -739,13 +788,23 @@ pub struct TextureManager { dynamic_textures: HashMap>, free_dynamics: Vec, + + skins: HashMap>, + + _skin_thread: thread::JoinHandle<()>, } impl TextureManager { - fn new(res: Arc>) -> TextureManager { + fn new(res: Arc>) -> (TextureManager, mpsc::Sender, mpsc::Receiver<(String, Option)>) { + let (tx, rx) = mpsc::channel(); + let (stx, srx) = mpsc::channel(); + let skin_thread = thread::spawn(|| Self::process_skins(srx, tx)); let mut tm = TextureManager { textures: HashMap::with_hasher(BuildHasherDefault::default()), - version: 0xFFFF, + version: { + let ver = res.read().unwrap().version(); + ver + }, resources: res, atlases: Vec::new(), animated_textures: Vec::new(), @@ -753,9 +812,12 @@ impl TextureManager { dynamic_textures: HashMap::with_hasher(BuildHasherDefault::default()), free_dynamics: Vec::new(), + skins: HashMap::with_hasher(BuildHasherDefault::default()), + + _skin_thread: skin_thread, }; tm.add_defaults(); - tm + (tm, stx, rx) } fn add_defaults(&mut self) { @@ -778,6 +840,29 @@ impl TextureManager { ]); } + fn process_skins(recv: mpsc::Receiver, reply: mpsc::Sender<(String, Option)>) { + use hyper; + use std::io::Read; + let client = hyper::Client::new(); + loop { + // TODO: Cache + let hash = recv.recv().unwrap(); + trace!("Fetching skin {:?}", hash); + let url = format!("http://textures.minecraft.net/texture/{}", hash); + let mut res = client.get(&url).send().unwrap(); + let mut buf = vec![]; + res.read_to_end(&mut buf).unwrap(); + let img = match image::load_from_memory(&buf) { + Ok(val) => val, + Err(_) => { + reply.send((hash, None)).unwrap(); + continue; + } + }; + reply.send((hash, Some(img))).unwrap(); + } + } + fn update_textures(&mut self, version: usize) { self.pending_uploads.clear(); self.atlases.clear(); @@ -810,6 +895,48 @@ impl TextureManager { } } + fn get_skin(&self, url: &str) -> Option { + let hash = &url["http://textures.minecraft.net/texture/".len()..]; + if let Some(skin) = self.skins.get(hash) { + skin.fetch_add(1, Ordering::Relaxed); + } + self.get_texture(&format!("steven-dynamic:skin-{}", hash)) + } + + pub fn release_skin(&self, url: &str) { + let hash = &url["http://textures.minecraft.net/texture/".len()..]; + if let Some(skin) = self.skins.get(hash) { + skin.fetch_sub(1, Ordering::Relaxed); + } + } + + fn load_skin(&mut self, renderer: &Renderer, url: &str) { + let hash = &url["http://textures.minecraft.net/texture/".len()..]; + let res = self.resources.clone(); + // TODO: This shouldn't be hardcoded to steve but instead + // have a way to select alex as a default. + let mut val = res.read().unwrap().open("minecraft", "textures/entity/steve.png").unwrap(); + let mut data = Vec::new(); + val.read_to_end(&mut data).unwrap(); + let img = image::load_from_memory(&data).unwrap(); + self.put_dynamic(&format!("skin-{}", hash), img); + self.skins.insert(hash.to_owned(), AtomicIsize::new(0)); + renderer.skin_request.send(hash.to_owned()).unwrap(); + } + + fn update_skin(&mut self, hash: String, img: image::DynamicImage) { + if !self.skins.contains_key(&hash) { return; } + let tex = self.get_texture(&format!("steven-dynamic:skin-{}", hash)).unwrap(); + let rect = atlas::Rect { + x: tex.x, + y: tex.y, + width: tex.width, + height: tex.height, + }; + + self.pending_uploads.push((tex.atlas, rect, img.to_rgba().into_vec())); + } + fn get_texture(&self, name: &str) -> Option { if let Some(_) = name.find(':') { self.textures.get(name).cloned() @@ -1005,7 +1132,12 @@ impl TextureManager { }; self.pending_uploads.push((tex.atlas, rect, data)); let t = tex.relative(0.0, 0.0, (width as f32) / (tex.width as f32), (height as f32) / (tex.height as f32)); - self.dynamic_textures.insert(name.to_owned(), (tex, img)); + self.dynamic_textures.insert(name.to_owned(), (tex.clone(), img)); + // We need to rename the texture itself so that get_texture calls + // work with the new name + let mut old = self.textures.remove(&tex.name).unwrap(); + old.name = name.to_owned(); + self.textures.insert(format!("steven-dynamic:{}", name), old); t } else { let tex = self.put_texture("steven-dynamic", name, width as u32, height as u32, data); diff --git a/src/server/mod.rs b/src/server/mod.rs index c913413..fc10be3 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -488,6 +488,10 @@ impl Server { fn on_game_join(&mut self, join: packet::play::clientbound::JoinGame) { let gamemode = Gamemode::from_int((join.gamemode & 0x7) as i32); let player = entity::player::create_local(&mut self.entities); + if let Some(info) = self.players.get(&self.uuid) { + let model = self.entities.get_component_mut_direct::(player).unwrap(); + model.set_skin(info.skin_url.clone()); + } *self.entities.get_component_mut(player, self.gamemode).unwrap() = gamemode; // TODO: Temp self.entities.get_component_mut(player, self.player_movement).unwrap().flying = gamemode.can_fly(); @@ -610,6 +614,16 @@ impl Server { info.skin_url = Some(skin_url.to_owned()); } } + + // Refresh our own skin when the server sends it to us. + // The join game packet can come before this packet meaning + // we may not have the skin in time for spawning ourselves. + // This isn't an issue for other players because this packet + // must come before the spawn player packet. + if info.uuid == self.uuid { + let model = self.entities.get_component_mut_direct::(self.player.unwrap()).unwrap(); + model.set_skin(info.skin_url.clone()); + } }, UpdateGamemode { uuid, gamemode } => { if let Some(info) = self.players.get_mut(&uuid) {