diff --git a/common/src/net/error.rs b/common/src/net/error.rs deleted file mode 100644 index c3dd162fc9..0000000000 --- a/common/src/net/error.rs +++ /dev/null @@ -1,74 +0,0 @@ -#[derive(Debug)] -pub enum PostError { - InvalidMessage, - InternalError, - Disconnected, -} - -#[derive(Debug)] -pub enum PostErrorInternal { - Io(std::io::Error), - Serde(bincode::Error), - ChannelRecv(std::sync::mpsc::TryRecvError), - ChannelSend, // Empty because I couldn't figure out how to handle generic type in mpsc::TrySendError properly - MsgSizeLimitExceeded, - MioError, -} - -impl<'a, T: Into<&'a PostErrorInternal>> From for PostError { - fn from(err: T) -> Self { - match err.into() { - // TODO: Are I/O errors always disconnect errors? - PostErrorInternal::Io(_) => PostError::Disconnected, - PostErrorInternal::Serde(_) => PostError::InvalidMessage, - PostErrorInternal::MsgSizeLimitExceeded => PostError::InvalidMessage, - PostErrorInternal::MioError => PostError::InternalError, - PostErrorInternal::ChannelRecv(_) => PostError::InternalError, - PostErrorInternal::ChannelSend => PostError::InternalError, - } - } -} - -impl From for PostError { - fn from(err: PostErrorInternal) -> Self { - (&err).into() - } -} - -impl From for PostErrorInternal { - fn from(err: std::io::Error) -> Self { - PostErrorInternal::Io(err) - } -} - -impl From for PostErrorInternal { - fn from(err: bincode::Error) -> Self { - PostErrorInternal::Serde(err) - } -} - -impl From for PostErrorInternal { - fn from(err: std::sync::mpsc::TryRecvError) -> Self { - PostErrorInternal::ChannelRecv(err) - } -} - - - -impl From for PostError { - fn from(err: std::io::Error) -> Self { - (&PostErrorInternal::from(err)).into() - } -} - -impl From for PostError { - fn from(err: bincode::Error) -> Self { - (&PostErrorInternal::from(err)).into() - } -} - -impl From for PostError { - fn from(err: std::sync::mpsc::TryRecvError) -> Self { - (&PostErrorInternal::from(err)).into() - } -} diff --git a/common/src/net/mod.rs b/common/src/net/mod.rs index 9439bf74e1..c1c7fd4107 100644 --- a/common/src/net/mod.rs +++ b/common/src/net/mod.rs @@ -1,8 +1,5 @@ pub mod data; -pub mod error; pub mod post; -pub mod postbox; -pub mod postoffice; // Reexports pub use self::{ diff --git a/common/src/net/post.rs b/common/src/net/post.rs index 4bd4b3bfd1..b9160ec35b 100644 --- a/common/src/net/post.rs +++ b/common/src/net/post.rs @@ -116,7 +116,7 @@ impl PostOffice { pub fn new_connections(&mut self) -> impl ExactSizeIterator> { let mut conns = VecDeque::new(); - if let Some(_) = self.err { + if self.err.is_some() { return conns.into_iter(); } @@ -132,7 +132,7 @@ impl PostOffice { for event in events { match event.token() { // Keep reading new postboxes from the channel - POSTBOX_TOKEN => loop { + POSTBOX_TOK => loop { match self.postbox_rx.try_recv() { Ok(Ok(conn)) => conns.push_back(conn), Err(TryRecvError::Empty) => break, @@ -141,7 +141,7 @@ impl PostOffice { return conns.into_iter(); }, Ok(Err(err)) => { - self.err = Some(err.into()); + self.err = Some(err); return conns.into_iter(); }, } @@ -209,7 +209,7 @@ pub struct PostBox { impl PostBox { pub fn to_server>(addr: A) -> Result { - Self::from_tcpstream(TcpStream::connect(&addr.into())?) + Self::from_tcpstream(TcpStream::from_stream(std::net::TcpStream::connect(&addr.into())?)?) } fn from_tcpstream(tcp_stream: TcpStream) -> Result { @@ -254,7 +254,7 @@ impl PostBox { pub fn new_messages(&mut self) -> impl ExactSizeIterator { let mut msgs = VecDeque::new(); - if let Some(_) = self.err { + if self.err.is_some() { return msgs.into_iter(); } @@ -270,7 +270,7 @@ impl PostBox { for event in events { match event.token() { // Keep reading new messages from the channel - RECV_TOKEN => loop { + RECV_TOK => loop { match self.recv_rx.try_recv() { Ok(Ok(msg)) => msgs.push_back(msg), Err(TryRecvError::Empty) => break, @@ -279,7 +279,7 @@ impl PostBox { return msgs.into_iter(); }, Ok(Err(err)) => { - self.err = Some(err.into()); + self.err = Some(err); return msgs.into_iter(); }, } @@ -322,17 +322,16 @@ fn postbox_worker( for event in &events { match event.token() { - CTRL_TOK => loop { + CTRL_TOK => match ctrl_rx.try_recv() { Ok(CtrlMsg::Shutdown) => { break 'work; }, - Err(TryRecvError::Empty) => break, + Err(TryRecvError::Empty) => (), Err(err) => { recv_tx.send(Err(err.into()))?; break 'work; }, - } }, SEND_TOK => loop { match send_rx.try_recv() { @@ -340,7 +339,7 @@ fn postbox_worker( let mut msg_bytes = match bincode::serialize(&outgoing_msg) { Ok(bytes) => bytes, Err(err) => { - recv_tx.send(Err((*err).into())); + recv_tx.send(Err((*err).into()))?; break 'work; }, }; @@ -355,7 +354,7 @@ fn postbox_worker( match tcp_stream.write_all(&packet) { Ok(()) => {}, Err(err) => { - recv_tx.send(Err(err.into())); + recv_tx.send(Err(err.into()))?; break 'work; }, } @@ -368,11 +367,11 @@ fn postbox_worker( match tcp_stream.take_error() { Ok(None) => {}, Ok(Some(err)) => { - recv_tx.send(Err(err.into())); + recv_tx.send(Err(err.into()))?; break 'work; }, Err(err) => { - recv_tx.send(Err(err.into())); + recv_tx.send(Err(err.into()))?; break 'work; }, } @@ -380,7 +379,7 @@ fn postbox_worker( RecvState::ReadHead(head) => if head.len() == 8 { let len = usize::from_le_bytes(<[u8; 8]>::try_from(head.as_slice()).unwrap()); if len > MAX_MSG_BYTES { - recv_tx.send(Err(Error::InvalidMsg)); + recv_tx.send(Err(Error::InvalidMsg))?; break 'work; } else if len == 0 { recv_state = RecvState::ReadHead(Vec::with_capacity(8)); @@ -427,7 +426,7 @@ fn postbox_worker( } } - tcp_stream.shutdown(Shutdown::Both); + tcp_stream.shutdown(Shutdown::Both)?; Ok(()) } @@ -453,6 +452,21 @@ fn connect() { assert_eq!(postoffice.error(), None); } +#[test] +fn connect_fail() { + let listen_addr = ([0; 4], 12345); + let connect_addr = ([127, 0, 0, 1], 12212); + + let mut postoffice = PostOffice::::bind(listen_addr).unwrap(); + + // We should start off with 0 incoming connections + thread::sleep(Duration::from_millis(250)); + assert_eq!(postoffice.new_connections().len(), 0); + assert_eq!(postoffice.error(), None); + + assert!(PostBox::::to_server(connect_addr).is_err()); +} + #[test] fn connection_count() { let srv_addr = ([127, 0, 0, 1], 12346); @@ -469,7 +483,7 @@ fn connection_count() { postboxes.push(PostBox::::to_server(srv_addr).unwrap()); } - // 10 postboxes created, we should have 10 + // 5 postboxes created, we should have 5 thread::sleep(Duration::from_millis(3500)); let incoming = postoffice.new_connections(); assert_eq!(incoming.len(), 5); diff --git a/common/src/net/postbox.rs b/common/src/net/postbox.rs deleted file mode 100644 index 21d9466637..0000000000 --- a/common/src/net/postbox.rs +++ /dev/null @@ -1,270 +0,0 @@ -// Standard -use std::{ - collections::VecDeque, - convert::TryFrom, - io::{ - ErrorKind, - Read, - }, - net::SocketAddr, - thread, - time::Duration, - sync::mpsc::TryRecvError, -}; - -// External -use bincode; -use mio::{net::TcpStream, Events, Poll, PollOpt, Ready, Token}; -use mio_extras::channel::{channel, Receiver, Sender}; - -// Crate -use super::{ - data::ControlMsg, - error::{ - PostError, - PostErrorInternal, - }, - PostRecv, - PostSend, -}; - -// Constants -const CTRL_TOKEN: Token = Token(0); // Token for thread control messages -const DATA_TOKEN: Token = Token(1); // Token for thread data exchange -const CONN_TOKEN: Token = Token(2); // Token for TcpStream for the PostBox child thread -const MESSAGE_SIZE_CAP: u64 = 1 << 20; // Maximum accepted length of a packet - -/// A high-level wrapper of [`TcpStream`](mio::net::TcpStream). -/// [`PostBox`] takes care of serializing sent packets and deserializing received packets in the background, providing a simple API for sending and receiving objects over network. -pub struct PostBox -where - S: PostSend, - R: PostRecv, -{ - handle: Option>, - ctrl: Sender, - recv: Receiver>, - send: Sender, - poll: Poll, - err: Option, -} - -impl PostBox -where - S: PostSend, - R: PostRecv, -{ - /// Creates a new [`PostBox`] connected to specified address, meant to be used by the client - pub fn to_server>(addr: A) -> Result, PostError> { - let connection = TcpStream::connect(&addr.into())?; - Self::from_tcpstream(connection) - } - - /// Creates a new [`PostBox`] from an existing connection, meant to be used by [`PostOffice`](super::PostOffice) on the server - pub fn from_tcpstream(connection: TcpStream) -> Result, PostError> { - let (ctrl_tx, ctrl_rx) = channel(); // Control messages - let (send_tx, send_rx) = channel(); // main thread -[data to be serialized and sent]> worker thread - let (recv_tx, recv_rx) = channel(); // main thread <[received and deserialized data]- worker thread - let thread_poll = Poll::new().unwrap(); - let postbox_poll = Poll::new().unwrap(); - thread_poll - .register(&connection, CONN_TOKEN, Ready::readable(), PollOpt::edge()) - .unwrap(); - thread_poll - .register(&ctrl_rx, CTRL_TOKEN, Ready::readable(), PollOpt::edge()) - .unwrap(); - thread_poll - .register(&send_rx, DATA_TOKEN, Ready::readable(), PollOpt::edge()) - .unwrap(); - postbox_poll - .register(&recv_rx, DATA_TOKEN, Ready::readable(), PollOpt::edge()) - .unwrap(); - let handle = thread::Builder::new() - .name("postbox_worker".into()) - .spawn(move || postbox_thread(connection, ctrl_rx, send_rx, recv_tx, thread_poll))?; - Ok(PostBox { - handle: Some(handle), - ctrl: ctrl_tx, - recv: recv_rx, - send: send_tx, - poll: postbox_poll, - err: None, - }) - } - - /// Return an `Option` indicating the current status of the `PostBox`. - pub fn status(&self) -> Option { - self.err.as_ref().map(|err| err.into()) - } - - /// Non-blocking sender method - pub fn send(&mut self, data: S) -> Result<(), PostError> { - match &mut self.err { - err @ None => if let Err(_) = self.send.send(data) { - *err = Some(PostErrorInternal::MioError); - Err(err.as_ref().unwrap().into()) - } else { - Ok(()) - }, - err => Err(err.as_ref().unwrap().into()), - } - } - - /// Non-blocking receiver method returning an iterator over already received and deserialized objects - /// # Errors - /// If the other side disconnects PostBox won't realize that until you try to send something - pub fn new_messages(&mut self) -> impl ExactSizeIterator { - let mut events = Events::with_capacity(4096); - let mut items = VecDeque::new(); - - // If an error occured, or previously occured, just give up - if let Some(_) = self.err { - return items.into_iter(); - } else if let Err(err) = self.poll.poll(&mut events, Some(Duration::new(0, 0))) { - self.err = Some(err.into()); - return items.into_iter(); - } - - for event in events { - match event.token() { - DATA_TOKEN => loop { - match self.recv.try_recv() { - Ok(Ok(item)) => items.push_back(item), - Err(TryRecvError::Empty) => break, - Err(err) => self.err = Some(err.into()), - Ok(Err(err)) => self.err = Some(err.into()), - } - }, - _ => (), - } - } - - items.into_iter() - } -} - -fn postbox_thread( - mut connection: TcpStream, - ctrl_rx: Receiver, - send_rx: Receiver, - recv_tx: Sender>, - poll: Poll, -) where - S: PostSend, - R: PostRecv, -{ - // Receiving related variables - let mut events = Events::with_capacity(64); - let mut recv_buff = Vec::new(); - let mut recv_nextlen: u64 = 0; - loop { - let mut disconnected = false; - poll.poll(&mut events, Some(Duration::from_millis(20))) - .expect("Failed to execute poll(), most likely fault of the OS"); - println!("FINISHED POLL!"); - for event in events.iter() { - println!("EVENT!"); - match event.token() { - CTRL_TOKEN => match ctrl_rx.try_recv().unwrap() { - ControlMsg::Shutdown => return, - }, - CONN_TOKEN => match connection.read_to_end(&mut recv_buff) { - Ok(_) => {} - // Returned when all the data has been read - Err(ref e) if e.kind() == ErrorKind::WouldBlock => {} - Err(e) => recv_tx.send(Err(e.into())).unwrap(), - }, - DATA_TOKEN => { - let msg = send_rx.try_recv().unwrap(); - println!("Send: {:?}", msg); - let mut packet = bincode::serialize(&msg).unwrap(); - packet.splice(0..0, (packet.len() as u64).to_be_bytes().iter().cloned()); - match connection.write_bufs(&[packet.as_slice().into()]) { - Ok(_) => { println!("Sent!"); } - Err(e) => { - println!("Send error!"); - recv_tx.send(Err(e.into())).unwrap(); - } - }; - } - _ => {} - } - } - loop { - if recv_nextlen == 0 && recv_buff.len() >= 8 { - println!("Read nextlen"); - recv_nextlen = u64::from_be_bytes( - <[u8; 8]>::try_from(recv_buff.drain(0..8).collect::>().as_slice()).unwrap(), - ); - if recv_nextlen > MESSAGE_SIZE_CAP { - recv_tx.send(Err(PostErrorInternal::MsgSizeLimitExceeded)).unwrap(); - connection.shutdown(std::net::Shutdown::Both).unwrap(); - recv_buff.drain(..); - recv_nextlen = 0; - break; - } - } - if recv_buff.len() as u64 >= recv_nextlen && recv_nextlen != 0 { - match bincode::deserialize(recv_buff - .drain( - 0..usize::try_from(recv_nextlen) - .expect("Message size was larger than usize (insane message size and 32 bit OS)"), - ) - .collect::>() - .as_slice()) { - Ok(msg) => { - println!("Recv: {:?}", msg); - recv_tx - .send(Ok(msg)) - .unwrap(); - recv_nextlen = 0; - } - Err(e) => { - println!("Recv error: {:?}", e); - recv_tx.send(Err(e.into())).unwrap(); - recv_nextlen = 0; - } - } - } else { - break; - } - } - match connection.take_error().unwrap() { - Some(e) => { - if e.kind() == ErrorKind::BrokenPipe { - disconnected = true; - } - recv_tx.send(Err(e.into())).unwrap(); - } - None => {} - } - if disconnected == true { - break; - } - } - - // Loop after disconnected - loop { - poll.poll(&mut events, None) - .expect("Failed to execute poll(), most likely fault of the OS"); - for event in events.iter() { - match event.token() { - CTRL_TOKEN => match ctrl_rx.try_recv().unwrap() { - ControlMsg::Shutdown => return, - }, - _ => {} - } - } - } -} - -impl Drop for PostBox -where - S: PostSend, - R: PostRecv, -{ - fn drop(&mut self) { - self.ctrl.send(ControlMsg::Shutdown).unwrap_or(()); - self.handle.take().map(|handle| handle.join()); - } -} diff --git a/common/src/net/postoffice.rs b/common/src/net/postoffice.rs deleted file mode 100644 index e8d70330ad..0000000000 --- a/common/src/net/postoffice.rs +++ /dev/null @@ -1,150 +0,0 @@ -// Standard -use core::time::Duration; -use std::{ - collections::VecDeque, - net::SocketAddr, - thread, - sync::mpsc::TryRecvError, -}; - -// External -use mio::{net::TcpListener, Events, Poll, PollOpt, Ready, Token}; -use mio_extras::channel::{channel, Receiver, Sender}; - -// Crate -use super::{ - data::ControlMsg, - error::{ - PostError, - PostErrorInternal, - }, - postbox::PostBox, - PostRecv, - PostSend, -}; - -// Constants -const CTRL_TOKEN: Token = Token(0); // Token for thread control messages -const DATA_TOKEN: Token = Token(1); // Token for thread data exchange -const CONN_TOKEN: Token = Token(2); // Token for TcpStream for the PostBox child thread - -/// A high-level wrapper of [`TcpListener`](mio::net::TcpListener). -/// [`PostOffice`] listens for incoming connections in the background and wraps them into [`PostBox`]es, providing a simple non-blocking API for receiving them. -pub struct PostOffice -where - S: PostSend, - R: PostRecv, -{ - handle: Option>, - ctrl: Sender, - recv: Receiver, PostErrorInternal>>, - poll: Poll, - err: Option, -} - -impl PostOffice -where - S: PostSend, - R: PostRecv, -{ - /// Creates a new [`PostOffice`] listening on specified address - pub fn new>(addr: A) -> Result { - let listener = TcpListener::bind(&addr.into())?; - let (ctrl_tx, ctrl_rx) = channel(); - let (recv_tx, recv_rx) = channel(); - - let thread_poll = Poll::new()?; - let postbox_poll = Poll::new()?; - thread_poll.register(&listener, CONN_TOKEN, Ready::readable(), PollOpt::edge())?; - thread_poll.register(&ctrl_rx, CTRL_TOKEN, Ready::readable(), PollOpt::edge())?; - postbox_poll.register(&recv_rx, DATA_TOKEN, Ready::readable(), PollOpt::edge())?; - - let handle = thread::Builder::new() - .name("postoffice_worker".into()) - .spawn(move || postoffice_thread(listener, ctrl_rx, recv_tx, thread_poll))?; - - Ok(PostOffice { - handle: Some(handle), - ctrl: ctrl_tx, - recv: recv_rx, - poll: postbox_poll, - err: None, - }) - } - - /// Return an `Option` indicating the current status of the `PostOffice`. - pub fn status(&self) -> Option { - self.err.as_ref().map(|err| err.into()) - } - - /// Non-blocking method returning an iterator over new connections wrapped in [`PostBox`]es - pub fn new_connections( - &mut self, - ) -> impl ExactSizeIterator> { - let mut events = Events::with_capacity(256); - let mut conns = VecDeque::new(); - - // If an error occured, or previously occured, just give up - if let Some(_) = self.err { - return conns.into_iter(); - } else if let Err(err) = self.poll.poll(&mut events, Some(Duration::new(0, 0))) { - self.err = Some(err.into()); - return conns.into_iter(); - } - - for event in events { - match event.token() { - DATA_TOKEN => loop { - match self.recv.try_recv() { - Ok(Ok(conn)) => conns.push_back(conn), - Err(TryRecvError::Empty) => break, - Err(err) => self.err = Some(err.into()), - Ok(Err(err)) => self.err = Some(err.into()), - } - }, - _ => (), - } - } - conns.into_iter() - } -} - -fn postoffice_thread( - listener: TcpListener, - ctrl_rx: Receiver, - recv_tx: Sender, PostErrorInternal>>, - poll: Poll, -) where - S: PostSend, - R: PostRecv, -{ - let mut events = Events::with_capacity(256); - loop { - poll.poll(&mut events, None).expect("Failed to execute recv_poll.poll() in PostOffce receiver thread, most likely fault of the OS."); - for event in events.iter() { - match event.token() { - CTRL_TOKEN => match ctrl_rx.try_recv().unwrap() { - ControlMsg::Shutdown => return, - }, - CONN_TOKEN => { - let (conn, _addr) = listener.accept().unwrap(); - recv_tx.send(PostBox::from_tcpstream(conn) - // TODO: Is it okay to count a failure to create a postbox here as an 'internal error'? - .map_err(|_| PostErrorInternal::MioError)).unwrap(); - } - _ => (), - } - } - } -} - -impl Drop for PostOffice -where - S: PostSend, - R: PostRecv, -{ - fn drop(&mut self) { - self.ctrl.send(ControlMsg::Shutdown).unwrap_or(()); // If this fails the thread is dead already - self.handle.take().map(|handle| handle.join()); - } -} diff --git a/voxygen/src/hud/chat.rs b/voxygen/src/hud/chat.rs index 5202e971c5..2947c9c695 100644 --- a/voxygen/src/hud/chat.rs +++ b/voxygen/src/hud/chat.rs @@ -3,7 +3,7 @@ use conrod_core::{ input::Key, position::Dimension, text::font::Id as FontId, - widget::{Button, Id, List, Rectangle, Text, TextEdit}, + widget::{Id, Button, List, Rectangle, Text, TextEdit}, widget_ids, Color, Colorable, Positionable, Sizeable, UiCell, Widget, }; use std::collections::VecDeque; @@ -15,8 +15,6 @@ widget_ids! { input, input_bg, chat_arrow, - chat_arrow_up, - chat_arrow_down, } } // Chat Behaviour: @@ -78,12 +76,7 @@ impl Chat { fn scroll_to_bottom(&self, ui_widgets: &mut UiCell) { ui_widgets.scroll_widget(self.ids.message_box, [0.0, std::f64::MAX]); } - pub fn update_layout( - &mut self, - ui_widgets: &mut UiCell, - font: FontId, - imgs: &super::Imgs, - ) -> Option { + pub(super) fn update_layout(&mut self, ui_widgets: &mut UiCell, font: FontId, imgs: &super::Imgs) -> Option { // Maintain scrolling if self.new_messages { self.scroll_new_messages(ui_widgets); @@ -138,39 +131,20 @@ impl Chat { // s.set(ui_widgets) //} - // Chat Arrows + // Chat Arrow if !self.scrolled_to_bottom(ui_widgets) { if Button::image(imgs.chat_arrow) - .w_h(22.0, 22.0) - .hover_image(imgs.chat_arrow_mo) - .press_image(imgs.chat_arrow_press) - .bottom_right_with_margins_on(self.ids.message_box_bg, 2.0, 2.0) - .set(self.ids.chat_arrow, ui_widgets) - .was_clicked() + .w_h(22.0, 22.0) + .hover_image(imgs.chat_arrow_mo) + .press_image(imgs.chat_arrow_press) + .bottom_right_with_margins_on(self.ids.message_box_bg, 2.0, 2.0) + .set(self.ids.chat_arrow, ui_widgets) + .was_clicked() { self.scroll_to_bottom(ui_widgets); } } - // Up and Down Arrows => Scroll the chat up/down one row per click; - //if Button::image(imgs.chat_arrow_up) - //.w_h(22.0, 22.0) - //.hover_image(imgs.chat_arrow_up_mo) - //.press_image(imgs.chat_arrow_up_press) - //.up_from(self.ids.chat_arrow_down, 60.0) - //.set(self.ids.chat_arrow_up, ui_widgets) - //.was_clicked() - //{}; - - //if Button::image(imgs.chat_arrow_down) - // .w_h(22.0, 22.0) - //.hover_image(imgs.chat_arrow_down_mo) - //.press_image(imgs.chat_arrow_down_press) - //.bottom_right_with_margins_on(self.ids.message_box_bg, 73.0, 2.0) - // .set(self.ids.chat_arrow_down, ui_widgets) - //.was_clicked() - //{}; - // If enter is pressed send the current message if ui_widgets .widget_input(self.ids.input) diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 80a1cfd845..879b9da653 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -6,8 +6,7 @@ use crate::{ window::{Event as WinEvent, Key, Window}, }; use conrod_core::{ - color, - event::Input, + color, image::Id as ImgId, text::font::Id as FontId, widget::{Button, Image, Rectangle, Scrollbar, Text}, @@ -123,7 +122,7 @@ widget_ids! { } // TODO: make macro to mimic widget_ids! for images ids or find another solution to simplify addition of new images. -struct Imgs { +pub(self) struct Imgs { //Missing: ActionBar, Health/Mana/Energy Bar & Char Window BG/Frame // Bag bag: ImgId, diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index 2beb15bc3b..3ba0130622 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -5,8 +5,9 @@ use crate::{ session::SessionState, GlobalState, PlayState, PlayStateResult, }; +use client::{self, Client}; use common::clock::Clock; -use std::time::Duration; +use std::{cell::RefCell, rc::Rc, time::Duration}; use ui::CharSelectionUi; use vek::*; @@ -14,13 +15,15 @@ const FPS: u64 = 60; pub struct CharSelectionState { char_selection_ui: CharSelectionUi, + client: Rc>, } impl CharSelectionState { /// Create a new `CharSelectionState` - pub fn new(window: &mut Window) -> Self { + pub fn new(window: &mut Window, client: Rc>) -> Self { Self { char_selection_ui: CharSelectionUi::new(window), + client, } } } @@ -59,7 +62,7 @@ impl PlayState for CharSelectionState { match event { ui::Event::Logout => return PlayStateResult::Pop, ui::Event::Play => return PlayStateResult::Push( - Box::new(SessionState::new(&mut global_state.window).unwrap()) // TODO: Handle this error + Box::new(SessionState::new(&mut global_state.window, self.client.clone())) ), } } @@ -67,6 +70,11 @@ impl PlayState for CharSelectionState { // Draw the UI to the screen self.char_selection_ui.render(global_state.window.renderer_mut()); + // Tick the client (currently only to keep the connection alive) + self.client.borrow_mut().tick(client::Input::default(), clock.get_last_delta()) + .expect("Failed to tick the client"); + self.client.borrow_mut().cleanup(); + // Finish the frame global_state.window.renderer_mut().flush(); global_state diff --git a/voxygen/src/menu/char_selection/ui.rs b/voxygen/src/menu/char_selection/ui.rs index f2b7613c77..50b95b9b6f 100644 --- a/voxygen/src/menu/char_selection/ui.rs +++ b/voxygen/src/menu/char_selection/ui.rs @@ -6,10 +6,9 @@ use crate::{ use conrod_core::{ color, color::TRANSPARENT, - event::Input, image::Id as ImgId, text::font::Id as FontId, - widget::{text_box::Event as TextBoxEvent, Button, Image, Rectangle, Text, TextBox, TitleBar}, + widget::{text_box::Event as TextBoxEvent, Button, Image, Rectangle, Text, TextBox}, widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget, }; @@ -224,7 +223,12 @@ impl Imgs { fn new(ui: &mut Ui, renderer: &mut Renderer) -> Imgs { let mut load = |filename| { let image = image::open( - &[env!("CARGO_MANIFEST_DIR"), "/../assets/voxygen/", filename].concat(), + &[ + env!("CARGO_MANIFEST_DIR"), + "/../assets/voxygen/", + filename, + ] + .concat(), ) .unwrap(); ui.new_image(renderer, &image).unwrap() @@ -850,18 +854,14 @@ impl CharSelectionUi { const HUMAN_DESC: &str = "The former nomads were only recently able to gain a foothold in the world of Veloren. \n\ \n\ - Their greatest strengths are their adaptability and intelligence, which makes them allrounders in many fields. \n\ - \n\ - Some become wicked witches, slimy scoundrels, and members of the underworld, while others become witch-hunters, sages, and noble knights. \n\ - \n\ - This diversity however creates constant conflict and antagonism between humans themselves."; + Their greatest strengths are their adaptability and intelligence, which makes them allrounders in many fields."; const ORC_DESC: &str = "They are considered brutal, rude and combative. \n\ - But once you gained their trust they will be loyal friends \n\ - that follow a strict code of honor in all of their actions. \n\ - \n\ - Their warriors are masters of melee combat, but their true power \ - comes from the magical rituals of their powerful shamans."; + But once you gained their trust they will be loyal friends \n\ + that follow a strict code of honor in all of their actions. \n\ + \n\ + Their warriors are masters of melee combat, but their true power \ + comes from the magical rituals of their powerful shamans."; const DWARF_DESC: &str = "Smoking chimneys, the sound of countless hammers and hoes. \ Infinite tunnel systems to track down even the last chunk of metal in the ground. \n\ diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index c32b2a3d5d..0dfe93880d 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -5,6 +5,7 @@ use crate::{ window::{Event, Window}, GlobalState, PlayState, PlayStateResult, }; +use client::{self, Client}; use common::clock::Clock; use std::time::Duration; use ui::{Event as MainMenuEvent, MainMenuUi}; @@ -56,14 +57,42 @@ impl PlayState for MainMenuState { // Maintain the UI for event in self.main_menu_ui.maintain(global_state.window.renderer_mut()) { - match event { - MainMenuEvent::LoginAttempt{ username, server_address } => - // For now just start a new session - return PlayStateResult::Push( - Box::new(CharSelectionState::new(&mut global_state.window)) - ), - MainMenuEvent::Quit => return PlayStateResult::Shutdown, - } + 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) { + Ok(client) => { + return PlayStateResult::Push( + Box::new(CharSelectionState::new( + &mut global_state.window, + std::rc::Rc::new(std::cell::RefCell::new(client.with_test_state())) // <--- 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, + } } // Draw the UI to the screen diff --git a/voxygen/src/menu/main/ui.rs b/voxygen/src/menu/main/ui.rs index e50a405ade..583c3d3588 100644 --- a/voxygen/src/menu/main/ui.rs +++ b/voxygen/src/menu/main/ui.rs @@ -6,8 +6,9 @@ use crate::{ use conrod_core::{ color::TRANSPARENT, image::Id as ImgId, + position::Dimension, text::font::Id as FontId, - widget::{text_box::Event as TextBoxEvent, Button, Image, TextBox}, + widget::{text_box::Event as TextBoxEvent, Button, Image, Rectangle, Text, TextBox}, widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget, }; @@ -21,6 +22,8 @@ widget_ids! { // Login, Singleplayer login_button, login_text, + login_error, + login_error_bg, address_text, address_bg, address_field, @@ -101,6 +104,7 @@ pub struct MainMenuUi { font_opensans: FontId, username: String, server_address: String, + login_error: Option, } impl MainMenuUi { @@ -134,7 +138,8 @@ impl MainMenuUi { font_metamorph, font_opensans, username: "Username".to_string(), - server_address: "Server Address".to_string(), + server_address: "veloren.mac94.de".to_string(), + login_error: None, } } @@ -149,7 +154,7 @@ impl MainMenuUi { .w_h(346.0, 111.0) .top_left_with_margins(30.0, 40.0) .label("Alpha 0.1") - .label_rgba(255.0, 255.0, 255.0, 1.0) + .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)) @@ -159,6 +164,7 @@ impl MainMenuUi { // Used when the login button is pressed, or enter is pressed within input field macro_rules! login { () => { + self.login_error = None; events.push(Event::LoginAttempt { username: self.username.clone(), server_address: self.server_address.clone(), @@ -176,7 +182,7 @@ impl MainMenuUi { .mid_bottom_with_margin_on(self.ids.username_bg, 44.0 / 2.0) .font_size(20) .font_id(self.font_opensans) - .text_color(Color::Rgba(220.0, 220.0, 220.0, 0.8)) + .text_color(Color::Rgba(0.86, 0.86, 0.86, 0.8)) // transparent background .color(TRANSPARENT) .border_color(TRANSPARENT) @@ -187,9 +193,28 @@ impl MainMenuUi { // Note: TextBox limits the input string length to what fits in it self.username = username.to_string(); } - TextBoxEvent::Enter => login!(), + TextBoxEvent::Enter => { login!(); } } } + // Login error + if let Some(msg) = &self.login_error { + let text = Text::new(&msg) + .rgba(0.5, 0.0, 0.0, 1.0) + .font_size(30) + .font_id(self.font_opensans); + let x = match text.get_x_dimension(ui_widgets) { + Dimension::Absolute(x) => x + 10.0, + _ => 0.0, + }; + Rectangle::fill([x, 40.0]) + .rgba(0.2, 0.3, 0.3, 0.7) + .parent(ui_widgets.window) + .up_from(self.ids.username_bg, 35.0) + .set(self.ids.login_error_bg, ui_widgets); + text + .middle_of(self.ids.login_error_bg) + .set(self.ids.login_error, ui_widgets); + } // Server address Image::new(self.imgs.input_bg) .w_h(337.0, 67.0) @@ -200,7 +225,7 @@ impl MainMenuUi { .mid_bottom_with_margin_on(self.ids.address_bg, 44.0 / 2.0) .font_size(20) .font_id(self.font_opensans) - .text_color(Color::Rgba(220.0, 220.0, 220.0, 0.8)) + .text_color(Color::Rgba(0.86, 0.86, 0.86, 0.8)) // transparent background .color(TRANSPARENT) .border_color(TRANSPARENT) @@ -210,7 +235,7 @@ impl MainMenuUi { TextBoxEvent::Update(server_address) => { self.server_address = server_address.to_string(); } - TextBoxEvent::Enter => login!(), + TextBoxEvent::Enter => { login!(); } } } // Login button @@ -221,7 +246,7 @@ impl MainMenuUi { .down_from(self.ids.address_bg, 20.0) .align_middle_x_of(self.ids.address_bg) .label("Login") - .label_rgba(220.0, 220.0, 220.0, 0.8) + .label_rgba(0.86, 0.86, 0.86, 0.8) .label_font_size(28) .label_y(conrod_core::position::Relative::Scalar(5.0)) .set(self.ids.login_button, ui_widgets) @@ -229,7 +254,7 @@ impl MainMenuUi { { login!(); } - //Singleplayer button + // Singleplayer button if Button::image(self.imgs.login_button) .hover_image(self.imgs.login_button_hover) .press_image(self.imgs.login_button_press) @@ -237,7 +262,7 @@ impl MainMenuUi { .down_from(self.ids.login_button, 20.0) .align_middle_x_of(self.ids.address_bg) .label("Singleplayer") - .label_rgba(220.0, 220.0, 220.0, 0.8) + .label_rgba(0.86, 0.86, 0.86, 0.8) .label_font_size(26) .label_y(conrod_core::position::Relative::Scalar(5.0)) .label_x(conrod_core::position::Relative::Scalar(2.0)) @@ -253,7 +278,7 @@ impl MainMenuUi { .hover_image(self.imgs.button_hover) .press_image(self.imgs.button_press) .label("Quit") - .label_rgba(220.0, 220.0, 220.0, 0.8) + .label_rgba(0.86, 0.86, 0.86, 0.8) .label_font_size(20) .label_y(conrod_core::position::Relative::Scalar(3.0)) .set(self.ids.quit_button, ui_widgets) @@ -268,7 +293,7 @@ impl MainMenuUi { .hover_image(self.imgs.button_hover) .press_image(self.imgs.button_press) .label("Settings") - .label_rgba(220.0, 220.0, 220.0, 0.8) + .label_rgba(0.86, 0.86, 0.86, 0.8) .label_font_size(20) .label_y(conrod_core::position::Relative::Scalar(3.0)) .set(self.ids.settings_button, ui_widgets) @@ -281,7 +306,7 @@ impl MainMenuUi { .hover_image(self.imgs.button_hover) .press_image(self.imgs.button_press) .label("Servers") - .label_rgba(220.0, 220.0, 220.0, 0.8) + .label_rgba(0.86, 0.86, 0.86, 0.8) .label_font_size(20) .label_y(conrod_core::position::Relative::Scalar(3.0)) .set(self.ids.servers_button, ui_widgets) @@ -291,6 +316,10 @@ impl MainMenuUi { events } + pub fn login_error(&mut self, msg: String) { + self.login_error = Some(msg); + } + pub fn handle_event(&mut self, event: ui::Event) { self.ui.handle_event(event); } diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs index e794bf2e07..17edd052a4 100644 --- a/voxygen/src/render/mod.rs +++ b/voxygen/src/render/mod.rs @@ -30,8 +30,8 @@ pub use self::{ Locals as TerrainLocals, }, ui::{ - push_quad_to_mesh as push_ui_quad_to_mesh, - push_tri_to_mesh as push_ui_tri_to_mesh, + create_quad as create_ui_quad, + create_tri as create_ui_tri, Mode as UiMode, UiPipeline, }, diff --git a/voxygen/src/render/pipelines/ui.rs b/voxygen/src/render/pipelines/ui.rs index 7a1c72fb5d..0f3a3b4800 100644 --- a/voxygen/src/render/pipelines/ui.rs +++ b/voxygen/src/render/pipelines/ui.rs @@ -12,7 +12,6 @@ use super::super::{ Pipeline, TgtColorFmt, TgtDepthFmt, - Mesh, Quad, Tri, }; @@ -66,7 +65,7 @@ impl Mode { } } -pub fn push_quad_to_mesh(mesh: &mut Mesh, rect: Aabr, uv_rect: Aabr, color: [f32; 4], mode: Mode) { +pub fn create_quad(rect: Aabr, uv_rect: Aabr, color: [f32; 4], mode: Mode) -> Quad { let mode_val = mode.value(); let v = |pos, uv| { Vertex { @@ -83,15 +82,15 @@ pub fn push_quad_to_mesh(mesh: &mut Mesh, rect: Aabr, uv_rect: let (l, b, r, t) = aabr_to_lbrt(rect); let (uv_l, uv_b, uv_r, uv_t) = aabr_to_lbrt(uv_rect); - mesh.push_quad(Quad::new( + Quad::new( v([r, t], [uv_r, uv_t]), v([l, t], [uv_l, uv_t]), v([l, b], [uv_l, uv_b]), v([r, b], [uv_r, uv_b]), - )); + ) } -pub fn push_tri_to_mesh(mesh: &mut Mesh, tri: [[f32; 2]; 3], uv_tri: [[f32; 2]; 3], color: [f32; 4], mode: Mode) { +pub fn create_tri(tri: [[f32; 2]; 3], uv_tri: [[f32; 2]; 3], color: [f32; 4], mode: Mode) -> Tri { let mode_val = mode.value(); let v = |pos, uv| { Vertex { @@ -101,9 +100,9 @@ pub fn push_tri_to_mesh(mesh: &mut Mesh, tri: [[f32; 2]; 3], uv_tri: mode: mode_val, } }; - mesh.push_tri(Tri::new( + Tri::new( v([tri[0][0], tri[0][1]], [uv_tri[0][0], uv_tri[0][1]]), v([tri[1][0], tri[1][1]], [uv_tri[1][0], uv_tri[1][1]]), v([tri[2][0], tri[2][1]], [uv_tri[2][0], uv_tri[2][1]]), - )); + ) } diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 3568c8beef..5a37c9b5f6 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -1,50 +1,48 @@ -// Standard -use std::time::Duration; - -// Library +use std::{cell::RefCell, rc::Rc, time::Duration}; use vek::*; - -// Project -use client::{self, Client}; use common::clock::Clock; - -// Convert Input to a valid string -use std::net::ToSocketAddrs; - -// Crate +use client::{ + self, + Client, +}; use crate::{ - hud::{Event as HudEvent, Hud}, + Error, + PlayState, + PlayStateResult, + GlobalState, key_state::KeyState, + window::{Event, Key, Window}, render::Renderer, scene::Scene, - window::{Event, Key, Window}, - Error, GlobalState, PlayState, PlayStateResult, + hud::{Hud, Event as HudEvent}, }; const FPS: u64 = 60; pub struct SessionState { scene: Scene, - client: Client, + client: Rc>, key_state: KeyState, hud: Hud, } + /// Represents an active game session (i.e: one that is being played) impl SessionState { /// Create a new `SessionState` - pub fn new(window: &mut Window) -> Result { - let client = Client::new(([127, 0, 0, 1], 59003))?.with_test_state(); // <--- TODO: Remove this - Ok(Self { - // Create a scene for this session. The scene handles visible elements of the game world - scene: Scene::new(window.renderer_mut(), &client), + pub fn new(window: &mut Window, client: Rc>) -> Self { + // Create a scene for this session. The scene handles visible elements of the game world + let scene = Scene::new(window.renderer_mut(), &client.borrow()); + Self { + scene, client, key_state: KeyState::new(), hud: Hud::new(window), - }) + } } } + // The background colour const BG_COLOR: Rgba = Rgba { r: 0.0, @@ -65,7 +63,7 @@ impl SessionState { let dir_vec = self.key_state.dir_vec(); let move_dir = unit_vecs.0 * dir_vec[0] + unit_vecs.1 * dir_vec[1]; - for event in self.client.tick(client::Input { move_dir }, dt)? { + for event in self.client.borrow_mut().tick(client::Input { move_dir }, dt)? { match event { client::Event::Chat(msg) => { self.hud.new_message(msg); @@ -78,7 +76,7 @@ impl SessionState { /// Clean up the session (and the client attached to it) after a tick pub fn cleanup(&mut self) { - self.client.cleanup(); + self.client.borrow_mut().cleanup(); } /// Render the session to the screen. @@ -110,7 +108,7 @@ impl PlayState for SessionState { for x in -6..7 { for y in -6..7 { for z in -1..2 { - self.client.load_chunk(Vec3::new(x, y, z)); + self.client.borrow_mut().load_chunk(Vec3::new(x, y, z)); } } } @@ -119,6 +117,7 @@ impl PlayState for SessionState { loop { // Handle window events for event in global_state.window.fetch_events() { + // Pass all events to the ui first if self.hud.handle_event(event.clone()) { continue; @@ -167,16 +166,15 @@ impl PlayState for SessionState { self.tick(clock.get_last_delta()) .expect("Failed to tick the scene"); - // Maintain the scene - self.scene - .maintain(global_state.window.renderer_mut(), &self.client); + // Maintain the scene + self.scene.maintain(global_state.window.renderer_mut(), &self.client.borrow()); // Maintain the UI for event in self.hud.maintain(global_state.window.renderer_mut()) { match event { HudEvent::SendMessage(msg) => { // TODO: Handle result - self.client.send_chat(msg); - } + self.client.borrow_mut().send_chat(msg); + }, HudEvent::Logout => return PlayStateResult::Pop, HudEvent::Quit => return PlayStateResult::Shutdown, } diff --git a/voxygen/src/ui/mod.rs b/voxygen/src/ui/mod.rs index d7101e86f9..8cd9cc92b0 100644 --- a/voxygen/src/ui/mod.rs +++ b/voxygen/src/ui/mod.rs @@ -29,8 +29,8 @@ use crate::{ Texture, UiPipeline, UiMode, - push_ui_quad_to_mesh, - push_ui_tri_to_mesh, + create_ui_quad, + create_ui_tri, }, window::Window, }; @@ -412,13 +412,12 @@ impl Ui { min: Vec2::new(uv_l, uv_b), max: Vec2::new(uv_r, uv_t), }; - push_ui_quad_to_mesh( - &mut mesh, + mesh.push_quad(create_ui_quad( gl_aabr(rect), uv, color, UiMode::Image, - ); + )); } PrimitiveKind::Text { color, text, font_id } => { @@ -463,13 +462,12 @@ impl Ui { (screen_rect.min.y as f32 / screen_h - 0.5) * -2.0, ), }; - push_ui_quad_to_mesh( - &mut mesh, + mesh.push_quad(create_ui_quad( rect, uv, color, UiMode::Text, - ); + )); } } } @@ -483,8 +481,7 @@ impl Ui { switch_to_plain_state!(); - push_ui_quad_to_mesh( - &mut mesh, + mesh.push_quad(create_ui_quad( gl_aabr(rect), Aabr { min: Vec2::new(0.0, 0.0), @@ -492,7 +489,7 @@ impl Ui { }, color, UiMode::Geometry, - ); + )); } PrimitiveKind::TrianglesSingleColor { color, triangles } => { // Don't draw transparent triangle or switch state if there are actually no triangles @@ -518,13 +515,12 @@ impl Ui { p1.into_array(), p3.into_array(), ]}; - push_ui_tri_to_mesh( - &mut mesh, + mesh.push_tri(create_ui_tri( triangle, [[0.0; 2]; 3], color, UiMode::Geometry, - ); + )); } }