From 0ca35638fcd71ad3be606ed6d7c81ec8ee41aac2 Mon Sep 17 00:00:00 2001 From: Vijfhoek Date: Thu, 3 Jun 2021 04:06:59 +0200 Subject: [PATCH] Move chunk loading/saving logic to World, sort chunk loading based on distance to camera --- Cargo.lock | 60 ++++++++++++++--- Cargo.toml | 2 +- src/chunk.rs | 64 ++++++++---------- src/state/world_state.rs | 88 +++++++------------------ src/world.rs | 136 +++++++++++++++++++++++++++++++-------- 5 files changed, 213 insertions(+), 137 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 734004d..8a635fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -518,6 +518,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "futures" version = "0.3.15" @@ -799,6 +809,12 @@ dependencies = [ "weezl", ] +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + [[package]] name = "glow" version = "0.9.0" @@ -970,6 +986,15 @@ dependencies = [ "web-sys", ] +[[package]] +name = "itertools" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.10.0" @@ -1124,16 +1149,16 @@ dependencies = [ "futures", "gltf", "image", - "itertools", + "itertools 0.10.0", "log", "noise", "rayon", "rmp-serde", "serde", "serde_repr", + "sled", "wgpu", "winit", - "zstd", ] [[package]] @@ -1694,6 +1719,23 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" +[[package]] +name = "sled" +version = "0.34.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d0132f3e393bcb7390c60bb45769498cf4550bcb7a21d7f95c02b69f6362cdc" +dependencies = [ + "crc32fast", + "crossbeam-epoch", + "crossbeam-utils", + "fs2", + "fxhash", + "libc", + "log", + "parking_lot", + "zstd", +] + [[package]] name = "slotmap" version = "0.4.0" @@ -2065,18 +2107,18 @@ dependencies = [ [[package]] name = "zstd" -version = "0.8.2+zstd.1.5.0" +version = "0.5.4+zstd.1.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c83508bcbbdc9c3abcf77e8e56773d3ffcd2479e0933caab2e7d6b5a9e183aae" +checksum = "69996ebdb1ba8b1517f61387a883857818a66c8a295f487b1ffd8fd9d2c82910" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "4.1.0+zstd.1.5.0" +version = "2.0.6+zstd.1.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d30375f78e185ca4c91930f42ea2c0162f9aa29737032501f93b79266d985ae7" +checksum = "98aa931fb69ecee256d44589d19754e61851ae4769bf963b385119b1cc37a49e" dependencies = [ "libc", "zstd-sys", @@ -2084,10 +2126,12 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "1.6.0+zstd.1.5.0" +version = "1.4.18+zstd.1.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2141bed8922b427761470e6bbfeff255da94fa20b0bbeab0d9297fcaf71e3aa7" +checksum = "a1e6e8778706838f43f771d80d37787cb2fe06dafe89dd3aebaf6721b9eaec81" dependencies = [ "cc", + "glob", + "itertools 0.9.0", "libc", ] diff --git a/Cargo.toml b/Cargo.toml index 6c47552..3d07f11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ serde_repr = "0.1.7" rmp-serde = "0.15.4" itertools = "0.10.0" serde = { version = "1.0.126", features = ["derive"] } -zstd = "0.8.2" +sled = { version = "0.34.6", features = ["compression"] } [profile.release] debug = true diff --git a/src/chunk.rs b/src/chunk.rs index 8c2832b..9e65c42 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -1,8 +1,4 @@ -use std::{ - collections::VecDeque, - io::{Read, Write}, - usize, -}; +use std::{collections::VecDeque, usize}; use crate::{geometry::Geometry, quad::Quad, vertex::BlockVertex}; use ahash::{AHashMap, AHashSet}; @@ -10,11 +6,10 @@ use cgmath::{Point3, Vector3}; use noise::utils::{NoiseMapBuilder, PlaneMapBuilder}; use rayon::iter::{IntoParallelIterator, ParallelIterator}; -use serde::Serialize; use serde::{ de::{SeqAccess, Visitor}, ser::{SerializeSeq, Serializer}, - Deserialize, + Deserialize, Serialize, }; use serde_repr::{Deserialize_repr, Serialize_repr}; @@ -131,7 +126,7 @@ impl<'de> Deserialize<'de> for Chunk { } impl Chunk { - pub fn generate(chunk_x: i32, chunk_y: i32, chunk_z: i32) -> Self { + pub fn generate(&mut self, chunk_x: isize, chunk_y: isize, chunk_z: isize) { let fbm = noise::Fbm::new(); const TERRAIN_NOISE_SCALE: f64 = 0.1 / 16.0 * CHUNK_SIZE as f64; @@ -162,42 +157,41 @@ impl Chunk { ) .build(); - let mut blocks: ChunkBlocks = [[[Default::default(); CHUNK_SIZE]; CHUNK_SIZE]; CHUNK_SIZE]; for z in 0..CHUNK_SIZE { for x in 0..CHUNK_SIZE { let v = terrain_noise.get_value(x, z) * 20.0 + 128.0; - let v = v.round() as i32; + let v = v.round() as isize; let s = stone_noise.get_value(x, z) * 20.0 + 4.5; - let s = (s.round() as i32).min(10).max(3); + let s = (s.round() as isize).min(10).max(3); - let stone_max = (v - s - chunk_y * CHUNK_SIZE as i32).min(CHUNK_SIZE as i32); + let stone_max = (v - s - chunk_y * CHUNK_ISIZE).min(CHUNK_ISIZE); for y in 0..stone_max { - blocks[y as usize][z][x] = Some(Block { + self.blocks[y as usize][z][x] = Some(Block { block_type: BlockType::Stone, }); } - let dirt_max = (v - chunk_y * CHUNK_SIZE as i32).min(CHUNK_SIZE as i32); + let dirt_max = (v - chunk_y * CHUNK_ISIZE).min(CHUNK_ISIZE); for y in stone_max.max(0)..dirt_max { - blocks[y as usize][z][x] = Some(Block { + self.blocks[y as usize][z][x] = Some(Block { block_type: BlockType::Dirt, }); } - if dirt_max >= 0 && dirt_max < CHUNK_SIZE as i32 { - blocks[dirt_max as usize][z][x] = Some(Block { + if dirt_max >= 0 && dirt_max < CHUNK_ISIZE { + self.blocks[dirt_max as usize][z][x] = Some(Block { block_type: BlockType::Grass, }); } if chunk_y == 0 { - blocks[0][z][x] = Some(Block { + self.blocks[0][z][x] = Some(Block { block_type: BlockType::Bedrock, }); } - if chunk_y < 128 / CHUNK_SIZE as i32 { - for layer in blocks.iter_mut() { + if chunk_y < 128 / CHUNK_ISIZE { + for layer in self.blocks.iter_mut() { if layer[z][x].is_none() { layer[z][x] = Some(Block { block_type: BlockType::Water, @@ -207,8 +201,6 @@ impl Chunk { } } } - - Self { blocks } } #[rustfmt::skip] @@ -400,26 +392,22 @@ impl Chunk { Self::quads_to_geometry(quads) } - pub fn save(&self, position: Point3) -> anyhow::Result<()> { + pub fn save(&self, position: Point3, store: &sled::Db) -> anyhow::Result<()> { let data = rmp_serde::encode::to_vec_named(self)?; - let compressed = zstd::block::compress(&data, 0)?; - - let path = format!("chunks/{}_{}_{}.bin", position.x, position.y, position.z); - let mut file = std::fs::File::create(&path)?; - file.write(&compressed)?; - + let key = format!("{}_{}_{}", position.x, position.y, position.z); + store.insert(key, data)?; Ok(()) } - pub fn load(&mut self, position: Point3) -> anyhow::Result<()> { - let path = format!("chunks/{}_{}_{}.bin", position.x, position.y, position.z); - let mut file = std::fs::File::open(&path)?; + pub fn load(&mut self, position: Point3, store: &sled::Db) -> anyhow::Result { + let key = format!("{}_{}_{}", position.x, position.y, position.z); - let mut compressed = Vec::new(); - file.read_to_end(&mut compressed)?; - let data = zstd::block::decompress(&compressed, 1024 * 1024)?; - - *self = rmp_serde::decode::from_slice(&data)?; - Ok(()) + if let Some(data) = store.get(key)? { + *self = rmp_serde::decode::from_slice(&data)?; + Ok(false) + } else { + self.generate(position.x, position.y, position.z); + Ok(true) + } } } diff --git a/src/state/world_state.rs b/src/state/world_state.rs index ecc2050..9975002 100644 --- a/src/state/world_state.rs +++ b/src/state/world_state.rs @@ -1,9 +1,5 @@ -use std::{ - collections::VecDeque, - time::{Duration, Instant}, -}; +use std::time::{Duration, Instant}; -use ahash::AHashMap; use cgmath::{EuclideanSpace, InnerSpace, Point3, Rad, Vector2, Vector3}; use wgpu::{ util::{BufferInitDescriptor, DeviceExt}, @@ -38,15 +34,11 @@ pub struct WorldState { pub time_bind_group: wgpu::BindGroup, pub world: World, - pub chunk_buffers: AHashMap, GeometryBuffers>, - pub chunk_save_queue: VecDeque>, - pub chunk_load_queue: VecDeque>, time: Time, time_buffer: wgpu::Buffer, wireframe: bool, shader: wgpu::ShaderModule, render_pipeline_layout: wgpu::PipelineLayout, - pub highlighted: Option<(Point3, Vector3)>, pub forward_pressed: bool, pub backward_pressed: bool, @@ -222,18 +214,19 @@ impl WorldState { }) } + /// TODO Move to World pub fn update_world_geometry(&mut self, render_context: &RenderContext) { let instant = Instant::now(); - let world_geometry = self.world.to_geometry(self.highlighted); - self.chunk_buffers.clear(); + let world_geometry = self.world.to_geometry(self.world.highlighted); + self.world.chunk_buffers.clear(); for (chunk_position, chunk_geometry) in world_geometry { let buffers = GeometryBuffers::from_geometry( render_context, &chunk_geometry, BufferUsage::empty(), ); - self.chunk_buffers.insert(chunk_position, buffers); + self.world.chunk_buffers.insert(chunk_position, buffers); } let elapsed = instant.elapsed(); @@ -258,23 +251,6 @@ impl WorldState { )); } - pub fn update_chunk_geometry( - &mut self, - render_context: &RenderContext, - chunk_position: Point3, - ) { - let chunk = &mut self.world.chunks.get(&chunk_position).unwrap(); - let offset = chunk_position * CHUNK_ISIZE; - let geometry = chunk.to_geometry( - offset, - World::highlighted_for_chunk(self.highlighted, &chunk_position).as_ref(), - ); - - let buffers = - GeometryBuffers::from_geometry(render_context, &geometry, BufferUsage::empty()); - self.chunk_buffers.insert(chunk_position, buffers); - } - pub fn toggle_wireframe(&mut self, render_context: &RenderContext) { self.wireframe = !self.wireframe; self.render_pipeline = Self::create_render_pipeline( @@ -286,8 +262,6 @@ impl WorldState { } pub fn new(render_context: &RenderContext) -> WorldState { - let world = World::generate(); - let texture_manager = Self::create_textures(render_context); let (camera, projection) = Self::create_camera(render_context); @@ -297,6 +271,8 @@ impl WorldState { let (time, time_buffer, time_layout, time_bind_group) = Self::create_time(render_context); + let world = World::new(); + let shader = render_context.device.create_shader_module( &(wgpu::ShaderModuleDescriptor { label: Some("shader"), @@ -317,10 +293,8 @@ impl WorldState { &time_layout, ], }); - let render_pipeline = Self::create_render_pipeline(&render_context, &shader, &render_pipeline_layout, false); - let depth_texture = Texture::create_depth_texture(render_context, "depth_texture"); let mut world_state = Self { @@ -340,11 +314,8 @@ impl WorldState { time_bind_group, world, - chunk_buffers: AHashMap::new(), - chunk_load_queue: VecDeque::new(), - chunk_save_queue: VecDeque::new(), + wireframe: false, - highlighted: None, up_speed: 0.0, sprinting: false, @@ -352,7 +323,7 @@ impl WorldState { backward_pressed: false, left_pressed: false, right_pressed: false, - creative: false, + creative: true, }; world_state.update_world_geometry(render_context); @@ -399,7 +370,8 @@ impl WorldState { let camera_pos = self.camera.position.to_vec(); let camera_pos = Vector2::new(camera_pos.x, camera_pos.z); - for (position, buffers) in &self.chunk_buffers { + // TODO Move to World + for (position, buffers) in &self.world.chunk_buffers { let pos = (position * CHUNK_ISIZE).cast().unwrap(); let pos = Vector2::new(pos.x, pos.z); if (pos - camera_pos).magnitude() > 300.0 { @@ -419,6 +391,7 @@ impl WorldState { render_pass .set_index_buffer(index_buffer.unwrap().slice(..), wgpu::IndexFormat::Uint32); render_pass.draw_indexed(0..self.world.npc.indices.len() as u32, 0, 0..1); + triangle_count += self.world.npc.indices.len() / 3; } triangle_count @@ -436,31 +409,33 @@ impl WorldState { } } + /// TODO Move to World fn update_aim(&mut self, render_context: &RenderContext) { let camera = &self.camera; - let old = self.highlighted; + let old = self.world.highlighted; let new = self.world.raycast(camera.position, camera.direction()); let old_chunk = old.map(|(pos, _)| pos.map(|n| n.div_euclid(CHUNK_ISIZE))); let new_chunk = new.map(|(pos, _)| pos.map(|n| n.div_euclid(CHUNK_ISIZE))); if old != new { - self.highlighted = new; + self.world.highlighted = new; if let Some(old_chunk_) = old_chunk { - self.update_chunk_geometry(render_context, old_chunk_); + self.world.update_chunk_geometry(render_context, old_chunk_); } if let Some(new_chunk_) = new_chunk { // Don't update the same chunk twice if old_chunk != new_chunk { - self.update_chunk_geometry(render_context, new_chunk_); + self.world.update_chunk_geometry(render_context, new_chunk_); } } } } + /// TODO Move to World pub fn input_mouse_button(&mut self, button: &MouseButton, render_context: &RenderContext) { let camera = &self.camera; @@ -468,7 +443,8 @@ impl WorldState { if let Some((pos, face_normal)) = world.raycast(camera.position, camera.direction()) { if button == &MouseButton::Left { world.set_block(pos.x as isize, pos.y as isize, pos.z as isize, None); - self.update_chunk_geometry(render_context, pos / CHUNK_ISIZE); + self.world + .update_chunk_geometry(render_context, pos / CHUNK_ISIZE); } else if button == &MouseButton::Right { let new_pos = pos.cast().unwrap() + face_normal; @@ -481,7 +457,8 @@ impl WorldState { }), ); - self.update_chunk_geometry(render_context, pos / CHUNK_ISIZE); + self.world + .update_chunk_geometry(render_context, pos / CHUNK_ISIZE); } } } @@ -494,8 +471,6 @@ impl WorldState { VirtualKeyCode::A => self.left_pressed = pressed, VirtualKeyCode::D => self.right_pressed = pressed, VirtualKeyCode::F2 if pressed => self.creative = !self.creative, - VirtualKeyCode::F3 if pressed => self.chunk_save_queue.extend(self.world.chunks.keys()), - VirtualKeyCode::F4 if pressed => self.chunk_load_queue.extend(self.world.chunks.keys()), VirtualKeyCode::Space => { self.up_speed = if pressed { if self.creative { @@ -565,26 +540,11 @@ impl WorldState { } pub fn update(&mut self, dt: Duration, render_context: &RenderContext) { - if let Some(position) = self.chunk_load_queue.pop_front() { - let chunk = self.world.chunks.entry(position).or_default(); - if let Err(err) = chunk.load(position) { - eprintln!("Failed to load chunk {:?}: {:?}", position, err); - } else { - self.update_chunk_geometry(render_context, position); - println!("Loaded chunk {:?}", position); - } - } else if let Some(position) = self.chunk_save_queue.pop_front() { - let chunk = self.world.chunks.get(&position).unwrap(); - if let Err(err) = chunk.save(position) { - eprintln!("Failed to save chunk {:?}: {:?}", position, err); - } else { - println!("Saved chunk {:?}", position); - } - } - self.update_position(dt); self.update_aim(render_context); + self.world.update(render_context, &self.camera); + self.view .update_view_projection(&self.camera, &self.projection); render_context diff --git a/src/world.rs b/src/world.rs index 608c52d..cdbe5dc 100644 --- a/src/world.rs +++ b/src/world.rs @@ -1,45 +1,124 @@ -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; use crate::{ - chunk::{Block, Chunk, CHUNK_ISIZE, CHUNK_SIZE}, - geometry::Geometry, + camera::Camera, + chunk::{Block, Chunk, CHUNK_ISIZE}, + geometry::{Geometry, GeometryBuffers}, npc::Npc, + render_context::RenderContext, vertex::BlockVertex, }; +use ahash::AHashMap; use cgmath::{EuclideanSpace, InnerSpace, Point3, Vector3}; -use rayon::prelude::*; +use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; +use wgpu::BufferUsage; pub struct World { pub chunks: HashMap, Chunk>, pub npc: Npc, + + pub chunk_database: sled::Db, + pub chunk_save_queue: VecDeque>, + pub chunk_load_queue: VecDeque>, + pub chunk_generate_queue: VecDeque>, + pub chunk_buffers: AHashMap, GeometryBuffers>, + + pub highlighted: Option<(Point3, Vector3)>, } -const WORLD_SIZE: Vector3 = Vector3::new( - 8 * 16 / CHUNK_SIZE, - 16 * 16 / CHUNK_SIZE, - 8 * 16 / CHUNK_SIZE, -); +pub const RENDER_DISTANCE: isize = 8; +pub const WORLD_HEIGHT: isize = 16 * 16 / CHUNK_ISIZE; impl World { - pub fn generate() -> Self { + pub fn new() -> Self { + let chunks = HashMap::new(); let npc = Npc::load(); - let half: Vector3 = WORLD_SIZE.cast().unwrap() / 2; - let coords: Vec<_> = - itertools::iproduct!(-half.x..half.x, 0..WORLD_SIZE.y as isize, -half.z..half.z) - .collect(); + let chunk_database = sled::Config::new() + .path("chunks") + .mode(sled::Mode::HighThroughput) + .use_compression(true) + .open() + .unwrap(); - let chunks: HashMap<_, _> = coords - .par_iter() - .map(|&(x, y, z)| { - ( - Point3::new(x, y, z), - Chunk::generate(x as i32, y as i32, z as i32), - ) - }) - .collect(); + Self { + chunks, + npc, - Self { chunks, npc } + chunk_database, + chunk_load_queue: VecDeque::new(), + chunk_save_queue: VecDeque::new(), + chunk_generate_queue: VecDeque::new(), + chunk_buffers: AHashMap::new(), + + highlighted: None, + } + } + + pub fn update(&mut self, render_context: &RenderContext, camera: &Camera) { + if let Some(position) = self.chunk_load_queue.pop_front() { + let chunk = self.chunks.entry(position).or_default(); + match chunk.load(position, &self.chunk_database) { + Err(error) => { + eprintln!("Failed to load/generate chunk {:?}: {:?}", position, error) + } + Ok(true) => { + self.update_chunk_geometry(render_context, position); + self.chunk_save_queue.push_back(position); + // println!("Generated chunk {:?}", position); + } + Ok(false) => { + self.update_chunk_geometry(render_context, position); + // println!("Loaded chunk {:?}", position); + } + } + } else if let Some(position) = self.chunk_save_queue.pop_front() { + let chunk = self.chunks.get(&position).unwrap(); + if let Err(err) = chunk.save(position, &self.chunk_database) { + eprintln!("Failed to save chunk {:?}: {:?}", position, err); + } else { + // println!("Saved chunk {:?}", position); + } + } + + // Load new chunks, if necessary + let camera_pos: Point3 = camera.position.cast().unwrap(); + let camera_chunk: Point3 = camera_pos.map(|n| n.div_euclid(CHUNK_ISIZE)); + let mut load_queue = Vec::new(); + for (x, y, z) in itertools::iproduct!( + -RENDER_DISTANCE..RENDER_DISTANCE, + 0..WORLD_HEIGHT, + -RENDER_DISTANCE..RENDER_DISTANCE + ) { + let point: Point3 = Point3::new(x + camera_chunk.x, y, z + camera_chunk.z); + if !self.chunks.contains_key(&point) && !self.chunk_load_queue.contains(&point) { + load_queue.push(point); + } + } + + // TODO Sort based on where camera is looking + load_queue.sort_unstable_by_key(|f| { + (f.x * CHUNK_ISIZE - camera_pos.x).abs() + (f.y * CHUNK_ISIZE - camera_pos.y).abs() + }); + + self.chunk_load_queue.extend(load_queue); + } + + pub fn update_chunk_geometry( + &mut self, + render_context: &RenderContext, + chunk_position: Point3, + ) { + let chunk = &mut self.chunks.get(&chunk_position).unwrap(); + let offset = chunk_position * CHUNK_ISIZE; + let geometry = chunk.to_geometry( + offset, + World::highlighted_for_chunk(self.highlighted, &chunk_position).as_ref(), + ); + + let buffers = + GeometryBuffers::from_geometry(render_context, &geometry, BufferUsage::empty()); + self.chunk_buffers.insert(chunk_position, buffers); } pub fn highlighted_for_chunk( @@ -105,16 +184,21 @@ impl World { } pub fn set_block(&mut self, x: isize, y: isize, z: isize, block: Option) { - if let Some(chunk) = self.chunks.get_mut(&Point3::new( + let chunk_position = Point3::new( x.div_euclid(CHUNK_ISIZE), y.div_euclid(CHUNK_ISIZE), z.div_euclid(CHUNK_ISIZE), - )) { + ); + + if let Some(chunk) = self.chunks.get_mut(&chunk_position) { let bx = x.rem_euclid(CHUNK_ISIZE) as usize; let by = y.rem_euclid(CHUNK_ISIZE) as usize; let bz = z.rem_euclid(CHUNK_ISIZE) as usize; chunk.blocks[by][bz][bx] = block; } + + self.chunk_save_queue + .push_back(chunk_position / CHUNK_ISIZE); } fn calc_scale(vector: Vector3, scalar: f32) -> f32 {