From 9030fee99f26e2005fbe13151d349235c34d3cd0 Mon Sep 17 00:00:00 2001 From: Imbris Date: Fri, 8 Oct 2021 13:20:50 -0400 Subject: [PATCH 1/2] Ensure cancelled chunks are not loaded into the terrain --- server/src/chunk_generator.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/server/src/chunk_generator.rs b/server/src/chunk_generator.rs index 0559b4a460..87124c0cf4 100644 --- a/server/src/chunk_generator.rs +++ b/server/src/chunk_generator.rs @@ -65,14 +65,17 @@ impl ChunkGenerator { } pub fn recv_new_chunk(&mut self) -> Option { - if let Ok((key, res)) = self.chunk_rx.try_recv() { - self.pending_chunks.remove(&key); - self.metrics.chunks_served.inc(); - // TODO: do anything else if res is an Err? - Some((key, res)) - } else { - None + // Make sure chunk wasn't cancelled and if it was check to see if there are more + // chunks to receive + while let Ok((key, res)) = self.chunk_rx.try_recv() { + if self.pending_chunks.remove(&key).is_some() { + self.metrics.chunks_served.inc(); + // TODO: do anything else if res is an Err? + return Some((key, res)); + } } + + None } pub fn pending_chunks(&self) -> impl Iterator> + '_ { From 8987389e8a24ffdf38bb702ce28364b736cb5519 Mon Sep 17 00:00:00 2001 From: Imbris Date: Fri, 8 Oct 2021 14:31:04 -0400 Subject: [PATCH 2/2] Submit chunk requests to the generator within the terrain system right before receiving new chunks, so that duplicate work is not done for chunks that just finished but were not yet added to the terrain. --- common/src/event.rs | 1 - server/src/events/mod.rs | 10 ---------- server/src/lib.rs | 8 +++++++- server/src/sys/mod.rs | 2 +- server/src/sys/msg/terrain.rs | 29 ++++++++++++++-------------- server/src/sys/terrain.rs | 36 +++++++++++++++++++++++++++++++++-- 6 files changed, 56 insertions(+), 30 deletions(-) diff --git a/common/src/event.rs b/common/src/event.rs index c9eded34a1..4c7d1bc896 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -148,7 +148,6 @@ pub enum ServerEvent { CreateWaypoint(Vec3), ClientDisconnect(EcsEntity, DisconnectReason), ClientDisconnectWithoutPersistence(EcsEntity), - ChunkRequest(EcsEntity, Vec2), Command(EcsEntity, String, Vec), /// Send a chat message to the player from an npc or other player Chat(comp::UnresolvedChatMsg), diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index 201d9a1258..35911547e4 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -51,7 +51,6 @@ impl Server { span!(_guard, "handle_events", "Server::handle_events"); let mut frontend_events = Vec::new(); - let mut requested_chunks = Vec::new(); let mut commands = Vec::new(); let mut chat_messages = Vec::new(); @@ -191,10 +190,6 @@ impl Server { true, )) }, - - ServerEvent::ChunkRequest(entity, key) => { - requested_chunks.push((entity, key)); - }, ServerEvent::Command(entity, name, args) => { commands.push((entity, name, args)); }, @@ -245,11 +240,6 @@ impl Server { } } - // Generate requested chunks - for (entity, key) in requested_chunks { - self.generate_chunk(entity, key); - } - for (entity, name, args) in commands { self.process_command(entity, name, args); } diff --git a/server/src/lib.rs b/server/src/lib.rs index f73147b480..be13805968 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -180,6 +180,11 @@ impl BattleModeBuffer { } } +pub struct ChunkRequest { + entity: EcsEntity, + key: Vec2, +} + pub struct Server { state: State, world: Arc, @@ -234,6 +239,7 @@ impl Server { path: data_dir.to_owned(), }); state.ecs_mut().insert(EventBus::::default()); + state.ecs_mut().insert(Vec::::new()); state.ecs_mut().insert(LoginProvider::new( settings.auth_server_address.clone(), Arc::clone(&runtime), @@ -1071,7 +1077,7 @@ impl Server { pub fn generate_chunk(&mut self, entity: EcsEntity, key: Vec2) { let ecs = self.state.ecs(); - let slow_jobs = ecs.write_resource::(); + let slow_jobs = ecs.read_resource::(); ecs.write_resource::().generate_chunk( Some(entity), key, diff --git a/server/src/sys/mod.rs b/server/src/sys/mod.rs index 64ea2d60b8..d0f76023c6 100644 --- a/server/src/sys/mod.rs +++ b/server/src/sys/mod.rs @@ -27,7 +27,7 @@ pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) { dispatch::(dispatch_builder, &[&projectile::Sys::sys_name()]); //Note: server should not depend on interpolation system dispatch::(dispatch_builder, &[]); - dispatch::(dispatch_builder, &[]); + dispatch::(dispatch_builder, &[&msg::terrain::Sys::sys_name()]); dispatch::(dispatch_builder, &[]); dispatch::(dispatch_builder, &[]); dispatch::(dispatch_builder, &[]); diff --git a/server/src/sys/msg/terrain.rs b/server/src/sys/msg/terrain.rs index a68cd5f85b..bf6c81b715 100644 --- a/server/src/sys/msg/terrain.rs +++ b/server/src/sys/msg/terrain.rs @@ -1,7 +1,6 @@ -use crate::{client::Client, metrics::NetworkRequestMetrics, presence::Presence}; +use crate::{client::Client, metrics::NetworkRequestMetrics, presence::Presence, ChunkRequest}; use common::{ comp::Pos, - event::{EventBus, ServerEvent}, spiral::Spiral2d, terrain::{TerrainChunkSize, TerrainGrid}, vol::RectVolSize, @@ -9,7 +8,7 @@ use common::{ use common_ecs::{Job, Origin, ParMode, Phase, System}; use common_net::msg::{ClientGeneral, SerializedTerrainChunk, ServerGeneral}; use rayon::iter::ParallelIterator; -use specs::{Entities, Join, ParJoin, Read, ReadExpect, ReadStorage}; +use specs::{Entities, Join, ParJoin, ReadExpect, ReadStorage, Write}; use tracing::{debug, trace}; /// This system will handle new messages from clients @@ -19,9 +18,9 @@ impl<'a> System<'a> for Sys { #[allow(clippy::type_complexity)] type SystemData = ( Entities<'a>, - Read<'a, EventBus>, ReadExpect<'a, TerrainGrid>, ReadExpect<'a, NetworkRequestMetrics>, + Write<'a, Vec>, ReadStorage<'a, Pos>, ReadStorage<'a, Presence>, ReadStorage<'a, Client>, @@ -35,21 +34,19 @@ impl<'a> System<'a> for Sys { job: &mut Job, ( entities, - server_event_bus, terrain, network_metrics, + mut chunk_requests, positions, presences, clients, ): Self::SystemData, ) { - let mut server_emitter = server_event_bus.emitter(); - job.cpu_stats.measure(ParMode::Rayon); - let mut events = (&entities, &clients, (&presences).maybe()) + let mut new_chunk_requests = (&entities, &clients, (&presences).maybe()) .par_join() .map(|(entity, client, maybe_presence)| { - let mut events = Vec::new(); + let mut chunk_requests = Vec::new(); let _ = super::try_recv_all(client, 5, |client, msg| { let presence = match maybe_presence { Some(g) => g, @@ -93,7 +90,7 @@ impl<'a> System<'a> for Sys { }, None => { network_metrics.chunks_generation_triggered.inc(); - events.push(ServerEvent::ChunkRequest(entity, key)); + chunk_requests.push(ChunkRequest { entity, key }); }, } } else { @@ -117,19 +114,21 @@ impl<'a> System<'a> for Sys { for rpos in Spiral2d::new().take((crate::MIN_VD as usize + 1).pow(2)) { let key = player_chunk + rpos; if terrain.get_key(key).is_none() { - events.push(ServerEvent::ChunkRequest(entity, key)); + // TODO: @zesterer do we want to be sending these chunk to the client + // even if they aren't requested? If we don't we could replace the + // entity here with Option and pass in None. + chunk_requests.push(ChunkRequest { entity, key }); } } } - events + chunk_requests }) .flatten() .collect::>(); job.cpu_stats.measure(ParMode::Single); - for event in events.drain(..) { - server_emitter.emit(event); - } + + chunk_requests.append(&mut new_chunk_requests); } } diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 932f98a27d..91a7741d42 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -1,5 +1,10 @@ #[cfg(feature = "persistent_world")] use crate::TerrainPersistence; +#[cfg(not(feature = "worldgen"))] +use test_world::{IndexOwned, World}; +#[cfg(feature = "worldgen")] +use world::{IndexOwned, World}; + use crate::{ chunk_generator::ChunkGenerator, client::Client, @@ -7,14 +12,15 @@ use crate::{ presence::{Presence, RepositionOnChunkLoad}, rtsim::RtSim, settings::Settings, - SpawnPoint, Tick, + ChunkRequest, SpawnPoint, Tick, }; use common::{ comp::{self, agent, bird_medium, BehaviorCapability, ForceUpdate, Pos, Waypoint}, event::{EventBus, ServerEvent}, generation::EntityInfo, lottery::LootSpec, - resources::Time, + resources::{Time, TimeOfDay}, + slowjob::SlowJobPool, terrain::TerrainGrid, LoadoutBuilder, SkillSetBuilder, }; @@ -104,10 +110,15 @@ impl<'a> System<'a> for Sys { Read<'a, Tick>, Read<'a, SpawnPoint>, Read<'a, Settings>, + Read<'a, TimeOfDay>, + ReadExpect<'a, SlowJobPool>, + ReadExpect<'a, IndexOwned>, + ReadExpect<'a, Arc>, ReadExpect<'a, NetworkRequestMetrics>, WriteExpect<'a, ChunkGenerator>, WriteExpect<'a, TerrainGrid>, Write<'a, TerrainChanges>, + Write<'a, Vec>, WriteExpect<'a, RtSim>, TerrainPersistenceData<'a>, WriteStorage<'a, Pos>, @@ -131,10 +142,15 @@ impl<'a> System<'a> for Sys { tick, spawn_point, server_settings, + time_of_day, + slow_jobs, + index, + world, network_metrics, mut chunk_generator, mut terrain, mut terrain_changes, + mut chunk_requests, mut rtsim, mut _terrain_persistence, mut positions, @@ -149,6 +165,22 @@ impl<'a> System<'a> for Sys { ) { let mut server_emitter = server_event_bus.emitter(); + // Generate requested chunks + // + // Submit requests for chunks right before receiving finished chunks so that we + // don't create duplicate work for chunks that just finished but are not + // yet added to the terrain. + chunk_requests.drain(..).for_each(|request| { + chunk_generator.generate_chunk( + Some(request.entity), + request.key, + &slow_jobs, + Arc::clone(&world), + index.clone(), + *time_of_day, + ) + }); + // Fetch any generated `TerrainChunk`s and insert them into the terrain. // Also, send the chunk data to anybody that is close by. let mut new_chunks = Vec::new();