From 4ced0e92613e8f320a9a5d38fb19444eec713386 Mon Sep 17 00:00:00 2001 From: Patrick Owen Date: Thu, 11 Jan 2024 23:12:09 -0500 Subject: [PATCH] Do graph node population before character movement This fixes a bug where if a client connects to the server before the server runs its first step, the player is in an unpopulated graph node when the character controller tries to run, causing a panic due to not knowing which way is upwards. --- server/src/sim.rs | 105 +++++++++++++++++++++++----------------------- 1 file changed, 52 insertions(+), 53 deletions(-) diff --git a/server/src/sim.rs b/server/src/sim.rs index d6b8c948..0793ac57 100644 --- a/server/src/sim.rs +++ b/server/src/sim.rs @@ -69,11 +69,6 @@ impl Sim { result .load_all_voxels(save) .expect("save file must be of a valid format"); - ensure_nearby( - &mut result.graph, - &Position::origin(), - f64::from(result.cfg.view_distance), - ); result } @@ -280,6 +275,58 @@ impl Sim { let mut pending_block_updates: Vec = vec![]; + // Extend graph structure + for (_, (position, _)) in self.world.query::<(&mut Position, &mut Character)>().iter() { + 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 fresh_voxel_data = 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.preloaded_voxel_data.remove(&chunk) { + fresh_voxel_data.push((chunk, voxel_data.serialize(self.cfg.chunk_size))); + self.modified_chunks.insert(chunk); + self.graph.populate_chunk(chunk, voxel_data, true) + } + } + } + + // 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. + let chunk_generation_distance = dodeca::BOUNDING_SPHERE_RADIUS + + self.cfg.character.character_radius as f64 + + self.cfg.character.speed_cap as f64 * self.cfg.step_interval.as_secs_f64() + + self.cfg.character.ground_distance_tolerance as f64 + + self.cfg.character.block_reach as f64 + + 0.001; + + // Load all chunks around entities corresponding to clients, which correspond to entities + // with a "Character" component. + for (_, (position, _)) in self.world.query::<(&Position, &Character)>().iter() { + let nodes = nearby_nodes(&self.graph, position, chunk_generation_distance); + for &(node, _) in &nodes { + for vertex in dodeca::Vertex::iter() { + let chunk = ChunkId::new(node, vertex); + if let Chunk::Fresh = self + .graph + .get_chunk(chunk) + .expect("all nodes must be populated before loading their chunks") + { + if let Some(params) = + ChunkParams::new(self.cfg.chunk_size, &self.graph, chunk) + { + self.graph + .populate_chunk(chunk, params.generate_voxels(), false); + } + } + } + } + } + // Simulate for (entity, (position, character, input)) in self .world @@ -303,12 +350,8 @@ impl Sim { self.graph_entities.insert(position.node, entity); } self.dirty_nodes.insert(position.node); - 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() { @@ -327,18 +370,6 @@ impl Sim { spawns.push((id, dump_entity(&self.world, entity))); } - let mut fresh_voxel_data = 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.preloaded_voxel_data.remove(&chunk) { - fresh_voxel_data.push((chunk, voxel_data.serialize(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"); } @@ -361,38 +392,6 @@ impl Sim { voxel_data: fresh_voxel_data, }; - // 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. - let chunk_generation_distance = dodeca::BOUNDING_SPHERE_RADIUS - + self.cfg.character.character_radius as f64 - + self.cfg.character.speed_cap as f64 * self.cfg.step_interval.as_secs_f64() - + self.cfg.character.ground_distance_tolerance as f64 - + self.cfg.character.block_reach as f64 - + 0.001; - - // Load all chunks around entities corresponding to clients, which correspond to entities - // with a "Character" component. - for (_, (position, _)) in self.world.query::<(&Position, &Character)>().iter() { - let nodes = nearby_nodes(&self.graph, position, chunk_generation_distance); - for &(node, _) in &nodes { - for vertex in dodeca::Vertex::iter() { - let chunk = ChunkId::new(node, vertex); - if let Chunk::Fresh = self - .graph - .get_chunk(chunk) - .expect("all nodes must be populated before loading their chunks") - { - if let Some(params) = - ChunkParams::new(self.cfg.chunk_size, &self.graph, chunk) - { - self.graph - .populate_chunk(chunk, params.generate_voxels(), false); - } - } - } - } - } - // TODO: Omit unchanged (e.g. freshly spawned) entities (dirty flag?) let delta = StateDelta { latest_input: 0, // To be filled in by the caller