Merge branch 'sharp/cancel-chunk-gen' into 'master'

Allow canceling chunk generation.

See merge request veloren/veloren!499
This commit is contained in:
Joshua Barretto 2019-09-16 19:15:47 +00:00
commit 7e6fbda34a
4 changed files with 95 additions and 48 deletions

View File

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

View File

@ -58,7 +58,7 @@ pub enum ServerMsg {
InventoryUpdate(comp::Inventory),
TerrainChunkUpdate {
key: Vec2<i32>,
chunk: Box<TerrainChunk>,
chunk: Result<Box<TerrainChunk>, ()>,
},
TerrainBlockUpdates(HashMap<Vec3<i32>, Block>),
Error(ServerError),

View File

@ -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<i32>, (TerrainChunk, ChunkSupplement))>,
chunk_rx: channel::Receiver<(Vec2<i32>, (TerrainChunk, ChunkSupplement))>,
pending_chunks: HashSet<Vec2<i32>>,
chunk_tx: channel::Sender<(
Vec2<i32>,
Result<(TerrainChunk, ChunkSupplement), EcsEntity>,
)>,
chunk_rx: channel::Receiver<(
Vec2<i32>,
Result<(TerrainChunk, ChunkSupplement), EcsEntity>,
)>,
pending_chunks: HashMap<Vec2<i32>, Arc<AtomicBool>>,
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::<comp::Player>(),
&self.state.ecs().read_storage::<comp::Pos>(),
)
.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::<comp::Player>(),
&self.state.ecs().read_storage::<comp::Pos>(),
)
.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<i32>) {
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<i32>) {
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) {

View File

@ -65,7 +65,11 @@ impl World {
BlockGen::new(self, ColumnGen::new(&self.sim))
}
pub fn generate_chunk(&self, chunk_pos: Vec2<i32>) -> (TerrainChunk, ChunkSupplement) {
pub fn generate_chunk(
&self,
chunk_pos: Vec2<i32>,
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))
}
}