From 9a3f53c32c4beaaa12f8f68e0700e72cddc608dd Mon Sep 17 00:00:00 2001 From: Maxicarlos08 Date: Tue, 26 Sep 2023 00:27:46 +0200 Subject: [PATCH] added status updates to loading screen --- Cargo.lock | 1 + assets/voxygen/i18n/en/hud/misc.ftl | 8 ++++ client/Cargo.toml | 1 + client/src/lib.rs | 30 ++++++++++++++ voxygen/src/menu/main/client_init.rs | 10 ++++- voxygen/src/menu/main/mod.rs | 28 +++++++++---- voxygen/src/menu/main/ui/connecting.rs | 54 ++++++++++++++++++++++---- voxygen/src/menu/main/ui/mod.rs | 17 ++++++++ 8 files changed, 134 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 46ea56fcca..543bfe3b2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6785,6 +6785,7 @@ dependencies = [ "authc", "byteorder", "clap", + "crossbeam-channel", "hashbrown 0.13.2", "image", "num 0.4.1", diff --git a/assets/voxygen/i18n/en/hud/misc.ftl b/assets/voxygen/i18n/en/hud/misc.ftl index cc99b03fe2..b4656f251e 100644 --- a/assets/voxygen/i18n/en/hud/misc.ftl +++ b/assets/voxygen/i18n/en/hud/misc.ftl @@ -60,3 +60,11 @@ hud-stay= Stay hud-sit = Sit hud-steer = Steer hud-portal = Portal +hud-init-stage-singleplayer = Starting singleplayer server... +hud-init-stage-multiplayer = Starting multiplayer +hud-init-stage-client-connection-establish = Establishing connection to server +hud-init-stage-client-request-server-version = Wating for server version +hud-init-stage-client-authentication = Authenticating +hud-init-stage-client-load-init-data = Loading initialization data from server +hud-init-stage-client-starting-client = Preparing Client... +hud-init-stage-render-pipeline = Creating render pipeline ({ $done }/{ $total }) diff --git a/client/Cargo.toml b/client/Cargo.toml index f459225045..30c94e5e23 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -30,6 +30,7 @@ tracing = { workspace = true } rayon = { workspace = true } specs = { workspace = true, features = ["serde", "storage-event-control", "derive"] } vek = { workspace = true } +crossbeam-channel = { workspace = true } hashbrown = { workspace = true } authc = { git = "https://gitlab.com/veloren/auth.git", rev = "abb1a705827984e11706d7bb97fb7a459e1e6533" } # xMAC94x/current_master_till_refactored branch diff --git a/client/src/lib.rs b/client/src/lib.rs index f19d253afa..d5bfcbe50b 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -66,6 +66,7 @@ use common_net::{ use common_state::State; use common_systems::add_local_systems; use comp::BuffKind; +use crossbeam_channel::Sender; use hashbrown::{HashMap, HashSet}; use image::DynamicImage; use network::{ConnectAddr, Network, Participant, Pid, Stream}; @@ -115,6 +116,22 @@ pub enum Event { SpectatePosition(Vec3), } +#[derive(Debug)] +pub enum ClientInitStage { + /// A connection to the server is being created + ConnectionEstablish, + /// Waiting for server version + WatingForServerVersion, + /// We're currently authenticating with the server + Authentication, + /// Loading map data, site information, recipe information and other + /// initialization data + LoadingInitData, + /// Prepare data received by the server to be used by the client (insert + /// data into the ECS, render map) + StartingClient, +} + pub struct WorldData { /// Just the "base" layer for LOD; currently includes colors and nothing /// else. In the future we'll add more layers, like shadows, rivers, and @@ -290,9 +307,17 @@ impl Client { username: &str, password: &str, auth_trusted: impl FnMut(&str) -> bool, + init_stage_update: Option>, ) -> Result { + let update_stage = |stage: ClientInitStage| { + if let Some(updater) = &init_stage_update { + let _ = updater.send(stage); + } + }; + let network = Network::new(Pid::new(), &runtime); + update_stage(ClientInitStage::ConnectionEstablish); let mut participant = match addr { ConnectionArgs::Tcp { hostname, @@ -322,6 +347,7 @@ impl Client { let in_game_stream = participant.opened().await?; let terrain_stream = participant.opened().await?; + update_stage(ClientInitStage::WatingForServerVersion); register_stream.send(ClientType::Game)?; let server_info: ServerInfo = register_stream.recv().await?; if server_info.git_hash != *common::util::GIT_HASH { @@ -340,6 +366,7 @@ impl Client { ping_stream.send(PingMsg::Ping)?; + update_stage(ClientInitStage::Authentication); // Register client Self::register( username, @@ -350,6 +377,7 @@ impl Client { ) .await?; + update_stage(ClientInitStage::LoadingInitData); // Wait for initial sync let mut ping_interval = tokio::time::interval(Duration::from_secs(1)); let ServerInit::GameSync { @@ -373,6 +401,7 @@ impl Client { } }; + update_stage(ClientInitStage::StartingClient); // Spawn in a blocking thread (leaving the network thread free). This is mostly // useful for bots. let mut task = tokio::task::spawn_blocking(move || { @@ -2978,6 +3007,7 @@ mod tests { username, password, |suggestion: &str| suggestion == auth_server, + None, )); let localisation = LocalizationHandle::load_expect("en"); diff --git a/voxygen/src/menu/main/client_init.rs b/voxygen/src/menu/main/client_init.rs index cf5ab2daf2..55720bfcc8 100644 --- a/voxygen/src/menu/main/client_init.rs +++ b/voxygen/src/menu/main/client_init.rs @@ -1,7 +1,7 @@ use client::{ addr::ConnectionArgs, error::{Error as ClientError, NetworkConnectError, NetworkError}, - Client, ServerInfo, + Client, ClientInitStage, ServerInfo, }; use crossbeam_channel::{unbounded, Receiver, Sender, TryRecvError}; use std::{ @@ -39,6 +39,7 @@ pub struct AuthTrust(String, bool); // server). pub struct ClientInit { rx: Receiver, + stage_rx: Receiver, trust_tx: Sender, cancel: Arc, } @@ -51,6 +52,7 @@ impl ClientInit { ) -> Self { let (tx, rx) = unbounded(); let (trust_tx, trust_rx) = unbounded(); + let (init_stage_tx, init_stare_rx) = unbounded(); let cancel = Arc::new(AtomicBool::new(false)); let cancel2 = Arc::clone(&cancel); @@ -73,6 +75,7 @@ impl ClientInit { break; } let mut mismatched_server_info = None; + let new_init_tx = init_stage_tx.clone(); match Client::new( connection_args.clone(), Arc::clone(&runtime2), @@ -80,6 +83,7 @@ impl ClientInit { &username, &password, trust_fn, + Some(new_init_tx), ) .await { @@ -116,6 +120,7 @@ impl ClientInit { ClientInit { rx, + stage_rx: init_stare_rx, trust_tx, cancel, } @@ -132,6 +137,9 @@ impl ClientInit { } } + /// Poll for connection stage updates from the client + pub fn stage_update(&self) -> Option { self.stage_rx.try_recv().ok() } + /// Report trust status of auth server pub fn auth_trust(&self, auth_server: String, trusted: bool) { let _ = self.trust_tx.send(AuthTrust(auth_server, trusted)); diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index cf01b5baea..809ca63e07 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -14,7 +14,7 @@ use crate::{ use client::{ addr::ConnectionArgs, error::{InitProtocolError, NetworkConnectError, NetworkError}, - Client, ServerInfo, + Client, ClientInitStage, ServerInfo, }; use client_init::{ClientInit, Error as InitError, Msg as InitMsg}; use common::comp; @@ -26,6 +26,14 @@ use tokio::runtime; use tracing::error; use ui::{Event as MainMenuEvent, MainMenuUi}; +pub enum DetailedInitializationStage { + // TODO: Map generation and server startup progress + Singleplayer, + StartingMultiplayer, + Client(ClientInitStage), + CreatingRenderPipeline(usize, usize), +} + // TODO: show status messages for waiting on server creation, client init, and // pipeline creation (we can show progress of pipeline creation) enum InitState { @@ -187,6 +195,12 @@ impl PlayState for MainMenuState { _ => {}, } } + + if let Some(client_stage_update) = self.init.client().and_then(|init| init.stage_update()) { + self.main_menu_ui + .update_stage(DetailedInitializationStage::Client(client_stage_update)); + } + // Poll client creation. match self.init.client().and_then(|init| init.poll()) { Some(InitMsg::Done(Ok(mut client))) => { @@ -263,13 +277,13 @@ impl PlayState for MainMenuState { // 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() + if let Some((done, total)) = &global_state.window.renderer().pipeline_creation_status() { + self.main_menu_ui.update_stage( + DetailedInitializationStage::CreatingRenderPipeline(*done, *total), + ); + // If complete go to char select screen + } else { // Always succeeds since we check above if let InitState::Pipeline(client) = core::mem::replace(&mut self.init, InitState::None) diff --git a/voxygen/src/menu/main/ui/connecting.rs b/voxygen/src/menu/main/ui/connecting.rs index 6778d52918..6bae83df3e 100644 --- a/voxygen/src/menu/main/ui/connecting.rs +++ b/voxygen/src/menu/main/ui/connecting.rs @@ -2,6 +2,7 @@ use super::{ConnectionState, Imgs, Message}; use crate::{ game_input::GameInput, + menu::main::DetailedInitializationStage, settings::ControlSettings, ui::{ fonts::IcedFonts as Fonts, @@ -9,6 +10,7 @@ use crate::{ Graphic, }, }; +use client::ClientInitStage; use common::assets::{self, AssetExt}; use i18n::Localization; use iced::{button, Align, Column, Container, Length, Row, Space, Text}; @@ -85,6 +87,7 @@ impl Screen { fonts: &Fonts, imgs: &Imgs, connection_state: &ConnectionState, + init_stage: &DetailedInitializationStage, time: f64, i18n: &Localization, button_style: style::button::Style, @@ -133,6 +136,46 @@ impl Screen { Space::new(Length::Fill, Length::Fill).into() }; + let stage = { + let stage_message = match init_stage { + DetailedInitializationStage::Singleplayer => { + i18n.get_msg("hud-init-stage-singleplayer") + }, + DetailedInitializationStage::StartingMultiplayer => { + i18n.get_msg("hud-init-stage-multiplayer") + }, + DetailedInitializationStage::Client(client_stage) => match client_stage { + ClientInitStage::ConnectionEstablish => { + i18n.get_msg("hud-init-stage-client-connection-establish") + }, + ClientInitStage::WatingForServerVersion => { + i18n.get_msg("hud-init-stage-client-request-server-version") + }, + ClientInitStage::Authentication => { + i18n.get_msg("hud-init-stage-client-authentication") + }, + ClientInitStage::LoadingInitData => { + i18n.get_msg("hud-init-stage-client-load-init-data") + }, + ClientInitStage::StartingClient => { + i18n.get_msg("hud-init-stage-client-starting-client") + }, + }, + DetailedInitializationStage::CreatingRenderPipeline(done, total) => i18n + .get_msg_ctx( + "hud-init-stage-render-pipeline", + &i18n::fluent_args! { "done" => done, "total" => total }, + ), + }; + + Container::new(Text::new(stage_message).size(fonts.cyri.scale(25))) + .width(Length::Fill) + .height(Length::Fill) + .padding(10) + .align_y(Align::End) + .into() + }; + let cancel = Container::new(neat_button( &mut self.cancel_button, i18n.get_msg("common-cancel"), @@ -160,13 +203,10 @@ impl Screen { .padding(10) .align_x(Align::End); - let bottom_content = Row::with_children(vec![ - Space::new(Length::Fill, Length::Shrink).into(), - tip_cancel.into(), - gear.into(), - ]) - .align_items(Align::Center) - .width(Length::Fill); + let bottom_content = + Row::with_children(vec![stage, tip_cancel.into(), gear.into()]) + .align_items(Align::Center) + .width(Length::Fill); let left_art = Image::new(imgs.loading_art_l) .width(Length::Units(12)) diff --git a/voxygen/src/menu/main/ui/mod.rs b/voxygen/src/menu/main/ui/mod.rs index 06fe475b82..e2b65c8457 100644 --- a/voxygen/src/menu/main/ui/mod.rs +++ b/voxygen/src/menu/main/ui/mod.rs @@ -29,6 +29,8 @@ use rand::{seq::SliceRandom, thread_rng}; use std::time::Duration; use tracing::warn; +use super::DetailedInitializationStage; + // TODO: what is this? (showed up in rebase) //const COL1: Color = Color::Rgba(0.07, 0.1, 0.1, 0.9); @@ -179,6 +181,7 @@ enum Screen { Connecting { screen: connecting::Screen, connection_state: ConnectionState, + init_stage: DetailedInitializationStage, }, #[cfg(feature = "singleplayer")] WorldSelector { @@ -405,10 +408,12 @@ impl Controls { Screen::Connecting { screen, connection_state, + init_stage, } => screen.view( &self.fonts, &self.imgs, connection_state, + init_stage, self.time, &self.i18n.read(), button_style, @@ -480,6 +485,7 @@ impl Controls { self.screen = Screen::Connecting { screen: connecting::Screen::new(ui), connection_state: ConnectionState::InProgress, + init_stage: DetailedInitializationStage::Singleplayer, }; events.push(Event::StartSingleplayer); }, @@ -520,6 +526,7 @@ impl Controls { self.screen = Screen::Connecting { screen: connecting::Screen::new(ui), connection_state: ConnectionState::InProgress, + init_stage: DetailedInitializationStage::StartingMultiplayer, }; events.push(Event::LoginAttempt { @@ -629,6 +636,12 @@ impl Controls { } } + fn init_stage(&mut self, stage: DetailedInitializationStage) { + if let Screen::Connecting { init_stage, .. } = &mut self.screen { + *init_stage = stage + } + } + fn tab(&mut self) { if let Screen::Login { screen, .. } = &mut self.screen { // TODO: add select all function in iced @@ -714,6 +727,10 @@ impl MainMenuUi { pub fn show_info(&mut self, msg: String) { self.controls.connection_error(msg); } + pub fn update_stage(&mut self, stage: DetailedInitializationStage) { + self.controls.init_stage(stage); + } + pub fn connected(&mut self) { self.controls.exit_connect_screen(); } pub fn cancel_connection(&mut self) { self.controls.exit_connect_screen(); }