Add asynchronus client creation to Voxygen

Former-commit-id: 0c11cc8a7ddc7b425e036c2357c233e376002c6e
This commit is contained in:
Imbris 2019-04-14 23:28:29 +00:00 committed by Joshua Barretto
parent 490d255d48
commit e377e8ff09
3 changed files with 184 additions and 61 deletions

View File

@ -0,0 +1,88 @@
use client::{error::Error as ClientError, Client};
use common::comp;
use std::{
sync::mpsc::{channel, Receiver, TryRecvError},
thread::{self, JoinHandle},
};
#[derive(Debug)]
pub enum Error {
// Error parsing input string or error resolving host name
BadAddress(std::io::Error),
// Parsing yielded an empty iterator (specifically to_socket_addrs())
NoAddress,
// Parsing/host name resolution successful but could not connect
ConnectionFailed(ClientError),
}
// Used to asynchronusly parse the server address, resolve host names, and create the client (which involves establishing a connection to the server)
pub struct ClientInit {
rx: Receiver<Result<Client, Error>>,
}
impl ClientInit {
pub fn new(
connection_args: (String, u16, bool),
client_args: (comp::Player, Option<comp::Character>, u64),
) -> Self {
let (server_address, default_port, prefer_ipv6) = connection_args;
let (player, character, view_distance) = client_args;
let (tx, rx) = channel();
let handle = Some(thread::spawn(move || {
use std::net::ToSocketAddrs;
// Parses ip address or resolves hostname
// Note: if you use an ipv6 address the number after the last colon will be used as the port unless you use [] around the address
match server_address
.to_socket_addrs()
.or((server_address.as_ref(), default_port).to_socket_addrs())
{
Ok(socket_adders) => {
let (first_addrs, second_addrs) =
socket_adders.partition::<Vec<_>, _>(|a| a.is_ipv6() == prefer_ipv6);
let mut last_err = None;
for socket_addr in first_addrs.into_iter().chain(second_addrs) {
match Client::new(socket_addr, player.clone(), character, view_distance) {
Ok(client) => {
tx.send(Ok(client));
return;
}
Err(err) => {
match err {
// assume connection failed and try next address
ClientError::Network(_) => {
last_err = Some(Error::ConnectionFailed(err))
}
// TODO: handle error?
_ => panic!(
"Unexpected non-network error when creating client: {:?}",
err
),
}
}
}
}
// Parsing/host name resolution successful but no connection succeeded
tx.send(Err(last_err.unwrap_or(Error::NoAddress)));
}
Err(err) => {
// Error parsing input string or error resolving host name
tx.send(Err(Error::BadAddress(err)));
}
}
}));
ClientInit { rx }
}
// Returns None is the thread is still running
// Otherwise returns the Result of client creation
pub fn poll(&self) -> Option<Result<Client, Error>> {
match self.rx.try_recv() {
Ok(result) => Some(result),
Err(TryRecvError::Empty) => None,
Err(TryRecvError::Disconnected) => panic!("Thread panicked or already finished"),
}
}
}

View File

