mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Add asynchronus client creation to Voxygen
Former-commit-id: 0c11cc8a7ddc7b425e036c2357c233e376002c6e
This commit is contained in:
parent
490d255d48
commit
e377e8ff09
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;
|
||||
|
||||
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()) {
|
||||
match event {
|
||||
MainMenuEvent::LoginAttempt{ username, server_address } => {
|
||||
use std::net::ToSocketAddrs;
|
||||
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(
|
||||
// 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)) // <--- 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());
|
||||
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,
|
||||
} => {
|
||||
const DEFAULT_PORT: u16 = 59003;
|
||||
// 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,
|
||||
}
|
||||
|
@ -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,6 +244,26 @@ impl MainMenuUi {
|
||||
}
|
||||
}
|
||||
// Login button
|
||||
// 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)
|
||||
@ -250,12 +273,14 @@ impl MainMenuUi {
|
||||
.label("Login")
|
||||
.label_color(TEXT_COLOR)
|
||||
.label_font_size(24)
|
||||
.label_y(conrod_core::position::Relative::Scalar(5.0))
|
||||
.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) {
|
||||
|
Loading…
Reference in New Issue
Block a user