Add chunk I/O and unloading

This commit is contained in:
Elias Stepanik 2025-06-17 23:01:50 +02:00
parent 9e2d294d69
commit 01ec312e90
4 changed files with 124 additions and 4 deletions

View File

@ -1,21 +1,24 @@
use crate::plugins::environment::systems::voxels::atlas::VoxelTextureAtlas;
use crate::plugins::environment::systems::voxels::chunk_io::{
save_dirty_chunks_system, unload_far_chunks,
};
use crate::plugins::environment::systems::voxels::debug::{draw_grid, visualize_octree_system};
use crate::plugins::environment::systems::voxels::lod::update_chunk_lods;
use crate::plugins::environment::systems::voxels::meshing_gpu::{
GpuMeshingWorker, queue_gpu_meshing,
};
use bevy_app_compute::prelude::{AppComputePlugin, AppComputeWorkerPlugin};
use crate::plugins::environment::systems::voxels::queue_systems;
use crate::plugins::environment::systems::voxels::queue_systems::{
enqueue_visible_chunks, process_chunk_queue,
};
use crate::plugins::environment::systems::voxels::render_chunks::rebuild_dirty_chunks;
use crate::plugins::environment::systems::voxels::atlas::{VoxelTextureAtlas};
use crate::plugins::environment::systems::voxels::structure::{
ChunkBudget, ChunkCullingCfg, ChunkQueue, MeshBufferPool, PrevCameraChunk, SparseVoxelOctree,
SpawnedChunks,
};
use bevy::app::{App, Plugin, PreStartup, PreUpdate, Startup};
use bevy::prelude::*;
use bevy_app_compute::prelude::{AppComputePlugin, AppComputeWorkerPlugin};
pub struct EnvironmentPlugin;
impl Plugin for EnvironmentPlugin {
@ -39,7 +42,7 @@ impl Plugin for EnvironmentPlugin {
});
app.insert_resource(ChunkBudget { per_frame: 20 });
app.init_resource::<PrevCameraChunk>();
/* app.add_systems(Update, log_mesh_count);*/
/* app.add_systems(Update, log_mesh_count);*/
app
// ------------------------------------------------------------------------
// resources
@ -57,8 +60,10 @@ impl Plugin for EnvironmentPlugin {
enqueue_visible_chunks,
process_chunk_queue.after(enqueue_visible_chunks),
update_chunk_lods.after(process_chunk_queue),
unload_far_chunks.after(update_chunk_lods),
rebuild_dirty_chunks.after(process_chunk_queue), // 4. (re)mesh dirty chunks
queue_gpu_meshing.after(rebuild_dirty_chunks),
save_dirty_chunks_system.after(rebuild_dirty_chunks),
/* ---------- optional debug drawing ------- */
visualize_octree_system
.run_if(should_visualize_octree)

View File

@ -0,0 +1,35 @@
use crate::plugins::environment::systems::voxels::structure::*;
use bevy::prelude::*;
use std::path::Path;
const CHUNK_DIR: &str = "chunks";
/// Save all dirty chunks to disk.
pub fn save_dirty_chunks_system(mut tree_q: Query<&mut SparseVoxelOctree>) {
let Ok(mut tree) = tree_q.get_single_mut() else {
return;
};
let _ = tree.save_dirty_chunks(Path::new(CHUNK_DIR));
}
/// Unload chunks that reached the maximum LOD distance.
pub fn unload_far_chunks(
mut commands: Commands,
mut tree_q: Query<&mut SparseVoxelOctree>,
mut spawned: ResMut<SpawnedChunks>,
chunks: Query<(Entity, &Chunk, &ChunkLod)>,
) {
let Ok(mut tree) = tree_q.get_single_mut() else {
return;
};
for (ent, chunk, lod) in chunks.iter() {
if lod.0 == tree.max_depth - 1 {
if let Err(e) = tree.save_chunk(chunk.key, Path::new(CHUNK_DIR)) {
error!("failed to save chunk {:?}: {e}", chunk.key);
}
tree.unload_chunk(chunk.key);
spawned.0.remove(&chunk.key);
commands.entity(ent).despawn_recursive();
}
}
}

View File

@ -3,11 +3,12 @@ pub mod helper;
pub mod octree;
pub mod structure;
pub mod atlas;
mod chunk;
pub mod chunk_io;
pub mod culling;
pub mod lod;
mod meshing;
pub mod meshing_gpu;
pub mod queue_systems;
pub mod render_chunks;
pub mod atlas;

View File

@ -572,4 +572,83 @@ impl SparseVoxelOctree {
self.occupied_chunks.insert(key);
}
}
/// Collect all voxels contained within a chunk.
pub fn voxels_in_chunk(&self, key: ChunkKey) -> Vec<(Vec3, Voxel)> {
let step = self.get_spacing_at_depth(self.max_depth);
let chunk_size = CHUNK_SIZE as f32 * step;
let center = self.chunk_center_world(key);
let half = Vec3::splat(chunk_size / 2.0);
let min = center - half;
let max = center + half;
Self::collect_voxels_from_node(&self.root, self.size, self.center)
.into_iter()
.filter_map(|(pos, voxel, _)| {
if pos.x >= min.x
&& pos.x < max.x
&& pos.y >= min.y
&& pos.y < max.y
&& pos.z >= min.z
&& pos.z < max.z
{
Some((pos, voxel))
} else {
None
}
})
.collect()
}
/// Save a single chunk to disk inside the specified directory.
pub fn save_chunk<P: AsRef<Path>>(&self, key: ChunkKey, dir: P) -> io::Result<()> {
let voxels = self.voxels_in_chunk(key);
if voxels.is_empty() {
return Ok(());
}
std::fs::create_dir_all(&dir)?;
let path = dir
.as_ref()
.join(format!("chunk_{}_{}_{}.bin", key.0, key.1, key.2));
let data =
bincode::serialize(&voxels).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
std::fs::write(path, data)
}
/// Load a single chunk from disk and insert its voxels.
pub fn load_chunk<P: AsRef<Path>>(&mut self, key: ChunkKey, dir: P) -> io::Result<()> {
let path = dir
.as_ref()
.join(format!("chunk_{}_{}_{}.bin", key.0, key.1, key.2));
if !path.exists() {
return Ok(());
}
let bytes = std::fs::read(path)?;
let voxels: Vec<(Vec3, Voxel)> =
bincode::deserialize(&bytes).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
for (pos, voxel) in voxels {
self.insert(pos, voxel);
}
self.clear_dirty_flags();
Ok(())
}
/// Remove all voxels from the specified chunk.
pub fn unload_chunk(&mut self, key: ChunkKey) {
let voxels = self.voxels_in_chunk(key);
for (pos, _) in voxels {
self.remove(pos);
}
self.clear_dirty_flags();
}
/// Save all currently dirty chunks to disk and clear the dirty set.
pub fn save_dirty_chunks<P: AsRef<Path>>(&mut self, dir: P) -> io::Result<()> {
let keys: Vec<_> = self.dirty_chunks.iter().copied().collect();
for key in keys {
let _ = self.save_chunk(key, &dir);
}
self.clear_dirty_flags();
Ok(())
}
}