diff --git a/Cargo.lock b/Cargo.lock index 360f4ed..a17dfb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2049,6 +2049,7 @@ dependencies = [ "glob", "lzzzz", "noise", + "num_cpus", "rand 0.8.3", "serde", "shaderc", diff --git a/Cargo.toml b/Cargo.toml index 50f69ce..ef10aa7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ lzzzz = "0.8" # Lz4 compression futures = "0.3" bytemuck = { version = "1.5", features = [ "derive" ] } byteorder = "1.4" +num_cpus = "1.13" # Deprecated #image = "0.23.14" diff --git a/src/game/mod.rs b/src/game/mod.rs index 3416255..ee1360d 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -41,14 +41,8 @@ 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}; - instance.world.load_chunk_async(pos); + instance.world.load_chunk(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); @@ -103,11 +97,7 @@ 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_async(pos); - // if self.world.load_chunk(pos) { - // self.world.get_chunk(&pos).unwrap().dirty = true; - // } + self.world.load_chunk(pos); } } self.prev_chunk_pos = Some(chunk_pos); diff --git a/src/game/world/loader.rs b/src/game/world/loader.rs new file mode 100644 index 0000000..79ac29f --- /dev/null +++ b/src/game/world/loader.rs @@ -0,0 +1,120 @@ +use std::cmp::{min, max}; +use cgmath::Vector3; +use std::fs; +use std::io::{Read, Write}; +use std::thread; +use std::sync::{mpsc, mpsc::Receiver, mpsc::Sender}; +use std::sync::{Arc, Mutex}; +use super::{chunk, chunk::WorldChunk}; +use crate::game::world::WorldGen; + +type ChunkPos = Vector3; +type ChunkPosPair = (ChunkPos, WorldChunk); +type ArcRecv = Arc>>; + +pub struct ChunkLoader { + receiver: Receiver, + queue: Sender +} + +impl ChunkLoader { + + pub fn new(world_name: String) -> Self { + + // Create channels + let (result_tx, result_rx) = mpsc::channel(); + let (input_tx, input_rx) = mpsc::channel(); + let input_rx = Arc::new(Mutex::new(input_rx)); + + // Figure out number of workers + let mut n = num_cpus::get_physical(); + n = min(max(n / 2, 2), 4); + + // Spawn n workers + for _ in 0..n { + let q = Arc::clone(&input_rx); + let tx = result_tx.clone(); + let name = world_name.clone(); + thread::spawn(move || { ChunkLoader::worker(q, tx, name) }); + } + println!("Spawned {} chunk loader workers", n); + + // Build struct + Self { + receiver: result_rx, + queue: input_tx + } + } + + /* + * Receive loaded chunks + */ + pub fn receive(&mut self) -> Vec { + let mut result = Vec::new(); + while let Ok((pos, chunk)) = self.receiver.try_recv() { + result.push((pos, chunk)); + } + result + } + + /* + * Schedule loading chunks + */ + pub fn load(&mut self, pos: ChunkPos) { + self.queue.send(pos); + } + + /* + * Take position from queue and load chunk + */ + fn worker(queue: ArcRecv, tx: Sender, name: String) { + loop { + // Wait for job + let pos; + { + // Get lock + let rx = queue.lock(); + if rx.is_err() { break; } + // Receive next pos + let ps = rx.unwrap().recv(); + if ps.is_err() { break; } + pos = ps.unwrap(); + // Release lock now (end of scope) + } + + // Try loading chunk from file + let filename = format!("chunk_{}_{}_{}.dat", pos.x, pos.y, pos.z); + let filepath = dirs::data_local_dir().unwrap().join("Voxelgame").join("saves").join(&name).join(filename); + if filepath.is_file() { + 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); + + // send + if let Err(_) = tx.send((pos, chunk)) { + break; + } + continue; + } + + // Try generating chunk + 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 + 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 + tx.send((pos, chunk)).unwrap(); + } + + println!("ChunkLoader: worker finished"); + } +} \ No newline at end of file diff --git a/src/game/world/mod.rs b/src/game/world/mod.rs index de0ce71..041a4c7 100644 --- a/src/game/world/mod.rs +++ b/src/game/world/mod.rs @@ -1,5 +1,6 @@ mod generator; mod data; +mod loader; pub mod chunk; pub mod block; use std::thread; @@ -15,10 +16,11 @@ use generator::WorldGen; use chunk::WorldChunk; use block::WorldBlock; use data::WorldData; +use loader::ChunkLoader; pub struct World { data: WorldData, - chunk_channel: (Sender<(Vector3, WorldChunk)>, Receiver<(Vector3, WorldChunk)>), + loader: ChunkLoader, chunks: HashMap, WorldChunk>, } @@ -26,8 +28,8 @@ impl World { pub fn init(name: String) -> Self { let mut instance = Self { - data: WorldData { name, ..Default::default() }, - chunk_channel: mpsc::channel(), + data: WorldData { name: name.clone(), ..Default::default() }, + loader: ChunkLoader::new(name.clone()), chunks: HashMap::new(), }; instance.load_metadata(); @@ -35,10 +37,26 @@ impl World { instance } + /* + * Receives loaded chunks from ChunkLoader + */ + pub fn update(&mut self) { + for (pos, chunk) in self.loader.receive() { + self.chunks.insert(pos, chunk); + } + } + + /* + * Sends chunk position to the ChunkLoader + */ + pub fn load_chunk(&mut self, pos: Vector3) { + self.loader.load(pos); + } + /* * Loads metadata */ - fn load_metadata(&mut self) -> bool { + fn load_metadata(&mut self, ) -> bool { let filepath = dirs::data_local_dir().unwrap().join("Voxelgame").join(self.data.name.clone()).join("world.dat"); if let Ok(mut file) = fs::File::open(filepath) { let mut buf = Vec::new(); @@ -52,7 +70,7 @@ impl World { } /* - * Saves metadata and all loaded chunks + * Saves metadata and all loaded chunks to files */ pub fn save_all(&self) { // Get directory @@ -73,136 +91,17 @@ impl World { datafile.write_all(datastr.as_bytes()).expect("Failed to write world metadata"); } + /* + * Returns chunk at given world chunk position + */ pub fn get_chunk(&mut self, chunk_wpos: &Vector3) -> Option<&mut WorldChunk> { self.chunks.get_mut(chunk_wpos) } /* - * 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. + * Returns vec of references to all currently loaded chunks */ - 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); - return false; - } - - // Try loading chunk from file - 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); - - 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); - self.chunks.insert(pos, chunk); - println!("Chunk at {:?} loaded ({} ms)", pos, timer.elapsed().as_millis()); - return true; - } - - // 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"); - - // Add to list - self.chunks.insert(pos, chunk); - - // Stats - let t1 = timer1.elapsed().as_millis(); - let t0 = timer0.elapsed().as_millis() - t1; - println!("Chunk at {:?} generated ({} ms) and saved ({} ms)", pos, t0, t1); - 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; - // let bl_pos = block_wpos % chunk::SIZE as i32; - // // Return block if exists - // if let Some(chunk) = self.chunks.get_mut(&ch_pos) { - // return Some(chunk.get_block(&bl_pos)); - // } - // None - // } } \ No newline at end of file diff --git a/src/renderer/ui.rs b/src/renderer/ui.rs index 2f6a14f..87db3d3 100644 --- a/src/renderer/ui.rs +++ b/src/renderer/ui.rs @@ -5,7 +5,7 @@ use futures::executor::LocalPool; use futures::task::SpawnExt; const WHITE: [f32; 4] = [1.0, 1.0, 1.0, 1.0]; -const BLUE: [f32; 4] = [0.0, 0.2, 1.0, 1.0]; +//const BLUE: [f32; 4] = [0.0, 0.2, 1.0, 1.0]; const GREEN: [f32; 4] = [0.1, 1.0, 0.0, 1.0];