diff --git a/client/Cargo.toml b/client/Cargo.toml index ac82be29e1..bc4284934f 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -6,7 +6,6 @@ edition = "2018" [dependencies] common = { package = "veloren-common", path = "../common" } -world = { package = "veloren-world", path = "../world" } specs = "0.14" vek = "0.9" diff --git a/client/src/lib.rs b/client/src/lib.rs index 0101408ed4..c20cca643c 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -15,7 +15,7 @@ use std::{ net::SocketAddr, }; use vek::*; -use threadpool; +use threadpool::ThreadPool; use specs::Builder; use common::{ comp, @@ -24,7 +24,6 @@ use common::{ net::PostBox, msg::{ClientMsg, ServerMsg}, }; -use world::World; const SERVER_TIMEOUT: f64 = 5.0; // Seconds @@ -33,7 +32,7 @@ pub enum Event { } pub struct Client { - thread_pool: threadpool::ThreadPool, + thread_pool: ThreadPool, last_ping: f64, postbox: PostBox, @@ -41,10 +40,7 @@ pub struct Client { tick: u64, state: State, player: Option, - - // Testing - world: World, - pub chunk: Option, + view_distance: u64, } impl Client { @@ -54,6 +50,7 @@ impl Client { addr: A, player: comp::Player, character: Option, + view_distance: u64, ) -> Result { let mut postbox = PostBox::to_server(addr)?; @@ -85,10 +82,7 @@ impl Client { tick: 0, state, player, - - // Testing - world: World::new(), - chunk: None, + view_distance, }) } @@ -204,6 +198,7 @@ impl Client { ServerMsg::Chat(msg) => frontend_events.push(Event::Chat(msg)), ServerMsg::SetPlayerEntity(uid) => self.player = Some(self.state.ecs().entity_from_uid(uid).unwrap()), // TODO: Don't unwrap here! ServerMsg::EcsSync(sync_package) => self.state.ecs_mut().sync_with_package(sync_package), + ServerMsg::TerrainChunkUpdate { key, chunk } => self.state.insert_chunk(key, chunk), } } } else if let Some(err) = self.postbox.error() { diff --git a/common/src/msg/client.rs b/common/src/msg/client.rs index e8b59414ed..e2339aada3 100644 --- a/common/src/msg/client.rs +++ b/common/src/msg/client.rs @@ -1,6 +1,6 @@ use crate::comp; -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize)] pub enum ClientMsg { Connect { player: comp::Player, diff --git a/common/src/msg/server.rs b/common/src/msg/server.rs index 21a21f506e..db0ae2ed81 100644 --- a/common/src/msg/server.rs +++ b/common/src/msg/server.rs @@ -1,6 +1,8 @@ +use vek::*; +use crate::terrain::TerrainChunk; use super::EcsPacket; -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize)] pub enum ServerMsg { Handshake { ecs_state: sphynx::StatePackage, @@ -12,4 +14,8 @@ pub enum ServerMsg { Chat(String), SetPlayerEntity(u64), EcsSync(sphynx::SyncPackage), + TerrainChunkUpdate { + key: Vec3, + chunk: TerrainChunk, + }, } diff --git a/common/src/net/post.rs b/common/src/net/post.rs index afd3d8956d..6338424912 100644 --- a/common/src/net/post.rs +++ b/common/src/net/post.rs @@ -56,8 +56,8 @@ impl From> for Error { } } -pub trait PostSend = 'static + serde::Serialize + Send + fmt::Debug; -pub trait PostRecv = 'static + serde::de::DeserializeOwned + Send + fmt::Debug; +pub trait PostSend = 'static + serde::Serialize + Send; +pub trait PostRecv = 'static + serde::de::DeserializeOwned + Send; const TCP_TOK: Token = Token(0); const CTRL_TOK: Token = Token(1); diff --git a/common/src/state.rs b/common/src/state.rs index 1e7f9a076d..6c7a0ba9b9 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -1,4 +1,7 @@ -use std::time::Duration; +use std::{ + time::Duration, + collections::HashSet, +}; use shred::{Fetch, FetchMut}; use specs::{ Builder, @@ -17,7 +20,10 @@ use vek::*; use crate::{ comp, sys, - terrain::TerrainMap, + terrain::{ + TerrainMap, + TerrainChunk, + }, msg::EcsPacket, }; @@ -36,17 +42,17 @@ struct Time(f64); pub struct DeltaTime(pub f64); pub struct Changes { - pub new_chunks: Vec>, - pub changed_chunks: Vec>, - pub removed_chunks: Vec>, + pub new_chunks: HashSet>, + pub changed_chunks: HashSet>, + pub removed_chunks: HashSet>, } impl Changes { pub fn default() -> Self { Self { - new_chunks: vec![], - changed_chunks: vec![], - removed_chunks: vec![], + new_chunks: HashSet::new(), + changed_chunks: HashSet::new(), + removed_chunks: HashSet::new(), } } @@ -152,12 +158,23 @@ impl State { /// Get a reference to this state's terrain. pub fn terrain(&self) -> Fetch { - self.ecs.internal().read_resource::() + self.ecs + .internal() + .read_resource::() } - // TODO: Get rid of this since it shouldn't be needed - pub fn terrain_mut(&mut self) -> FetchMut { - self.ecs.internal_mut().write_resource::() + /// Insert the provided chunk into this state's terrain. + pub fn insert_chunk(&mut self, key: Vec3, chunk: TerrainChunk) { + if self.ecs + .internal_mut() + .write_resource::() + .insert(key, chunk) + .is_some() + { + self.changes.changed_chunks.insert(key); + } else { + self.changes.new_chunks.insert(key); + } } /// Execute a single tick, simulating the game state by the given duration. diff --git a/common/src/terrain/biome.rs b/common/src/terrain/biome.rs index 81bd29967d..a177c44158 100644 --- a/common/src/terrain/biome.rs +++ b/common/src/terrain/biome.rs @@ -1,3 +1,6 @@ +use serde_derive::{Serialize, Deserialize}; + +#[derive(Clone, Serialize, Deserialize)] pub enum BiomeKind { Void, Grassland, diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index d24314f2e9..6098a5c5dd 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -1,10 +1,10 @@ -// Library use vek::*; +use serde_derive::{Serialize, Deserialize}; // Crate use crate::vol::Vox; -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub struct Block { kind: u8, color: [u8; 3], diff --git a/common/src/terrain/mod.rs b/common/src/terrain/mod.rs index f860e14bcb..97008cde50 100644 --- a/common/src/terrain/mod.rs +++ b/common/src/terrain/mod.rs @@ -7,10 +7,8 @@ pub use self::{ biome::BiomeKind, }; -// Library use vek::*; - -// Crate +use serde_derive::{Serialize, Deserialize}; use crate::{ vol::VolSize, volumes::{ @@ -21,6 +19,7 @@ use crate::{ // TerrainChunkSize +#[derive(Clone, Serialize, Deserialize)] pub struct TerrainChunkSize; impl VolSize for TerrainChunkSize { @@ -29,6 +28,7 @@ impl VolSize for TerrainChunkSize { // TerrainChunkMeta +#[derive(Clone, Serialize, Deserialize)] pub struct TerrainChunkMeta { biome: BiomeKind, } diff --git a/common/src/volumes/chunk.rs b/common/src/volumes/chunk.rs index abdea07997..1c99301b63 100644 --- a/common/src/volumes/chunk.rs +++ b/common/src/volumes/chunk.rs @@ -3,6 +3,7 @@ use std::marker::PhantomData; // Library use vek::*; +use serde_derive::{Serialize, Deserialize}; // Local use crate::vol::{ @@ -23,6 +24,7 @@ pub enum ChunkErr { // V = Voxel // S = Size (replace when const generics are a thing) // M = Metadata +#[derive(Clone, Serialize, Deserialize)] pub struct Chunk { vox: Vec, meta: M, diff --git a/common/src/volumes/vol_map.rs b/common/src/volumes/vol_map.rs index 954ac5cc16..2060b63989 100644 --- a/common/src/volumes/vol_map.rs +++ b/common/src/volumes/vol_map.rs @@ -129,4 +129,8 @@ impl VolMap { pub fn remove(&mut self, key: &Vec3) -> Option> { self.chunks.remove(key) } + + pub fn key_pos(&self, key: Vec3) -> Vec3 { + key * S::SIZE.map(|e| e as i32) + } } diff --git a/server/Cargo.toml b/server/Cargo.toml index f7f9f31dcb..a5b590a902 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -10,3 +10,4 @@ world = { package = "veloren-world", path = "../world" } specs = "0.14" vek = "0.9" +threadpool = "1.7" diff --git a/server/src/client.rs b/server/src/client.rs index fed88d9ae4..71d8aae6ab 100644 --- a/server/src/client.rs +++ b/server/src/client.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use specs::Entity as EcsEntity; use common::{ comp, @@ -14,7 +15,6 @@ pub enum ClientState { pub struct Client { pub state: ClientState, - pub entity: EcsEntity, pub postbox: PostBox, pub last_ping: f64, } @@ -26,36 +26,42 @@ impl Client { } pub struct Clients { - clients: Vec, + clients: HashMap, } impl Clients { pub fn empty() -> Self { Self { - clients: Vec::new(), + clients: HashMap::new(), } } - pub fn add(&mut self, client: Client) { - self.clients.push(client); + pub fn add(&mut self, entity: EcsEntity, client: Client) { + self.clients.insert(entity, client); } - pub fn remove_if bool>(&mut self, f: F) { - self.clients.drain_filter(f); + pub fn remove_if bool>(&mut self, mut f: F) { + self.clients.retain(|entity, client| !f(*entity, client)); + } + + pub fn notify(&mut self, entity: EcsEntity, msg: ServerMsg) { + if let Some(client) = self.clients.get_mut(&entity) { + client.notify(msg); + } } pub fn notify_connected(&mut self, msg: ServerMsg) { - for client in &mut self.clients { + for client in self.clients.values_mut() { if client.state == ClientState::Connected { - client.postbox.send(msg.clone()); + client.notify(msg.clone()); } } } - pub fn notify_connected_except(&mut self, entity: EcsEntity, msg: ServerMsg) { - for client in &mut self.clients { - if client.entity != entity && client.state == ClientState::Connected { - client.postbox.send(msg.clone()); + pub fn notify_connected_except(&mut self, except_entity: EcsEntity, msg: ServerMsg) { + for (entity, client) in self.clients.iter_mut() { + if client.state == ClientState::Connected && *entity != except_entity { + client.notify(msg.clone()); } } } diff --git a/server/src/lib.rs b/server/src/lib.rs index 35946c683e..0d3d4f872c 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -13,6 +13,7 @@ pub use crate::{ use std::{ time::Duration, net::SocketAddr, + sync::mpsc, }; use specs::{ Entity as EcsEntity, @@ -22,11 +23,13 @@ use specs::{ saveload::MarkedBuilder, }; use vek::*; +use threadpool::ThreadPool; use common::{ comp, state::State, net::PostOffice, msg::{ServerMsg, ClientMsg}, + terrain::TerrainChunk, }; use world::World; use crate::client::{ @@ -56,18 +59,30 @@ pub struct Server { postoffice: PostOffice, clients: Clients, + + thread_pool: ThreadPool, + chunk_tx: mpsc::Sender<(Vec3, TerrainChunk)>, + chunk_rx: mpsc::Receiver<(Vec3, TerrainChunk)>, } impl Server { /// Create a new `Server`. #[allow(dead_code)] pub fn new() -> Result { + let (chunk_tx, chunk_rx) = mpsc::channel(); + Ok(Self { state: State::new(), world: World::new(), postoffice: PostOffice::bind(SocketAddr::from(([0; 4], 59003)))?, clients: Clients::empty(), + + thread_pool: threadpool::Builder::new() + .thread_name("veloren-worker".into()) + .build(), + chunk_tx, + chunk_rx, }) } @@ -119,6 +134,27 @@ impl Server { // Tick the client's LocalState (step 3) self.state.tick(dt); + // Fetch any generated `TerrainChunk`s and insert them into the terrain + // Also, send the chunk data to anybody that is close by + for (key, chunk) in self.chunk_rx.try_iter() { + // Send the chunk to all nearby players + for (entity, player, pos) in ( + &self.state.ecs().internal().entities(), + &self.state.ecs().internal().read_storage::(), + &self.state.ecs().internal().read_storage::(), + ).join() { + // TODO: Distance check + // if self.state.terrain().key_pos(key) + + self.clients.notify(entity, ServerMsg::TerrainChunkUpdate { + key, + chunk: chunk.clone(), + }); + } + + self.state.insert_chunk(key, chunk); + } + // Synchronise clients with the new state of the world self.sync_clients(); @@ -143,9 +179,8 @@ impl Server { .create_entity_synced() .build(); - self.clients.add(Client { + self.clients.add(entity, Client { state: ClientState::Connecting, - entity, postbox, last_ping: self.state.get_time(), }); @@ -166,7 +201,7 @@ impl Server { let mut new_chat_msgs = Vec::new(); let mut disconnected_clients = Vec::new(); - self.clients.remove_if(|client| { + self.clients.remove_if(|entity, client| { let mut disconnect = false; let new_msgs = client.postbox.new_messages(); @@ -181,12 +216,12 @@ impl Server { ClientMsg::Connect { player, character } => { // Write client components - state.write_component(client.entity, player); - state.write_component(client.entity, comp::phys::Pos(Vec3::zero())); - state.write_component(client.entity, comp::phys::Vel(Vec3::zero())); - state.write_component(client.entity, comp::phys::Dir(Vec3::unit_y())); + state.write_component(entity, player); + state.write_component(entity, comp::phys::Pos(Vec3::zero())); + state.write_component(entity, comp::phys::Vel(Vec3::zero())); + state.write_component(entity, comp::phys::Dir(Vec3::unit_y())); if let Some(character) = character { - state.write_component(client.entity, character); + state.write_component(entity, character); } client.state = ClientState::Connected; @@ -196,7 +231,7 @@ impl Server { ecs_state: state.ecs().gen_state_package(), player_entity: state .ecs() - .uid_from_entity(client.entity) + .uid_from_entity(entity) .unwrap() .into(), }); @@ -207,11 +242,11 @@ impl Server { ClientMsg::Connect { .. } => disconnect = true, // Not allowed when already connected ClientMsg::Ping => client.postbox.send(ServerMsg::Pong), ClientMsg::Pong => {}, - ClientMsg::Chat(msg) => new_chat_msgs.push((client.entity, msg)), + ClientMsg::Chat(msg) => new_chat_msgs.push((entity, msg)), ClientMsg::PlayerPhysics { pos, vel, dir } => { - state.write_component(client.entity, pos); - state.write_component(client.entity, vel); - state.write_component(client.entity, dir); + state.write_component(entity, pos); + state.write_component(entity, vel); + state.write_component(entity, dir); }, ClientMsg::Disconnect => disconnect = true, }, @@ -228,7 +263,7 @@ impl Server { } if disconnect { - disconnected_clients.push(client.entity); + disconnected_clients.push(entity); true } else { false diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index 4b95bb6d46..88c08cb0b8 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -58,7 +58,7 @@ impl PlayState for MainMenuState { global_state.window.renderer_mut().clear(BG_COLOR); - // Maintain the UI + // Maintain the UI (TODO: Maybe clean this up a little to avoid rightward drift?) for event in self.main_menu_ui.maintain(global_state.window.renderer_mut()) { match event { MainMenuEvent::LoginAttempt{ username, server_address } => { @@ -70,7 +70,7 @@ impl PlayState for MainMenuState { Ok(mut socket_adders) => { while let Some(socket_addr) = socket_adders.next() { // TODO: handle error - match Client::new(socket_addr, comp::Player::new(username.clone()), Some(comp::Character::test())) { + match Client::new(socket_addr, comp::Player::new(username.clone()), Some(comp::Character::test()), 300) { Ok(client) => { return PlayStateResult::Push( Box::new(CharSelectionState::new(