diff --git a/client/src/graphics/draw.rs b/client/src/graphics/draw.rs index 3dd45155..c0900994 100644 --- a/client/src/graphics/draw.rs +++ b/client/src/graphics/draw.rs @@ -482,6 +482,9 @@ impl Draw { .expect("positionless entity in graph"); if let Some(character_model) = self.loader.get(self.character_model) { if let Ok(ch) = sim.world.get::<&Character>(entity) { + if !ch.state.active { + continue; + } let transform = transform * pos.local * na::Matrix4::new_scaling(sim.cfg().meters_to_absolute) diff --git a/common/src/proto.rs b/common/src/proto.rs index b8db0c82..542016ba 100644 --- a/common/src/proto.rs +++ b/common/src/proto.rs @@ -42,6 +42,7 @@ pub struct StateDelta { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CharacterState { + pub active: bool, pub velocity: na::Vector3, pub on_ground: bool, pub orientation: na::UnitQuaternion, diff --git a/save/src/lib.rs b/save/src/lib.rs index 65f519f3..3dae31e9 100644 --- a/save/src/lib.rs +++ b/save/src/lib.rs @@ -138,6 +138,16 @@ impl Reader { .map(|n| Ok(n.map_err(GetError::from)?.0.value())) .collect() } + + /// Temporary function to load all entity-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_entity_node_ids(&self) -> Result, GetError> { + self.entity_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 99083887..e883929b 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -161,7 +161,7 @@ impl Server { ClientEvent::Hello(hello) => { assert!(client.handles.is_none()); let snapshot = Arc::new(self.sim.snapshot()); - let (id, entity) = self.sim.spawn_character(hello); + let (id, entity) = self.sim.activate_or_spawn_character(hello); let (ordered_send, ordered_recv) = mpsc::channel(32); ordered_send.try_send(snapshot).unwrap(); let (unordered_send, unordered_recv) = mpsc::channel(32); @@ -199,7 +199,7 @@ impl Server { fn cleanup_client(&mut self, client: ClientId) { if let Some(ref x) = self.clients[client].handles { - self.sim.destroy(x.character); + self.sim.deactivate_character(x.character); } self.clients.remove(client); } diff --git a/server/src/sim.rs b/server/src/sim.rs index dac719b4..801dc598 100644 --- a/server/src/sim.rs +++ b/server/src/sim.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use anyhow::Context; -use common::dodeca::Vertex; +use common::dodeca::{Side, Vertex}; use common::node::VoxelData; use common::proto::{BlockUpdate, Inventory, SerializedVoxelData}; use common::world::Material; @@ -69,6 +69,9 @@ impl Sim { .load_all_voxels(save) .expect("save file must be of a valid format"); result + .load_all_entities(save) + .expect("save file must be of a valid format"); + result } pub fn save(&mut self, save: &mut save::Save) -> Result<(), save::DbError> { @@ -85,6 +88,9 @@ impl Sim { let mut tx = save.write()?; let mut writer = tx.get()?; for (_, (pos, ch)) in self.world.query::<(&Position, &Character)>().iter() { + if !ch.state.active { + continue; + } writer.put_character( &ch.name, &save::Character { @@ -109,6 +115,84 @@ impl Sim { Ok(()) } + fn load_all_entities(&mut self, save: &save::Save) -> anyhow::Result<()> { + let mut read = save.read()?; + for node_hash in read.get_all_entity_node_ids()? { + let Some(entity_node) = read.get_entity_node(node_hash)? else { + continue; + }; + let node_id = self.graph.from_hash(node_hash); + for entity_bytes in entity_node.entities { + let save_entity: SaveEntity = postcard::from_bytes(&entity_bytes)?; + self.load_entity(&mut read, node_id, save_entity)?; + } + } + Ok(()) + } + + fn load_entity( + &mut self, + read: &mut save::Reader, + node: NodeId, + save_entity: SaveEntity, + ) -> anyhow::Result<()> { + let entity_id = EntityId::from_bits(u64::from_le_bytes(save_entity.entity)); + let mut entity_builder = EntityBuilder::new(); + entity_builder.add(entity_id); + for (component_type, component_bytes) in save_entity.components { + if component_type == ComponentType::Position as u64 { + let column_slice: [f32; 16] = postcard::from_bytes(&component_bytes)?; + entity_builder.add(Position { + node, + local: na::Matrix4::from_column_slice(&column_slice), + }); + } else if component_type == ComponentType::Name as u64 { + let name = String::from_utf8(component_bytes)?; + // Ensure that every node occupied by a character is generated. + if let Some(character) = read.get_character(&name)? { + let mut current_node = NodeId::ROOT; + for side in character + .path + .into_iter() + .map(|side| Side::from_index(side as usize)) + { + current_node = self.graph.ensure_neighbor(current_node, side); + } + if current_node != node { + // Skip loading named entities that are in the wrong place. This can happen + // when there are multiple entities with the same name, which has been possible + // in the past. + return Ok(()); + } + } else { + // Skip loading named entities that lack path information. + return Ok(()); + } + // Prepare all relevant components that are needed to support ComponentType::Name + entity_builder.add(Character { + name, + state: CharacterState { + active: false, + velocity: na::Vector3::zeros(), + on_ground: false, + orientation: na::UnitQuaternion::identity(), + }, + }); + entity_builder.add(CharacterInput { + movement: na::Vector3::zeros(), + jump: false, + no_clip: false, + block_update: None, + }); + entity_builder.add(Inventory { contents: vec![] }); + } + } + let entity = self.world.spawn(entity_builder.build()); + self.graph_entities.insert(node, entity); + self.entity_ids.insert(entity_id, entity); + Ok(()) + } + fn load_all_voxels(&mut self, save: &save::Save) -> anyhow::Result<()> { let mut read = save.read()?; for node_hash in read.get_all_voxel_node_ids()? { @@ -185,7 +269,15 @@ impl Sim { save::VoxelNode { chunks } } - pub fn spawn_character(&mut self, hello: ClientHello) -> (EntityId, Entity) { + pub fn activate_or_spawn_character(&mut self, hello: ClientHello) -> (EntityId, Entity) { + for (entity, (entity_id, character)) in + self.world.query::<(&EntityId, &mut Character)>().iter() + { + if character.name == hello.name { + character.state.active = true; + return (*entity_id, entity); + } + } let position = Position { node: NodeId::ROOT, local: math::translate_along(&(na::Vector3::y() * 1.4)), @@ -193,6 +285,7 @@ impl Sim { let character = Character { name: hello.name, state: CharacterState { + active: true, orientation: na::one(), velocity: na::Vector3::zeros(), on_ground: false, @@ -208,6 +301,12 @@ impl Sim { self.spawn((position, character, inventory, initial_input)) } + pub fn deactivate_character(&mut self, entity: Entity) { + if let Ok(mut character) = self.world.get::<&mut Character>(entity) { + character.state.active = false; + } + } + fn spawn(&mut self, bundle: impl DynamicBundle) -> (EntityId, Entity) { let id = self.new_id(); let mut entity_builder = EntityBuilder::new(); @@ -290,7 +389,12 @@ impl Sim { let _guard = span.enter(); // Extend graph structure - for (_, (position, _)) in self.world.query::<(&mut Position, &mut Character)>().iter() { + for (_, (position, character)) in + self.world.query::<(&mut Position, &mut Character)>().iter() + { + if !character.state.active { + continue; + } ensure_nearby(&mut self.graph, position, self.cfg.view_distance); } @@ -321,7 +425,10 @@ impl Sim { // 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() { + for (_, (position, character)) in self.world.query::<(&Position, &Character)>().iter() { + if !character.state.active { + continue; + } let nodes = nearby_nodes(&self.graph, position, chunk_generation_distance); for &(node, _) in &nodes { for vertex in dodeca::Vertex::iter() { @@ -349,6 +456,9 @@ impl Sim { .query::<(&mut Position, &mut Character, &CharacterInput)>() .iter() { + if !character.state.active { + continue; + } let prev_node = position.node; character_controller::run_character_step( &self.cfg,