mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'async_login' into 'master'
Add asynchronus client creation to Voxygen See merge request veloren/veloren!26 Former-commit-id: 51278bbde64ac9dd48bc34a6e7b7922958883f73
This commit is contained in:
88
voxygen/src/menu/main/client_init.rs
Normal file
88
voxygen/src/menu/main/client_init.rs
Normal 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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
mod client_init;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
use super::char_selection::CharSelectionState;
|
use super::char_selection::CharSelectionState;
|
||||||
@ -5,11 +6,8 @@ use crate::{
|
|||||||
window::{Event, Window},
|
window::{Event, Window},
|
||||||
GlobalState, PlayState, PlayStateResult,
|
GlobalState, PlayState, PlayStateResult,
|
||||||
};
|
};
|
||||||
use client::{self, Client};
|
use client_init::{ClientInit, Error as InitError};
|
||||||
use common::{
|
use common::{clock::Clock, comp};
|
||||||
comp,
|
|
||||||
clock::Clock,
|
|
||||||
};
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use ui::{Event as MainMenuEvent, MainMenuUi};
|
use ui::{Event as MainMenuEvent, MainMenuUi};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
@ -42,6 +40,9 @@ impl PlayState for MainMenuState {
|
|||||||
// Set up an fps clock
|
// Set up an fps clock
|
||||||
let mut clock = Clock::new();
|
let mut clock = Clock::new();
|
||||||
|
|
||||||
|
// Used for client creation
|
||||||
|
let mut client_init: Option<ClientInit> = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Handle window events
|
// Handle window events
|
||||||
for event in global_state.window.fetch_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);
|
global_state.window.renderer_mut().clear(BG_COLOR);
|
||||||
|
|
||||||
// Maintain the UI (TODO: Maybe clean this up a little to avoid rightward drift?)
|
// Poll client creation
|
||||||
for event in self.main_menu_ui.maintain(global_state.window.renderer_mut()) {
|
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 {
|
match event {
|
||||||
MainMenuEvent::LoginAttempt{ username, server_address } => {
|
MainMenuEvent::LoginAttempt {
|
||||||
use std::net::ToSocketAddrs;
|
username,
|
||||||
|
server_address,
|
||||||
|
} => {
|
||||||
const DEFAULT_PORT: u16 = 59003;
|
const DEFAULT_PORT: u16 = 59003;
|
||||||
// Parses ip address or resolves hostname
|
// Don't try to connect if there is already a connection in progress
|
||||||
// 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
|
client_init = client_init.or(Some(ClientInit::new(
|
||||||
match server_address.to_socket_addrs().or((server_address.as_str(), DEFAULT_PORT).to_socket_addrs()) {
|
(server_address, DEFAULT_PORT, false),
|
||||||
Ok(mut socket_adders) => {
|
(
|
||||||
while let Some(socket_addr) = socket_adders.next() {
|
comp::Player::new(username.clone()),
|
||||||
// TODO: handle error
|
Some(comp::Character::test()),
|
||||||
match Client::new(socket_addr, comp::Player::new(username.clone()), Some(comp::Character::test()), 300) {
|
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
MainMenuEvent::Quit => return PlayStateResult::Shutdown,
|
MainMenuEvent::Quit => return PlayStateResult::Shutdown,
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ use common::assets;
|
|||||||
use conrod_core::{
|
use conrod_core::{
|
||||||
color::TRANSPARENT,
|
color::TRANSPARENT,
|
||||||
image::Id as ImgId,
|
image::Id as ImgId,
|
||||||
position::Dimension,
|
position::{Dimension, Relative},
|
||||||
text::font::Id as FontId,
|
text::font::Id as FontId,
|
||||||
widget::{text_box::Event as TextBoxEvent, Button, Image, Rectangle, Text, TextBox},
|
widget::{text_box::Event as TextBoxEvent, Button, Image, Rectangle, Text, TextBox},
|
||||||
widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget,
|
widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget,
|
||||||
@ -103,6 +103,7 @@ pub struct MainMenuUi {
|
|||||||
username: String,
|
username: String,
|
||||||
server_address: String,
|
server_address: String,
|
||||||
login_error: Option<String>,
|
login_error: Option<String>,
|
||||||
|
connecting: Option<std::time::Instant>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MainMenuUi {
|
impl MainMenuUi {
|
||||||
@ -138,6 +139,7 @@ impl MainMenuUi {
|
|||||||
username: "Username".to_string(),
|
username: "Username".to_string(),
|
||||||
server_address: "veloren.mac94.de".to_string(),
|
server_address: "veloren.mac94.de".to_string(),
|
||||||
login_error: None,
|
login_error: None,
|
||||||
|
connecting: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,8 +156,8 @@ impl MainMenuUi {
|
|||||||
.label("Alpha 0.1")
|
.label("Alpha 0.1")
|
||||||
.label_rgba(1.0, 1.0, 1.0, 1.0)
|
.label_rgba(1.0, 1.0, 1.0, 1.0)
|
||||||
.label_font_size(10)
|
.label_font_size(10)
|
||||||
.label_y(conrod_core::position::Relative::Scalar(-40.0))
|
.label_y(Relative::Scalar(-40.0))
|
||||||
.label_x(conrod_core::position::Relative::Scalar(-100.0))
|
.label_x(Relative::Scalar(-100.0))
|
||||||
.set(self.ids.v_logo, ui_widgets);
|
.set(self.ids.v_logo, ui_widgets);
|
||||||
|
|
||||||
// Input fields
|
// Input fields
|
||||||
@ -163,6 +165,7 @@ impl MainMenuUi {
|
|||||||
macro_rules! login {
|
macro_rules! login {
|
||||||
() => {
|
() => {
|
||||||
self.login_error = None;
|
self.login_error = None;
|
||||||
|
self.connecting = Some(std::time::Instant::now());
|
||||||
events.push(Event::LoginAttempt {
|
events.push(Event::LoginAttempt {
|
||||||
username: self.username.clone(),
|
username: self.username.clone(),
|
||||||
server_address: self.server_address.clone(),
|
server_address: self.server_address.clone(),
|
||||||
@ -241,21 +244,43 @@ impl MainMenuUi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Login button
|
// Login button
|
||||||
if Button::image(self.imgs.login_button)
|
// Change button text and remove hover/press images if a connection is in progress
|
||||||
.hover_image(self.imgs.login_button_hover)
|
if let Some(start) = self.connecting {
|
||||||
.press_image(self.imgs.login_button_press)
|
Button::image(self.imgs.login_button)
|
||||||
.w_h(258.0, 68.0)
|
.w_h(258.0, 68.0)
|
||||||
.down_from(self.ids.address_bg, 20.0)
|
.down_from(self.ids.address_bg, 20.0)
|
||||||
.align_middle_x_of(self.ids.address_bg)
|
.align_middle_x_of(self.ids.address_bg)
|
||||||
.label("Login")
|
.label("Connecting...")
|
||||||
.label_color(TEXT_COLOR)
|
.label_color({
|
||||||
.label_font_size(24)
|
let pulse = ((start.elapsed().as_millis() as f32 * 0.008).sin() + 1.0) / 2.0;
|
||||||
.label_y(conrod_core::position::Relative::Scalar(5.0))
|
Color::Rgba(
|
||||||
.set(self.ids.login_button, ui_widgets)
|
TEXT_COLOR.red() * (pulse / 2.0 + 0.5),
|
||||||
.was_clicked()
|
TEXT_COLOR.green() * (pulse / 2.0 + 0.5),
|
||||||
{
|
TEXT_COLOR.blue() * (pulse / 2.0 + 0.5),
|
||||||
login!();
|
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
|
// Singleplayer button
|
||||||
if Button::image(self.imgs.login_button)
|
if Button::image(self.imgs.login_button)
|
||||||
.hover_image(self.imgs.login_button_hover)
|
.hover_image(self.imgs.login_button_hover)
|
||||||
@ -266,8 +291,8 @@ impl MainMenuUi {
|
|||||||
.label("Singleplayer")
|
.label("Singleplayer")
|
||||||
.label_color(TEXT_COLOR)
|
.label_color(TEXT_COLOR)
|
||||||
.label_font_size(26)
|
.label_font_size(26)
|
||||||
.label_y(conrod_core::position::Relative::Scalar(5.0))
|
.label_y(Relative::Scalar(5.0))
|
||||||
.label_x(conrod_core::position::Relative::Scalar(2.0))
|
.label_x(Relative::Scalar(2.0))
|
||||||
.set(self.ids.singleplayer_button, ui_widgets)
|
.set(self.ids.singleplayer_button, ui_widgets)
|
||||||
.was_clicked()
|
.was_clicked()
|
||||||
{
|
{
|
||||||
@ -282,7 +307,7 @@ impl MainMenuUi {
|
|||||||
.label("Quit")
|
.label("Quit")
|
||||||
.label_color(TEXT_COLOR)
|
.label_color(TEXT_COLOR)
|
||||||
.label_font_size(20)
|
.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)
|
.set(self.ids.quit_button, ui_widgets)
|
||||||
.was_clicked()
|
.was_clicked()
|
||||||
{
|
{
|
||||||
@ -297,7 +322,7 @@ impl MainMenuUi {
|
|||||||
.label("Settings")
|
.label("Settings")
|
||||||
.label_color(TEXT_COLOR)
|
.label_color(TEXT_COLOR)
|
||||||
.label_font_size(20)
|
.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)
|
.set(self.ids.settings_button, ui_widgets)
|
||||||
.was_clicked()
|
.was_clicked()
|
||||||
{};
|
{};
|
||||||
@ -310,7 +335,7 @@ impl MainMenuUi {
|
|||||||
.label("Servers")
|
.label("Servers")
|
||||||
.label_color(TEXT_COLOR)
|
.label_color(TEXT_COLOR)
|
||||||
.label_font_size(20)
|
.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)
|
.set(self.ids.servers_button, ui_widgets)
|
||||||
.was_clicked()
|
.was_clicked()
|
||||||
{};
|
{};
|
||||||
@ -320,6 +345,11 @@ impl MainMenuUi {
|
|||||||
|
|
||||||
pub fn login_error(&mut self, msg: String) {
|
pub fn login_error(&mut self, msg: String) {
|
||||||
self.login_error = Some(msg);
|
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) {
|
pub fn handle_event(&mut self, event: ui::Event) {
|
||||||
|
Reference in New Issue
Block a user