diff --git a/src/main.rs b/src/main.rs index e200e19..52dc8b6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,7 @@ #![feature(rc_would_unwrap)] extern crate sdl2; +extern crate zip; extern crate image; extern crate time; extern crate byteorder; @@ -179,10 +180,8 @@ fn main() { info!("Starting steven"); - let resource_manager = Arc::new(RwLock::new(resources::Manager::new())); - { - resource_manager.write().unwrap().tick(); - } + let (res, mut resui) = resources::Manager::new(); + let resource_manager = Arc::new(RwLock::new(res)); let sdl = sdl2::init().unwrap(); let sdl_video = sdl.video().unwrap(); @@ -232,11 +231,6 @@ fn main() { let mut events = sdl.event_pump().unwrap(); while !game.should_close { - let version = { - let mut res = game.resource_manager.write().unwrap(); - res.tick(); - res.version() - }; let now = time::now(); let diff = now - last_frame; @@ -244,6 +238,12 @@ fn main() { let delta = (diff.num_nanoseconds().unwrap() as f64) / frame_time; let (width, height) = window.drawable_size(); + let version = { + let mut res = game.resource_manager.write().unwrap(); + res.tick(&mut resui, &mut ui_container, delta); + res.version() + }; + let vsync_changed = *game.vars.get(settings::R_VSYNC); if vsync != vsync_changed { vsync = vsync_changed; diff --git a/src/resources.rs b/src/resources.rs index e5c65ae..25033e1 100644 --- a/src/resources.rs +++ b/src/resources.rs @@ -12,9 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -extern crate hyper; -extern crate zip; - extern crate steven_resources as internal; use std::thread; @@ -22,8 +19,15 @@ use std::path; use std::io; use std::fs; use std::sync::mpsc; +use std::sync::{Arc, Mutex}; + +use hyper; +use zip; + +use ui; const RESOURCES_VERSION: &'static str = "1.9.2"; +const VANILLA_CLIENT_URL: &'static str = "https://launcher.mojang.com/mc/game/1.9.2/client/19106fd5e222dca0f2dde9f66db8384c9a7db957/client.jar"; pub trait Pack: Sync + Send { fn open(&self, name: &str) -> Option>; @@ -34,20 +38,50 @@ pub struct Manager { version: usize, vanilla_chan: Option>, + vanilla_progress: Arc>, +} + +pub struct ManagerUI { + progress_ui: Vec, +} + +struct ProgressUI { + task_name: String, + task_file: String, + position: f64, + closing: bool, + progress: f64, + + background: ui::ImageRef, + progress_bar: ui::ImageRef, +} + +struct Progress { + tasks: Vec, +} + +struct Task { + task_name: String, + task_file: String, + total: u64, + progress: u64, } unsafe impl Sync for Manager {} impl Manager { - pub fn new() -> Manager { + pub fn new() -> (Manager, ManagerUI) { let mut m = Manager { packs: Vec::new(), version: 0, vanilla_chan: None, + vanilla_progress: Arc::new(Mutex::new(Progress { + tasks: vec![], + })), }; m.add_pack(Box::new(InternalPack)); m.download_vanilla(); - m + (m, ManagerUI { progress_ui: vec!{} }) } /// Returns the 'version' of the manager. The version is @@ -77,7 +111,7 @@ impl Manager { ret } - pub fn tick(&mut self) { + pub fn tick(&mut self, mui: &mut ManagerUI, ui_container: &mut ui::Container, delta: f64) { // Check to see if the download of vanilla has completed // (if it was started) let mut done = false; @@ -90,6 +124,109 @@ impl Manager { self.vanilla_chan = None; self.load_vanilla(); } + + const UI_HEIGHT: f64 = 32.0; + + let mut progress = self.vanilla_progress.lock().unwrap(); + progress.tasks.retain(|v| v.progress < v.total); + // Find out what we have to work with + for task in &progress.tasks { + if !mui.progress_ui.iter() + .filter(|v| v.task_file == task.task_file) + .any(|v| v.task_name == task.task_name) { + // Add a ui element for it + let background = ui::ImageBuilder::new() + .texture("steven:solid") + .position(0.0, -UI_HEIGHT) + .size(300.0, UI_HEIGHT) + .colour((0, 0, 0, 100)) + .draw_index(100) + .alignment(ui::VAttach::Bottom, ui::HAttach::Left) + .create(ui_container); + + ui::ImageBuilder::new() + .texture("steven:solid") + .position(0.0, 0.0) + .size(300.0, 10.0) + .colour((0, 0, 0, 200)) + .attach(&mut *background.borrow_mut()); + ui::TextBuilder::new() + .text(&*task.task_name) + .position(3.0, 0.0) + .scale_x(0.5) + .scale_y(0.5) + .draw_index(1) + .attach(&mut *background.borrow_mut()); + ui::TextBuilder::new() + .text(&*task.task_file) + .position(3.0, 12.0) + .scale_x(0.5) + .scale_y(0.5) + .draw_index(1) + .attach(&mut *background.borrow_mut()); + + let progress_bar = ui::ImageBuilder::new() + .texture("steven:solid") + .position(0.0, 0.0) + .size(0.0, 10.0) + .colour((0, 255, 0, 255)) + .alignment(ui::VAttach::Bottom, ui::HAttach::Left) + .attach(&mut *background.borrow_mut()); + + mui.progress_ui.push(ProgressUI { + task_name: task.task_name.clone(), + task_file: task.task_file.clone(), + position: -UI_HEIGHT, + closing: false, + progress: 0.0, + background: background, + progress_bar: progress_bar, + }); + } + } + for ui in &mut mui.progress_ui { + let mut found = false; + let mut prog = 1.0; + for task in progress.tasks.iter() + .filter(|v| v.task_file == ui.task_file) + .filter(|v| v.task_name == ui.task_name) { + found = true; + prog = task.progress as f64 / task.total as f64; + } + if !found { + ui.closing = true; + ui.position = -UI_HEIGHT; + } + ui.progress = prog; + } + let mut offset = 0.0; + for ui in &mut mui.progress_ui { + if ui.closing { + continue; + } + ui.position = offset; + offset += UI_HEIGHT; + } + // Move elements + let delta = delta.min(5.0); + for ui in &mut mui.progress_ui { + let mut background = ui.background.borrow_mut(); + if (background.y - ui.position).abs() < 0.7 * delta { + background.y = ui.position; + } else { + background.y += (ui.position - background.y).signum() * 0.7 * delta; + } + let mut bar = ui.progress_bar.borrow_mut(); + let target_size = (300.0 * ui.progress).min(300.0); + if (bar.width - target_size).abs() < 1.0 * delta { + bar.width = target_size; + } else { + bar.width += ((target_size - bar.width).signum() * delta).max(0.0); + } + } + + // Clean up dead elements + mui.progress_ui.retain(|v| v.position >= -UI_HEIGHT && !v.closing); } fn add_pack(&mut self, pck: Box) { @@ -113,32 +250,39 @@ impl Manager { let (send, recv) = mpsc::channel(); self.vanilla_chan = Some(recv); - info!("Vanilla assets missing, obtaining"); + let progress_info = self.vanilla_progress.clone(); thread::spawn(move || { let client = hyper::Client::new(); - let url = format!("https://s3.amazonaws.com/Minecraft.Download/versions/{0}/{0}.jar", - RESOURCES_VERSION); - let res = client.get(&url) + let res = client.get(VANILLA_CLIENT_URL) .send() .unwrap(); let mut file = fs::File::create(format!("{}.tmp", RESOURCES_VERSION)).unwrap(); let length = *res.headers.get::().unwrap(); - let mut progress = ProgressRead { - read: res, - progress: 0, - total: *length, - }; - io::copy(&mut progress, &mut file).unwrap(); + let task_file = format!("./resources-{}", RESOURCES_VERSION); + Self::add_task(&progress_info, "Downloading Core Assets", &task_file, *length); + { + let mut progress = ProgressRead { + read: res, + progress: &progress_info, + task_name: "Downloading Core Assets".into(), + task_file: task_file, + }; + io::copy(&mut progress, &mut file).unwrap(); + } // Copy the resources from the zip let file = fs::File::open(format!("{}.tmp", RESOURCES_VERSION)).unwrap(); let mut zip = zip::ZipArchive::new(file).unwrap(); + let task_file = format!("./resources-{}", RESOURCES_VERSION); + Self::add_task(&progress_info, "Unpacking Core Assets", &task_file, zip.len() as u64); + let loc = format!("./resources-{}", RESOURCES_VERSION); let location = path::Path::new(&loc); let count = zip.len(); for i in 0..count { + Self::add_task_progress(&progress_info, "Unpacking Core Assets", &task_file, 1); let mut file = zip.by_index(i).unwrap(); if !file.name().starts_with("assets/") { continue; @@ -150,12 +294,30 @@ impl Manager { } fs::File::create(location.join("steven.assets")).unwrap(); // Marker file - info!("Done"); send.send(true).unwrap(); fs::remove_file(format!("{}.tmp", RESOURCES_VERSION)).unwrap(); }); } + + fn add_task(progress: &Arc>, name: &str, file: &str, length: u64) { + let mut info = progress.lock().unwrap(); + info.tasks.push(Task { + task_name: name.into(), + task_file: file.into(), + total: length, + progress: 0, + }); + } + + fn add_task_progress(progress: &Arc>, name: &str, file: &str, prog: u64) { + let mut progress = progress.lock().unwrap(); + for task in progress.tasks.iter_mut() + .filter(|v| v.task_file == file) + .filter(|v| v.task_name == name) { + task.progress += prog as u64; + } + } } struct DirPack { @@ -182,18 +344,17 @@ impl Pack for InternalPack { } } -struct ProgressRead { +struct ProgressRead<'a, T> { read: T, - total: u64, - progress: u64, + progress: &'a Arc>, + task_name: String, + task_file: String, } -impl io::Read for ProgressRead { +impl <'a, T: io::Read> io::Read for ProgressRead<'a, T> { fn read(&mut self, buf: &mut [u8]) -> io::Result { let size = try!(self.read.read(buf)); - self.progress += size as u64; - trace!("Progress: {:.2}", - (self.progress as f64) / (self.total as f64)); + Manager::add_task_progress(self.progress, &self.task_name, &self.task_file, size as u64); Ok(size) } } diff --git a/src/screen/mod.rs b/src/screen/mod.rs index fbece93..6f59014 100644 --- a/src/screen/mod.rs +++ b/src/screen/mod.rs @@ -26,12 +26,11 @@ pub use self::settings_menu::{SettingsMenu, VideoSettingsMenu, AudioSettingsMenu use render; use ui; -#[allow(unused_variables)] pub trait Screen { // Called once - fn init(&mut self, _renderer: &mut render::Renderer, ui_container: &mut ui::Container) { + fn init(&mut self, _renderer: &mut render::Renderer, _ui_container: &mut ui::Container) { } - fn deinit(&mut self, _renderer: &mut render::Renderer, ui_container: &mut ui::Container) { + fn deinit(&mut self, _renderer: &mut render::Renderer, _ui_container: &mut ui::Container) { } // May be called multiple times @@ -45,7 +44,7 @@ pub trait Screen { ui_container: &mut ui::Container) -> Option>; // Events - fn on_scroll(&mut self, x: f64, y: f64) { + fn on_scroll(&mut self, _x: f64, _y: f64) { } fn is_closable(&self) -> bool {