Groundwork for fixing #36 and rewrite of client timeouts so that they don't use Instant and Duration

This commit is contained in:
Capucho 2020-03-01 22:18:22 +00:00
parent 68fbc2d1b6
commit a97b694dfe
6 changed files with 77 additions and 48 deletions

View File

@ -40,11 +40,11 @@ use vek::*;
// The duration of network inactivity until the player is kicked // The duration of network inactivity until the player is kicked
// @TODO: in the future, this should be configurable on the server // @TODO: in the future, this should be configurable on the server
// and be provided to the client // 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 // After this duration has elapsed, the user will begin getting kick warnings in
// their chat window // their chat window
const SERVER_TIMEOUT_GRACE_PERIOD: Duration = Duration::from_secs(14); const SERVER_TIMEOUT_GRACE_PERIOD: f64 = 14.0;
pub enum Event { pub enum Event {
Chat { Chat {
@ -64,8 +64,8 @@ pub struct Client {
postbox: PostBox<ClientMsg, ServerMsg>, postbox: PostBox<ClientMsg, ServerMsg>,
last_server_ping: Instant, last_server_ping: f64,
last_server_pong: Instant, last_server_pong: f64,
last_ping_delta: f64, last_ping_delta: f64,
tick: u64, tick: u64,
@ -152,8 +152,8 @@ impl Client {
postbox, postbox,
last_server_ping: Instant::now(), last_server_ping: 0.0,
last_server_pong: Instant::now(), last_server_pong: 0.0,
last_ping_delta: 0.0, last_ping_delta: 0.0,
tick: 0, tick: 0,
@ -481,9 +481,9 @@ impl Client {
} }
// Send a ping to the server once every second // 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.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. // 6) Update the server about the player's physics attributes.
@ -528,16 +528,14 @@ impl Client {
// Check that we have an valid connection. // Check that we have an valid connection.
// Use the last ping time as a 1s rate limiter, we only notify the user once per // Use the last ping time as a 1s rate limiter, we only notify the user once per
// second // second
if Instant::now().duration_since(self.last_server_ping) > Duration::from_secs(1) { if self.state.get_time() - self.last_server_ping > 1. {
let duration_since_last_pong = Instant::now().duration_since(self.last_server_pong); 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 // 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 duration_since_last_pong >= SERVER_TIMEOUT_GRACE_PERIOD {
if let Some(seconds_until_kick) = if self.state.get_time() - duration_since_last_pong > 0. {
SERVER_TIMEOUT.checked_sub(duration_since_last_pong)
{
frontend_events.push(Event::DisconnectionNotification( 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::Ping => self.postbox.send_message(ClientMsg::Pong),
ServerMsg::Pong => { ServerMsg::Pong => {
self.last_server_pong = Instant::now(); self.last_server_pong = self.state.get_time();
self.last_ping_delta = Instant::now() self.last_ping_delta =
.duration_since(self.last_server_ping) (self.state.get_time() - self.last_server_ping).round();
.as_secs_f64();
}, },
ServerMsg::ChatMsg { message, chat_type } => { ServerMsg::ChatMsg { message, chat_type } => {
frontend_events.push(Event::Chat { 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() { } else if let Some(err) = self.postbox.error() {
return Err(err.into()); return Err(err.into());
// We regularily ping in the tick method // 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); return Err(Error::ServerTimeout);
} }
Ok(frontend_events) Ok(frontend_events)

View File

@ -357,7 +357,7 @@ impl Show {
fn toggle_ui(&mut self) { self.ui = !self.ui; } 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 if self.bag
|| self.esc_menu || self.esc_menu
|| self.map || self.map
@ -379,9 +379,21 @@ impl Show {
self.character_window = false; self.character_window = false;
self.open_windows = Windows::None; self.open_windows = Windows::None;
self.want_grab = true; 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 { } else {
self.esc_menu = true; self.esc_menu = true;
self.want_grab = false; 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); self.ui.focus_widget(None);
} else { } else {
// Close windows on esc // Close windows on esc
self.show.toggle_windows(); self.show.toggle_windows(global_state);
} }
true true
}, },

View File

@ -32,6 +32,7 @@ use crate::{
menu::main::MainMenuState, menu::main::MainMenuState,
meta::Meta, meta::Meta,
settings::Settings, settings::Settings,
singleplayer::Singleplayer,
window::Window, window::Window,
}; };
use common::assets::{load, load_expect}; use common::assets::{load, load_expect};
@ -45,6 +46,8 @@ pub struct GlobalState {
window: Window, window: Window,
audio: AudioFrontend, audio: AudioFrontend,
info_message: Option<String>, info_message: Option<String>,
singleplayer: Option<Singleplayer>,
paused: bool,
} }
impl GlobalState { impl GlobalState {
@ -135,6 +138,8 @@ fn main() {
settings, settings,
meta, meta,
info_message: None, info_message: None,
singleplayer: None,
paused: false,
}; };
// Try to load the localization and log missing entries // Try to load the localization and log missing entries

View File

@ -18,7 +18,6 @@ use ui::{Event as MainMenuEvent, MainMenuUi};
pub struct MainMenuState { pub struct MainMenuState {
main_menu_ui: MainMenuUi, main_menu_ui: MainMenuUi,
singleplayer: Option<Singleplayer>,
} }
impl MainMenuState { impl MainMenuState {
@ -26,7 +25,6 @@ impl MainMenuState {
pub fn new(global_state: &mut GlobalState) -> Self { pub fn new(global_state: &mut GlobalState) -> Self {
Self { Self {
main_menu_ui: MainMenuUi::new(global_state), 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 // Reset singleplayer server if it was running already
self.singleplayer = None; global_state.singleplayer = None;
loop { loop {
// Handle window events. // Handle window events.
@ -119,7 +117,7 @@ impl PlayState for MainMenuState {
// client_init contains Some(ClientInit), which spawns a thread which // client_init contains Some(ClientInit), which spawns a thread which
// contains a TcpStream::connect() call This call is // contains a TcpStream::connect() call This call is
// blocking TODO fix when the network rework happens // blocking TODO fix when the network rework happens
self.singleplayer = None; global_state.singleplayer = None;
client_init = None; client_init = None;
self.main_menu_ui.cancel_connection(); self.main_menu_ui.cancel_connection();
}, },
@ -127,7 +125,7 @@ impl PlayState for MainMenuState {
MainMenuEvent::StartSingleplayer => { MainMenuEvent::StartSingleplayer => {
let (singleplayer, server_settings) = Singleplayer::new(None); // TODO: Make client and server use the same thread pool 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( attempt_login(
global_state, global_state,

View File

@ -379,13 +379,15 @@ impl PlayState for SessionState {
self.inputs.look_dir = cam_dir; self.inputs.look_dir = cam_dir;
// Perform an in-game tick. if !global_state.paused {
if let Err(err) = self.tick(clock.get_avg_delta()) { // Perform an in-game tick.
global_state.info_message = if let Err(err) = self.tick(clock.get_avg_delta()) {
Some(localized_strings.get("common.connection_lost").to_owned()); global_state.info_message =
error!("[session] Failed to tick the scene: {:?}", err); 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. // Maintain global state.
@ -609,13 +611,15 @@ impl PlayState for SessionState {
} }
} }
// Maintain the scene. if !global_state.paused {
self.scene.maintain( // Maintain the scene.
global_state.window.renderer_mut(), self.scene.maintain(
&mut global_state.audio, global_state.window.renderer_mut(),
&self.client.borrow(), &mut global_state.audio,
global_state.settings.graphics.gamma, &self.client.borrow(),
); global_state.settings.graphics.gamma,
);
}
// Render the session. // Render the session.
self.render(global_state.window.renderer_mut()); self.render(global_state.window.renderer_mut());

View File

@ -12,6 +12,7 @@ const TPS: u64 = 30;
enum Msg { enum Msg {
Stop, Stop,
Pause(bool),
} }
/// Used to start and stop the background thread running the server /// Used to start and stop the background thread running the server
@ -50,6 +51,8 @@ impl Singleplayer {
settings, settings,
) )
} }
pub fn pause(&self, paused: bool) { let _ = self.sender.send(Msg::Pause(paused)); }
} }
impl Drop for Singleplayer { impl Drop for Singleplayer {
@ -64,8 +67,26 @@ fn run_server(mut server: Server, rec: Receiver<Msg>) {
// Set up an fps clock // Set up an fps clock
let mut clock = Clock::start(); let mut clock = Clock::start();
let mut paused = false;
loop { 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 let events = server
.tick(Input::default(), clock.get_last_delta()) .tick(Input::default(), clock.get_last_delta())
.expect("Failed to tick server!"); .expect("Failed to tick server!");
@ -81,14 +102,6 @@ fn run_server(mut server: Server, rec: Receiver<Msg>) {
// Clean up the server after a tick. // Clean up the server after a tick.
server.cleanup(); server.cleanup();
match rec.try_recv() {
Ok(_msg) => break,
Err(err) => match err {
TryRecvError::Empty => (),
TryRecvError::Disconnected => break,
},
}
// Wait for the next tick. // Wait for the next tick.
clock.tick(Duration::from_millis(1000 / TPS)); clock.tick(Duration::from_millis(1000 / TPS));
} }