From c417e8c5b96048ca5207081721c8151cc0228021 Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Fri, 16 Jul 2021 15:42:50 -0400 Subject: [PATCH] Defer repositioning until after chunks are generated normally, to reduce latency and avoid duplicate work. --- common/src/comp/location.rs | 7 +++++ common/src/comp/mod.rs | 2 +- common/src/terrain/mod.rs | 42 ++++++++++++++++++++++++++++ common/state/src/state.rs | 1 + server/src/events/entity_creation.rs | 9 ++---- server/src/state_ext.rs | 24 +++------------- server/src/sys/terrain.rs | 26 ++++++++++++++--- world/src/lib.rs | 36 +----------------------- 8 files changed, 81 insertions(+), 66 deletions(-) diff --git a/common/src/comp/location.rs b/common/src/comp/location.rs index 7ecef35163..bd2ebb678a 100644 --- a/common/src/comp/location.rs +++ b/common/src/comp/location.rs @@ -45,3 +45,10 @@ impl Component for WaypointArea { impl Default for WaypointArea { fn default() -> Self { Self(5.0) } } + +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub struct RepositionOnChunkLoad; + +impl Component for RepositionOnChunkLoad { + type Storage = IdvStorage; +} diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 2242ad236e..10da4be975 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -80,7 +80,7 @@ pub use self::{ slot, Inventory, InventoryUpdate, InventoryUpdateEvent, }, last::Last, - location::{Waypoint, WaypointArea}, + location::{RepositionOnChunkLoad, Waypoint, WaypointArea}, misc::Object, ori::Ori, phys::{ diff --git a/common/src/terrain/mod.rs b/common/src/terrain/mod.rs index 19cc22bdd5..f31e906ba7 100644 --- a/common/src/terrain/mod.rs +++ b/common/src/terrain/mod.rs @@ -175,6 +175,48 @@ impl TerrainGrid { } } +impl TerrainChunk { + /// Find the highest or lowest accessible position within the chunk + pub fn find_accessible_pos(&self, spawn_wpos: Vec2, ascending: bool) -> Vec3 { + let min_z = self.get_min_z(); + let max_z = self.get_max_z(); + + let pos = Vec3::new( + spawn_wpos.x, + spawn_wpos.y, + if ascending { min_z } else { max_z }, + ); + (0..(max_z - min_z)) + .map(|z_diff| { + if ascending { + pos + Vec3::unit_z() * z_diff + } else { + pos - Vec3::unit_z() * z_diff + } + }) + .find(|test_pos| { + let chunk_relative_xy = test_pos + .xy() + .map2(TerrainChunkSize::RECT_SIZE, |e, sz| e.rem_euclid(sz as i32)); + self.get( + Vec3::new(chunk_relative_xy.x, chunk_relative_xy.y, test_pos.z) + - Vec3::unit_z(), + ) + .map_or(false, |b| b.is_filled()) + && (0..3).all(|z| { + self.get( + Vec3::new(chunk_relative_xy.x, chunk_relative_xy.y, test_pos.z) + + Vec3::unit_z() * z, + ) + .map_or(true, |b| !b.is_solid()) + }) + }) + .unwrap_or(pos) + .map(|e| e as f32) + + 0.5 + } +} + // Terrain helper functions used across multiple crates. /// Computes the position Vec2 of a SimChunk from an index, where the index was diff --git a/common/state/src/state.rs b/common/state/src/state.rs index 4bf741311b..5c8d26ea7c 100644 --- a/common/state/src/state.rs +++ b/common/state/src/state.rs @@ -182,6 +182,7 @@ impl State { ecs.register::>(); ecs.register::(); ecs.register::(); + ecs.register::(); ecs.register::(); ecs.register::(); ecs.register::(); diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index b9fd5bee70..8112123c96 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -41,12 +41,9 @@ pub fn handle_loaded_character_data( Option, ), ) { - server.state.update_character_data( - entity, - loaded_components, - &*server.world, - &server.index.as_index_ref(), - ); + server + .state + .update_character_data(entity, loaded_components); sys::subscription::initialize_region_subscription(server.state.ecs(), entity); } diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index eea380aeff..4a20594fd6 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -97,13 +97,7 @@ pub trait StateExt { fn initialize_character_data(&mut self, entity: EcsEntity, character_id: CharacterId); /// Update the components associated with the entity's current character. /// Performed after loading component data from the database - fn update_character_data( - &mut self, - entity: EcsEntity, - components: PersistedComponents, - world: &world::World, - index: &world::IndexRef, - ); + fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents); /// Iterates over registered clients and send each `ServerMsg` fn send_chat(&self, msg: comp::UnresolvedChatMsg); fn notify_players(&self, msg: ServerGeneral); @@ -486,13 +480,7 @@ impl StateExt for State { } } - fn update_character_data( - &mut self, - entity: EcsEntity, - components: PersistedComponents, - world: &world::World, - index: &world::IndexRef, - ) { + fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents) { let (body, stats, skill_set, inventory, waypoint) = components; if let Some(player_uid) = self.read_component_copied::(entity) { @@ -537,13 +525,9 @@ impl StateExt for State { ); if let Some(waypoint) = waypoint { - // Avoid spawning the player in terrain by finding the highest solid point in - // their waypoint's column to spawn them at - let spawn_pos = - world.find_accessible_pos(*index, waypoint.get_pos().xy().as_::(), false); - + self.write_component_ignore_entity_dead(entity, comp::RepositionOnChunkLoad); self.write_component_ignore_entity_dead(entity, waypoint); - self.write_component_ignore_entity_dead(entity, comp::Pos(spawn_pos)); + self.write_component_ignore_entity_dead(entity, comp::Pos(waypoint.get_pos())); self.write_component_ignore_entity_dead(entity, comp::Vel(Vec3::zero())); self.write_component_ignore_entity_dead(entity, comp::ForceUpdate); } diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 77afd1f2b1..bb8e7b1b60 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -3,7 +3,7 @@ use crate::{ presence::Presence, rtsim::RtSim, settings::Settings, SpawnPoint, Tick, }; use common::{ - comp::{self, agent, bird_medium, Alignment, BehaviorCapability, Pos}, + comp::{self, agent, bird_medium, Alignment, BehaviorCapability, Pos, RepositionOnChunkLoad}, event::{EventBus, ServerEvent}, generation::{get_npc_name, EntityInfo}, npc::NPC_NAMES, @@ -14,7 +14,7 @@ use common_ecs::{Job, Origin, Phase, System}; use common_net::msg::{SerializedTerrainChunk, ServerGeneral}; use common_state::TerrainChanges; use comp::Behavior; -use specs::{Join, Read, ReadExpect, ReadStorage, Write, WriteExpect}; +use specs::{Entities, Join, Read, ReadExpect, ReadStorage, Write, WriteExpect, WriteStorage}; use std::sync::Arc; use vek::*; @@ -93,9 +93,11 @@ impl<'a> System<'a> for Sys { WriteExpect<'a, TerrainGrid>, Write<'a, TerrainChanges>, WriteExpect<'a, RtSim>, - ReadStorage<'a, Pos>, + WriteStorage<'a, Pos>, ReadStorage<'a, Presence>, ReadStorage<'a, Client>, + Entities<'a>, + WriteStorage<'a, RepositionOnChunkLoad>, ); const NAME: &'static str = "terrain"; @@ -114,9 +116,11 @@ impl<'a> System<'a> for Sys { mut terrain, mut terrain_changes, mut rtsim, - positions, + mut positions, presences, clients, + entities, + mut reposition_on_load, ): Self::SystemData, ) { let mut server_emitter = server_event_bus.emitter(); @@ -306,6 +310,20 @@ impl<'a> System<'a> for Sys { } } + for (entity, pos) in (&entities, &mut positions).join() { + if reposition_on_load.get(entity).is_some() { + // If the player is in the new chunk and is marked as needing repositioning once + // the chunk loads (e.g. from having just logged in), reposition them + let chunk_pos = terrain.pos_key(pos.0.map(|e| e as i32)); + if let Some((_, chunk)) = new_chunks.iter().find(|(key, _)| *key == chunk_pos) { + pos.0 = chunk + .find_accessible_pos(pos.0.xy().as_::(), false) + .as_::(); + reposition_on_load.remove(entity); + } + } + } + // Send the chunk to all nearby players. use rayon::iter::{IntoParallelIterator, ParallelIterator}; new_chunks.into_par_iter().for_each(|(key, chunk)| { diff --git a/world/src/lib.rs b/world/src/lib.rs index b59600521b..29531e8b8b 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -202,42 +202,8 @@ impl World { let (tc, _cs) = self .generate_chunk(index, chunk_pos, || false, None) .unwrap(); - let min_z = tc.get_min_z(); - let max_z = tc.get_max_z(); - let pos = Vec3::new( - spawn_wpos.x, - spawn_wpos.y, - if ascending { min_z } else { max_z }, - ); - (0..(max_z - min_z)) - .map(|z_diff| { - if ascending { - pos + Vec3::unit_z() * z_diff - } else { - pos - Vec3::unit_z() * z_diff - } - }) - .find(|test_pos| { - let chunk_relative_xy = test_pos - .xy() - .map2(TerrainChunkSize::RECT_SIZE, |e, sz| e.rem_euclid(sz as i32)); - tc.get( - Vec3::new(chunk_relative_xy.x, chunk_relative_xy.y, test_pos.z) - - Vec3::unit_z(), - ) - .map_or(false, |b| b.is_filled()) - && (0..3).all(|z| { - tc.get( - Vec3::new(chunk_relative_xy.x, chunk_relative_xy.y, test_pos.z) - + Vec3::unit_z() * z, - ) - .map_or(true, |b| !b.is_solid()) - }) - }) - .unwrap_or(pos) - .map(|e| e as f32) - + 0.5 + tc.find_accessible_pos(spawn_wpos, ascending) } #[allow(clippy::eval_order_dependence)]