Added basic networked communications, chat communication

Former-commit-id: 06bafdf69486f4da5fbc416835e34c5bed8c2caa
This commit is contained in:
Joshua Barretto 2019-03-03 22:02:38 +00:00
parent 187b7939d7
commit dbbcc1e80e
21 changed files with 393 additions and 52 deletions

View File

@ -2,6 +2,7 @@
members = [ members = [
"common", "common",
"client", "client",
"chat-cli",
"server", "server",
"server-cli", "server-cli",
"voxygen", "voxygen",

3
chat-cli/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
**/*.rs.bk
Cargo.lock

12
chat-cli/Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "veloren-chat-cli"
version = "0.1.0"
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
edition = "2018"
[dependencies]
client = { package = "veloren-client", path = "../client" }
common = { package = "veloren-common", path = "../common" }
log = "0.4"
pretty_env_logger = "0.3"

37
chat-cli/src/main.rs Normal file
View File

@ -0,0 +1,37 @@
use std::time::Duration;
use log::info;
use client::{Input, Client, Event};
use common::clock::Clock;
const FPS: u64 = 60;
fn main() {
// Init logging
pretty_env_logger::init();
info!("Starting chat-cli...");
// Set up an fps clock
let mut clock = Clock::new();
// Create client
let mut client = Client::new(([127, 0, 0, 1], 59003))
.expect("Failed to create client instance");
loop {
let events = client.tick(Input::default(), clock.get_last_delta())
.expect("Failed to tick client");
for event in events {
match event {
Event::Chat(msg) => println!("[chat] {}", msg),
}
}
// Clean up the server after a tick
client.cleanup();
// Wait for the next tick
clock.tick(Duration::from_millis(1000 / FPS));
}
}

17
client/src/error.rs Normal file
View File

@ -0,0 +1,17 @@
use common::net::PostError;
#[derive(Debug)]
pub enum Error {
Network(PostError),
ServerShutdown,
Other(String),
}
impl From<PostError> for Error {
fn from(err: PostError) -> Self {
match err {
PostError::Disconnected => Error::ServerShutdown,
err => Error::Network(err),
}
}
}

14
client/src/input.rs Normal file
View File

@ -0,0 +1,14 @@
use vek::*;
pub struct Input {
// TODO: Use this type to manage client input
pub move_dir: Vec2<f32>,
}
impl Default for Input {
fn default() -> Self {
Input {
move_dir: Vec2::zero(),
}
}
}

View File

@ -1,29 +1,38 @@
// Standard pub mod error;
use std::time::Duration; pub mod input;
// Library // Reexports
use specs::Entity as EcsEntity; pub use specs::Entity as EcsEntity;
pub use crate::{
error::Error,
input::Input,
};
use std::{
time::Duration,
net::SocketAddr,
};
use vek::*; use vek::*;
use threadpool; use threadpool;
use common::{
// Project comp::phys::Vel,
use common::{comp::phys::Vel, state::State, terrain::TerrainChunk}; state::State,
terrain::TerrainChunk,
net::PostBox,
msg::{ClientMsg, ServerMsg},
};
use world::World; use world::World;
#[derive(Debug)] pub enum Event {
pub enum Error { Chat(String),
ServerShutdown,
Other(String),
}
pub struct Input {
// TODO: Use this type to manage client input
pub move_dir: Vec2<f32>,
} }
pub struct Client { pub struct Client {
thread_pool: threadpool::ThreadPool, thread_pool: threadpool::ThreadPool,
last_ping: f64,
postbox: PostBox<ClientMsg, ServerMsg>,
tick: u64, tick: u64,
state: State, state: State,
player: Option<EcsEntity>, player: Option<EcsEntity>,
@ -35,25 +44,35 @@ pub struct Client {
impl Client { impl Client {
/// Create a new `Client`. /// Create a new `Client`.
pub fn new() -> Self { #[allow(dead_code)]
Self { pub fn new<A: Into<SocketAddr>>(addr: A) -> Result<Self, Error> {
let state = State::new();
let mut postbox = PostBox::to_server(addr)?;
postbox.send(ClientMsg::Chat(String::from("Hello, world!")));
Ok(Self {
thread_pool: threadpool::Builder::new() thread_pool: threadpool::Builder::new()
.thread_name("veloren-worker".into()) .thread_name("veloren-worker".into())
.build(), .build(),
last_ping: state.get_time(),
postbox,
tick: 0, tick: 0,
state: State::new(), state,
player: None, player: None,
// Testing // Testing
world: World::new(), world: World::new(),
chunk: None, chunk: None,
} })
} }
/// Get a reference to the client's worker thread pool. This pool should be used for any /// Get a reference to the client's worker thread pool. This pool should be used for any
/// computationally expensive operations that run outside of the main thread (i.e: threads that /// computationally expensive operations that run outside of the main thread (i.e: threads that
/// block on I/O operations are exempt). /// block on I/O operations are exempt).
#[allow(dead_code)]
pub fn thread_pool(&self) -> &threadpool::ThreadPool { &self.thread_pool } pub fn thread_pool(&self) -> &threadpool::ThreadPool { &self.thread_pool }
// TODO: Get rid of this // TODO: Get rid of this
@ -70,23 +89,34 @@ impl Client {
} }
/// Get a reference to the client's game state. /// Get a reference to the client's game state.
#[allow(dead_code)]
pub fn state(&self) -> &State { &self.state } pub fn state(&self) -> &State { &self.state }
/// Get a mutable reference to the client's game state. /// Get a mutable reference to the client's game state.
#[allow(dead_code)]
pub fn state_mut(&mut self) -> &mut State { &mut self.state } pub fn state_mut(&mut self) -> &mut State { &mut self.state }
/// Get the player entity /// Get the player entity
#[allow(dead_code)]
pub fn player(&self) -> Option<EcsEntity> { pub fn player(&self) -> Option<EcsEntity> {
self.player self.player
} }
/// Get the current tick number. /// Get the current tick number.
#[allow(dead_code)]
pub fn get_tick(&self) -> u64 { pub fn get_tick(&self) -> u64 {
self.tick self.tick
} }
/// Send a chat message to the server
#[allow(dead_code)]
pub fn send_chat(&mut self, msg: String) -> Result<(), Error> {
Ok(self.postbox.send(ClientMsg::Chat(msg))?)
}
/// Execute a single client tick, handle input and update the game state by the given duration /// Execute a single client tick, handle input and update the game state by the given duration
pub fn tick(&mut self, input: Input, dt: Duration) -> Result<(), Error> { #[allow(dead_code)]
pub fn tick(&mut self, input: Input, dt: Duration) -> Result<Vec<Event>, Error> {
// This tick function is the centre of the Veloren universe. Most client-side things are // This tick function is the centre of the Veloren universe. Most client-side things are
// managed from here, and as such it's important that it stays organised. Please consult // managed from here, and as such it's important that it stays organised. Please consult
// the core developers before making significant changes to this code. Here is the // the core developers before making significant changes to this code. Here is the
@ -99,7 +129,13 @@ impl Client {
// 4) Go through the terrain update queue and apply all changes to the terrain // 4) Go through the terrain update queue and apply all changes to the terrain
// 5) Finish the tick, passing control of the main thread back to the frontend // 5) Finish the tick, passing control of the main thread back to the frontend
// (step 1) // Build up a list of events for this frame, to be passed to the frontend
let mut frontend_events = Vec::new();
// Handle new messages from the server
frontend_events.append(&mut self.handle_new_messages()?);
// Step 3
if let Some(p) = self.player { if let Some(p) = self.player {
// TODO: remove this // TODO: remove this
const PLAYER_VELOCITY: f32 = 100.0; const PLAYER_VELOCITY: f32 = 100.0;
@ -113,12 +149,40 @@ impl Client {
// Finish the tick, pass control back to the frontend (step 6) // Finish the tick, pass control back to the frontend (step 6)
self.tick += 1; self.tick += 1;
Ok(()) Ok(frontend_events)
} }
/// Clean up the client after a tick /// Clean up the client after a tick
#[allow(dead_code)]
pub fn cleanup(&mut self) { pub fn cleanup(&mut self) {
// Cleanup the local state // Cleanup the local state
self.state.cleanup(); self.state.cleanup();
} }
/// Handle new server messages
fn handle_new_messages(&mut self) -> Result<Vec<Event>, Error> {
let mut frontend_events = Vec::new();
// Step 1
let new_msgs = self.postbox.new_messages();
if new_msgs.len() > 0 {
self.last_ping = self.state.get_time();
for msg in new_msgs {
match msg {
ServerMsg::Chat(msg) => frontend_events.push(Event::Chat(msg)),
ServerMsg::Shutdown => return Err(Error::ServerShutdown),
}
}
}
Ok(frontend_events)
}
}
impl Drop for Client {
fn drop(&mut self) {
self.postbox.send(ClientMsg::Disconnect).unwrap();
}
} }

View File

@ -6,6 +6,7 @@ extern crate serde_derive;
pub mod clock; pub mod clock;
pub mod comp; pub mod comp;
pub mod figure; pub mod figure;
pub mod msg;
pub mod state; pub mod state;
pub mod sys; pub mod sys;
pub mod terrain; pub mod terrain;

5
common/src/msg/client.rs Normal file
View File

@ -0,0 +1,5 @@
#[derive(Debug, Serialize, Deserialize)]
pub enum ClientMsg {
Chat(String),
Disconnect,
}

6
common/src/msg/mod.rs Normal file
View File

@ -0,0 +1,6 @@
pub mod server;
pub mod client;
// Reexports
pub use server::ServerMsg;
pub use client::ClientMsg;

5
common/src/msg/server.rs Normal file
View File

@ -0,0 +1,5 @@
#[derive(Debug, Serialize, Deserialize)]
pub enum ServerMsg {
Chat(String),
Shutdown,
}

View File

@ -112,7 +112,7 @@ where
/// Non-blocking receiver method returning an iterator over already received and deserialized objects /// Non-blocking receiver method returning an iterator over already received and deserialized objects
/// # Errors /// # Errors
/// If the other side disconnects PostBox won't realize that until you try to send something /// If the other side disconnects PostBox won't realize that until you try to send something
pub fn recv_iter(&mut self) -> impl Iterator<Item = R> { pub fn new_messages(&mut self) -> impl ExactSizeIterator<Item = R> {
let mut events = Events::with_capacity(4096); let mut events = Events::with_capacity(4096);
let mut items = VecDeque::new(); let mut items = VecDeque::new();

View File

@ -79,7 +79,7 @@ where
/// Non-blocking method returning an iterator over new connections wrapped in [`PostBox`]es /// Non-blocking method returning an iterator over new connections wrapped in [`PostBox`]es
pub fn new_connections( pub fn new_connections(
&mut self, &mut self,
) -> impl Iterator<Item = PostBox<S, R>> { ) -> impl ExactSizeIterator<Item = PostBox<S, R>> {
let mut events = Events::with_capacity(256); let mut events = Events::with_capacity(256);
let mut conns = VecDeque::new(); let mut conns = VecDeque::new();

View File

@ -72,6 +72,19 @@ impl State {
} }
} }
/// Register a component with the state's ECS
pub fn with_component<T: Component>(mut self) -> Self
where <T as Component>::Storage: Default
{
self.ecs_world.register::<T>();
self
}
/// Delete an entity from the state's ECS, if it exists
pub fn delete_entity(&mut self, entity: EcsEntity) {
let _ = self.ecs_world.delete_entity(entity);
}
// TODO: Get rid of this // TODO: Get rid of this
pub fn new_test_player(&mut self) -> EcsEntity { pub fn new_test_player(&mut self) -> EcsEntity {
self.ecs_world self.ecs_world

View File

@ -1,11 +1,6 @@
// Standard
use std::time::Duration; use std::time::Duration;
// Library
use log::info; use log::info;
use server::{Input, Event, Server};
// Project
use server::{self, Server};
use common::clock::Clock; use common::clock::Clock;
const FPS: u64 = 60; const FPS: u64 = 60;
@ -20,12 +15,21 @@ fn main() {
let mut clock = Clock::new(); let mut clock = Clock::new();
// Create server // Create server
let mut server = Server::new(); let mut server = Server::new()
.expect("Failed to create server instance");
loop { loop {
server.tick(server::Input {}, clock.get_last_delta()) let events = server.tick(Input::default(), clock.get_last_delta())
.expect("Failed to tick server"); .expect("Failed to tick server");
for event in events {
match event {
Event::ClientConnected { ecs_entity } => println!("Client connected!"),
Event::ClientDisconnected { ecs_entity } => println!("Client disconnected!"),
Event::Chat { msg, .. } => println!("[chat] {}", msg),
}
}
// Clean up the server after a tick // Clean up the server after a tick
server.cleanup(); server.cleanup();

11
server/src/client.rs Normal file
View File

@ -0,0 +1,11 @@
use specs::Entity as EcsEntity;
use common::{
msg::{ServerMsg, ClientMsg},
net::PostBox,
};
pub struct Client {
pub ecs_entity: EcsEntity,
pub postbox: PostBox<ServerMsg, ClientMsg>,
pub last_ping: f64,
}

13
server/src/error.rs Normal file
View File

@ -0,0 +1,13 @@
use common::net::PostError;
#[derive(Debug)]
pub enum Error {
Network(PostError),
Other(String),
}
impl From<PostError> for Error {
fn from(err: PostError) -> Self {
Error::Network(err)
}
}

9
server/src/input.rs Normal file
View File

@ -0,0 +1,9 @@
pub struct Input {
// TODO: Use this type to manage server input
}
impl Default for Input {
fn default() -> Self {
Input {}
}
}

View File

@ -1,47 +1,81 @@
// Standard #![feature(drain_filter)]
use std::time::Duration;
// Internal pub mod client;
use common::state::State; pub mod error;
pub mod input;
// Reexports
pub use crate::{
error::Error,
input::Input,
};
use std::{
time::Duration,
net::SocketAddr,
};
use specs::Entity as EcsEntity;
use common::{
state::State,
net::PostOffice,
msg::{ServerMsg, ClientMsg},
};
use world::World; use world::World;
use crate::client::Client;
#[derive(Debug)] const CLIENT_TIMEOUT: f64 = 5.0; // Seconds
pub enum Error {
Other(String),
}
pub struct Input { pub enum Event {
// TODO: Use this type to manage server input ClientConnected {
ecs_entity: EcsEntity,
},
ClientDisconnected {
ecs_entity: EcsEntity,
},
Chat {
ecs_entity: EcsEntity,
msg: String,
},
} }
pub struct Server { pub struct Server {
state: State, state: State,
world: World, world: World,
// TODO: Add "meta" state here postoffice: PostOffice<ServerMsg, ClientMsg>,
clients: Vec<Client>,
} }
impl Server { impl Server {
/// Create a new `Server`. /// Create a new `Server`.
pub fn new() -> Self { #[allow(dead_code)]
Self { pub fn new() -> Result<Self, Error> {
Ok(Self {
state: State::new(), state: State::new(),
world: World::new(), world: World::new(),
}
postoffice: PostOffice::new(SocketAddr::from(([0; 4], 59003)))?,
clients: Vec::new(),
})
} }
/// Get a reference to the server's game state. /// Get a reference to the server's game state.
#[allow(dead_code)]
pub fn state(&self) -> &State { &self.state } pub fn state(&self) -> &State { &self.state }
/// Get a mutable reference to the server's game state. /// Get a mutable reference to the server's game state.
#[allow(dead_code)]
pub fn state_mut(&mut self) -> &mut State { &mut self.state } pub fn state_mut(&mut self) -> &mut State { &mut self.state }
/// Get a reference to the server's world. /// Get a reference to the server's world.
#[allow(dead_code)]
pub fn world(&self) -> &World { &self.world } pub fn world(&self) -> &World { &self.world }
/// Get a mutable reference to the server's world. /// Get a mutable reference to the server's world.
#[allow(dead_code)]
pub fn world_mut(&mut self) -> &mut World { &mut self.world } pub fn world_mut(&mut self) -> &mut World { &mut self.world }
/// Execute a single server tick, handle input and update the game state by the given duration /// Execute a single server tick, handle input and update the game state by the given duration
pub fn tick(&mut self, input: Input, dt: Duration) -> Result<(), Error> { #[allow(dead_code)]
pub fn tick(&mut self, input: Input, dt: Duration) -> Result<Vec<Event>, Error> {
// This tick function is the centre of the Veloren universe. Most server-side things are // This tick function is the centre of the Veloren universe. Most server-side things are
// managed from here, and as such it's important that it stays organised. Please consult // managed from here, and as such it's important that it stays organised. Please consult
// the core developers before making significant changes to this code. Here is the // the core developers before making significant changes to this code. Here is the
@ -56,16 +90,108 @@ impl Server {
// 6) Send relevant state updates to all clients // 6) Send relevant state updates to all clients
// 7) Finish the tick, passing control of the main thread back to the frontend // 7) Finish the tick, passing control of the main thread back to the frontend
// Build up a list of events for this frame, to be passed to the frontend
let mut frontend_events = Vec::new();
// If networking has problems, handle them
if let Some(err) = self.postoffice.status() {
return Err(err.into());
}
// Handle new client connections (step 2)
frontend_events.append(&mut self.handle_new_connections()?);
// Handle new messages from clients
frontend_events.append(&mut self.handle_new_messages()?);
// Tick the client's LocalState (step 3) // Tick the client's LocalState (step 3)
self.state.tick(dt); self.state.tick(dt);
// Finish the tick, pass control back to the frontend (step 6) // Finish the tick, pass control back to the frontend (step 6)
Ok(()) Ok(frontend_events)
} }
/// Clean up the server after a tick /// Clean up the server after a tick
#[allow(dead_code)]
pub fn cleanup(&mut self) { pub fn cleanup(&mut self) {
// Cleanup the local state // Cleanup the local state
self.state.cleanup(); self.state.cleanup();
} }
/// Handle new client connections
fn handle_new_connections(&mut self) -> Result<Vec<Event>, Error> {
let mut frontend_events = Vec::new();
for postbox in self.postoffice.new_connections() {
// TODO: Don't use this method
let ecs_entity = self.state.new_test_player();
frontend_events.push(Event::ClientConnected {
ecs_entity,
});
self.clients.push(Client {
ecs_entity,
postbox,
last_ping: self.state.get_time(),
});
}
Ok(frontend_events)
}
/// Handle new client messages
fn handle_new_messages(&mut self) -> Result<Vec<Event>, Error> {
let mut frontend_events = Vec::new();
let state = &mut self.state;
let mut new_chat_msgs = Vec::new();
self.clients.drain_filter(|client| {
let mut disconnected = false;
let new_msgs = client.postbox.new_messages();
// Update client ping
if new_msgs.len() > 0 {
client.last_ping = state.get_time();
// Process incoming messages
for msg in new_msgs {
match msg {
ClientMsg::Chat(msg) => new_chat_msgs.push((client.ecs_entity, msg)),
ClientMsg::Disconnect => disconnected = true,
}
}
} else if
state.get_time() - client.last_ping > CLIENT_TIMEOUT ||
client.postbox.status().is_some()
{
disconnected = true;
}
if disconnected {
state.delete_entity(client.ecs_entity);
frontend_events.push(Event::ClientDisconnected {
ecs_entity: client.ecs_entity,
});
true
} else {
false
}
});
// Handle new chat messages
for (ecs_entity, msg) in new_chat_msgs {
for client in &mut self.clients {
let _ = client.postbox.send(ServerMsg::Chat(msg.clone()));
}
frontend_events.push(Event::Chat {
ecs_entity,
msg,
});
}
Ok(frontend_events)
}
} }

View File

@ -35,7 +35,7 @@ impl Animation for RunAnimation {
//skeleton.br_foot.offset = Vec3::new(0.0, wavecos_slow * 1.0, wave_slow * 2.0 + wave_dip * 1.0); //skeleton.br_foot.offset = Vec3::new(0.0, wavecos_slow * 1.0, wave_slow * 2.0 + wave_dip * 1.0);
//skeleton.br_foot.ori = Quaternion::rotation_x(0.0 + wave_slow * 10.1); //skeleton.br_foot.ori = Quaternion::rotation_x(0.0 + wave_slow * 10.1);
skeleton.bl_foot.offset = Vec3::new(0.0, 0.0, 80.0); skeleton.bl_foot.offset = Vec3::new(0.0, 0.0, 0.0);
skeleton.bl_foot.ori = Quaternion::rotation_x(wave_slow * 2.0); skeleton.bl_foot.ori = Quaternion::rotation_x(wave_slow * 2.0);
//skeleton.bl_foot.offset = Vec3::new(0.0, wavecos_slow * 1.0, wave_slow * 2.0 + wave_dip * 1.0); //skeleton.bl_foot.offset = Vec3::new(0.0, wavecos_slow * 1.0, wave_slow * 2.0 + wave_dip * 1.0);
//skeleton.bl_foot.ori = Quaternion::rotation_x(0.5 + wave_slow * 0.1); //skeleton.bl_foot.ori = Quaternion::rotation_x(0.5 + wave_slow * 0.1);

View File

@ -84,7 +84,7 @@ impl Scene {
[ [
Some(load_segment("dragonhead.vox").generate_mesh(Vec3::new(2.0, -12.0, 2.0))), Some(load_segment("dragonhead.vox").generate_mesh(Vec3::new(2.0, -12.0, 2.0))),
Some(load_segment("dragon_body.vox").generate_mesh(Vec3::new(0.0, 0.0, 0.0))), Some(load_segment("dragon_body.vox").generate_mesh(Vec3::new(0.0, 0.0, 0.0))),
Some(load_segment("dragon_lfoot.vox").generate_mesh(Vec3::new(10.0, 10.0, -80.0))), Some(load_segment("dragon_lfoot.vox").generate_mesh(Vec3::new(0.0, 10.0, -4.0))),
Some(load_segment("dragon_rfoot.vox").generate_mesh(Vec3::new(0.0, 10.0, -4.0))), Some(load_segment("dragon_rfoot.vox").generate_mesh(Vec3::new(0.0, 10.0, -4.0))),
Some(load_segment("dragon_rfoot.vox").generate_mesh(Vec3::new(0.0, -10.0, -4.0))), Some(load_segment("dragon_rfoot.vox").generate_mesh(Vec3::new(0.0, -10.0, -4.0))),
Some(load_segment("dragon_lfoot.vox").generate_mesh(Vec3::new(0.0, 0.0, 0.0))), Some(load_segment("dragon_lfoot.vox").generate_mesh(Vec3::new(0.0, 0.0, 0.0))),