From a97b694dfe69a95bdfe5224c145799c9d630e700 Mon Sep 17 00:00:00 2001 From: Capucho Date: Sun, 1 Mar 2020 22:18:22 +0000 Subject: [PATCH] Groundwork for fixing #36 and rewrite of client timeouts so that they don't use Instant and Duration --- client/src/lib.rs | 37 +++++++++++++++++------------------- voxygen/src/hud/mod.rs | 16 ++++++++++++++-- voxygen/src/main.rs | 5 +++++ voxygen/src/menu/main/mod.rs | 8 +++----- voxygen/src/session.rs | 30 ++++++++++++++++------------- voxygen/src/singleplayer.rs | 29 ++++++++++++++++++++-------- 6 files changed, 77 insertions(+), 48 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index 57ee0fb4d7..5bb2068dc6 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -40,11 +40,11 @@ use vek::*; // The duration of network inactivity until the player is kicked // @TODO: in the future, this should be configurable on the server // and be provided to the client -const SERVER_TIMEOUT: Duration = Duration::from_secs(20); +const SERVER_TIMEOUT: f64 = 20.0; // After this duration has elapsed, the user will begin getting kick warnings in // their chat window -const SERVER_TIMEOUT_GRACE_PERIOD: Duration = Duration::from_secs(14); +const SERVER_TIMEOUT_GRACE_PERIOD: f64 = 14.0; pub enum Event { Chat { @@ -64,8 +64,8 @@ pub struct Client { postbox: PostBox, - last_server_ping: Instant, - last_server_pong: Instant, + last_server_ping: f64, + last_server_pong: f64, last_ping_delta: f64, tick: u64, @@ -152,8 +152,8 @@ impl Client { postbox, - last_server_ping: Instant::now(), - last_server_pong: Instant::now(), + last_server_ping: 0.0, + last_server_pong: 0.0, last_ping_delta: 0.0, tick: 0, @@ -481,9 +481,9 @@ impl Client { } // Send a ping to the server once every second - if Instant::now().duration_since(self.last_server_ping) > Duration::from_secs(1) { + if self.state.get_time() - self.last_server_ping > 1. { self.postbox.send_message(ClientMsg::Ping); - self.last_server_ping = Instant::now(); + self.last_server_ping = self.state.get_time(); } // 6) Update the server about the player's physics attributes. @@ -528,16 +528,14 @@ impl Client { // Check that we have an valid connection. // Use the last ping time as a 1s rate limiter, we only notify the user once per // second - if Instant::now().duration_since(self.last_server_ping) > Duration::from_secs(1) { - let duration_since_last_pong = Instant::now().duration_since(self.last_server_pong); + if self.state.get_time() - self.last_server_ping > 1. { + let duration_since_last_pong = self.state.get_time() - self.last_server_pong; // Dispatch a notification to the HUD warning they will be kicked in {n} seconds - if duration_since_last_pong.as_secs() >= SERVER_TIMEOUT_GRACE_PERIOD.as_secs() { - if let Some(seconds_until_kick) = - SERVER_TIMEOUT.checked_sub(duration_since_last_pong) - { + if duration_since_last_pong >= SERVER_TIMEOUT_GRACE_PERIOD { + if self.state.get_time() - duration_since_last_pong > 0. { frontend_events.push(Event::DisconnectionNotification( - seconds_until_kick.as_secs(), + (self.state.get_time() - duration_since_last_pong).round() as u64, )); } } @@ -591,11 +589,10 @@ impl Client { ServerMsg::Ping => self.postbox.send_message(ClientMsg::Pong), ServerMsg::Pong => { - self.last_server_pong = Instant::now(); + self.last_server_pong = self.state.get_time(); - self.last_ping_delta = Instant::now() - .duration_since(self.last_server_ping) - .as_secs_f64(); + self.last_ping_delta = + (self.state.get_time() - self.last_server_ping).round(); }, ServerMsg::ChatMsg { message, chat_type } => { frontend_events.push(Event::Chat { message, chat_type }) @@ -712,7 +709,7 @@ impl Client { } else if let Some(err) = self.postbox.error() { return Err(err.into()); // We regularily ping in the tick method - } else if Instant::now().duration_since(self.last_server_pong) > SERVER_TIMEOUT { + } else if self.state.get_time() - self.last_server_pong > SERVER_TIMEOUT { return Err(Error::ServerTimeout); } Ok(frontend_events) diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 760bd4a2ef..e4e0605839 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -357,7 +357,7 @@ impl Show { fn toggle_ui(&mut self) { self.ui = !self.ui; } - fn toggle_windows(&mut self) { + fn toggle_windows(&mut self, global_state: &mut GlobalState) { if self.bag || self.esc_menu || self.map @@ -379,9 +379,21 @@ impl Show { self.character_window = false; self.open_windows = Windows::None; self.want_grab = true; + + // Unpause the game if we are on singleplayer + if let Some(ref singleplayer) = global_state.singleplayer { + singleplayer.pause(false); + global_state.paused = false; + }; } else { self.esc_menu = true; self.want_grab = false; + + // Pause the game if we are on singleplayer + if let Some(ref singleplayer) = global_state.singleplayer { + singleplayer.pause(true); + global_state.paused = true; + }; } } @@ -1992,7 +2004,7 @@ impl Hud { self.ui.focus_widget(None); } else { // Close windows on esc - self.show.toggle_windows(); + self.show.toggle_windows(global_state); } true }, diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index 53536055ce..8379044eea 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -32,6 +32,7 @@ use crate::{ menu::main::MainMenuState, meta::Meta, settings::Settings, + singleplayer::Singleplayer, window::Window, }; use common::assets::{load, load_expect}; @@ -45,6 +46,8 @@ pub struct GlobalState { window: Window, audio: AudioFrontend, info_message: Option, + singleplayer: Option, + paused: bool, } impl GlobalState { @@ -135,6 +138,8 @@ fn main() { settings, meta, info_message: None, + singleplayer: None, + paused: false, }; // Try to load the localization and log missing entries diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index 9ce56b48dc..504e3d6dc9 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -18,7 +18,6 @@ use ui::{Event as MainMenuEvent, MainMenuUi}; pub struct MainMenuState { main_menu_ui: MainMenuUi, - singleplayer: Option, } impl MainMenuState { @@ -26,7 +25,6 @@ impl MainMenuState { pub fn new(global_state: &mut GlobalState) -> Self { Self { main_menu_ui: MainMenuUi::new(global_state), - singleplayer: None, } } } @@ -47,7 +45,7 @@ impl PlayState for MainMenuState { } // Reset singleplayer server if it was running already - self.singleplayer = None; + global_state.singleplayer = None; loop { // Handle window events. @@ -119,7 +117,7 @@ impl PlayState for MainMenuState { // 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 - self.singleplayer = None; + global_state.singleplayer = None; client_init = None; self.main_menu_ui.cancel_connection(); }, @@ -127,7 +125,7 @@ impl PlayState for MainMenuState { MainMenuEvent::StartSingleplayer => { let (singleplayer, server_settings) = Singleplayer::new(None); // TODO: Make client and server use the same thread pool - self.singleplayer = Some(singleplayer); + global_state.singleplayer = Some(singleplayer); attempt_login( global_state, diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index a45ab1ad0f..cd79db199d 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -379,13 +379,15 @@ impl PlayState for SessionState { self.inputs.look_dir = cam_dir; - // Perform an in-game tick. - if let Err(err) = self.tick(clock.get_avg_delta()) { - global_state.info_message = - Some(localized_strings.get("common.connection_lost").to_owned()); - error!("[session] Failed to tick the scene: {:?}", err); + if !global_state.paused { + // Perform an in-game tick. + if let Err(err) = self.tick(clock.get_avg_delta()) { + global_state.info_message = + Some(localized_strings.get("common.connection_lost").to_owned()); + error!("[session] Failed to tick the scene: {:?}", err); - return PlayStateResult::Pop; + return PlayStateResult::Pop; + } } // Maintain global state. @@ -609,13 +611,15 @@ impl PlayState for SessionState { } } - // Maintain the scene. - self.scene.maintain( - global_state.window.renderer_mut(), - &mut global_state.audio, - &self.client.borrow(), - global_state.settings.graphics.gamma, - ); + if !global_state.paused { + // Maintain the scene. + self.scene.maintain( + global_state.window.renderer_mut(), + &mut global_state.audio, + &self.client.borrow(), + global_state.settings.graphics.gamma, + ); + } // Render the session. self.render(global_state.window.renderer_mut()); diff --git a/voxygen/src/singleplayer.rs b/voxygen/src/singleplayer.rs index a1b3cec031..c03e79457d 100644 --- a/voxygen/src/singleplayer.rs +++ b/voxygen/src/singleplayer.rs @@ -12,6 +12,7 @@ const TPS: u64 = 30; enum Msg { Stop, + Pause(bool), } /// Used to start and stop the background thread running the server @@ -50,6 +51,8 @@ impl Singleplayer { settings, ) } + + pub fn pause(&self, paused: bool) { let _ = self.sender.send(Msg::Pause(paused)); } } impl Drop for Singleplayer { @@ -64,8 +67,26 @@ fn run_server(mut server: Server, rec: Receiver) { // Set up an fps clock let mut clock = Clock::start(); + let mut paused = false; loop { + match rec.try_recv() { + Ok(msg) => match msg { + Msg::Stop => break, + Msg::Pause(val) => paused = val, + }, + Err(err) => match err { + TryRecvError::Empty => (), + TryRecvError::Disconnected => break, + }, + } + + if paused { + // Wait for the next tick. + clock.tick(Duration::from_millis(1000 / TPS)); + continue; + } + let events = server .tick(Input::default(), clock.get_last_delta()) .expect("Failed to tick server!"); @@ -81,14 +102,6 @@ fn run_server(mut server: Server, rec: Receiver) { // Clean up the server after a tick. server.cleanup(); - match rec.try_recv() { - Ok(_msg) => break, - Err(err) => match err { - TryRecvError::Empty => (), - TryRecvError::Disconnected => break, - }, - } - // Wait for the next tick. clock.tick(Duration::from_millis(1000 / TPS)); }