@ -1,3 +1,4 @@
mod client_init;
mod ui;
use super::char_selection::CharSelectionState;
@ -5,11 +6,8 @@ use crate::{
window::{Event, Window},
GlobalState, PlayState, PlayStateResult,
};
use client::{self, Client};
use common::{
comp,
clock::Clock,
};
use client_init::{ClientInit, Error as InitError};
use common::{clock::Clock, comp};
use std::time::Duration;
use ui::{Event as MainMenuEvent, MainMenuUi};
use vek::*;
@ -42,6 +40,9 @@ impl PlayState for MainMenuState {
// Set up an fps clock
let mut clock = Clock::new();
// Used for client creation
let mut client_init: Option<ClientInit> = None;
loop {
// Handle window events
for event in global_state.window.fetch_events() {
@ -58,41 +59,45 @@ impl PlayState for MainMenuState {
global_state.window.renderer_mut().clear(BG_COLOR);
// Maintain the UI (TODO: Maybe clean this up a little to avoid rightward drift?)
for event in self.main_menu_ui.maintain(global_state.window.renderer_mut()) {
// Poll client creation
match client_init.as_ref().and_then(|init| init.poll()) {
Some(Ok(client)) => {
self.main_menu_ui.connected();
return PlayStateResult::Push(Box::new(CharSelectionState::new(
&mut global_state.window,
std::rc::Rc::new(std::cell::RefCell::new(client)),
)));
}
Some(Err(err)) => {
client_init = None;
self.main_menu_ui.login_error(match err {
InitError::BadAddress(_) | InitError::NoAddress => "No such host is known",
InitError::ConnectionFailed(_) => "Could not connect to address",
}.to_string());
},
None => {}
}
// Maintain the UI
for event in self
.main_menu_ui
.maintain(global_state.window.renderer_mut())
{
match event {
MainMenuEvent::LoginAttempt{ username, server_address } => {
use std::net::ToSocketAddrs;
MainMenuEvent::LoginAttempt {
username,
server_address,
} => {
const DEFAULT_PORT: u16 = 59003;
// Parses ip address or resolves hostname
// Note: if you use an ipv6 address the number after the last colon will be used as the port unless you use [] around the address
match server_address.to_socket_addrs().or((server_address.as_str(), DEFAULT_PORT).to_socket_addrs()) {
Ok(mut socket_adders) => {
while let Some(socket_addr) = socket_adders.next() {
// TODO: handle error
match Client::new(socket_addr, comp::Player::new(username.clone()), Some(comp::Character::test()), 300) {
Ok(client) => {
return PlayStateResult::Push(
Box::new(CharSelectionState::new(
&mut global_state.window,
std::rc::Rc::new(std::cell::RefCell::new(client)) // <--- TODO: Remove this
))
);
}
Err(client::Error::Network(_)) => {} // assume connection failed and try next address
Err(err) => {
panic!("Unexpected non Network error when creating client: {:?}", err);
}
}
}
// Parsing/host name resolution successful but no connection succeeded
self.main_menu_ui.login_error("Could not connect to address".to_string());
}
Err(err) => {
// Error parsing input string or error resolving host name
self.main_menu_ui.login_error("No such host is known".to_string());
}
}
// Don't try to connect if there is already a connection in progress
client_init = client_init.or(Some(ClientInit::new(
(server_address, DEFAULT_PORT, false),
(
comp::Player::new(username.clone()),
Some(comp::Character::test()),
300,
),
)));
}
MainMenuEvent::Quit => return PlayStateResult::Shutdown,
}

View File

@ -7,7 +7,7 @@ use common::assets;
use conrod_core::{
color::TRANSPARENT,
image::Id as ImgId,
position::Dimension,
position::{Dimension, Relative},
text::font::Id as FontId,
widget::{text_box::Event as TextBoxEvent, Button, Image, Rectangle, Text, TextBox},
widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget,
@ -103,6 +103,7 @@ pub struct MainMenuUi {
username: String,
server_address: String,
login_error: Option<String>,
connecting: Option<std::time::Instant>,
}
impl MainMenuUi {
@ -138,6 +139,7 @@ impl MainMenuUi {
username: "Username".to_string(),
server_address: "veloren.mac94.de".to_string(),
login_error: None,
connecting: None,
}
}
@ -154,8 +156,8 @@ impl MainMenuUi {
.label("Alpha 0.1")
.label_rgba(1.0, 1.0, 1.0, 1.0)
.label_font_size(10)
.label_y(conrod_core::position::Relative::Scalar(-40.0))
.label_x(conrod_core::position::Relative::Scalar(-100.0))
.label_y(Relative::Scalar(-40.0))
.label_x(Relative::Scalar(-100.0))
.set(self.ids.v_logo, ui_widgets);
// Input fields
@ -163,6 +165,7 @@ impl MainMenuUi {
macro_rules! login {
() => {
self.login_error = None;
self.connecting = Some(std::time::Instant::now());
events.push(Event::LoginAttempt {
username: self.username.clone(),
server_address: self.server_address.clone(),
@ -241,21 +244,43 @@ impl MainMenuUi {
}
}
// Login button
if Button::image(self.imgs.login_button)
.hover_image(self.imgs.login_button_hover)
.press_image(self.imgs.login_button_press)
.w_h(258.0, 68.0)
.down_from(self.ids.address_bg, 20.0)
.align_middle_x_of(self.ids.address_bg)
.label("Login")
.label_color(TEXT_COLOR)
.label_font_size(24)
.label_y(conrod_core::position::Relative::Scalar(5.0))
.set(self.ids.login_button, ui_widgets)
.was_clicked()
{
login!();
}
// Change button text and remove hover/press images if a connection is in progress
if let Some(start) = self.connecting {
Button::image(self.imgs.login_button)
.w_h(258.0, 68.0)
.down_from(self.ids.address_bg, 20.0)
.align_middle_x_of(self.ids.address_bg)
.label("Connecting...")
.label_color({
let pulse = ((start.elapsed().as_millis() as f32 * 0.008).sin() + 1.0) / 2.0;
Color::Rgba(
TEXT_COLOR.red() * (pulse / 2.0 + 0.5),
TEXT_COLOR.green() * (pulse / 2.0 + 0.5),
TEXT_COLOR.blue() * (pulse / 2.0 + 0.5),
pulse / 4.0 + 0.75,
)
})
.label_font_size(24)
.label_y(Relative::Scalar(5.0))
.set(self.ids.login_button, ui_widgets);
} else {
if Button::image(self.imgs.login_button)
.hover_image(self.imgs.login_button_hover)
.press_image(self.imgs.login_button_press)
.w_h(258.0, 68.0)
.down_from(self.ids.address_bg, 20.0)
.align_middle_x_of(self.ids.address_bg)
.label("Login")
.label_color(TEXT_COLOR)
.label_font_size(24)
.label_y(Relative::Scalar(5.0))
.set(self.ids.login_button, ui_widgets)
.was_clicked()
{
login!();
}
};
// Singleplayer button
if Button::image(self.imgs.login_button)
.hover_image(self.imgs.login_button_hover)
@ -266,8 +291,8 @@ impl MainMenuUi {
.label("Singleplayer")
.label_color(TEXT_COLOR)
.label_font_size(26)
.label_y(conrod_core::position::Relative::Scalar(5.0))
.label_x(conrod_core::position::Relative::Scalar(2.0))
.label_y(Relative::Scalar(5.0))
.label_x(Relative::Scalar(2.0))
.set(self.ids.singleplayer_button, ui_widgets)
.was_clicked()
{
@ -282,7 +307,7 @@ impl MainMenuUi {
.label("Quit")
.label_color(TEXT_COLOR)
.label_font_size(20)
.label_y(conrod_core::position::Relative::Scalar(3.0))
.label_y(Relative::Scalar(3.0))
.set(self.ids.quit_button, ui_widgets)
.was_clicked()
{
@ -297,7 +322,7 @@ impl MainMenuUi {
.label("Settings")
.label_color(TEXT_COLOR)
.label_font_size(20)
.label_y(conrod_core::position::Relative::Scalar(3.0))
.label_y(Relative::Scalar(3.0))
.set(self.ids.settings_button, ui_widgets)
.was_clicked()
{};
@ -310,7 +335,7 @@ impl MainMenuUi {
.label("Servers")
.label_color(TEXT_COLOR)
.label_font_size(20)
.label_y(conrod_core::position::Relative::Scalar(3.0))
.label_y(Relative::Scalar(3.0))
.set(self.ids.servers_button, ui_widgets)
.was_clicked()
{};
@ -320,6 +345,11 @@ impl MainMenuUi {
pub fn login_error(&mut self, msg: String) {
self.login_error = Some(msg);
self.connecting = None;
}
pub fn connected(&mut self) {
self.connecting = None;
}
pub fn handle_event(&mut self, event: ui::Event) {