From f8760e876c4307cef0fe9e633454ab80b3fb10a6 Mon Sep 17 00:00:00 2001 From: Piotrek Date: Sat, 15 May 2021 20:48:59 +0200 Subject: [PATCH] Loading and generating chunks in a separate threads --- src/game/mod.rs | 33 +++++++++++++------ src/game/world/chunk.rs | 17 +++------- src/game/world/mod.rs | 72 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 98 insertions(+), 24 deletions(-) diff --git a/src/game/mod.rs b/src/game/mod.rs index 5c5a5fa..3416255 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -41,12 +41,14 @@ impl Game { for x in -RENDER_DIST..RENDER_DIST+1 { for z in -RENDER_DIST..RENDER_DIST+1 { let pos = Vector3{x, y: 0, z}; - if instance.world.load_chunk(pos) { - let chunk = instance.world.get_chunk(&pos).unwrap(); - chunk.write_to(renderer, pos * world::chunk::SIZE as i32); - println!("Writing chunk to {:?}", pos * world::chunk::SIZE as i32); - count += 1; - } + instance.world.load_chunk_async(pos); + count += 1; + //if instance.world.load_chunk(pos) { + //let chunk = instance.world.get_chunk(&pos).unwrap(); + //chunk.write_to(renderer, pos * world::chunk::SIZE as i32); + //println!("Writing chunk to {:?}", pos * world::chunk::SIZE as i32); + //count += 1; + //} } } println!("Preloaded {} chunks in {} ms (about {} voxels)", count, timer.elapsed().as_millis(), count * world::block::SIZE_QB); @@ -102,14 +104,25 @@ impl Game { for a in -RENDER_DIST..RENDER_DIST+1 { let pos = center + Vector3{x: mask.z*a, y: 0, z: mask.x*a}; - self.world.load_chunk(pos); - let chunk = self.world.get_chunk(&pos).unwrap(); - chunk.write_to(renderer, pos * world::chunk::SIZE as i32); + self.world.load_chunk_async(pos); + // if self.world.load_chunk(pos) { + // self.world.get_chunk(&pos).unwrap().dirty = true; + // } } } - self.prev_chunk_pos = Some(chunk_pos); } + + // Send dirty chunks to renderer + // TODO: Only send chunks in rendering range + // TODO: Unload chunks above certain radius + self.world.update(); + for (pos, chunk) in self.world.all_chunks() { + if chunk.dirty { + chunk.write_to(renderer, pos * world::chunk::SIZE as i32); + chunk.dirty = false; + } + } } pub fn input(&mut self, event: &Event<()>) { diff --git a/src/game/world/chunk.rs b/src/game/world/chunk.rs index 7ad102b..779ba5c 100644 --- a/src/game/world/chunk.rs +++ b/src/game/world/chunk.rs @@ -24,14 +24,16 @@ const NUM_WORKERS: usize = 8; pub struct WorldChunk { nodes: Box<[u32]>, - blocks: Vec + blocks: Vec, + pub dirty: bool // true by default, set to false when uploaded to renderer. should be set to true when unloaded (TODO) } impl WorldChunk { pub fn new() -> Self { Self { nodes: vec![0_u32; SIZE_QB].into_boxed_slice(), - blocks: Vec::new() + blocks: Vec::new(), + dirty: true } } @@ -110,7 +112,6 @@ impl WorldChunk { bytes } - #[allow(unused)] pub fn all_blocks(&self) -> Vec<(Vector3, &WorldBlock)> { self.nodes.iter().enumerate() .filter(|(_, n)| **n != 0) @@ -133,7 +134,7 @@ impl WorldChunk { // Get block index let index = (block_pos.x + SIZE as i32 * (block_pos.y + SIZE as i32 * block_pos.z)) as usize; let mut value = self.nodes[index]; - // Allocate new block + // Allocate new block if needed if value == 0 { value = (self.blocks.len() + 1) as u32; self.nodes[index] = value; @@ -143,14 +144,6 @@ impl WorldChunk { &mut self.blocks[(value-1) as usize] } - pub fn get_block(&self, block_pos: &Vector3) -> &WorldBlock { - // Get block index - let index = (block_pos.x + SIZE as i32 * (block_pos.y + SIZE as i32 * block_pos.z)) as usize; - let value = self.nodes[index]; - // Return block reference - &self.blocks[(value-1) as usize] - } - pub fn write_to(&self, renderer: &mut dyn RendererView, offset: Vector3) { for (blpos, block) in self.all_blocks() { renderer.write(&(offset + blpos), block); diff --git a/src/game/world/mod.rs b/src/game/world/mod.rs index cc338ce..de0ce71 100644 --- a/src/game/world/mod.rs +++ b/src/game/world/mod.rs @@ -2,7 +2,8 @@ mod generator; mod data; pub mod chunk; pub mod block; -use std::sync::Arc; +use std::thread; +use std::sync::{mpsc, mpsc::Sender, mpsc::Receiver}; use std::time::Instant; use std::path::PathBuf; use std::io::Write; @@ -17,6 +18,7 @@ use data::WorldData; pub struct World { data: WorldData, + chunk_channel: (Sender<(Vector3, WorldChunk)>, Receiver<(Vector3, WorldChunk)>), chunks: HashMap, WorldChunk>, } @@ -25,6 +27,7 @@ impl World { pub fn init(name: String) -> Self { let mut instance = Self { data: WorldData { name, ..Default::default() }, + chunk_channel: mpsc::channel(), chunks: HashMap::new(), }; instance.load_metadata(); @@ -78,7 +81,7 @@ impl World { * Loads chunk into memory * It will try to load the chunk from file, and if that fails it will generate the chunk and save it. */ - pub fn load_chunk(&mut self, pos: Vector3) ->bool { + pub fn load_chunk(&mut self, pos: Vector3) -> bool { // Abort if chunk is already loaded if let Some(_) = self.chunks.get(&pos) { println!("Chunk at {:?} already loaded", pos); @@ -127,6 +130,71 @@ impl World { true } + pub fn load_chunk_async(&mut self, pos: Vector3) { + // Abort if chunk is already loaded + if let Some(ch) = self.chunks.get_mut(&pos) { + println!("Chunk at {:?} already loaded", pos); + ch.dirty = true; + return; + } + + // Prepare + let filename = format!("chunk_{}_{}_{}.dat", pos.x, pos.y, pos.z); + let filepath = dirs::data_local_dir().unwrap().join("Voxelgame").join("saves").join(&self.data.name).join(filename); + + // Spawn thread + let tx = self.chunk_channel.0.clone(); + let t = thread::spawn(move || { + // Try loading chunk from file + if filepath.is_file() { + let timer = Instant::now(); + let mut buff = Vec::new(); + let mut file = fs::File::open(filepath).expect("Failed to open chunk file"); + file.read_to_end(&mut buff).expect("Failed to read chunk file"); + let chunk = WorldChunk::from_bytes(buff); + tx.send((pos, chunk)).unwrap(); + println!("Chunk at {:?} loaded ({} ms)", pos, timer.elapsed().as_millis()); + return; + } + + // Try generating chunk + let timer0 = Instant::now(); + let mut chunk = WorldChunk::new(); + for x in 0..chunk::SIZE as i32 { + for z in 0..chunk::SIZE as i32 { + let bpos = Vector3::{x,y:0,z}; + let block = chunk.get_block_mut(&bpos); + WorldGen::generate(&(pos*chunk::SIZE as i32 + bpos), block); + } + } + // Save generated chunk to file + let timer1 = Instant::now(); + fs::create_dir_all(&filepath.parent().unwrap()).expect("Failed to create world directory"); + let mut file = fs::File::create(filepath).expect("Failed to create chunk file"); + file.write(&chunk.to_bytes()).expect("Failed to write chunk file"); + // Send it + tx.send((pos, chunk)).unwrap(); + let t1 = timer1.elapsed().as_millis(); + let t0 = timer0.elapsed().as_millis() - t1; + println!("Chunk at {:?} generated ({} ms) and saved ({} ms)", pos, t0, t1); + }); + + // debug + //t.join().unwrap(); + } + + pub fn update(&mut self) { + // Receive all loaded chunks + let rx = &self.chunk_channel.1; + for (pos, chunk) in rx.try_iter() { + self.chunks.insert(pos, chunk); + } + } + + pub fn all_chunks(&mut self) -> Vec<(&Vector3, &mut WorldChunk)> { + self.chunks.iter_mut().collect() + } + // pub fn get_block(&mut self, block_wpos: &Vector3) -> Option<&WorldBlock> { // // Split world space to chunk and block space // let ch_pos = block_wpos / chunk::SIZE as i32;