From 7daec14ba7f523d09db13c25f0516cf384bbe9f1 Mon Sep 17 00:00:00 2001 From: Piotrek Date: Thu, 20 May 2021 14:05:43 +0200 Subject: [PATCH] More informative chunk states, much cleaner --- src/game/mod.rs | 128 ++++++++++++++++++++------------------- src/game/world/chunk.rs | 15 +++-- src/game/world/loader.rs | 3 +- src/game/world/mod.rs | 64 +++++++------------- src/main.rs | 5 +- src/renderer/mod.rs | 10 +-- 6 files changed, 109 insertions(+), 116 deletions(-) diff --git a/src/game/mod.rs b/src/game/mod.rs index de3b0c3..0cb72f9 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -7,6 +7,7 @@ use cgmath::Vector3; use winit::event::Event; use player::Player; use world::World; +use world::chunk::{WorldChunk, ChunkState}; pub const RENDER_DIST: i32 = 3; @@ -38,7 +39,7 @@ impl Game { // Preload chunks around player for x in -RENDER_DIST..RENDER_DIST { for z in -RENDER_DIST..RENDER_DIST { - instance.world.load_chunk(Vector3{x, y: 0, z}); + instance.world.load(Vector3{x, y: 0, z}); } } @@ -46,33 +47,15 @@ impl Game { instance } - fn load_chunks(&mut self, renderer: &mut impl RendererView, chunk_pos: Vector3) { - // Wait for previous chunks to be loaded before loading new ones - if !renderer.is_busy() { - if self.prev_chunk_pos == None { self.prev_chunk_pos = Some(chunk_pos); } - let prev_chunk_pos = self.prev_chunk_pos.unwrap(); - let mut dir = chunk_pos - prev_chunk_pos; - - // Shift max by 1, in one axis - if dir.x != 0 { dir = Vector3{x: dir.x.clamp(-1, 1), y:0, z:0 } } - else if dir.y != 0 { dir = Vector3{x:0, y:dir.y.clamp(-1, 1), z:0 } } - else if dir.z != 0 { dir = Vector3{x:0, y:0, z:dir.z.clamp(-1, 1) } } - else { return; } - - // Shift world - println!("Shiftin by {:?}", dir); - renderer.shift(dir * world::chunk::SIZE as i32); - - // Load chunks at the edge - let center = chunk_pos.clone() + RENDER_DIST*dir; - let mask = Vector3{x: dir.x.abs(), y: dir.y.abs(), z: dir.z.abs()}; - for a in -RENDER_DIST..RENDER_DIST { - let pos = center + Vector3{x: mask.z*a, y: 0, z: mask.x*a}; - self.world.load_chunk(pos); - } - - self.prev_chunk_pos = Some(prev_chunk_pos+dir); - } + fn debug_info(&mut self, renderer: &mut impl RendererView) { + // Update pointing direction + let yaw = self.player.camera_controller.get_yaw(); + let mut yaw_str = ""; + if yaw >= 315.0 || yaw <= 45.0 { yaw_str = "+z"; } + else if yaw >= 45.0 && yaw < 135.0 { yaw_str = "-x"; } + else if yaw >= 135.0 && yaw < 225.0 { yaw_str = "-z"; } + else if yaw >= 225.0 && yaw < 315.0 { yaw_str = "+x"; } + renderer.get_ui().set_text("World", 0, format!("Direction: {}", yaw_str)); } pub fn update(&mut self, delta: f32, renderer: &mut impl RendererView) { @@ -84,26 +67,13 @@ impl Game { let cam_pos = camera.get_position(); let cam_pos = Vector3{x: cam_pos.x as i32, y: 0/*cam_pos.y as i32*/, z: cam_pos.z as i32}; let chunk_pos = cam_pos / world::chunk::SIZE as i32; + if self.prev_pos == None || self.prev_pos != Some(cam_pos) { + renderer.get_ui().set_text("World", 1, format!("Position: {}, {}, {}", cam_pos.x, cam_pos.y, cam_pos.z)); + self.prev_pos = Some(cam_pos); + } - // Update pointing direction - // let yaw = self.player.camera_controller.get_yaw(); - // let mut yaw_str = ""; - // if yaw >= 315.0 || yaw <= 45.0 { yaw_str = "+z"; } - // else if yaw >= 45.0 && yaw < 135.0 { yaw_str = "-x"; } - // else if yaw >= 135.0 && yaw < 225.0 { yaw_str = "-z"; } - // else if yaw >= 225.0 && yaw < 315.0 { yaw_str = "+x"; } - // renderer.get_ui().set_text("World", 0, format!("Direction: {}", yaw_str)); - - // Block position - // if self.prev_pos == None || self.prev_pos != Some(cam_pos) { - // renderer.get_ui().set_text("World", 1, format!("Position: {}, {}, {}", cam_pos.x, cam_pos.y, cam_pos.z)); - // self.prev_pos = Some(cam_pos); - // } - - // Chunk position - // if self.prev_chunk_pos == None || self.prev_chunk_pos != Some(chunk_pos) { - // renderer.get_ui().set_text("World", 2, format!("Chunk: {}, {}, {}", chunk_pos.x, chunk_pos.y, chunk_pos.z)); - // } + // Debug + self.debug_info(renderer); // Receives chunks from workers self.world.update(); @@ -113,36 +83,70 @@ impl Game { // If chunk position changed if self.prev_chunk_pos == None || self.prev_chunk_pos != Some(chunk_pos) { + renderer.get_ui().set_text("World", 2, format!("Chunk: {}, {}, {}", chunk_pos.x, chunk_pos.y, chunk_pos.z)); // Shift world if self.prev_chunk_pos == None { self.prev_chunk_pos = Some(chunk_pos); } let prev_chunk_pos = self.prev_chunk_pos.unwrap(); let mut dir = chunk_pos - prev_chunk_pos; + if dir.x != 0 { dir = Vector3{x: dir.x.clamp(-1, 1), y:0, z:0 } } else if dir.y != 0 { dir = Vector3{x:0, y:dir.y.clamp(-1, 1), z:0 } } else if dir.z != 0 { dir = Vector3{x:0, y:0, z:dir.z.clamp(-1, 1) } } else { return; } - renderer.shift(dir * world::chunk::SIZE as i32); - // Load chunks in radius - for ox in -RENDER_DIST..RENDER_DIST { - for oz in -RENDER_DIST..RENDER_DIST { - let x = chunk_pos.x+ox; - let z = chunk_pos.z+oz; - self.world.load_chunk(Vector3{x, y: 0, z}); - } - } + renderer.shift(dir * world::chunk::SIZE as i32); // Save previous self.prev_chunk_pos = Some(prev_chunk_pos+dir); } - // Send dirty to gpu - for (pos, chunk) in self.world.all_chunks() { - if chunk.dirty { - chunk.dirty = false; - chunk.loaded = renderer.write_chunk(pos, chunk); - if !chunk.loaded { println!("Failed to load {:?}", pos); } + // Load chunks in radius + //TODO: y direction + for ox in -RENDER_DIST..RENDER_DIST+1 { + for oz in -RENDER_DIST..RENDER_DIST+1 { + // Load chunk + let pos = Vector3{x: chunk_pos.x+ox, y: 0, z: chunk_pos.z+oz}; + self.world.load(pos).and_then(|ch| -> Option<()> { + // Chunk is ready, schedule it for writing + if ch.state == ChunkState::Ready || ch.state == ChunkState::Stale { + ch.state = ChunkState::Waiting; + } + None + }); + } + } + + // Send waiting chunks to gpu + for (pos, chunk) in self.world.all() { + if let Some(chunk) = chunk { + // Skip stale chunks + if chunk.state == ChunkState::Stale { + continue; + } + + // Calculate if chunk is within render distance + let ox = pos.x - chunk_pos.x; + let oy = pos.y - chunk_pos.y; + let oz = pos.z - chunk_pos.z; + let dist = RENDER_DIST; + let within_distance = ox > -dist && ox < dist && oy > -dist && oy < dist && oz > -dist && oz < dist; + + // Check if chunk is within render distance + if within_distance && chunk.state == ChunkState::Waiting { + // Write chunk. If succeded change its state to Visible, otherwise make it Ready again. + if renderer.write_chunk(pos, chunk) { + chunk.state = ChunkState::Visible; + } else { + chunk.state = ChunkState::Ready; + } + } + + // If not within render distance and set as visible + if !within_distance && chunk.state == ChunkState::Visible { + // Set chunk to stale, a.k.a. was visible but is not anymore + chunk.state = ChunkState::Stale; + } } } } diff --git a/src/game/world/chunk.rs b/src/game/world/chunk.rs index fa93fb0..7ea3f87 100644 --- a/src/game/world/chunk.rs +++ b/src/game/world/chunk.rs @@ -19,12 +19,19 @@ const SIZE_QB: usize = SIZE*SIZE*SIZE; // deflate fast 1.23kB 1.22s 5.46s // lz4 1.6kB 0.07s 1.23s +#[derive(PartialEq)] +pub enum ChunkState { + Partial, // Not yet loaded + Ready, // Loaded + Waiting, // Scheduled to be uploaded + Visible, // Visible + Stale, // Scheduled to be unloaded +} pub struct WorldChunk { nodes: Box<[u32]>, blocks: Vec, - pub dirty: bool, // Request write to GPU - pub loaded: bool // Written to GPU + pub state: ChunkState, } impl WorldChunk { @@ -32,8 +39,7 @@ impl WorldChunk { Self { nodes: vec![0_u32; SIZE_QB].into_boxed_slice(), blocks: Vec::new(), - dirty: true, - loaded: false + state: ChunkState::Partial, } } @@ -68,6 +74,7 @@ impl WorldChunk { instance.blocks.push(block); } + instance.state = ChunkState::Ready; Some(instance) } diff --git a/src/game/world/loader.rs b/src/game/world/loader.rs index 19e03ef..989f0f0 100644 --- a/src/game/world/loader.rs +++ b/src/game/world/loader.rs @@ -5,7 +5,7 @@ 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 super::{chunk, chunk::{WorldChunk, ChunkState}}; use crate::game::world::WorldGen; type ChunkPos = Vector3; @@ -112,6 +112,7 @@ impl ChunkLoader { WorldGen::generate(&(pos*chunk::SIZE as i32 + bpos), block); } } + chunk.state = ChunkState::Ready; // 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"); diff --git a/src/game/world/mod.rs b/src/game/world/mod.rs index 507bd37..4cd46bd 100644 --- a/src/game/world/mod.rs +++ b/src/game/world/mod.rs @@ -3,9 +3,6 @@ mod data; mod loader; pub mod chunk; pub mod block; -use std::thread; -use std::sync::{mpsc, mpsc::Sender, mpsc::Receiver}; -use std::time::Instant; use std::path::PathBuf; use std::io::Write; use std::io::Read; @@ -21,7 +18,7 @@ use loader::ChunkLoader; pub struct World { data: WorldData, loader: ChunkLoader, - chunks: HashMap, WorldChunk>, + chunks: HashMap, Option>, } impl World { @@ -37,38 +34,31 @@ impl World { instance } + /* + * Returns vec of references to all currently loaded chunks + */ + pub fn all(&mut self) -> Vec<(&Vector3, &mut Option)> { + self.chunks.iter_mut().collect() + } + /* * Receives loaded chunks from ChunkLoader */ pub fn update(&mut self) { for (pos, chunk) in self.loader.receive() { - self.chunks.insert(pos, chunk); + self.chunks.insert(pos, Some(chunk)); } } /* - * Sends chunk position to the ChunkLoader + * Sends chunk position to the ChunkLoader if chunk is not loaded + * Returns loaded chunk otherwise */ - pub fn load_chunk(&mut self, pos: Vector3) { - if let Some(chunk) = self.chunks.get_mut(&pos) { - if !chunk.loaded { - println!("Chunk {:?} exists but not loaded! Loading now", pos); - chunk.dirty = true; - } - } else { - self.loader.load(pos); - } - } - - /* - * - */ - pub fn unload_chunk(&mut self, pos: Vector3) { - if let Some(chunk) = self.chunks.get_mut(&pos) { - chunk.loaded = false; - chunk.dirty = false; - // TODO: Mark chunk as "to be unloaded" and send it to renderer for freeing resources - } + pub fn load(&mut self, pos: Vector3) -> Option<&mut WorldChunk> { + let mut exists = false; + let result : Option<&mut WorldChunk> = self.chunks.entry(pos).and_modify(|_| { exists = true; }).or_insert(None).as_mut(); + if !exists { self.loader.load(pos); } + result } /* @@ -97,9 +87,11 @@ impl World { // Save all chunks for (pos, chunk) in self.chunks.iter() { - let filepath = dirpath.join(format!("chunk_{}_{}_{}.dat", pos.x, pos.y, pos.z)); - let mut file = fs::File::create(filepath).expect("Failed to create chunk file"); - file.write(&chunk.to_bytes()).expect("Failed to write chunk file"); + if let Some(chunk) = chunk { + let filepath = dirpath.join(format!("chunk_{}_{}_{}.dat", pos.x, pos.y, pos.z)); + let mut file = fs::File::create(filepath).expect("Failed to create chunk file"); + file.write(&chunk.to_bytes()).expect("Failed to write chunk file"); + } } // Save metadata @@ -108,18 +100,4 @@ impl World { let mut datafile = fs::File::create(datapath).expect("Failed to create world metadata file"); 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) - } - - /* - * Returns vec of references to all currently loaded chunks - */ - pub fn all_chunks(&mut self) -> Vec<(&Vector3, &mut WorldChunk)> { - self.chunks.iter_mut().collect() - } } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index e5a0928..eb34f8f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +use std::time::Instant; use cgmath::Vector3; use crate::renderer::renderer_view::RendererView; use winit::{ @@ -89,9 +90,11 @@ fn main() { // Loop Event::MainEventsCleared => { // Update + let timer = Instant::now(); let delta = delta_timer.elapsed().as_secs_f32(); game.update(delta, &mut renderer); - delta_timer = std::time::Instant::now(); + delta_timer = Instant::now(); + renderer.ui.set_text("Performance", 1, format!("Update: {} ms", timer.elapsed().as_millis())); // Redraw if running //if let GameState::Running = game.get_state() { diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 8d5f45c..dfc43e5 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -133,7 +133,7 @@ impl Renderer { // Update content let timer = Instant::now(); self.buffers.content.update(&self.queue); - self.ui.set_text("Performance", 1, format!("Update content: {}ms", timer.elapsed().as_micros() as f64 / 1000.0)); + self.ui.set_text("Performance", 3, format!("Update content: {}ms", timer.elapsed().as_micros() as f64 / 1000.0)); // Update uniform buffer let timer = Instant::now(); @@ -143,12 +143,12 @@ impl Renderer { self.buffers.uniforms.values.update(&self.camera, cam_offset, self.start.elapsed().as_secs_f32()); self.queue.write_buffer(&self.buffers.uniforms.buffer, 0, bytemuck::cast_slice(&[self.buffers.uniforms.values])); - self.ui.set_text("Performance", 2, format!("Update uniform: {}ms", timer.elapsed().as_micros() as f64 / 1000.0)); + self.ui.set_text("Performance", 4, format!("Update uniform: {}ms", timer.elapsed().as_micros() as f64 / 1000.0)); // Get next frame to render to let timer = Instant::now(); let frame = self.swapchain.get_current_frame()?.output; - self.ui.set_text("Performance", 3, format!("Swapchain get frame: {}ms", timer.elapsed().as_micros() as f64 / 1000.0)); + self.ui.set_text("Performance", 5, format!("Swapchain get frame: {}ms", timer.elapsed().as_micros() as f64 / 1000.0)); // Create encoder that will build command buffer for us let timer = Instant::now(); @@ -156,13 +156,13 @@ impl Renderer { self.raytrace_pass.render(&mut encoder, &mut self.buffers, &self.texture);//&frame.view); self.postprocess_pass.render(&mut encoder, &frame.view); self.ui.render(&self.device, &mut encoder, &frame.view, 1280, 720); - self.ui.set_text("Performance", 4, format!("Build renderpass: {}ms", timer.elapsed().as_micros() as f64 / 1000.0)); + self.ui.set_text("Performance", 6, format!("Build renderpass: {}ms", timer.elapsed().as_micros() as f64 / 1000.0)); // Submit encoder (command buffer) let timer = Instant::now(); self.queue.submit(std::iter::once(encoder.finish())); self.ui.recall(); - self.ui.set_text("Performance", 5, format!("Submit queue: {}ms", timer.elapsed().as_micros() as f64 / 1000.0)); + self.ui.set_text("Performance", 7, format!("Submit queue: {}ms", timer.elapsed().as_micros() as f64 / 1000.0)); // Stats let content_stats = self.buffers.content.stats();