Merged with UI-Input

Former-commit-id: f18e92dcac87c3b2806065078c70527ffd805876
This commit is contained in:
Pfauenauge90 2019-04-04 16:45:57 +02:00
parent 6d6c585808
commit 6ebff7ce2c
15 changed files with 191 additions and 642 deletions

View File

@ -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<T> 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<PostErrorInternal> for PostError {
fn from(err: PostErrorInternal) -> Self {
(&err).into()
}
}
impl From<std::io::Error> for PostErrorInternal {
fn from(err: std::io::Error) -> Self {
PostErrorInternal::Io(err)
}
}
impl From<bincode::Error> for PostErrorInternal {
fn from(err: bincode::Error) -> Self {
PostErrorInternal::Serde(err)
}
}
impl From<std::sync::mpsc::TryRecvError> for PostErrorInternal {
fn from(err: std::sync::mpsc::TryRecvError) -> Self {
PostErrorInternal::ChannelRecv(err)
}
}
impl From<std::io::Error> for PostError {
fn from(err: std::io::Error) -> Self {
(&PostErrorInternal::from(err)).into()
}
}
impl From<bincode::Error> for PostError {
fn from(err: bincode::Error) -> Self {
(&PostErrorInternal::from(err)).into()
}
}
impl From<std::sync::mpsc::TryRecvError> for PostError {
fn from(err: std::sync::mpsc::TryRecvError) -> Self {
(&PostErrorInternal::from(err)).into()
}
}

View File

@ -1,8 +1,5 @@
pub mod data;
pub mod error;
pub mod post;
pub mod postbox;
pub mod postoffice;
// Reexports
pub use self::{

View File

@ -116,7 +116,7 @@ impl<S: PostSend, R: PostRecv> PostOffice<S, R> {
pub fn new_connections(&mut self) -> impl ExactSizeIterator<Item=PostBox<S, R>> {
let mut conns = VecDeque::new();
if let Some(_) = self.err {
if self.err.is_some() {
return conns.into_iter();
}
@ -132,7 +132,7 @@ impl<S: PostSend, R: PostRecv> PostOffice<S, R> {
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<S: PostSend, R: PostRecv> PostOffice<S, R> {
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<S: PostSend, R: PostRecv> {
impl<S: PostSend, R: PostRecv> PostBox<S, R> {
pub fn to_server<A: Into<SocketAddr>>(addr: A) -> Result<Self, Error> {
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<Self, Error> {
@ -254,7 +254,7 @@ impl<S: PostSend, R: PostRecv> PostBox<S, R> {
pub fn new_messages(&mut self) -> impl ExactSizeIterator<Item=R> {
let mut msgs = VecDeque::new();
if let Some(_) = self.err {
if self.err.is_some() {
return msgs.into_iter();
}
@ -270,7 +270,7 @@ impl<S: PostSend, R: PostRecv> PostBox<S, R> {
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<S: PostSend, R: PostRecv> PostBox<S, R> {
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<S: PostSend, R: PostRecv>(
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<S: PostSend, R: PostRecv>(
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<S: PostSend, R: PostRecv>(
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<S: PostSend, R: PostRecv>(
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<S: PostSend, R: PostRecv>(
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<S: PostSend, R: PostRecv>(
}
}
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::<u32, f32>::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::<f32, u32>::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::<f32, u32>::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);

View File

@ -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<S, R>
where
S: PostSend,
R: PostRecv,
{
handle: Option<thread::JoinHandle<()>>,
ctrl: Sender<ControlMsg>,
recv: Receiver<Result<R, PostErrorInternal>>,
send: Sender<S>,
poll: Poll,
err: Option<PostErrorInternal>,
}
impl<S, R> PostBox<S, R>
where
S: PostSend,
R: PostRecv,
{
/// Creates a new [`PostBox`] connected to specified address, meant to be used by the client
pub fn to_server<A: Into<SocketAddr>>(addr: A) -> Result<PostBox<S, R>, 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<PostBox<S, R>, 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<PostError>` indicating the current status of the `PostBox`.
pub fn status(&self) -> Option<PostError> {
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<Item = R> {
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<S, R>(
mut connection: TcpStream,
ctrl_rx: Receiver<ControlMsg>,
send_rx: Receiver<S>,
recv_tx: Sender<Result<R, PostErrorInternal>>,
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::<Vec<u8>>().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::<Vec<u8>>()
.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<S, R> Drop for PostBox<S, R>
where
S: PostSend,
R: PostRecv,
{
fn drop(&mut self) {
self.ctrl.send(ControlMsg::Shutdown).unwrap_or(());
self.handle.take().map(|handle| handle.join());
}
}

View File

@ -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<S, R>
where
S: PostSend,
R: PostRecv,
{
handle: Option<thread::JoinHandle<()>>,
ctrl: Sender<ControlMsg>,
recv: Receiver<Result<PostBox<S, R>, PostErrorInternal>>,
poll: Poll,
err: Option<PostErrorInternal>,
}
impl<S, R> PostOffice<S, R>
where
S: PostSend,
R: PostRecv,
{
/// Creates a new [`PostOffice`] listening on specified address
pub fn new<A: Into<SocketAddr>>(addr: A) -> Result<Self, PostError> {
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<PostError>` indicating the current status of the `PostOffice`.
pub fn status(&self) -> Option<PostError> {
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<Item = PostBox<S, R>> {
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<S, R>(
listener: TcpListener,
ctrl_rx: Receiver<ControlMsg>,
recv_tx: Sender<Result<PostBox<S, R>, 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<S, R> Drop for PostOffice<S, R>
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());
}
}

View File

@ -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<String> {
pub(super) fn update_layout(&mut self, ui_widgets: &mut UiCell, font: FontId, imgs: &super::Imgs) -> Option<String> {
// 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)

View File

@ -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,

View File

@ -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<RefCell<Client>>,
}
impl CharSelectionState {
/// Create a new `CharSelectionState`
pub fn new(window: &mut Window) -> Self {
pub fn new(window: &mut Window, client: Rc<RefCell<Client>>) -> 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

View File

@ -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\

View File

@ -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

View File

@ -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<String>,
}
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);
}

View File

@ -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,
},

View File

@ -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<UiPipeline>, rect: Aabr<f32>, uv_rect: Aabr<f32>, color: [f32; 4], mode: Mode) {
pub fn create_quad(rect: Aabr<f32>, uv_rect: Aabr<f32>, color: [f32; 4], mode: Mode) -> Quad<UiPipeline> {
let mode_val = mode.value();
let v = |pos, uv| {
Vertex {
@ -83,15 +82,15 @@ pub fn push_quad_to_mesh(mesh: &mut Mesh<UiPipeline>, rect: Aabr<f32>, 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<UiPipeline>, 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<UiPipeline> {
let mode_val = mode.value();
let v = |pos, uv| {
Vertex {
@ -101,9 +100,9 @@ pub fn push_tri_to_mesh(mesh: &mut Mesh<UiPipeline>, 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]]),
));
)
}

View File

@ -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<RefCell<Client>>,
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<Self, Error> {
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<RefCell<Client>>) -> 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<f32> = 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,
}

View File

@ -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,
);
));
}
}