From add052cd3f3522f58c8864288c21688fdfb7ebef Mon Sep 17 00:00:00 2001 From: Patrick Owen Date: Sun, 17 Dec 2023 01:18:32 -0500 Subject: [PATCH] Save and load all modified chunks --- common/src/graph.rs | 5 +++ save/src/lib.rs | 10 +++++ server/src/lib.rs | 2 +- server/src/sim.rs | 92 +++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 100 insertions(+), 9 deletions(-) diff --git a/common/src/graph.rs b/common/src/graph.rs index 5a0c764d..ab2922ae 100644 --- a/common/src/graph.rs +++ b/common/src/graph.rs @@ -226,6 +226,11 @@ impl Graph { node.0 } + #[inline] + pub fn from_hash(&self, hash: u128) -> NodeId { + NodeId(hash) + } + /// Ensure all shorter neighbors of a not-yet-created child node exist and return them, excluding the given parent node fn populate_shorter_neighbors_of_child( &mut self, diff --git a/save/src/lib.rs b/save/src/lib.rs index 3e69625c..86197434 100644 --- a/save/src/lib.rs +++ b/save/src/lib.rs @@ -139,6 +139,16 @@ impl Reader<'_> { .map_err(GetError::DecompressionFailed)?; Ok(Some(Character::decode(&*self.accum)?)) } + + /// Temporary function to load all voxel-related save data at once. + /// TODO: Replace this implementation with a streaming implementation + /// that does not require loading everything at once + pub fn get_all_voxel_node_ids(&mut self) -> Result, GetError> { + self.voxel_nodes + .iter()? + .map(|n| Ok(n.map_err(GetError::from)?.0.value())) + .collect() + } } fn decompress( diff --git a/server/src/lib.rs b/server/src/lib.rs index eff613f2..9ab4be43 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -56,7 +56,7 @@ impl Server { fn new(params: SimConfig, save: Save) -> Self { let cfg = Arc::new(params); Self { - sim: Sim::new(cfg.clone()), + sim: Sim::new(cfg.clone(), &save), cfg, clients: DenseSlotMap::default(), save, diff --git a/server/src/sim.rs b/server/src/sim.rs index 53a2dec9..bc308206 100644 --- a/server/src/sim.rs +++ b/server/src/sim.rs @@ -1,6 +1,9 @@ use std::sync::Arc; -use common::proto::BlockUpdate; +use anyhow::Context; +use common::dodeca::Vertex; +use common::node::VoxelData; +use common::proto::{BlockUpdate, SerializableVoxelData}; use common::{node::ChunkId, GraphEntities}; use fxhash::{FxHashMap, FxHashSet}; use hecs::Entity; @@ -31,29 +34,36 @@ pub struct Sim { entity_ids: FxHashMap, world: hecs::World, graph: Graph, + pending_modified_chunks: FxHashMap, spawns: Vec, despawns: Vec, graph_entities: GraphEntities, dirty_nodes: FxHashSet, + dirty_voxel_nodes: FxHashSet, modified_chunks: FxHashSet, } impl Sim { - pub fn new(cfg: Arc) -> Self { + pub fn new(cfg: Arc, save: &save::Save) -> Self { let mut result = Self { rng: SmallRng::from_entropy(), step: 0, entity_ids: FxHashMap::default(), world: hecs::World::new(), graph: Graph::new(cfg.chunk_size), + pending_modified_chunks: FxHashMap::default(), spawns: Vec::new(), despawns: Vec::new(), graph_entities: GraphEntities::new(), dirty_nodes: FxHashSet::default(), + dirty_voxel_nodes: FxHashSet::default(), modified_chunks: FxHashSet::default(), cfg, }; + result + .load_all_voxels(save) + .expect("save file must be of a valid format"); ensure_nearby( &mut result.graph, &Position::origin(), @@ -85,16 +95,43 @@ impl Sim { } let dirty_nodes = self.dirty_nodes.drain().collect::>(); + let dirty_voxel_nodes = self.dirty_voxel_nodes.drain().collect::>(); for node in dirty_nodes { let entities = self.snapshot_node(node); writer.put_entity_node(self.graph.hash_of(node), &entities)?; } + for node in dirty_voxel_nodes { + let voxels = self.snapshot_voxel_node(node); + writer.put_voxel_node(self.graph.hash_of(node), &voxels)?; + } drop(writer); tx.commit()?; Ok(()) } + fn load_all_voxels(&mut self, save: &save::Save) -> anyhow::Result<()> { + let read_guard = save.read()?; + let mut read = read_guard.get()?; + for node_hash in read.get_all_voxel_node_ids()? { + let Some(voxel_node) = read.get_voxel_node(node_hash)? else { + continue; + }; + for chunk in voxel_node.chunks.iter() { + let voxels: SerializableVoxelData = postcard::from_bytes(&chunk.voxels)?; + let vertex = Vertex::iter() + .nth(chunk.vertex as usize) + .context("deserializing vertex ID")?; + self.pending_modified_chunks.insert( + ChunkId::new(self.graph.from_hash(node_hash), vertex), + VoxelData::from_serializable(&voxels, self.cfg.chunk_size) + .context("deserializing voxel data")?, + ); + } + } + Ok(()) + } + fn snapshot_node(&self, node: NodeId) -> save::EntityNode { let mut ids = Vec::new(); let mut character_transforms = Vec::new(); @@ -127,6 +164,30 @@ impl Sim { } } + fn snapshot_voxel_node(&self, node: NodeId) -> save::VoxelNode { + let mut chunks = vec![]; + let node_data = self.graph.get(node).as_ref().unwrap(); + for vertex in Vertex::iter() { + if !self.modified_chunks.contains(&ChunkId::new(node, vertex)) { + continue; + } + let mut serialized_voxels = Vec::new(); + let Chunk::Populated { ref voxels, .. } = node_data.chunks[vertex] else { + panic!("Unknown chunk listed as modified"); + }; + postcard_helpers::serialize( + &voxels.to_serializable(self.cfg.chunk_size), + &mut serialized_voxels, + ) + .unwrap(); + chunks.push(save::Chunk { + vertex: vertex as u32, + voxels: serialized_voxels, + }) + } + save::VoxelNode { chunks } + } + pub fn spawn_character(&mut self, hello: ClientHello) -> (EntityId, Entity) { let id = self.new_id(); info!(%id, name = %hello.name, "spawning character"); @@ -241,6 +302,9 @@ impl Sim { ensure_nearby(&mut self.graph, position, f64::from(self.cfg.view_distance)); } + let fresh_nodes = self.graph.fresh().to_vec(); + populate_fresh_nodes(&mut self.graph); + let mut accepted_block_updates: Vec = vec![]; for block_update in pending_block_updates.into_iter() { @@ -248,6 +312,7 @@ impl Sim { tracing::warn!("Block update received from ungenerated chunk"); } self.modified_chunks.insert(block_update.chunk_id); + self.dirty_voxel_nodes.insert(block_update.chunk_id.node); accepted_block_updates.push(block_update); } @@ -257,16 +322,28 @@ impl Sim { let id = *self.world.get::<&EntityId>(entity).unwrap(); spawns.push((id, dump_entity(&self.world, entity))); } - if !self.graph.fresh().is_empty() { + + let mut modified_chunks = vec![]; + for fresh_node in fresh_nodes.iter().copied() { + for vertex in Vertex::iter() { + let chunk = ChunkId::new(fresh_node, vertex); + if let Some(voxel_data) = self.pending_modified_chunks.remove(&chunk) { + modified_chunks.push((chunk, voxel_data.to_serializable(self.cfg.chunk_size))); + self.modified_chunks.insert(chunk); + self.graph.populate_chunk(chunk, voxel_data, true) + } + } + } + + if !fresh_nodes.is_empty() { trace!(count = self.graph.fresh().len(), "broadcasting fresh nodes"); } + let spawns = Spawns { step: self.step, spawns, despawns: std::mem::take(&mut self.despawns), - nodes: self - .graph - .fresh() + nodes: fresh_nodes .iter() .filter_map(|&id| { let side = self.graph.parent(id)?; @@ -277,9 +354,8 @@ impl Sim { }) .collect(), block_updates: accepted_block_updates, - modified_chunks: vec![], + modified_chunks, }; - populate_fresh_nodes(&mut self.graph); // We want to load all chunks that a player can interact with in a single step, so chunk_generation_distance // is set up to cover that distance.