Skip to content

Commit

Permalink
Do graph node population before character movement
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
patowen authored and Ralith committed Jan 18, 2024
1 parent 37625ea commit 4ced0e9
Showing 1 changed file with 52 additions and 53 deletions.
105 changes: 52 additions & 53 deletions server/src/sim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -280,6 +275,58 @@ impl Sim {

let mut pending_block_updates: Vec<BlockUpdate> = 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
Expand All @@ -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<BlockUpdate> = vec![];

for block_update in pending_block_updates.into_iter() {
Expand All @@ -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");
}
Expand All @@ -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
Expand Down

0 comments on commit 4ced0e9

Please sign in to comment.