From b4ad76372b4a9c644bd50013d70dd961423be80d Mon Sep 17 00:00:00 2001 From: Joshua Yanovski Date: Mon, 16 Sep 2019 03:38:53 +0200 Subject: [PATCH] Allow canceling chunk generation. Currently we only do this when no players are in range of the chunk. We also send the first client who posted the chunk a message indicating that it's canceled, the hope being that this will be a performance win in single player mode since you don't have to wait three seconds to realize that the server won't generate the chunk for you. We now check an atomic flag for every column sample in a chunk. We could probably do this less frequently, but since it's a relaxed load it has essentially no performance impact on Intel architectures. --- client/src/lib.rs | 4 +- common/src/msg/server.rs | 2 +- server/src/lib.rs | 104 +++++++++++++++++++++++---------------- 3 files changed, 66 insertions(+), 44 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index 870cb96670..4074046647 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -488,7 +488,9 @@ impl Client { self.state.write_component(self.entity, inventory) } ServerMsg::TerrainChunkUpdate { key, chunk } => { - self.state.insert_chunk(key, *chunk); + if let Ok(chunk) = chunk { + self.state.insert_chunk(key, *chunk); + } self.pending_chunks.remove(&key); } ServerMsg::TerrainBlockUpdates(mut blocks) => blocks diff --git a/common/src/msg/server.rs b/common/src/msg/server.rs index 5b12429a2a..50ef9081c5 100644 --- a/common/src/msg/server.rs +++ b/common/src/msg/server.rs @@ -58,7 +58,7 @@ pub enum ServerMsg { InventoryUpdate(comp::Inventory), TerrainChunkUpdate { key: Vec2, - chunk: Box, + chunk: Result, ()>, }, TerrainBlockUpdates(HashMap, Block>), Error(ServerError), diff --git a/server/src/lib.rs b/server/src/lib.rs index e8886ba002..fc289373b0 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -27,7 +27,7 @@ use common::{ vol::{ReadVol, RectVolSize, Vox}, }; use crossbeam::channel; -use hashbrown::HashSet; +use hashbrown::{HashMap, hash_map::Entry}; use log::debug; use metrics::ServerMetrics; use rand::Rng; @@ -35,7 +35,7 @@ use specs::{join::Join, world::EntityBuilder as EcsEntityBuilder, Builder, Entit use std::{ i32, net::SocketAddr, - sync::Arc, + sync::{Arc, atomic::{AtomicBool, Ordering}}, time::{Duration, Instant}, }; use uvth::{ThreadPool, ThreadPoolBuilder}; @@ -68,9 +68,9 @@ pub struct Server { clients: Clients, thread_pool: ThreadPool, - chunk_tx: channel::Sender<(Vec2, (TerrainChunk, ChunkSupplement))>, - chunk_rx: channel::Receiver<(Vec2, (TerrainChunk, ChunkSupplement))>, - pending_chunks: HashSet>, + chunk_tx: channel::Sender<(Vec2, Result<(TerrainChunk, ChunkSupplement), EcsEntity>)>, + chunk_rx: channel::Receiver<(Vec2, Result<(TerrainChunk, ChunkSupplement), EcsEntity>)>, + pending_chunks: HashMap, Arc>, server_settings: ServerSettings, server_info: ServerInfo, @@ -113,7 +113,7 @@ impl Server { .build(), chunk_tx, chunk_rx, - pending_chunks: HashSet::new(), + pending_chunks: HashMap::new(), server_info: ServerInfo { name: settings.server_name.clone(), @@ -463,7 +463,20 @@ impl Server { let before_tick_5 = Instant::now(); // 5) Fetch any generated `TerrainChunk`s and insert them into the terrain. // Also, send the chunk data to anybody that is close by. - if let Ok((key, (chunk, supplement))) = self.chunk_rx.try_recv() { + 'insert_terrain_chunks: while let Ok((key, res)) = self.chunk_rx.try_recv() { + let (chunk, supplement) = match res { + Ok((chunk, supplement)) => (chunk, supplement), + Err(entity) => { + self.clients.notify( + entity, + ServerMsg::TerrainChunkUpdate { + key, + chunk: Err(()), + }, + ); + break 'insert_terrain_chunks; + } + }; // Send the chunk to all nearby players. for (entity, view_distance, pos) in ( &self.state.ecs().entities(), @@ -485,7 +498,7 @@ impl Server { entity, ServerMsg::TerrainChunkUpdate { key, - chunk: Box::new(chunk.clone()), + chunk: Ok(Box::new(chunk.clone())), }, ); } @@ -552,32 +565,38 @@ impl Server { // Remove chunks that are too far from players. let mut chunks_to_remove = Vec::new(); - self.state.terrain().iter().for_each(|(chunk_key, _)| { - let mut should_drop = true; + self.state.terrain() + .iter().map(|(k, _)| k) + .chain(self.pending_chunks.keys().cloned()) + .for_each(|chunk_key| { + let mut should_drop = true; - // For each player with a position, calculate the distance. - for (player, pos) in ( - &self.state.ecs().read_storage::(), - &self.state.ecs().read_storage::(), - ) - .join() - { - if player - .view_distance - .map(|vd| chunk_in_vd(pos.0, chunk_key, &self.state.terrain(), vd)) - .unwrap_or(false) + // For each player with a position, calculate the distance. + for (player, pos) in ( + &self.state.ecs().read_storage::(), + &self.state.ecs().read_storage::(), + ) + .join() { - should_drop = false; - break; + if player + .view_distance + .map(|vd| chunk_in_vd(pos.0, chunk_key, &self.state.terrain(), vd)) + .unwrap_or(false) + { + should_drop = false; + break; + } } - } - if should_drop { - chunks_to_remove.push(chunk_key); - } - }); + if should_drop { + chunks_to_remove.push(chunk_key); + } + }); for key in chunks_to_remove { self.state.remove_chunk(key); + if let Some(cancel) = self.pending_chunks.remove(&key) { + cancel.store(true, Ordering::Relaxed); + } } let before_tick_6 = Instant::now(); @@ -604,10 +623,10 @@ impl Server { entity, ServerMsg::TerrainChunkUpdate { key: *chunk_key, - chunk: Box::new(match self.state.terrain().get_key(*chunk_key) { + chunk: Ok(Box::new(match self.state.terrain().get_key(*chunk_key) { Some(chunk) => chunk.clone(), None => break 'chunk, - }), + })), }, ); } @@ -997,10 +1016,10 @@ impl Server { Some(chunk) => { client.postbox.send_message(ServerMsg::TerrainChunkUpdate { key, - chunk: Box::new(chunk.clone()), + chunk: Ok(Box::new(chunk.clone())), }) } - None => requested_chunks.push(key), + None => requested_chunks.push((entity, key)) } } ClientState::Pending => {} @@ -1083,8 +1102,8 @@ impl Server { } // Generate requested chunks. - for key in requested_chunks { - self.generate_chunk(key); + for (entity, key) in requested_chunks { + self.generate_chunk(entity, key); } for (pos, block) in modified_blocks { @@ -1298,14 +1317,15 @@ impl Server { .clear(); } - pub fn generate_chunk(&mut self, key: Vec2) { - if self.pending_chunks.insert(key) { - let chunk_tx = self.chunk_tx.clone(); - let world = self.world.clone(); - self.thread_pool.execute(move || { - let _ = chunk_tx.send((key, world.generate_chunk(key))); - }); - } + pub fn generate_chunk(&mut self, entity: EcsEntity, key: Vec2) { + let v = if let Entry::Vacant(v) = self.pending_chunks.entry(key) { v } else { return; }; + let cancel = Arc::new(AtomicBool::new(false)); + v.insert(Arc::clone(&cancel)); + let chunk_tx = self.chunk_tx.clone(); + let world = self.world.clone(); + self.thread_pool.execute(move || { + let _ = chunk_tx.send((key, world.generate_chunk(key, &cancel).map_err(|_| entity))); + }); } fn process_chat_cmd(&mut self, entity: EcsEntity, cmd: String) {