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]
common = { package = "veloren-common", path = "../common" }
world = { package = "veloren-world", path = "../world" }
specs = "0.14"
vek = "0.9"

View File

@ -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<ClientMsg, ServerMsg>,
@ -41,10 +40,7 @@ pub struct Client {
tick: u64,
state: State,
player: Option<EcsEntity>,
// Testing
world: World,
pub chunk: Option<TerrainChunk>,
view_distance: u64,
}
impl Client {
@ -54,6 +50,7 @@ impl Client {
addr: A,
player: comp::Player,
character: Option<comp::Character>,
view_distance: u64,
) -> Result<Self, Error> {
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() {

View File

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

View File

@ -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<EcsPacket>,
@ -12,4 +14,8 @@ pub enum ServerMsg {
Chat(String),
SetPlayerEntity(u64),
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 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);

View File

@ -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<Vec3<i32>>,
pub changed_chunks: Vec<Vec3<i32>>,
pub removed_chunks: Vec<Vec3<i32>>,
pub new_chunks: HashSet<Vec3<i32>>,
pub changed_chunks: HashSet<Vec3<i32>>,
pub removed_chunks: HashSet<Vec3<i32>>,
}
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<TerrainMap> {
self.ecs.internal().read_resource::<TerrainMap>()
self.ecs
.internal()
.read_resource::<TerrainMap>()
}
// TODO: Get rid of this since it shouldn't be needed
pub fn terrain_mut(&mut self) -> FetchMut<TerrainMap> {
self.ecs.internal_mut().write_resource::<TerrainMap>()
/// Insert the provided chunk into this state's terrain.
pub fn insert_chunk(&mut self, key: Vec3<i32>, chunk: TerrainChunk) {
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.

View File

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

View File

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

View File

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

View File

@ -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<V: Vox, S: VolSize, M> {
vox: Vec<V>,
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>> {
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"
vek = "0.9"
threadpool = "1.7"

View File

@ -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<ServerMsg, ClientMsg>,
pub last_ping: f64,
}
@ -26,36 +26,42 @@ impl Client {
}
pub struct Clients {
clients: Vec<Client>,
clients: HashMap<EcsEntity, Client>,
}
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<F: FnMut(&mut Client) -> bool>(&mut self, f: F) {
self.clients.drain_filter(f);
pub fn remove_if<F: FnMut(EcsEntity, &mut Client) -> 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());
}
}
}

View File

@ -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<ServerMsg, ClientMsg>,
clients: Clients,
thread_pool: ThreadPool,
chunk_tx: mpsc::Sender<(Vec3<i32>, TerrainChunk)>,
chunk_rx: mpsc::Receiver<(Vec3<i32>, TerrainChunk)>,
}
impl Server {
/// Create a new `Server`.
#[allow(dead_code)]
pub fn new() -> Result<Self, Error> {
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::<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
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

View File

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