Move chunk loading/saving logic to World, sort chunk loading based on distance to camera
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Sijmen 2021-06-03 04:06:59 +02:00
parent a8936d9046
commit 0ca35638fc
Signed by: vijfhoek
GPG key ID: 82D05C89B28B0DAE
5 changed files with 213 additions and 137 deletions

60
Cargo.lock generated
View file

@ -518,6 +518,16 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 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]] [[package]]
name = "futures" name = "futures"
version = "0.3.15" version = "0.3.15"
@ -799,6 +809,12 @@ dependencies = [
"weezl", "weezl",
] ]
[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]] [[package]]
name = "glow" name = "glow"
version = "0.9.0" version = "0.9.0"
@ -970,6 +986,15 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "itertools"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.10.0" version = "0.10.0"
@ -1124,16 +1149,16 @@ dependencies = [
"futures", "futures",
"gltf", "gltf",
"image", "image",
"itertools", "itertools 0.10.0",
"log", "log",
"noise", "noise",
"rayon", "rayon",
"rmp-serde", "rmp-serde",
"serde", "serde",
"serde_repr", "serde_repr",
"sled",
"wgpu", "wgpu",
"winit", "winit",
"zstd",
] ]
[[package]] [[package]]
@ -1694,6 +1719,23 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" 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]] [[package]]
name = "slotmap" name = "slotmap"
version = "0.4.0" version = "0.4.0"
@ -2065,18 +2107,18 @@ dependencies = [
[[package]] [[package]]
name = "zstd" 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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c83508bcbbdc9c3abcf77e8e56773d3ffcd2479e0933caab2e7d6b5a9e183aae" checksum = "69996ebdb1ba8b1517f61387a883857818a66c8a295f487b1ffd8fd9d2c82910"
dependencies = [ dependencies = [
"zstd-safe", "zstd-safe",
] ]
[[package]] [[package]]
name = "zstd-safe" 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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d30375f78e185ca4c91930f42ea2c0162f9aa29737032501f93b79266d985ae7" checksum = "98aa931fb69ecee256d44589d19754e61851ae4769bf963b385119b1cc37a49e"
dependencies = [ dependencies = [
"libc", "libc",
"zstd-sys", "zstd-sys",
@ -2084,10 +2126,12 @@ dependencies = [
[[package]] [[package]]
name = "zstd-sys" 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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2141bed8922b427761470e6bbfeff255da94fa20b0bbeab0d9297fcaf71e3aa7" checksum = "a1e6e8778706838f43f771d80d37787cb2fe06dafe89dd3aebaf6721b9eaec81"
dependencies = [ dependencies = [
"cc", "cc",
"glob",
"itertools 0.9.0",
"libc", "libc",
] ]

View file

@ -23,7 +23,7 @@ serde_repr = "0.1.7"
rmp-serde = "0.15.4" rmp-serde = "0.15.4"
itertools = "0.10.0" itertools = "0.10.0"
serde = { version = "1.0.126", features = ["derive"] } serde = { version = "1.0.126", features = ["derive"] }
zstd = "0.8.2" sled = { version = "0.34.6", features = ["compression"] }
[profile.release] [profile.release]
debug = true debug = true

View file

@ -1,8 +1,4 @@
use std::{ use std::{collections::VecDeque, usize};
collections::VecDeque,
io::{Read, Write},
usize,
};
use crate::{geometry::Geometry, quad::Quad, vertex::BlockVertex}; use crate::{geometry::Geometry, quad::Quad, vertex::BlockVertex};
use ahash::{AHashMap, AHashSet}; use ahash::{AHashMap, AHashSet};
@ -10,11 +6,10 @@ use cgmath::{Point3, Vector3};
use noise::utils::{NoiseMapBuilder, PlaneMapBuilder}; use noise::utils::{NoiseMapBuilder, PlaneMapBuilder};
use rayon::iter::{IntoParallelIterator, ParallelIterator}; use rayon::iter::{IntoParallelIterator, ParallelIterator};
use serde::Serialize;
use serde::{ use serde::{
de::{SeqAccess, Visitor}, de::{SeqAccess, Visitor},
ser::{SerializeSeq, Serializer}, ser::{SerializeSeq, Serializer},
Deserialize, Deserialize, Serialize,
}; };
use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_repr::{Deserialize_repr, Serialize_repr};
@ -131,7 +126,7 @@ impl<'de> Deserialize<'de> for Chunk {
} }
impl 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(); let fbm = noise::Fbm::new();
const TERRAIN_NOISE_SCALE: f64 = 0.1 / 16.0 * CHUNK_SIZE as f64; const TERRAIN_NOISE_SCALE: f64 = 0.1 / 16.0 * CHUNK_SIZE as f64;
@ -162,42 +157,41 @@ impl Chunk {
) )
.build(); .build();
let mut blocks: ChunkBlocks = [[[Default::default(); CHUNK_SIZE]; CHUNK_SIZE]; CHUNK_SIZE];
for z in 0..CHUNK_SIZE { for z in 0..CHUNK_SIZE {
for x in 0..CHUNK_SIZE { for x in 0..CHUNK_SIZE {
let v = terrain_noise.get_value(x, z) * 20.0 + 128.0; 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 = 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 { 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, 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 { 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, block_type: BlockType::Dirt,
}); });
} }
if dirt_max >= 0 && dirt_max < CHUNK_SIZE as i32 { if dirt_max >= 0 && dirt_max < CHUNK_ISIZE {
blocks[dirt_max as usize][z][x] = Some(Block { self.blocks[dirt_max as usize][z][x] = Some(Block {
block_type: BlockType::Grass, block_type: BlockType::Grass,
}); });
} }
if chunk_y == 0 { if chunk_y == 0 {
blocks[0][z][x] = Some(Block { self.blocks[0][z][x] = Some(Block {
block_type: BlockType::Bedrock, block_type: BlockType::Bedrock,
}); });
} }
if chunk_y < 128 / CHUNK_SIZE as i32 { if chunk_y < 128 / CHUNK_ISIZE {
for layer in blocks.iter_mut() { for layer in self.blocks.iter_mut() {
if layer[z][x].is_none() { if layer[z][x].is_none() {
layer[z][x] = Some(Block { layer[z][x] = Some(Block {
block_type: BlockType::Water, block_type: BlockType::Water,
@ -207,8 +201,6 @@ impl Chunk {
} }
} }
} }
Self { blocks }
} }
#[rustfmt::skip] #[rustfmt::skip]
@ -400,26 +392,22 @@ impl Chunk {
Self::quads_to_geometry(quads) Self::quads_to_geometry(quads)
} }
pub fn save(&self, position: Point3<isize>) -> anyhow::Result<()> { pub fn save(&self, position: Point3<isize>, store: &sled::Db) -> anyhow::Result<()> {
let data = rmp_serde::encode::to_vec_named(self)?; let data = rmp_serde::encode::to_vec_named(self)?;
let compressed = zstd::block::compress(&data, 0)?; let key = format!("{}_{}_{}", position.x, position.y, position.z);
store.insert(key, data)?;
let path = format!("chunks/{}_{}_{}.bin", position.x, position.y, position.z);
let mut file = std::fs::File::create(&path)?;
file.write(&compressed)?;
Ok(()) Ok(())
} }
pub fn load(&mut self, position: Point3<isize>) -> anyhow::Result<()> { pub fn load(&mut self, position: Point3<isize>, store: &sled::Db) -> anyhow::Result<bool> {
let path = format!("chunks/{}_{}_{}.bin", position.x, position.y, position.z); let key = format!("{}_{}_{}", position.x, position.y, position.z);
let mut file = std::fs::File::open(&path)?;
let mut compressed = Vec::new(); if let Some(data) = store.get(key)? {
file.read_to_end(&mut compressed)?; *self = rmp_serde::decode::from_slice(&data)?;
let data = zstd::block::decompress(&compressed, 1024 * 1024)?; Ok(false)
} else {
*self = rmp_serde::decode::from_slice(&data)?; self.generate(position.x, position.y, position.z);
Ok(()) Ok(true)
}
} }
} }

View file

@ -1,9 +1,5 @@
use std::{ use std::time::{Duration, Instant};
collections::VecDeque,
time::{Duration, Instant},
};
use ahash::AHashMap;
use cgmath::{EuclideanSpace, InnerSpace, Point3, Rad, Vector2, Vector3}; use cgmath::{EuclideanSpace, InnerSpace, Point3, Rad, Vector2, Vector3};
use wgpu::{ use wgpu::{
util::{BufferInitDescriptor, DeviceExt}, util::{BufferInitDescriptor, DeviceExt},
@ -38,15 +34,11 @@ pub struct WorldState {
pub time_bind_group: wgpu::BindGroup, pub time_bind_group: wgpu::BindGroup,
pub world: World, pub world: World,
pub chunk_buffers: AHashMap<Point3<isize>, GeometryBuffers>,
pub chunk_save_queue: VecDeque<Point3<isize>>,
pub chunk_load_queue: VecDeque<Point3<isize>>,
time: Time, time: Time,
time_buffer: wgpu::Buffer, time_buffer: wgpu::Buffer,
wireframe: bool, wireframe: bool,
shader: wgpu::ShaderModule, shader: wgpu::ShaderModule,
render_pipeline_layout: wgpu::PipelineLayout, render_pipeline_layout: wgpu::PipelineLayout,
pub highlighted: Option<(Point3<isize>, Vector3<i32>)>,
pub forward_pressed: bool, pub forward_pressed: bool,
pub backward_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) { pub fn update_world_geometry(&mut self, render_context: &RenderContext) {
let instant = Instant::now(); let instant = Instant::now();
let world_geometry = self.world.to_geometry(self.highlighted); let world_geometry = self.world.to_geometry(self.world.highlighted);
self.chunk_buffers.clear(); self.world.chunk_buffers.clear();
for (chunk_position, chunk_geometry) in world_geometry { for (chunk_position, chunk_geometry) in world_geometry {
let buffers = GeometryBuffers::from_geometry( let buffers = GeometryBuffers::from_geometry(
render_context, render_context,
&chunk_geometry, &chunk_geometry,
BufferUsage::empty(), BufferUsage::empty(),
); );
self.chunk_buffers.insert(chunk_position, buffers); self.world.chunk_buffers.insert(chunk_position, buffers);
} }
let elapsed = instant.elapsed(); let elapsed = instant.elapsed();
@ -258,23 +251,6 @@ impl WorldState {
)); ));
} }
pub fn update_chunk_geometry(
&mut self,
render_context: &RenderContext,
chunk_position: Point3<isize>,
) {
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) { pub fn toggle_wireframe(&mut self, render_context: &RenderContext) {
self.wireframe = !self.wireframe; self.wireframe = !self.wireframe;
self.render_pipeline = Self::create_render_pipeline( self.render_pipeline = Self::create_render_pipeline(
@ -286,8 +262,6 @@ impl WorldState {
} }
pub fn new(render_context: &RenderContext) -> WorldState { pub fn new(render_context: &RenderContext) -> WorldState {
let world = World::generate();
let texture_manager = Self::create_textures(render_context); let texture_manager = Self::create_textures(render_context);
let (camera, projection) = Self::create_camera(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 (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( let shader = render_context.device.create_shader_module(
&(wgpu::ShaderModuleDescriptor { &(wgpu::ShaderModuleDescriptor {
label: Some("shader"), label: Some("shader"),
@ -317,10 +293,8 @@ impl WorldState {
&time_layout, &time_layout,
], ],
}); });
let render_pipeline = let render_pipeline =
Self::create_render_pipeline(&render_context, &shader, &render_pipeline_layout, false); Self::create_render_pipeline(&render_context, &shader, &render_pipeline_layout, false);
let depth_texture = Texture::create_depth_texture(render_context, "depth_texture"); let depth_texture = Texture::create_depth_texture(render_context, "depth_texture");
let mut world_state = Self { let mut world_state = Self {
@ -340,11 +314,8 @@ impl WorldState {
time_bind_group, time_bind_group,
world, world,
chunk_buffers: AHashMap::new(),
chunk_load_queue: VecDeque::new(),
chunk_save_queue: VecDeque::new(),
wireframe: false, wireframe: false,
highlighted: None,
up_speed: 0.0, up_speed: 0.0,
sprinting: false, sprinting: false,
@ -352,7 +323,7 @@ impl WorldState {
backward_pressed: false, backward_pressed: false,
left_pressed: false, left_pressed: false,
right_pressed: false, right_pressed: false,
creative: false, creative: true,
}; };
world_state.update_world_geometry(render_context); world_state.update_world_geometry(render_context);
@ -399,7 +370,8 @@ impl WorldState {
let camera_pos = self.camera.position.to_vec(); let camera_pos = self.camera.position.to_vec();
let camera_pos = Vector2::new(camera_pos.x, camera_pos.z); 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 = (position * CHUNK_ISIZE).cast().unwrap();
let pos = Vector2::new(pos.x, pos.z); let pos = Vector2::new(pos.x, pos.z);
if (pos - camera_pos).magnitude() > 300.0 { if (pos - camera_pos).magnitude() > 300.0 {
@ -419,6 +391,7 @@ impl WorldState {
render_pass render_pass
.set_index_buffer(index_buffer.unwrap().slice(..), wgpu::IndexFormat::Uint32); .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); 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 triangle_count
@ -436,31 +409,33 @@ impl WorldState {
} }
} }
/// TODO Move to World
fn update_aim(&mut self, render_context: &RenderContext) { fn update_aim(&mut self, render_context: &RenderContext) {
let camera = &self.camera; let camera = &self.camera;
let old = self.highlighted; let old = self.world.highlighted;
let new = self.world.raycast(camera.position, camera.direction()); let new = self.world.raycast(camera.position, camera.direction());
let old_chunk = old.map(|(pos, _)| pos.map(|n| n.div_euclid(CHUNK_ISIZE))); 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))); let new_chunk = new.map(|(pos, _)| pos.map(|n| n.div_euclid(CHUNK_ISIZE)));
if old != new { if old != new {
self.highlighted = new; self.world.highlighted = new;
if let Some(old_chunk_) = old_chunk { 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 { if let Some(new_chunk_) = new_chunk {
// Don't update the same chunk twice // Don't update the same chunk twice
if old_chunk != new_chunk { 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) { pub fn input_mouse_button(&mut self, button: &MouseButton, render_context: &RenderContext) {
let camera = &self.camera; let camera = &self.camera;
@ -468,7 +443,8 @@ impl WorldState {
if let Some((pos, face_normal)) = world.raycast(camera.position, camera.direction()) { if let Some((pos, face_normal)) = world.raycast(camera.position, camera.direction()) {
if button == &MouseButton::Left { if button == &MouseButton::Left {
world.set_block(pos.x as isize, pos.y as isize, pos.z as isize, None); 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 { } else if button == &MouseButton::Right {
let new_pos = pos.cast().unwrap() + face_normal; 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::A => self.left_pressed = pressed,
VirtualKeyCode::D => self.right_pressed = pressed, VirtualKeyCode::D => self.right_pressed = pressed,
VirtualKeyCode::F2 if pressed => self.creative = !self.creative, 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 => { VirtualKeyCode::Space => {
self.up_speed = if pressed { self.up_speed = if pressed {
if self.creative { if self.creative {
@ -565,26 +540,11 @@ impl WorldState {
} }
pub fn update(&mut self, dt: Duration, render_context: &RenderContext) { 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_position(dt);
self.update_aim(render_context); self.update_aim(render_context);
self.world.update(render_context, &self.camera);
self.view self.view
.update_view_projection(&self.camera, &self.projection); .update_view_projection(&self.camera, &self.projection);
render_context render_context

View file

@ -1,45 +1,124 @@
use std::collections::HashMap; use std::collections::{HashMap, VecDeque};
use crate::{ use crate::{
chunk::{Block, Chunk, CHUNK_ISIZE, CHUNK_SIZE}, camera::Camera,
geometry::Geometry, chunk::{Block, Chunk, CHUNK_ISIZE},
geometry::{Geometry, GeometryBuffers},
npc::Npc, npc::Npc,
render_context::RenderContext,
vertex::BlockVertex, vertex::BlockVertex,
}; };
use ahash::AHashMap;
use cgmath::{EuclideanSpace, InnerSpace, Point3, Vector3}; use cgmath::{EuclideanSpace, InnerSpace, Point3, Vector3};
use rayon::prelude::*; use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use wgpu::BufferUsage;
pub struct World { pub struct World {
pub chunks: HashMap<Point3<isize>, Chunk>, pub chunks: HashMap<Point3<isize>, Chunk>,
pub npc: Npc, pub npc: Npc,
pub chunk_database: sled::Db,
pub chunk_save_queue: VecDeque<Point3<isize>>,
pub chunk_load_queue: VecDeque<Point3<isize>>,
pub chunk_generate_queue: VecDeque<Point3<isize>>,
pub chunk_buffers: AHashMap<Point3<isize>, GeometryBuffers>,
pub highlighted: Option<(Point3<isize>, Vector3<i32>)>,
} }
const WORLD_SIZE: Vector3<usize> = Vector3::new( pub const RENDER_DISTANCE: isize = 8;
8 * 16 / CHUNK_SIZE, pub const WORLD_HEIGHT: isize = 16 * 16 / CHUNK_ISIZE;
16 * 16 / CHUNK_SIZE,
8 * 16 / CHUNK_SIZE,
);
impl World { impl World {
pub fn generate() -> Self { pub fn new() -> Self {
let chunks = HashMap::new();
let npc = Npc::load(); let npc = Npc::load();
let half: Vector3<isize> = WORLD_SIZE.cast().unwrap() / 2;
let coords: Vec<_> = let chunk_database = sled::Config::new()
itertools::iproduct!(-half.x..half.x, 0..WORLD_SIZE.y as isize, -half.z..half.z) .path("chunks")
.collect(); .mode(sled::Mode::HighThroughput)
.use_compression(true)
.open()
.unwrap();
let chunks: HashMap<_, _> = coords Self {
.par_iter() chunks,
.map(|&(x, y, z)| { npc,
(
Point3::new(x, y, z),
Chunk::generate(x as i32, y as i32, z as i32),
)
})
.collect();
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<isize> = camera.position.cast().unwrap();
let camera_chunk: Point3<isize> = 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<isize> = 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<isize>,
) {
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( 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<Block>) { pub fn set_block(&mut self, x: isize, y: isize, z: isize, block: Option<Block>) {
if let Some(chunk) = self.chunks.get_mut(&Point3::new( let chunk_position = Point3::new(
x.div_euclid(CHUNK_ISIZE), x.div_euclid(CHUNK_ISIZE),
y.div_euclid(CHUNK_ISIZE), y.div_euclid(CHUNK_ISIZE),
z.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 bx = x.rem_euclid(CHUNK_ISIZE) as usize;
let by = y.rem_euclid(CHUNK_ISIZE) as usize; let by = y.rem_euclid(CHUNK_ISIZE) as usize;
let bz = z.rem_euclid(CHUNK_ISIZE) as usize; let bz = z.rem_euclid(CHUNK_ISIZE) as usize;
chunk.blocks[by][bz][bx] = block; chunk.blocks[by][bz][bx] = block;
} }
self.chunk_save_queue
.push_back(chunk_position / CHUNK_ISIZE);
} }
fn calc_scale(vector: Vector3<f32>, scalar: f32) -> f32 { fn calc_scale(vector: Vector3<f32>, scalar: f32) -> f32 {