Defer repositioning until after chunks are generated normally, to reduce latency and avoid duplicate work.

This commit is contained in:
Avi Weinstock 2021-07-16 15:42:50 -04:00
parent 7555be0e25
commit c417e8c5b9
8 changed files with 81 additions and 66 deletions

View File

@ -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<Self>;
}

View File

@ -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::{

View File

@ -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<i32>, ascending: bool) -> Vec3<f32> {
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

View File

@ -182,6 +182,7 @@ impl State {
ecs.register::<comp::Last<comp::Ori>>();
ecs.register::<comp::Alignment>();
ecs.register::<comp::Agent>();
ecs.register::<comp::RepositionOnChunkLoad>();
ecs.register::<comp::WaypointArea>();
ecs.register::<comp::ForceUpdate>();
ecs.register::<comp::InventoryUpdate>();

View File

@ -41,12 +41,9 @@ pub fn handle_loaded_character_data(
Option<comp::Waypoint>,
),
) {
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);
}

View File

@ -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::<Uid>(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_::<i32>(), 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);
}

View File

@ -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_::<i32>(), false)
.as_::<f32>();
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)| {

View File

@ -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)]