From d19388250beb5a85ff657d50093e284db129ab9e Mon Sep 17 00:00:00 2001 From: Patrick Owen Date: Mon, 16 Oct 2023 23:03:42 -0400 Subject: [PATCH] Add initial server/client communication for block updates --- client/src/prediction.rs | 1 + client/src/sim.rs | 33 ++++++++++++++++----------------- common/src/proto.rs | 2 ++ server/src/lib.rs | 1 + server/src/sim.rs | 17 +++++++++++++++++ 5 files changed, 37 insertions(+), 17 deletions(-) diff --git a/client/src/prediction.rs b/client/src/prediction.rs index 6f7819c8..1098d784 100644 --- a/client/src/prediction.rs +++ b/client/src/prediction.rs @@ -119,6 +119,7 @@ mod tests { movement: na::Vector3::x(), jump: false, no_clip: true, + block_update: None, }; let mut pred = PredictedMotion::new(pos()); diff --git a/client/src/sim.rs b/client/src/sim.rs index f1ad7798..69f56d9b 100644 --- a/client/src/sim.rs +++ b/client/src/sim.rs @@ -161,7 +161,6 @@ impl Sim { // Send fresh input self.send_input(net); - self.handle_local_character_block_update(); self.place_block_pressed = false; self.break_block_pressed = false; @@ -300,6 +299,12 @@ impl Sim { self.graph.insert_child(node.parent, node.side); } populate_fresh_nodes(&mut self.graph); + for block_update in msg.block_updates.into_iter() { + if !self.graph.update_block(&block_update) { + // TODO: This case should be handled to properly support multiple players. + tracing::error!("Voxel data received from server for ungenerated chunk.") + } + } } fn spawn( @@ -346,6 +351,7 @@ impl Sim { movement: sanitize_motion_input(orientation * self.average_movement_input), jump: self.is_jumping, no_clip: self.no_clip, + block_update: self.get_local_character_block_update(), }; let generation = self .prediction @@ -378,6 +384,7 @@ impl Sim { / (self.since_input_sent.as_secs_f32() / self.cfg.step_interval.as_secs_f32()), jump: self.is_jumping, no_clip: self.no_clip, + block_update: None, }; character_controller::run_character_step( &self.cfg, @@ -421,13 +428,13 @@ impl Sim { } /// Provides the logic for the player to be able to place and break blocks at will - fn handle_local_character_block_update(&mut self) { + fn get_local_character_block_update(&self) -> Option { let placing = if self.place_block_pressed { true } else if self.break_block_pressed { false } else { - return; + return None; }; let view_position = self.view(); @@ -440,23 +447,18 @@ impl Sim { let Ok(ray_casting_result) = ray_casing_result else { tracing::warn!("Tried to run a raycast beyond generated terrain."); - return; + return None; }; - let Some(hit) = ray_casting_result else { - return; - }; + let hit = ray_casting_result?; let block_pos = if placing { - let Some(block_pos) = self.graph.get_block_neighbor( + self.graph.get_block_neighbor( hit.chunk, hit.voxel_coords, hit.face_axis, hit.face_direction, - ) else { - return; - }; - block_pos + )? } else { (hit.chunk, hit.voxel_coords) }; @@ -467,13 +469,10 @@ impl Sim { Material::Void }; - // Apply the block update, skipping if the chunk is unpopulated. - if !self.graph.update_block(&BlockUpdate { + Some(BlockUpdate { chunk_id: block_pos.0, coords: block_pos.1, new_material: material, - }) { - tracing::error!("Tried to update block in unpopulated chunk"); - } + }) } } diff --git a/common/src/proto.rs b/common/src/proto.rs index d74a9740..85a397e4 100644 --- a/common/src/proto.rs +++ b/common/src/proto.rs @@ -56,6 +56,7 @@ pub struct Spawns { pub spawns: Vec<(EntityId, Vec)>, pub despawns: Vec, pub nodes: Vec, + pub block_updates: Vec, } #[derive(Debug, Serialize, Deserialize)] @@ -71,6 +72,7 @@ pub struct CharacterInput { pub movement: na::Vector3, pub jump: bool, pub no_clip: bool, + pub block_update: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/server/src/lib.rs b/server/src/lib.rs index dc58e12a..39f296bb 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -124,6 +124,7 @@ impl Server { let r2 = if !spawns.spawns.is_empty() || !spawns.despawns.is_empty() || !spawns.nodes.is_empty() + || !spawns.block_updates.is_empty() { handles.ordered.try_send(spawns.clone()) } else { diff --git a/server/src/sim.rs b/server/src/sim.rs index 0067064a..35e5dd22 100644 --- a/server/src/sim.rs +++ b/server/src/sim.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use common::proto::BlockUpdate; use common::{node::ChunkId, GraphEntities}; use fxhash::{FxHashMap, FxHashSet}; use hecs::Entity; @@ -143,6 +144,7 @@ impl Sim { movement: na::Vector3::zeros(), jump: false, no_clip: true, + block_update: None, }; let entity = self.world.spawn((id, position, character, initial_input)); self.graph_entities.insert(position.node, entity); @@ -185,6 +187,7 @@ impl Sim { .tree() .map(|(side, parent)| FreshNode { side, parent }) .collect(), + block_updates: Vec::new(), }; for (entity, &id) in &mut self.world.query::<&EntityId>() { spawns.spawns.push((id, dump_entity(&self.world, entity))); @@ -196,6 +199,8 @@ impl Sim { let span = error_span!("step", step = self.step); let _guard = span.enter(); + let mut pending_block_updates: Vec = vec![]; + // Simulate for (entity, (position, character, input)) in self .world @@ -212,6 +217,7 @@ impl Sim { input, self.cfg.step_interval.as_secs_f32(), ); + pending_block_updates.extend(input.block_update.iter().cloned()); if prev_node != position.node { self.dirty_nodes.insert(prev_node); self.graph_entities.remove(prev_node, entity); @@ -221,6 +227,15 @@ impl Sim { ensure_nearby(&mut self.graph, position, f64::from(self.cfg.view_distance)); } + let mut accepted_block_updates: Vec = vec![]; + + for block_update in pending_block_updates.into_iter() { + if !self.graph.update_block(&block_update) { + tracing::warn!("Block update received from ungenerated chunk"); + } + accepted_block_updates.push(block_update); + } + // Capture state changes for broadcast to clients let mut spawns = Vec::with_capacity(self.spawns.len()); for entity in self.spawns.drain(..) { @@ -246,6 +261,7 @@ impl Sim { }) }) .collect(), + block_updates: accepted_block_updates, }; populate_fresh_nodes(&mut self.graph); @@ -255,6 +271,7 @@ impl Sim { + 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