Started work on server-side chunks

Former-commit-id: 84a6bd7358f67a77043c4b11c787538f073c8d28
This commit is contained in:
Joshua Barretto 2019-04-11 00:16:29 +01:00
parent 11630877e3
commit 3d9f8105e6
15 changed files with 130 additions and 62 deletions

View File

@ -6,7 +6,6 @@ edition = "2018"
[dependencies] [dependencies]
common = { package = "veloren-common", path = "../common" } common = { package = "veloren-common", path = "../common" }
world = { package = "veloren-world", path = "../world" }
specs = "0.14" specs = "0.14"
vek = "0.9" vek = "0.9"

View File

@ -15,7 +15,7 @@ use std::{
net::SocketAddr, net::SocketAddr,
}; };
use vek::*; use vek::*;
use threadpool; use threadpool::ThreadPool;
use specs::Builder; use specs::Builder;
use common::{ use common::{
comp, comp,
@ -24,7 +24,6 @@ use common::{
net::PostBox, net::PostBox,
msg::{ClientMsg, ServerMsg}, msg::{ClientMsg, ServerMsg},
}; };
use world::World;
const SERVER_TIMEOUT: f64 = 5.0; // Seconds const SERVER_TIMEOUT: f64 = 5.0; // Seconds
@ -33,7 +32,7 @@ pub enum Event {
} }
pub struct Client { pub struct Client {
thread_pool: threadpool::ThreadPool, thread_pool: ThreadPool,
last_ping: f64, last_ping: f64,
postbox: PostBox<ClientMsg, ServerMsg>, postbox: PostBox<ClientMsg, ServerMsg>,
@ -41,10 +40,7 @@ pub struct Client {
tick: u64, tick: u64,
state: State, state: State,
player: Option<EcsEntity>, player: Option<EcsEntity>,
view_distance: u64,
// Testing
world: World,
pub chunk: Option<TerrainChunk>,
} }
impl Client { impl Client {
@ -54,6 +50,7 @@ impl Client {
addr: A, addr: A,
player: comp::Player, player: comp::Player,
character: Option<comp::Character>, character: Option<comp::Character>,
view_distance: u64,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let mut postbox = PostBox::to_server(addr)?; let mut postbox = PostBox::to_server(addr)?;
@ -85,10 +82,7 @@ impl Client {
tick: 0, tick: 0,
state, state,
player, player,
view_distance,
// Testing
world: World::new(),
chunk: None,
}) })
} }
@ -204,6 +198,7 @@ impl Client {
ServerMsg::Chat(msg) => frontend_events.push(Event::Chat(msg)), 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::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::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() { } else if let Some(err) = self.postbox.error() {

View File

@ -1,6 +1,6 @@
use crate::comp; use crate::comp;
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize)]
pub enum ClientMsg { pub enum ClientMsg {
Connect { Connect {
player: comp::Player, player: comp::Player,

View File

@ -1,6 +1,8 @@
use vek::*;
use crate::terrain::TerrainChunk;
use super::EcsPacket; use super::EcsPacket;
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize)]
pub enum ServerMsg { pub enum ServerMsg {
Handshake { Handshake {
ecs_state: sphynx::StatePackage<EcsPacket>, ecs_state: sphynx::StatePackage<EcsPacket>,
@ -12,4 +14,8 @@ pub enum ServerMsg {
Chat(String), Chat(String),
SetPlayerEntity(u64), SetPlayerEntity(u64),
EcsSync(sphynx::SyncPackage<EcsPacket>), EcsSync(sphynx::SyncPackage<EcsPacket>),
TerrainChunkUpdate {
key: Vec3<i32>,
chunk: TerrainChunk,
},
} }

View File

@ -56,8 +56,8 @@ impl<T> From<mio_extras::channel::SendError<T>> for Error {
} }
} }
pub trait PostSend = 'static + serde::Serialize + Send + fmt::Debug; pub trait PostSend = 'static + serde::Serialize + Send;
pub trait PostRecv = 'static + serde::de::DeserializeOwned + Send + fmt::Debug; pub trait PostRecv = 'static + serde::de::DeserializeOwned + Send;
const TCP_TOK: Token = Token(0); const TCP_TOK: Token = Token(0);
const CTRL_TOK: Token = Token(1); const CTRL_TOK: Token = Token(1);

View File

@ -1,4 +1,7 @@
use std::time::Duration; use std::{
time::Duration,
collections::HashSet,
};
use shred::{Fetch, FetchMut}; use shred::{Fetch, FetchMut};
use specs::{ use specs::{
Builder, Builder,
@ -17,7 +20,10 @@ use vek::*;
use crate::{ use crate::{
comp, comp,
sys, sys,
terrain::TerrainMap, terrain::{
TerrainMap,
TerrainChunk,
},
msg::EcsPacket, msg::EcsPacket,
}; };
@ -36,17 +42,17 @@ struct Time(f64);
pub struct DeltaTime(pub f64); pub struct DeltaTime(pub f64);
pub struct Changes { pub struct Changes {
pub new_chunks: Vec<Vec3<i32>>, pub new_chunks: HashSet<Vec3<i32>>,
pub changed_chunks: Vec<Vec3<i32>>, pub changed_chunks: HashSet<Vec3<i32>>,
pub removed_chunks: Vec<Vec3<i32>>, pub removed_chunks: HashSet<Vec3<i32>>,
} }
impl Changes { impl Changes {
pub fn default() -> Self { pub fn default() -> Self {
Self { Self {
new_chunks: vec![], new_chunks: HashSet::new(),
changed_chunks: vec![], changed_chunks: HashSet::new(),
removed_chunks: vec![], removed_chunks: HashSet::new(),
} }
} }
@ -152,12 +158,23 @@ impl State {
/// Get a reference to this state's terrain. /// Get a reference to this state's terrain.
pub fn terrain(&self) -> Fetch<TerrainMap> { pub fn terrain(&self) -> Fetch<TerrainMap> {
self.ecs.internal().read_resource::<TerrainMap>() self.ecs
.internal()
.read_resource::<TerrainMap>()
} }
// TODO: Get rid of this since it shouldn't be needed /// Insert the provided chunk into this state's terrain.
pub fn terrain_mut(&mut self) -> FetchMut<TerrainMap> { pub fn insert_chunk(&mut self, key: Vec3<i32>, chunk: TerrainChunk) {
self.ecs.internal_mut().write_resource::<TerrainMap>() if self.ecs
.internal_mut()
.write_resource::<TerrainMap>()
.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. /// Execute a single tick, simulating the game state by the given duration.

View File

@ -1,3 +1,6 @@
use serde_derive::{Serialize, Deserialize};
#[derive(Clone, Serialize, Deserialize)]
pub enum BiomeKind { pub enum BiomeKind {
Void, Void,
Grassland, Grassland,

View File

@ -1,10 +1,10 @@
// Library
use vek::*; use vek::*;
use serde_derive::{Serialize, Deserialize};
// Crate // Crate
use crate::vol::Vox; use crate::vol::Vox;
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct Block { pub struct Block {
kind: u8, kind: u8,
color: [u8; 3], color: [u8; 3],

View File

@ -7,10 +7,8 @@ pub use self::{
biome::BiomeKind, biome::BiomeKind,
}; };
// Library
use vek::*; use vek::*;
use serde_derive::{Serialize, Deserialize};
// Crate
use crate::{ use crate::{
vol::VolSize, vol::VolSize,
volumes::{ volumes::{
@ -21,6 +19,7 @@ use crate::{
// TerrainChunkSize // TerrainChunkSize
#[derive(Clone, Serialize, Deserialize)]
pub struct TerrainChunkSize; pub struct TerrainChunkSize;
impl VolSize for TerrainChunkSize { impl VolSize for TerrainChunkSize {
@ -29,6 +28,7 @@ impl VolSize for TerrainChunkSize {
// TerrainChunkMeta // TerrainChunkMeta
#[derive(Clone, Serialize, Deserialize)]
pub struct TerrainChunkMeta { pub struct TerrainChunkMeta {
biome: BiomeKind, biome: BiomeKind,
} }

View File

@ -3,6 +3,7 @@ use std::marker::PhantomData;
// Library // Library
use vek::*; use vek::*;
use serde_derive::{Serialize, Deserialize};
// Local // Local
use crate::vol::{ use crate::vol::{
@ -23,6 +24,7 @@ pub enum ChunkErr {
// V = Voxel // V = Voxel
// S = Size (replace when const generics are a thing) // S = Size (replace when const generics are a thing)
// M = Metadata // M = Metadata
#[derive(Clone, Serialize, Deserialize)]
pub struct Chunk<V: Vox, S: VolSize, M> { pub struct Chunk<V: Vox, S: VolSize, M> {
vox: Vec<V>, vox: Vec<V>,
meta: M, meta: M,

View File

@ -129,4 +129,8 @@ impl<V: Vox, S: VolSize, M> VolMap<V, S, M> {
pub fn remove(&mut self, key: &Vec3<i32>) -> Option<Chunk<V, S, M>> { pub fn remove(&mut self, key: &Vec3<i32>) -> Option<Chunk<V, S, M>> {
self.chunks.remove(key) self.chunks.remove(key)
} }
pub fn key_pos(&self, key: Vec3<i32>) -> Vec3<i32> {
key * S::SIZE.map(|e| e as i32)
}
} }

View File

@ -10,3 +10,4 @@ world = { package = "veloren-world", path = "../world" }
specs = "0.14" specs = "0.14"
vek = "0.9" vek = "0.9"
threadpool = "1.7"

View File

@ -1,3 +1,4 @@
use std::collections::HashMap;
use specs::Entity as EcsEntity; use specs::Entity as EcsEntity;
use common::{ use common::{
comp, comp,
@ -14,7 +15,6 @@ pub enum ClientState {
pub struct Client { pub struct Client {
pub state: ClientState, pub state: ClientState,
pub entity: EcsEntity,
pub postbox: PostBox<ServerMsg, ClientMsg>, pub postbox: PostBox<ServerMsg, ClientMsg>,
pub last_ping: f64, pub last_ping: f64,
} }
@ -26,36 +26,42 @@ impl Client {
} }
pub struct Clients { pub struct Clients {
clients: Vec<Client>, clients: HashMap<EcsEntity, Client>,
} }
impl Clients { impl Clients {
pub fn empty() -> Self { pub fn empty() -> Self {
Self { Self {
clients: Vec::new(), clients: HashMap::new(),
} }
} }
pub fn add(&mut self, client: Client) { pub fn add(&mut self, entity: EcsEntity, client: Client) {
self.clients.push(client); self.clients.insert(entity, client);
} }
pub fn remove_if<F: FnMut(&mut Client) -> bool>(&mut self, f: F) { pub fn remove_if<F: FnMut(EcsEntity, &mut Client) -> bool>(&mut self, mut f: F) {
self.clients.drain_filter(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) { 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 { 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) { pub fn notify_connected_except(&mut self, except_entity: EcsEntity, msg: ServerMsg) {
for client in &mut self.clients { for (entity, client) in self.clients.iter_mut() {
if client.entity != entity && client.state == ClientState::Connected { if client.state == ClientState::Connected && *entity != except_entity {
client.postbox.send(msg.clone()); client.notify(msg.clone());
} }
} }
} }

View File

@ -13,6 +13,7 @@ pub use crate::{
use std::{ use std::{
time::Duration, time::Duration,
net::SocketAddr, net::SocketAddr,
sync::mpsc,
}; };
use specs::{ use specs::{
Entity as EcsEntity, Entity as EcsEntity,
@ -22,11 +23,13 @@ use specs::{
saveload::MarkedBuilder, saveload::MarkedBuilder,
}; };
use vek::*; use vek::*;
use threadpool::ThreadPool;
use common::{ use common::{
comp, comp,
state::State, state::State,
net::PostOffice, net::PostOffice,
msg::{ServerMsg, ClientMsg}, msg::{ServerMsg, ClientMsg},
terrain::TerrainChunk,
}; };
use world::World; use world::World;
use crate::client::{ use crate::client::{
@ -56,18 +59,30 @@ pub struct Server {
postoffice: PostOffice<ServerMsg, ClientMsg>, postoffice: PostOffice<ServerMsg, ClientMsg>,
clients: Clients, clients: Clients,
thread_pool: ThreadPool,
chunk_tx: mpsc::Sender<(Vec3<i32>, TerrainChunk)>,
chunk_rx: mpsc::Receiver<(Vec3<i32>, TerrainChunk)>,
} }
impl Server { impl Server {
/// Create a new `Server`. /// Create a new `Server`.
#[allow(dead_code)] #[allow(dead_code)]
pub fn new() -> Result<Self, Error> { pub fn new() -> Result<Self, Error> {
let (chunk_tx, chunk_rx) = mpsc::channel();
Ok(Self { Ok(Self {
state: State::new(), state: State::new(),
world: World::new(), world: World::new(),
postoffice: PostOffice::bind(SocketAddr::from(([0; 4], 59003)))?, postoffice: PostOffice::bind(SocketAddr::from(([0; 4], 59003)))?,
clients: Clients::empty(), 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) // Tick the client's LocalState (step 3)
self.state.tick(dt); 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::<comp::Player>(),
&self.state.ecs().internal().read_storage::<comp::phys::Pos>(),
).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 // Synchronise clients with the new state of the world
self.sync_clients(); self.sync_clients();
@ -143,9 +179,8 @@ impl Server {
.create_entity_synced() .create_entity_synced()
.build(); .build();
self.clients.add(Client { self.clients.add(entity, Client {
state: ClientState::Connecting, state: ClientState::Connecting,
entity,
postbox, postbox,
last_ping: self.state.get_time(), last_ping: self.state.get_time(),
}); });
@ -166,7 +201,7 @@ impl Server {
let mut new_chat_msgs = Vec::new(); let mut new_chat_msgs = Vec::new();
let mut disconnected_clients = Vec::new(); let mut disconnected_clients = Vec::new();
self.clients.remove_if(|client| { self.clients.remove_if(|entity, client| {
let mut disconnect = false; let mut disconnect = false;
let new_msgs = client.postbox.new_messages(); let new_msgs = client.postbox.new_messages();
@ -181,12 +216,12 @@ impl Server {
ClientMsg::Connect { player, character } => { ClientMsg::Connect { player, character } => {
// Write client components // Write client components
state.write_component(client.entity, player); state.write_component(entity, player);
state.write_component(client.entity, comp::phys::Pos(Vec3::zero())); state.write_component(entity, comp::phys::Pos(Vec3::zero()));
state.write_component(client.entity, comp::phys::Vel(Vec3::zero())); state.write_component(entity, comp::phys::Vel(Vec3::zero()));
state.write_component(client.entity, comp::phys::Dir(Vec3::unit_y())); state.write_component(entity, comp::phys::Dir(Vec3::unit_y()));
if let Some(character) = character { if let Some(character) = character {
state.write_component(client.entity, character); state.write_component(entity, character);
} }
client.state = ClientState::Connected; client.state = ClientState::Connected;
@ -196,7 +231,7 @@ impl Server {
ecs_state: state.ecs().gen_state_package(), ecs_state: state.ecs().gen_state_package(),
player_entity: state player_entity: state
.ecs() .ecs()
.uid_from_entity(client.entity) .uid_from_entity(entity)
.unwrap() .unwrap()
.into(), .into(),
}); });
@ -207,11 +242,11 @@ impl Server {
ClientMsg::Connect { .. } => disconnect = true, // Not allowed when already connected ClientMsg::Connect { .. } => disconnect = true, // Not allowed when already connected
ClientMsg::Ping => client.postbox.send(ServerMsg::Pong), ClientMsg::Ping => client.postbox.send(ServerMsg::Pong),
ClientMsg::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 } => { ClientMsg::PlayerPhysics { pos, vel, dir } => {
state.write_component(client.entity, pos); state.write_component(entity, pos);
state.write_component(client.entity, vel); state.write_component(entity, vel);
state.write_component(client.entity, dir); state.write_component(entity, dir);
}, },
ClientMsg::Disconnect => disconnect = true, ClientMsg::Disconnect => disconnect = true,
}, },
@ -228,7 +263,7 @@ impl Server {
} }
if disconnect { if disconnect {
disconnected_clients.push(client.entity); disconnected_clients.push(entity);
true true
} else { } else {
false false

View File

@ -58,7 +58,7 @@ impl PlayState for MainMenuState {
global_state.window.renderer_mut().clear(BG_COLOR); 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()) { for event in self.main_menu_ui.maintain(global_state.window.renderer_mut()) {
match event { match event {
MainMenuEvent::LoginAttempt{ username, server_address } => { MainMenuEvent::LoginAttempt{ username, server_address } => {
@ -70,7 +70,7 @@ impl PlayState for MainMenuState {
Ok(mut socket_adders) => { Ok(mut socket_adders) => {
while let Some(socket_addr) = socket_adders.next() { while let Some(socket_addr) = socket_adders.next() {
// TODO: handle error // 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) => { Ok(client) => {
return PlayStateResult::Push( return PlayStateResult::Push(
Box::new(CharSelectionState::new( Box::new(CharSelectionState::new(