ChunkLoader
This commit is contained in:
Generated
+1
@@ -2049,6 +2049,7 @@ dependencies = [
|
|||||||
"glob",
|
"glob",
|
||||||
"lzzzz",
|
"lzzzz",
|
||||||
"noise",
|
"noise",
|
||||||
|
"num_cpus",
|
||||||
"rand 0.8.3",
|
"rand 0.8.3",
|
||||||
"serde",
|
"serde",
|
||||||
"shaderc",
|
"shaderc",
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ lzzzz = "0.8" # Lz4 compression
|
|||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
bytemuck = { version = "1.5", features = [ "derive" ] }
|
bytemuck = { version = "1.5", features = [ "derive" ] }
|
||||||
byteorder = "1.4"
|
byteorder = "1.4"
|
||||||
|
num_cpus = "1.13"
|
||||||
|
|
||||||
# Deprecated
|
# Deprecated
|
||||||
#image = "0.23.14"
|
#image = "0.23.14"
|
||||||
|
|||||||
+2
-12
@@ -41,14 +41,8 @@ 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};
|
||||||
instance.world.load_chunk_async(pos);
|
instance.world.load_chunk(pos);
|
||||||
count += 1;
|
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);
|
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 {
|
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);
|
|
||||||
// if self.world.load_chunk(pos) {
|
|
||||||
// self.world.get_chunk(&pos).unwrap().dirty = true;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.prev_chunk_pos = Some(chunk_pos);
|
self.prev_chunk_pos = Some(chunk_pos);
|
||||||
|
|||||||
@@ -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<i32>;
|
||||||
|
type ChunkPosPair = (ChunkPos, WorldChunk);
|
||||||
|
type ArcRecv<T> = Arc<Mutex<Receiver<T>>>;
|
||||||
|
|
||||||
|
pub struct ChunkLoader {
|
||||||
|
receiver: Receiver<ChunkPosPair>,
|
||||||
|
queue: Sender<ChunkPos>
|
||||||
|
}
|
||||||
|
|
||||||
|
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<ChunkPosPair> {
|
||||||
|
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<ChunkPos>, tx: Sender<ChunkPosPair>, 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::<i32>{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");
|
||||||
|
}
|
||||||
|
}
|
||||||
+27
-128
@@ -1,5 +1,6 @@
|
|||||||
mod generator;
|
mod generator;
|
||||||
mod data;
|
mod data;
|
||||||
|
mod loader;
|
||||||
pub mod chunk;
|
pub mod chunk;
|
||||||
pub mod block;
|
pub mod block;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
@@ -15,10 +16,11 @@ use generator::WorldGen;
|
|||||||
use chunk::WorldChunk;
|
use chunk::WorldChunk;
|
||||||
use block::WorldBlock;
|
use block::WorldBlock;
|
||||||
use data::WorldData;
|
use data::WorldData;
|
||||||
|
use loader::ChunkLoader;
|
||||||
|
|
||||||
pub struct World {
|
pub struct World {
|
||||||
data: WorldData,
|
data: WorldData,
|
||||||
chunk_channel: (Sender<(Vector3<i32>, WorldChunk)>, Receiver<(Vector3<i32>, WorldChunk)>),
|
loader: ChunkLoader,
|
||||||
chunks: HashMap<Vector3<i32>, WorldChunk>,
|
chunks: HashMap<Vector3<i32>, WorldChunk>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,8 +28,8 @@ 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: name.clone(), ..Default::default() },
|
||||||
chunk_channel: mpsc::channel(),
|
loader: ChunkLoader::new(name.clone()),
|
||||||
chunks: HashMap::new(),
|
chunks: HashMap::new(),
|
||||||
};
|
};
|
||||||
instance.load_metadata();
|
instance.load_metadata();
|
||||||
@@ -35,10 +37,26 @@ impl World {
|
|||||||
instance
|
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<i32>) {
|
||||||
|
self.loader.load(pos);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Loads metadata
|
* 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");
|
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) {
|
if let Ok(mut file) = fs::File::open(filepath) {
|
||||||
let mut buf = Vec::new();
|
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) {
|
pub fn save_all(&self) {
|
||||||
// Get directory
|
// Get directory
|
||||||
@@ -73,136 +91,17 @@ impl World {
|
|||||||
datafile.write_all(datastr.as_bytes()).expect("Failed to write world metadata");
|
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<i32>) -> Option<&mut WorldChunk> {
|
pub fn get_chunk(&mut self, chunk_wpos: &Vector3<i32>) -> Option<&mut WorldChunk> {
|
||||||
self.chunks.get_mut(chunk_wpos)
|
self.chunks.get_mut(chunk_wpos)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Loads chunk into memory
|
* Returns vec of references to all currently loaded chunks
|
||||||
* 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 {
|
|
||||||
// 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::<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");
|
|
||||||
|
|
||||||
// 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<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)> {
|
pub fn all_chunks(&mut self) -> Vec<(&Vector3<i32>, &mut WorldChunk)> {
|
||||||
self.chunks.iter_mut().collect()
|
self.chunks.iter_mut().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn get_block(&mut self, block_wpos: &Vector3<i32>) -> 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
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
+1
-1
@@ -5,7 +5,7 @@ use futures::executor::LocalPool;
|
|||||||
use futures::task::SpawnExt;
|
use futures::task::SpawnExt;
|
||||||
|
|
||||||
const WHITE: [f32; 4] = [1.0, 1.0, 1.0, 1.0];
|
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];
|
const GREEN: [f32; 4] = [0.1, 1.0, 0.0, 1.0];
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user