Loading and generating chunks in a separate threads
This commit is contained in:
+23
-10
@@ -41,12 +41,14 @@ impl Game {
|
|||||||
for x in -RENDER_DIST..RENDER_DIST+1 {
|
for x in -RENDER_DIST..RENDER_DIST+1 {
|
||||||
for z in -RENDER_DIST..RENDER_DIST+1 {
|
for z in -RENDER_DIST..RENDER_DIST+1 {
|
||||||
let pos = Vector3{x, y: 0, z};
|
let pos = Vector3{x, y: 0, z};
|
||||||
if instance.world.load_chunk(pos) {
|
instance.world.load_chunk_async(pos);
|
||||||
let chunk = instance.world.get_chunk(&pos).unwrap();
|
count += 1;
|
||||||
chunk.write_to(renderer, pos * world::chunk::SIZE as i32);
|
//if instance.world.load_chunk(pos) {
|
||||||
println!("Writing chunk to {:?}", pos * world::chunk::SIZE as i32);
|
//let chunk = instance.world.get_chunk(&pos).unwrap();
|
||||||
count += 1;
|
//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);
|
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 {
|
for a in -RENDER_DIST..RENDER_DIST+1 {
|
||||||
let pos = center + Vector3{x: mask.z*a, y: 0, z: mask.x*a};
|
let pos = center + Vector3{x: mask.z*a, y: 0, z: mask.x*a};
|
||||||
|
|
||||||
self.world.load_chunk(pos);
|
self.world.load_chunk_async(pos);
|
||||||
let chunk = self.world.get_chunk(&pos).unwrap();
|
// if self.world.load_chunk(pos) {
|
||||||
chunk.write_to(renderer, pos * world::chunk::SIZE as i32);
|
// self.world.get_chunk(&pos).unwrap().dirty = true;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.prev_chunk_pos = Some(chunk_pos);
|
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<()>) {
|
pub fn input(&mut self, event: &Event<()>) {
|
||||||
|
|||||||
+5
-12
@@ -24,14 +24,16 @@ const NUM_WORKERS: usize = 8;
|
|||||||
|
|
||||||
pub struct WorldChunk {
|
pub struct WorldChunk {
|
||||||
nodes: Box<[u32]>,
|
nodes: Box<[u32]>,
|
||||||
blocks: Vec<WorldBlock>
|
blocks: Vec<WorldBlock>,
|
||||||
|
pub dirty: bool // true by default, set to false when uploaded to renderer. should be set to true when unloaded (TODO)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorldChunk {
|
impl WorldChunk {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
nodes: vec![0_u32; SIZE_QB].into_boxed_slice(),
|
nodes: vec![0_u32; SIZE_QB].into_boxed_slice(),
|
||||||
blocks: Vec::new()
|
blocks: Vec::new(),
|
||||||
|
dirty: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +112,6 @@ impl WorldChunk {
|
|||||||
bytes
|
bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
pub fn all_blocks(&self) -> Vec<(Vector3<i32>, &WorldBlock)> {
|
pub fn all_blocks(&self) -> Vec<(Vector3<i32>, &WorldBlock)> {
|
||||||
self.nodes.iter().enumerate()
|
self.nodes.iter().enumerate()
|
||||||
.filter(|(_, n)| **n != 0)
|
.filter(|(_, n)| **n != 0)
|
||||||
@@ -133,7 +134,7 @@ impl WorldChunk {
|
|||||||
// Get block index
|
// Get block index
|
||||||
let index = (block_pos.x + SIZE as i32 * (block_pos.y + SIZE as i32 * block_pos.z)) as usize;
|
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];
|
let mut value = self.nodes[index];
|
||||||
// Allocate new block
|
// Allocate new block if needed
|
||||||
if value == 0 {
|
if value == 0 {
|
||||||
value = (self.blocks.len() + 1) as u32;
|
value = (self.blocks.len() + 1) as u32;
|
||||||
self.nodes[index] = value;
|
self.nodes[index] = value;
|
||||||
@@ -143,14 +144,6 @@ impl WorldChunk {
|
|||||||
&mut self.blocks[(value-1) as usize]
|
&mut self.blocks[(value-1) as usize]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_block(&self, block_pos: &Vector3<i32>) -> &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<i32>) {
|
pub fn write_to(&self, renderer: &mut dyn RendererView, offset: Vector3<i32>) {
|
||||||
for (blpos, block) in self.all_blocks() {
|
for (blpos, block) in self.all_blocks() {
|
||||||
renderer.write(&(offset + blpos), block);
|
renderer.write(&(offset + blpos), block);
|
||||||
|
|||||||
+70
-2
@@ -2,7 +2,8 @@ mod generator;
|
|||||||
mod data;
|
mod data;
|
||||||
pub mod chunk;
|
pub mod chunk;
|
||||||
pub mod block;
|
pub mod block;
|
||||||
use std::sync::Arc;
|
use std::thread;
|
||||||
|
use std::sync::{mpsc, mpsc::Sender, mpsc::Receiver};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
@@ -17,6 +18,7 @@ use data::WorldData;
|
|||||||
|
|
||||||
pub struct World {
|
pub struct World {
|
||||||
data: WorldData,
|
data: WorldData,
|
||||||
|
chunk_channel: (Sender<(Vector3<i32>, WorldChunk)>, Receiver<(Vector3<i32>, WorldChunk)>),
|
||||||
chunks: HashMap<Vector3<i32>, WorldChunk>,
|
chunks: HashMap<Vector3<i32>, WorldChunk>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,6 +27,7 @@ impl World {
|
|||||||
pub fn init(name: String) -> Self {
|
pub fn init(name: String) -> Self {
|
||||||
let mut instance = Self {
|
let mut instance = Self {
|
||||||
data: WorldData { name, ..Default::default() },
|
data: WorldData { name, ..Default::default() },
|
||||||
|
chunk_channel: mpsc::channel(),
|
||||||
chunks: HashMap::new(),
|
chunks: HashMap::new(),
|
||||||
};
|
};
|
||||||
instance.load_metadata();
|
instance.load_metadata();
|
||||||
@@ -78,7 +81,7 @@ impl World {
|
|||||||
* Loads chunk into memory
|
* 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.
|
* 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<i32>) ->bool {
|
pub fn load_chunk(&mut self, pos: Vector3<i32>) -> bool {
|
||||||
// Abort if chunk is already loaded
|
// Abort if chunk is already loaded
|
||||||
if let Some(_) = self.chunks.get(&pos) {
|
if let Some(_) = self.chunks.get(&pos) {
|
||||||
println!("Chunk at {:?} already loaded", pos);
|
println!("Chunk at {:?} already loaded", pos);
|
||||||
@@ -127,6 +130,71 @@ impl World {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn load_chunk_async(&mut self, pos: Vector3<i32>) {
|
||||||
|
// 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::<i32>{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<i32>, &mut WorldChunk)> {
|
||||||
|
self.chunks.iter_mut().collect()
|
||||||
|
}
|
||||||
|
|
||||||
// pub fn get_block(&mut self, block_wpos: &Vector3<i32>) -> Option<&WorldBlock> {
|
// pub fn get_block(&mut self, block_wpos: &Vector3<i32>) -> Option<&WorldBlock> {
|
||||||
// // Split world space to chunk and block space
|
// // Split world space to chunk and block space
|
||||||
// let ch_pos = block_wpos / chunk::SIZE as i32;
|
// let ch_pos = block_wpos / chunk::SIZE as i32;
|
||||||
|
|||||||
Reference in New Issue
Block a user