Skip to content

Commit

Permalink
Load character entities from the save file
Browse files Browse the repository at this point in the history
  • Loading branch information
patowen committed Jul 28, 2024
1 parent e80465d commit 9bf9264
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 11 deletions.
6 changes: 6 additions & 0 deletions common/src/math.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ impl<N: RealField + Copy> MIsometry<N> {
pub fn from_columns_unchecked(columns: &[MVector<N>; 4]) -> Self {
Self(na::Matrix4::from_columns(&(*columns).map(|x| x.0)))
}
/// Creates an `MIsometry` with its elements filled with the components provided by a slice in column-major order.
/// It is the caller's responsibility to ensure that the resulting matrix is a valid isometry.
#[inline]
pub fn from_column_slice_unchecked(data: &[N]) -> Self {
Self(na::Matrix4::from_column_slice(data))
}
/// Minkowski transpose. Inverse for hyperbolic isometries
#[rustfmt::skip]
pub fn mtranspose(self) -> Self {
Expand Down
3 changes: 3 additions & 0 deletions common/src/proto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ pub struct Character {
pub state: CharacterState,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct InactiveCharacter(pub Character);

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Inventory {
pub contents: Vec<EntityId>,
Expand Down
10 changes: 10 additions & 0 deletions save/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<u128>, GetError> {
self.entity_nodes
.iter()?
.map(|n| Ok(n.map_err(GetError::from)?.0.value()))
.collect()
}
}

fn decompress(
Expand Down
9 changes: 7 additions & 2 deletions server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,12 @@ 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 Some((id, entity)) = self.sim.activate_or_spawn_character(&hello) else {
error!("could not spawn {} due to name conflict", hello.name);
client.conn.close(3u32.into(), b"name conflict");
self.cleanup_client(client_id);
return;
};
let (ordered_send, ordered_recv) = mpsc::channel(32);
ordered_send.try_send(snapshot).unwrap();
let (unordered_send, unordered_recv) = mpsc::channel(32);
Expand Down Expand Up @@ -199,7 +204,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);
}
Expand Down
177 changes: 168 additions & 9 deletions server/src/sim.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::sync::Arc;

use anyhow::Context;
use common::dodeca::Vertex;
use common::dodeca::{Side, Vertex};
use common::math::MIsometry;
use common::node::VoxelData;
use common::proto::{BlockUpdate, Inventory, SerializedVoxelData};
use common::proto::{BlockUpdate, InactiveCharacter, Inventory, SerializedVoxelData};
use common::world::Material;
use common::{node::ChunkId, GraphEntities};
use fxhash::{FxHashMap, FxHashSet};
Expand Down Expand Up @@ -69,6 +70,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> {
Expand Down Expand Up @@ -109,6 +113,104 @@ 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 {
self.load_component(
read,
&mut entity_builder,
node,
ComponentType::try_from(component_type as i32).unwrap(),
component_bytes,
)?;
}
let entity = self.world.spawn(entity_builder.build());
self.graph_entities.insert(node, entity);
self.entity_ids.insert(entity_id, entity);
Ok(())
}

fn load_component(
&mut self,
read: &mut save::Reader,
entity_builder: &mut EntityBuilder,
node: NodeId,
component_type: ComponentType,
component_bytes: Vec<u8>,
) -> anyhow::Result<()> {
match component_type {
ComponentType::Position => {
let column_slice: [f32; 16] = postcard::from_bytes(&component_bytes)?;
entity_builder.add(Position {
node,
local: MIsometry::from_column_slice_unchecked(&column_slice),
});
}
ComponentType::Name => {
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(InactiveCharacter(Character {
name,
state: CharacterState {
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![] });
}
}
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()? {
Expand Down Expand Up @@ -149,7 +251,11 @@ impl Sim {
postcard::to_stdvec(&pos.local.as_ref()).unwrap(),
));
}
if let Some(ch) = entity.get::<&Character>() {
if let Some(ch) = entity.get::<&Character>().or_else(|| {
entity
.get::<&InactiveCharacter>()
.map(|ich| hecs::Ref::map(ich, |ich| &ich.0)) // Extract Ref<Character> from Ref<InactiveCharacter>
}) {
components.push((ComponentType::Name as u64, ch.name.as_bytes().into()));
}
let mut repr = Vec::new();
Expand Down Expand Up @@ -185,13 +291,44 @@ impl Sim {
save::VoxelNode { chunks }
}

pub fn spawn_character(&mut self, hello: ClientHello) -> (EntityId, Entity) {
/// Activates or spawns a character with a given name, or returns None if there is already an active
/// character with that name
pub fn activate_or_spawn_character(
&mut self,
hello: &ClientHello,
) -> Option<(EntityId, Entity)> {
// Check for conflicting characters
if self
.world
.query::<&Character>()
.iter()
.any(|(_, character)| character.name == hello.name)
{
return None;
}

// Check for matching characters
let matching_character = self
.world
.query::<(&EntityId, &InactiveCharacter)>()
.iter()
.find(|(_, (_, inactive_character))| inactive_character.0.name == hello.name)
.map(|(entity, (entity_id, _))| (*entity_id, entity));
if let Some((entity_id, entity)) = matching_character {
info!(id = %entity_id, name = %hello.name, "activating character");
let inactive_character = self.world.remove_one::<InactiveCharacter>(entity).unwrap();
self.world.insert_one(entity, inactive_character.0).unwrap();
self.accumulated_changes.spawns.push(entity);
return Some((entity_id, entity));
}

// Spawn entirely new character
let position = Position {
node: NodeId::ROOT,
local: math::translate_along(&(na::Vector3::y() * 1.4)),
};
let character = Character {
name: hello.name,
name: hello.name.clone(),
state: CharacterState {
orientation: na::one(),
velocity: na::Vector3::zeros(),
Expand All @@ -205,7 +342,16 @@ impl Sim {
no_clip: true,
block_update: None,
};
self.spawn((position, character, inventory, initial_input))
Some(self.spawn((position, character, inventory, initial_input)))
}

pub fn deactivate_character(&mut self, entity: Entity) {
let entity_id = *self.world.get::<&EntityId>(entity).unwrap();
let character = self.world.remove_one::<Character>(entity).unwrap();
self.world
.insert_one(entity, InactiveCharacter(character))
.unwrap();
self.accumulated_changes.despawns.push(entity_id);
}

fn spawn(&mut self, bundle: impl DynamicBundle) -> (EntityId, Entity) {
Expand All @@ -225,7 +371,10 @@ impl Sim {
}

self.entity_ids.insert(id, entity);
self.accumulated_changes.spawns.push(entity);

if !self.world.satisfies::<&InactiveCharacter>(entity).unwrap() {
self.accumulated_changes.spawns.push(entity);
}

(id, entity)
}
Expand All @@ -249,7 +398,10 @@ impl Sim {
self.graph_entities.remove(position.node, entity);
}
self.world.despawn(entity).unwrap();
self.accumulated_changes.despawns.push(id);

if !self.world.satisfies::<&InactiveCharacter>(entity).unwrap() {
self.accumulated_changes.despawns.push(id);
}
}

/// Collect information about all entities, for transmission to new clients
Expand All @@ -268,7 +420,10 @@ impl Sim {
inventory_additions: Vec::new(),
inventory_removals: Vec::new(),
};
for (entity, &id) in &mut self.world.query::<&EntityId>() {
for (entity, &id) in &mut self
.world
.query::<hecs::Without<&EntityId, &InactiveCharacter>>()
{
spawns.spawns.push((id, dump_entity(&self.world, entity)));
}
for &chunk_id in self.modified_chunks.iter() {
Expand Down Expand Up @@ -487,7 +642,11 @@ impl Sim {
}
}

/// Collect all information about a particular entity for transmission to clients.
fn dump_entity(world: &hecs::World, entity: Entity) -> Vec<Component> {
if world.satisfies::<&InactiveCharacter>(entity).unwrap() {
panic!("Inactive characters should not be sent to clients")
}
let mut components = Vec::new();
if let Ok(x) = world.get::<&Position>(entity) {
components.push(Component::Position(*x));
Expand Down

0 comments on commit 9bf9264

Please sign in to comment.