From ce327445a72ef9e2a691039e57be0f8acfcbb21a Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 20 Jul 2019 16:37:01 +0100 Subject: [PATCH] Send block diffs instead of entire chunks on block change --- client/src/lib.rs | 3 ++ common/src/msg/server.rs | 7 +++- common/src/state.rs | 76 +++++++++++++++++++----------------- server/src/lib.rs | 30 ++++++++++---- voxygen/src/scene/terrain.rs | 46 ++++++++++++++++++++-- 5 files changed, 115 insertions(+), 47 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index 720d4022b2..52070b2eca 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -401,6 +401,9 @@ impl Client { self.state.insert_chunk(key, *chunk); self.pending_chunks.remove(&key); } + ServerMsg::TerrainBlockUpdates(mut blocks) => blocks + .drain() + .for_each(|(pos, block)| self.state.set_block(pos, block)), ServerMsg::StateAnswer(Ok(state)) => { self.client_state = state; } diff --git a/common/src/msg/server.rs b/common/src/msg/server.rs index e48423fca4..71bdff23f0 100644 --- a/common/src/msg/server.rs +++ b/common/src/msg/server.rs @@ -1,5 +1,9 @@ use super::{ClientState, EcsCompPacket, EcsResPacket}; -use crate::{comp, terrain::TerrainChunk}; +use crate::{ + comp, + terrain::{Block, TerrainChunk}, +}; +use fxhash::FxHashMap; use vek::*; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -41,6 +45,7 @@ pub enum ServerMsg { key: Vec2, chunk: Box, }, + TerrainBlockUpdates(FxHashMap, Block>), Error(ServerError), Disconnect, Shutdown, diff --git a/common/src/state.rs b/common/src/state.rs index 2898d78097..10b46ad890 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -8,6 +8,7 @@ use crate::{ terrain::{Block, TerrainChunk, TerrainMap}, vol::WriteVol, }; +use fxhash::{FxHashMap, FxHashSet}; use rayon::{ThreadPool, ThreadPoolBuilder}; use serde_derive::{Deserialize, Serialize}; use specs::{ @@ -16,11 +17,7 @@ use specs::{ Component, DispatcherBuilder, Entity as EcsEntity, }; use sphynx; -use std::{ - collections::{HashMap, HashSet}, - sync::Arc, - time::Duration, -}; +use std::{sync::Arc, time::Duration}; use vek::*; /// How much faster should an in-game day be compared to a real day? @@ -45,19 +42,19 @@ pub struct DeltaTime(pub f32); /// lag. Ideally, we'd avoid such a situation. const MAX_DELTA_TIME: f32 = 1.0; -pub struct TerrainChange { - blocks: HashMap, Block>, +pub struct BlockChange { + blocks: FxHashMap, Block>, } -impl Default for TerrainChange { +impl Default for BlockChange { fn default() -> Self { Self { - blocks: HashMap::new(), + blocks: FxHashMap::default(), } } } -impl TerrainChange { +impl BlockChange { pub fn set(&mut self, pos: Vec3, block: Block) { self.blocks.insert(pos, block); } @@ -67,22 +64,25 @@ impl TerrainChange { } } -pub struct ChunkChanges { - pub new_chunks: HashSet>, - pub modified_chunks: HashSet>, - pub removed_chunks: HashSet>, +pub struct TerrainChanges { + pub new_chunks: FxHashSet>, + pub modified_chunks: FxHashSet>, + pub removed_chunks: FxHashSet>, + pub modified_blocks: FxHashMap, Block>, } -impl Default for ChunkChanges { +impl Default for TerrainChanges { fn default() -> Self { Self { - new_chunks: HashSet::new(), - modified_chunks: HashSet::new(), - removed_chunks: HashSet::new(), + new_chunks: FxHashSet::default(), + modified_chunks: FxHashSet::default(), + removed_chunks: FxHashSet::default(), + modified_blocks: FxHashMap::default(), } } } -impl ChunkChanges { + +impl TerrainChanges { pub fn clear(&mut self) { self.new_chunks.clear(); self.modified_chunks.clear(); @@ -162,8 +162,8 @@ impl State { ecs.add_resource(Time(0.0)); ecs.add_resource(DeltaTime(0.0)); ecs.add_resource(TerrainMap::new().unwrap()); - ecs.add_resource(TerrainChange::default()); - ecs.add_resource(ChunkChanges::default()); + ecs.add_resource(BlockChange::default()); + ecs.add_resource(TerrainChanges::default()); } /// Register a component with the state's ECS. @@ -200,9 +200,9 @@ impl State { &mut self.ecs } - /// Get a reference to the `Changes` structure of the state. This contains - /// information about state that has changed since the last game tick. - pub fn chunk_changes(&self) -> Fetch { + /// Get a reference to the `TerrainChanges` structure of the state. This contains + /// information about terrain state that has changed since the last game tick. + pub fn terrain_changes(&self) -> Fetch { self.ecs.read_resource() } @@ -235,6 +235,11 @@ impl State { self.ecs.write_resource() } + /// Get a writable reference to this state's terrain. + pub fn set_block(&mut self, pos: Vec3, block: Block) { + self.ecs.write_resource::().set(pos, block); + } + /// Removes every chunk of the terrain. pub fn clear_terrain(&mut self) { let keys = self @@ -257,12 +262,12 @@ impl State { .is_some() { self.ecs - .write_resource::() + .write_resource::() .modified_chunks .insert(key); } else { self.ecs - .write_resource::() + .write_resource::() .new_chunks .insert(key); } @@ -277,7 +282,7 @@ impl State { .is_some() { self.ecs - .write_resource::() + .write_resource::() .removed_chunks .insert(key); } @@ -304,24 +309,25 @@ impl State { // Apply terrain changes let mut terrain = self.ecs.write_resource::(); - let mut chunk_changes = self.ecs.write_resource::(); self.ecs - .write_resource::() + .read_resource::() .blocks - .drain() + .iter() .for_each(|(pos, block)| { - if terrain.set(pos, block).is_ok() { - chunk_changes.modified_chunks.insert(terrain.pos_key(pos)); - } else { + if terrain.set(*pos, *block).is_err() { warn!("Tried to modify block outside of terrain at {:?}", pos); } }); + std::mem::swap( + &mut self.ecs.write_resource::().blocks, + &mut self.ecs.write_resource::().modified_blocks, + ) } /// Clean up the state after a tick. pub fn cleanup(&mut self) { // Clean up data structures from the last tick. - self.ecs.write_resource::().clear(); - self.ecs.write_resource::().clear(); + self.ecs.write_resource::().clear(); + self.ecs.write_resource::().clear(); } } diff --git a/server/src/lib.rs b/server/src/lib.rs index f9da79dbc1..f485dda503 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -17,7 +17,7 @@ use common::{ comp, msg::{ClientMsg, ClientState, RequestStateError, ServerError, ServerInfo, ServerMsg}, net::PostOffice, - state::{State, TerrainChange, TimeOfDay, Uid}, + state::{State, TimeOfDay, Uid}, terrain::{block::Block, TerrainChunk, TerrainChunkSize, TerrainMap}, vol::VolSize, vol::Vox, @@ -300,7 +300,7 @@ impl Server { self.sync_clients(); // Sync changed chunks - 'chunk: for chunk_key in &self.state.chunk_changes().modified_chunks { + 'chunk: for chunk_key in &self.state.terrain_changes().modified_chunks { let terrain = self.state.terrain(); for (entity, player, pos) in ( @@ -328,6 +328,19 @@ impl Server { } } } + // Sync changed blocks + let msg = + ServerMsg::TerrainBlockUpdates(self.state.terrain_changes().modified_blocks.clone()); + for (entity, player) in ( + &self.state.ecs().entities(), + &self.state.ecs().read_storage::(), + ) + .join() + { + if player.view_distance.is_some() { + self.clients.notify(entity, msg.clone()); + } + } // 7) Finish the tick, pass control back to the frontend. @@ -387,6 +400,7 @@ impl Server { let mut new_chat_msgs = Vec::new(); let mut disconnected_clients = Vec::new(); let mut requested_chunks = Vec::new(); + let mut modified_blocks = Vec::new(); self.clients.remove_if(|entity, client| { let mut disconnect = false; @@ -515,9 +529,7 @@ impl Server { .get(entity) .is_some() { - let mut terrain_change = - state.ecs().write_resource::(); - terrain_change.set(pos, Block::empty()); + modified_blocks.push((pos, Block::empty())); } } ClientMsg::PlaceBlock(pos, block) => { @@ -527,9 +539,7 @@ impl Server { .get(entity) .is_some() { - let mut terrain_change = - state.ecs().write_resource::(); - terrain_change.set(pos, block); + modified_blocks.push((pos, block)); } } ClientMsg::TerrainChunkRequest { key } => match client.client_state { @@ -616,6 +626,10 @@ impl Server { self.generate_chunk(key); } + for (pos, block) in modified_blocks { + self.state.set_block(pos, block); + } + Ok(frontend_events) } diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index dadee67ceb..09cbbbe97e 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -90,14 +90,14 @@ impl Terrain { // Add any recently created or changed chunks to the list of chunks to be meshed. for (modified, pos) in client .state() - .chunk_changes() + .terrain_changes() .modified_chunks .iter() .map(|c| (true, c)) .chain( client .state() - .chunk_changes() + .terrain_changes() .new_chunks .iter() .map(|c| (false, c)), @@ -137,8 +137,48 @@ impl Terrain { } } } + + // Add the chunks belonging to recently changed blocks to the list of chunks to be meshed + for pos in client + .state() + .terrain_changes() + .modified_blocks + .iter() + .map(|(p, _)| *p) + { + let chunk_pos = client.state().terrain().pos_key(pos); + + self.mesh_todo.insert( + chunk_pos, + ChunkMeshState { + pos: chunk_pos, + started_tick: current_tick, + active_worker: None, + }, + ); + + // Handle chunks on chunk borders + for x in -1..2 { + for y in -1..2 { + let neighbour_pos = pos + Vec3::new(x, y, 0); + let neighbour_chunk_pos = client.state().terrain().pos_key(neighbour_pos); + + if neighbour_chunk_pos != chunk_pos { + self.mesh_todo.insert( + neighbour_chunk_pos, + ChunkMeshState { + pos: neighbour_chunk_pos, + started_tick: current_tick, + active_worker: None, + }, + ); + } + } + } + } + // Remove any models for chunks that have been recently removed. - for pos in &client.state().chunk_changes().removed_chunks { + for pos in &client.state().terrain_changes().removed_chunks { self.chunks.remove(pos); self.mesh_todo.remove(pos); }