From 30f02af6a43cb5d798f5bda4e2669268b2cf3f70 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sun, 25 Apr 2021 21:30:17 -0400 Subject: [PATCH] Wait for ingame pipelines to build before entering char select, show pipeline rebuilding status text in the graphics settings tab --- voxygen/src/hud/settings_window/video.rs | 19 ++++ voxygen/src/menu/main/mod.rs | 125 ++++++++++++++++++----- voxygen/src/render/renderer.rs | 22 ++++ 3 files changed, 138 insertions(+), 28 deletions(-) diff --git a/voxygen/src/hud/settings_window/video.rs b/voxygen/src/hud/settings_window/video.rs index 064300ae5e..c064da77de 100644 --- a/voxygen/src/hud/settings_window/video.rs +++ b/voxygen/src/hud/settings_window/video.rs @@ -35,6 +35,7 @@ widget_ids! { window_scrollbar, reset_graphics_button, fps_counter, + pipeline_recreation_text, vd_slider, vd_text, vd_value, @@ -210,6 +211,24 @@ impl<'a> Widget for Video<'a> { .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(18)) .set(state.ids.fps_counter, ui); + + // Pipeline recreation status + if let Some((total, complete)) = self + .global_state + .window + .renderer() + .pipeline_recreation_status() + { + Text::new(&format!("Rebuilding pipelines: ({}/{})", complete, total)) + .down_from(state.ids.fps_counter, 10.0) + .align_right_of(state.ids.fps_counter) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + // TODO: make color pulse or something + .color(TEXT_COLOR) + .set(state.ids.pipeline_recreation_text, ui); + } + // View Distance Text::new(&self.localized_strings.get("hud.settings.view_distance")) .top_left_with_margins_on(state.ids.window, 10.0, 10.0) diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index ef7b894b55..d9d51937f9 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -12,7 +12,7 @@ use crate::{ use client::{ addr::ConnectionArgs, error::{InitProtocolError, NetworkConnectError, NetworkError}, - ServerInfo, + Client, ServerInfo, }; use client_init::{ClientInit, Error as InitError, Msg as InitMsg}; use common::comp; @@ -23,10 +23,29 @@ use tokio::runtime; use tracing::error; use ui::{Event as MainMenuEvent, MainMenuUi}; +// TODO: show status messages for waiting on server creation, client init, and +// pipeline creation (we can show progress of pipeline creation) +enum InitState { + None, + // Waiting on the client initialization + Client(ClientInit), + // Client initialized but still waiting on Renderer pipeline creation + Pipeline(Client), +} + +impl InitState { + fn client(&self) -> Option<&ClientInit> { + if let Self::Client(client_init) = &self { + Some(client_init) + } else { + None + } + } +} + pub struct MainMenuState { main_menu_ui: MainMenuUi, - // Used for client creation. - client_init: Option, + init: InitState, scene: Scene, } @@ -35,7 +54,7 @@ impl MainMenuState { pub fn new(global_state: &mut GlobalState) -> Self { Self { main_menu_ui: MainMenuUi::new(global_state), - client_init: None, + init: InitState::None, scene: Scene::new(global_state.window.renderer_mut()), } } @@ -78,14 +97,14 @@ impl PlayState for MainMenuState { "singleplayer".to_owned(), "".to_owned(), ConnectionArgs::Mpsc(14004), - &mut self.client_init, + &mut self.init, Some(runtime), ); }, Ok(Err(e)) => { error!(?e, "Could not start server"); global_state.singleplayer = None; - self.client_init = None; + self.init = InitState::None; self.main_menu_ui.cancel_connection(); self.main_menu_ui.show_info(format!("Error: {:?}", e)); }, @@ -108,19 +127,14 @@ impl PlayState for MainMenuState { } } // Poll client creation. - match self.client_init.as_ref().and_then(|init| init.poll()) { + match self.init.client().and_then(|init| init.poll()) { Some(InitMsg::Done(Ok(mut client))) => { - self.client_init = None; - self.main_menu_ui.connected(); // Register voxygen components / resources crate::ecs::init(client.state_mut().ecs_mut()); - return PlayStateResult::Push(Box::new(CharSelectionState::new( - global_state, - std::rc::Rc::new(std::cell::RefCell::new(client)), - ))); + self.init = InitState::Pipeline(client); }, Some(InitMsg::Done(Err(e))) => { - self.client_init = None; + self.init = InitState::None; tracing::trace!(?e, "raw Client Init error"); let e = get_client_msg_error(e, &global_state.i18n); // Log error for possible additional use later or incase that the error @@ -136,10 +150,7 @@ impl PlayState for MainMenuState { .contains(&auth_server) { // Can't fail since we just polled it, it must be Some - self.client_init - .as_ref() - .unwrap() - .auth_trust(auth_server, true); + self.init.client().unwrap().auth_trust(auth_server, true); } else { // Show warning that auth server is not trusted and prompt for approval self.main_menu_ui.auth_trust_prompt(auth_server); @@ -148,6 +159,64 @@ impl PlayState for MainMenuState { None => {}, } + // Tick the client to keep the connection alive if we are waiting on pipelines + let localized_strings = &global_state.i18n.read(); + if let InitState::Pipeline(client) = &mut self.init { + match client.tick( + comp::ControllerInputs::default(), + global_state.clock.dt(), + |_| {}, + ) { + Ok(events) => { + for event in events { + match event { + client::Event::SetViewDistance(vd) => { + global_state.settings.graphics.view_distance = vd; + global_state.settings.save_to_file_warn(); + }, + client::Event::Disconnect => { + global_state.info_message = Some( + localized_strings + .get("main.login.server_shut_down") + .to_owned(), + ); + self.init = InitState::None; + }, + _ => {}, + } + } + }, + Err(err) => { + global_state.info_message = + Some(localized_strings.get("common.connection_lost").to_owned()); + error!(?err, "[main menu] Failed to tick the client"); + self.init = InitState::None; + }, + } + } + + // Poll renderer pipeline creation + if let InitState::Pipeline(..) = &self.init { + // If not complete go to char select screen + if global_state + .window + .renderer() + .pipeline_creation_status() + .is_none() + { + // Always succeeds since we check above + if let InitState::Pipeline(client) = + core::mem::replace(&mut self.init, InitState::None) + { + self.main_menu_ui.connected(); + return PlayStateResult::Push(Box::new(CharSelectionState::new( + global_state, + std::rc::Rc::new(std::cell::RefCell::new(client)), + ))); + } + } + } + // Maintain the UI. for event in self .main_menu_ui @@ -184,19 +253,19 @@ impl PlayState for MainMenuState { username, password, connection_args, - &mut self.client_init, + &mut self.init, None, ); }, MainMenuEvent::CancelLoginAttempt => { - // client_init contains Some(ClientInit), which spawns a thread which contains a - // TcpStream::connect() call This call is blocking - // TODO fix when the network rework happens + // init contains InitState::Client(ClientInit), which spawns a thread which + // contains a TcpStream::connect() call This call is + // blocking TODO fix when the network rework happens #[cfg(feature = "singleplayer")] { global_state.singleplayer = None; } - self.client_init = None; + self.init = InitState::None; self.main_menu_ui.cancel_connection(); }, MainMenuEvent::ChangeLanguage(new_language) => { @@ -232,8 +301,8 @@ impl PlayState for MainMenuState { .insert(auth_server.clone()); global_state.settings.save_to_file_warn(); } - self.client_init - .as_ref() + self.init + .client() .map(|init| init.auth_trust(auth_server, trust)); }, } @@ -369,7 +438,7 @@ fn attempt_login( username: String, password: String, connection_args: ConnectionArgs, - client_init: &mut Option, + init: &mut InitState, runtime: Option>, ) { if let Err(err) = comp::Player::alias_validate(&username) { @@ -378,8 +447,8 @@ fn attempt_login( } // Don't try to connect if there is already a connection in progress. - if client_init.is_none() { - *client_init = Some(ClientInit::new( + if let InitState::None = init { + *init = InitState::Client(ClientInit::new( connection_args, username, password, diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index 55cc8678e4..1208c35711 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -388,6 +388,28 @@ impl Renderer { }) } + /// Check the status of the intial pipeline creation + /// Returns `None` if complete + /// Returns `Some((total, complete))` if in progress + pub fn pipeline_creation_status(&self) -> Option<(usize, usize)> { + if let State::Interface { creating, .. } = &self.state { + Some(creating.status()) + } else { + None + } + } + + /// Check the status the pipeline recreation + /// Returns `None` if pipelines are currently not being recreated + /// Returns `Some((total, complete))` if in progress + pub fn pipeline_recreation_status(&self) -> Option<(usize, usize)> { + if let State::Complete { recreating, .. } = &self.state { + recreating.as_ref().map(|r| r.status()) + } else { + None + } + } + /// Change the render mode. pub fn set_render_mode(&mut self, mode: RenderMode) -> Result<(), RenderError> { // TODO: are there actually any issues with the current mode not matching the