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..8d140a980d 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::{hash_map::Entry, HashMap}; use log::debug; use metrics::ServerMetrics; use rand::Rng; @@ -35,7 +35,10 @@ use specs::{join::Join, world::EntityBuilder as EcsEntityBuilder, Builder, Entit use std::{ i32, net::SocketAddr, - sync::Arc, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, time::{Duration, Instant}, }; use uvth::{ThreadPool, ThreadPoolBuilder}; @@ -68,9 +71,15 @@ 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 +122,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 +472,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(()), + }, + ); + continue 'insert_terrain_chunks; + } + }; // Send the chunk to all nearby players. for (entity, view_distance, pos) in ( &self.state.ecs().entities(), @@ -485,7 +507,7 @@ impl Server { entity, ServerMsg::TerrainChunkUpdate { key, - chunk: Box::new(chunk.clone()), + chunk: Ok(Box::new(chunk.clone())), }, ); } @@ -552,32 +574,40 @@ 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 +634,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 +1027,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 +1113,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 +1328,22 @@ 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 payload = world + .generate_chunk(key, || cancel.load(Ordering::Relaxed)) + .map_err(|_| entity); + let _ = chunk_tx.send((key, payload)); + }); } fn process_chat_cmd(&mut self, entity: EcsEntity, cmd: String) { diff --git a/world/src/lib.rs b/world/src/lib.rs index f0ce7896c7..a1a6ba4aa5 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -65,7 +65,11 @@ impl World { BlockGen::new(self, ColumnGen::new(&self.sim)) } - pub fn generate_chunk(&self, chunk_pos: Vec2) -> (TerrainChunk, ChunkSupplement) { + pub fn generate_chunk( + &self, + chunk_pos: Vec2, + mut should_continue: impl FnMut() -> bool, + ) -> Result<(TerrainChunk, ChunkSupplement), ()> { let air = Block::empty(); let stone = Block::new(BlockKind::Dense, Rgb::new(200, 220, 255)); let water = Block::new(BlockKind::Water, Rgb::new(60, 90, 190)); @@ -81,7 +85,7 @@ impl World { { Some((base_z, sim_chunk)) => (base_z as i32, sim_chunk), None => { - return ( + return Ok(( TerrainChunk::new( CONFIG.sea_level as i32, water, @@ -89,7 +93,7 @@ impl World { TerrainChunkMeta::void(), ), ChunkSupplement::default(), - ) + )) } }; @@ -101,6 +105,9 @@ impl World { let mut chunk = TerrainChunk::new(base_z, stone, air, meta); for x in 0..TerrainChunkSize::RECT_SIZE.x as i32 { for y in 0..TerrainChunkSize::RECT_SIZE.y as i32 { + if should_continue() { + return Err(()); + }; let wpos2d = Vec2::new(x, y) + Vec2::from(chunk_pos) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32); @@ -151,7 +158,7 @@ impl World { }, }; - (chunk, supplement) + Ok((chunk, supplement)) } }