From e49cafafbfb39078ed190049496eb2deb470edda Mon Sep 17 00:00:00 2001 From: Imbris Date: Fri, 29 Nov 2019 01:04:37 -0500 Subject: [PATCH] Actually send deletion messages --- Cargo.toml | 2 +- client/src/lib.rs | 13 ++- common/src/msg/ecs_packet.rs | 15 --- common/src/region.rs | 59 ++++++++-- common/src/state.rs | 2 +- common/src/sync/packet.rs | 28 ++--- common/src/sync/sync_ext.rs | 52 ++++----- common/src/sync/track.rs | 1 + common/src/sync/uid.rs | 8 +- server/src/cmd.rs | 5 +- server/src/lib.rs | 199 ++++++++++++++------------------- server/src/sys/entity_sync.rs | 46 +++++++- server/src/sys/message.rs | 12 +- server/src/sys/sentinel.rs | 187 +++++++++++++------------------ server/src/sys/subscription.rs | 116 +++++++++++++++++-- voxygen/src/hud/mod.rs | 11 +- world/src/sim/mod.rs | 2 +- 17 files changed, 440 insertions(+), 318 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4c463c2db7..9abd786022 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ opt-level = 2 overflow-checks = true debug-assertions = true panic = "abort" -debug = false +# debug = false codegen-units = 8 lto = false incremental = true diff --git a/client/src/lib.rs b/client/src/lib.rs index b59571a1f0..c1e0a5c365 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -562,10 +562,15 @@ impl Client { self.state.ecs_mut().apply_entity_package(entity_package) } ServerMsg::DeleteEntity(entity) => { - if let Some(entity) = self.state.ecs().entity_from_uid(entity) { - if entity != self.entity { - let _ = self.state.ecs_mut().delete_entity(entity); - } + if self + .state + .read_component_cloned::(self.entity) + .map(|u| u.into()) + != Some(entity) + { + self.state + .ecs_mut() + .delete_entity_and_clear_from_uid_allocator(entity); } } ServerMsg::EntityPos { entity, pos } => { diff --git a/common/src/msg/ecs_packet.rs b/common/src/msg/ecs_packet.rs index 83f1adef10..28964041ab 100644 --- a/common/src/msg/ecs_packet.rs +++ b/common/src/msg/ecs_packet.rs @@ -25,9 +25,6 @@ impl sync::ResPacket for EcsResPacket { sum_type! { #[derive(Clone, Debug, Serialize, Deserialize)] pub enum EcsCompPacket { - Pos(comp::Pos), - Vel(comp::Vel), - Ori(comp::Ori), Body(comp::Body), Player(comp::Player), CanBuild(comp::CanBuild), @@ -48,9 +45,6 @@ sum_type! { sum_type! { #[derive(Clone, Debug, Serialize, Deserialize)] pub enum EcsCompPhantom { - Pos(PhantomData), - Vel(PhantomData), - Ori(PhantomData), Body(PhantomData), Player(PhantomData), CanBuild(PhantomData), @@ -70,9 +64,6 @@ impl sync::CompPacket for EcsCompPacket { type Phantom = EcsCompPhantom; fn apply_insert(self, entity: specs::Entity, world: &specs::World) { match self { - EcsCompPacket::Pos(comp) => sync::handle_insert(comp, entity, world), - EcsCompPacket::Vel(comp) => sync::handle_insert(comp, entity, world), - EcsCompPacket::Ori(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Body(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Player(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::CanBuild(comp) => sync::handle_insert(comp, entity, world), @@ -90,9 +81,6 @@ impl sync::CompPacket for EcsCompPacket { } fn apply_modify(self, entity: specs::Entity, world: &specs::World) { match self { - EcsCompPacket::Pos(comp) => sync::handle_modify(comp, entity, world), - EcsCompPacket::Vel(comp) => sync::handle_modify(comp, entity, world), - EcsCompPacket::Ori(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Body(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Player(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::CanBuild(comp) => sync::handle_modify(comp, entity, world), @@ -110,9 +98,6 @@ impl sync::CompPacket for EcsCompPacket { } fn apply_remove(phantom: Self::Phantom, entity: specs::Entity, world: &specs::World) { match phantom { - EcsCompPhantom::Pos(_) => sync::handle_remove::(entity, world), - EcsCompPhantom::Vel(_) => sync::handle_remove::(entity, world), - EcsCompPhantom::Ori(_) => sync::handle_remove::(entity, world), EcsCompPhantom::Body(_) => sync::handle_remove::(entity, world), EcsCompPhantom::Player(_) => sync::handle_remove::(entity, world), EcsCompPhantom::CanBuild(_) => sync::handle_remove::(entity, world), diff --git a/common/src/region.rs b/common/src/region.rs index de8e5a526d..acf4aa04fa 100644 --- a/common/src/region.rs +++ b/common/src/region.rs @@ -104,6 +104,16 @@ impl RegionMap { // TODO special case large entities pub fn tick(&mut self, pos: ReadStorage, vel: ReadStorage, entities: Entities) { self.tick += 1; + // Clear events within each region + for i in 0..self.regions.len() { + self.regions + .get_index_mut(i) + .map(|(_, v)| v) + .unwrap() + .events + .clear(); + } + // Add any untracked entites for (pos, id) in (&pos, &entities, !&self.tracked_entities) .join() @@ -118,14 +128,6 @@ impl RegionMap { let mut regions_to_remove = Vec::new(); for i in 0..self.regions.len() { - // Clear events within each region - self.regions - .get_index_mut(i) - .map(|(_, v)| v) - .unwrap() - .events - .clear(); - for (maybe_pos, _maybe_vel, id) in ( pos.maybe(), vel.maybe(), @@ -215,6 +217,47 @@ impl RegionMap { pub fn key_pos(key: Vec2) -> Vec2 { key.map(|e| e << REGION_LOG2) } + /// Finds the region where a given entity is located using a given position to speed up the search + pub fn find_region(&self, entity: specs::Entity, pos: Vec3) -> Option> { + let id = entity.id(); + // Compute key for most likely region + let key = Self::pos_key(pos.map(|e| e as i32)); + // Get region + if let Some(region) = self.regions.get(&key) { + if region.entities().contains(id) { + return Some(key); + } else { + // Check neighbors + for i in 0..8 { + if let Some(idx) = region.neighbors[i] { + let (key, region) = self.regions.get_index(idx).unwrap(); + if region.entities().contains(id) { + return Some(*key); + } + } + } + } + } else { + // Check neighbors + for i in 0..8 { + let key = key + NEIGHBOR_OFFSETS[i]; + if let Some(region) = self.regions.get(&key) { + if region.entities().contains(id) { + return Some(key); + } + } + } + } + + // Scan though all regions + for (key, region) in self.iter() { + if region.entities().contains(id) { + return Some(key); + } + } + + None + } fn key_index(&self, key: Vec2) -> Option { self.regions.get_full(&key).map(|(i, _, _)| i) } diff --git a/common/src/state.rs b/common/src/state.rs index dbc9becad1..a2120e60c6 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -148,10 +148,10 @@ impl State { ecs.register::(); // Register synced resources used by the ECS. - ecs.add_resource(Time(0.0)); ecs.add_resource(TimeOfDay(0.0)); // Register unsynced resources used by the ECS. + ecs.add_resource(Time(0.0)); ecs.add_resource(DeltaTime(0.0)); ecs.add_resource(TerrainGrid::new().unwrap()); ecs.add_resource(BlockChange::default()); diff --git a/common/src/sync/packet.rs b/common/src/sync/packet.rs index b3cf45bf3c..f12d409960 100644 --- a/common/src/sync/packet.rs +++ b/common/src/sync/packet.rs @@ -2,6 +2,7 @@ use super::{ track::{Tracker, UpdateTracker}, uid::Uid, }; +use log::error; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use specs::{shred::Resource, Component, Entity, Join, ReadStorage, World}; use std::{ @@ -24,7 +25,9 @@ pub trait ResPacket: Clone + Debug + Send + 'static { /// Useful for implementing CompPacket trait pub fn handle_insert(comp: C, entity: Entity, world: &World) { - let _ = world.write_storage::().insert(entity, comp); + if let Err(err) = world.write_storage::().insert(entity, comp) { + error!("Error inserting component: {:?}", err); + }; } /// Useful for implementing CompPacket trait pub fn handle_modify(comp: C, entity: Entity, world: &World) { @@ -102,6 +105,7 @@ impl SyncPackage

{ uids: &ReadStorage<'a, Uid>, uid_tracker: &UpdateTracker, filter: impl Join + Copy, + deleted_entities: Vec, ) -> Self { // Add created and deleted entities let created_entities = (uids, filter, uid_tracker.inserted()) @@ -110,10 +114,15 @@ impl SyncPackage

{ .collect(); // TODO: handle modified uid? //created_entities.append(&mut (uids, filter, uid_tracker.inserted()).join().map(|(uid, _, _)| uid).collect()); - let deleted_entities = (uids, filter, uid_tracker.removed()) - .join() - .map(|(uid, _, _)| (*uid).into()) - .collect(); + // let deleted_entities = (uids.maybe(), filter, uid_tracker.removed()) + // .join() + // Why doesn't this panic?? + // .map(|(uid, _, _)| Into::::into(*uid.unwrap())) + // .collect::>(); + //let len = deleted_entities.len(); + //if len > 0 { + // println!("deleted {} in sync message", len); + // } Self { comp_updates: Vec::new(), @@ -124,7 +133,6 @@ impl SyncPackage

{ pub fn with_component<'a, C: Component + Clone + Send + Sync>( mut self, uids: &ReadStorage<'a, Uid>, - uid_tracker: &UpdateTracker, tracker: &impl Tracker, storage: &ReadStorage<'a, C>, filter: impl Join + Copy, @@ -136,13 +144,7 @@ impl SyncPackage

{ P::Phantom: TryInto>, C::Storage: specs::storage::Tracked, { - tracker.get_updates_for( - uids, - storage, - // Don't include updates for deleted entities - (filter, &!uid_tracker.removed()), - &mut self.comp_updates, - ); + tracker.get_updates_for(uids, storage, filter, &mut self.comp_updates); self } } diff --git a/common/src/sync/sync_ext.rs b/common/src/sync/sync_ext.rs index a375667111..1033f666cb 100644 --- a/common/src/sync/sync_ext.rs +++ b/common/src/sync/sync_ext.rs @@ -6,6 +6,7 @@ use super::{ track::UpdateTracker, uid::{Uid, UidAllocator}, }; +use log::error; use specs::{ saveload::{MarkedBuilder, MarkerAllocator}, world::Builder, @@ -20,6 +21,7 @@ pub trait WorldSyncExt { where C::Storage: Default + specs::storage::Tracked; fn create_entity_synced(&mut self) -> specs::EntityBuilder; + fn delete_entity_and_clear_from_uid_allocator(&mut self, uid: u64); fn uid_from_entity(&self, entity: specs::Entity) -> Option; fn entity_from_uid(&self, uid: u64) -> Option; fn apply_entity_package(&mut self, entity_package: EntityPackage

); @@ -35,14 +37,11 @@ impl WorldSyncExt for specs::World { fn register_sync_marker(&mut self) { self.register_synced::(); + // TODO: Consider only having allocator server side for now self.add_resource(UidAllocator::new()); } fn register_synced(&mut self) where - // P: From, - // C: TryFrom, - // P::Phantom: From>, - // P::Phantom: TryInto>, C::Storage: Default + specs::storage::Tracked, { self.register::(); @@ -50,26 +49,12 @@ impl WorldSyncExt for specs::World { } fn register_tracker(&mut self) where - // P: From, - // C: TryFrom, - // P::Phantom: From>, - // P::Phantom: TryInto>, C::Storage: Default + specs::storage::Tracked, { let tracker = UpdateTracker::::new(self); self.add_resource(tracker); } - /*fn insert_synced(&mut self, res: C) - //where - // R: From, - // C: TryFrom, - { - self.add_resource::(res); - - self.res_trackers.insert(ResUpdateTracker::::new()); - }*/ - fn create_entity_synced(&mut self) -> specs::EntityBuilder { self.create_entity().marked::() } @@ -94,6 +79,16 @@ impl WorldSyncExt for specs::World { } } + fn delete_entity_and_clear_from_uid_allocator(&mut self, uid: u64) { + // Clear from uid allocator + let maybe_entity = self.write_resource::().remove_entity(uid); + if let Some(entity) = maybe_entity { + if let Err(err) = self.delete_entity(entity) { + error!("Failed to delete entity: {:?}", err); + } + } + } + fn apply_state_package( &mut self, state_package: StatePackage, @@ -114,7 +109,7 @@ impl WorldSyncExt for specs::World { } // Initialize entities - //specs_world.maintain(); + //self.maintain(); } fn apply_sync_package(&mut self, package: SyncPackage

) { @@ -152,12 +147,7 @@ impl WorldSyncExt for specs::World { // Attempt to delete entities that were marked for deletion for entity_uid in deleted_entities { - let entity = self - .read_resource::() - .retrieve_entity_internal(entity_uid); - if let Some(entity) = entity { - let _ = self.delete_entity(entity); - } + self.delete_entity_and_clear_from_uid_allocator(entity_uid); } } fn apply_res_sync_package(&mut self, package: ResSyncPackage) { @@ -174,5 +164,15 @@ fn create_entity_with_uid(specs_world: &mut specs::World, entity_uid: u64) -> sp .read_resource::() .retrieve_entity_internal(entity_uid); - existing_entity.unwrap_or_else(|| specs_world.create_entity_synced().build()) + match existing_entity { + Some(entity) => entity, + None => { + let entity_builder = specs_world.create_entity(); + let uid = entity_builder + .world + .write_resource::() + .allocate(entity_builder.entity, Some(entity_uid)); + entity_builder.with(uid).build() + } + } } diff --git a/common/src/sync/track.rs b/common/src/sync/track.rs index 2f27551b83..f1137f5d28 100644 --- a/common/src/sync/track.rs +++ b/common/src/sync/track.rs @@ -76,6 +76,7 @@ where specs::storage::ComponentEvent::Modified(id) => { // We don't care about modification if the component was just added or was // removed + // Could potentially remove since this should theoretically never occur... if !self.removed.contains(*id) && !self.inserted.contains(*id) { self.modified.add(*id); } diff --git a/common/src/sync/uid.rs b/common/src/sync/uid.rs index e7dc31b241..87f69e87ed 100644 --- a/common/src/sync/uid.rs +++ b/common/src/sync/uid.rs @@ -56,6 +56,10 @@ impl UidAllocator { mapping: HashMap::new(), } } + // Useful for when a single entity is deleted because it doesn't reconstruct the entire hashmap + pub fn remove_entity(&mut self, id: u64) -> Option { + self.mapping.remove(&id) + } } impl Default for UidAllocator { @@ -69,14 +73,14 @@ impl MarkerAllocator for UidAllocator { let id = id.unwrap_or_else(|| { let id = self.index; self.index += 1; - self.mapping.insert(id, entity); id }); + self.mapping.insert(id, entity); Uid(id) } fn retrieve_entity_internal(&self, id: u64) -> Option { - self.mapping.get(&id).cloned() + self.mapping.get(&id).copied() } fn maintain(&mut self, entities: &EntitiesRes, storage: &ReadStorage) { diff --git a/server/src/cmd.rs b/server/src/cmd.rs index b3303cac21..22606d8ee2 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -21,6 +21,7 @@ use vek::*; use world::util::Sampler; use lazy_static::lazy_static; +use log::error; use scan_fmt::{scan_fmt, scan_fmt_some}; /// Struct representing a command that a user can run from server chat. @@ -1118,7 +1119,9 @@ fn handle_remove_lights( let size = to_delete.len(); for entity in to_delete { - let _ = server.state.ecs_mut().delete_entity(entity); + if let Err(err) = server.state.delete_entity_recorded(entity) { + error!("Failed to delete light: {:?}", err); + } } server.notify_client( diff --git a/server/src/lib.rs b/server/src/lib.rs index 7e2f5729d0..d49d4345b3 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -19,7 +19,7 @@ use crate::{ chunk_generator::ChunkGenerator, client::{Client, RegionSubscription}, cmd::CHAT_COMMANDS, - sys::sentinel::{TrackedComps, TrackedResources}, + sys::sentinel::{DeletedEntities, TrackedComps, TrackedResources}, }; use common::{ assets, comp, @@ -32,7 +32,7 @@ use common::{ terrain::{block::Block, TerrainChunkSize, TerrainGrid}, vol::{ReadVol, RectVolSize, Vox}, }; -use log::debug; +use log::{debug, error}; use metrics::ServerMetrics; use rand::Rng; use specs::{ @@ -166,6 +166,8 @@ impl Server { // Register trackers sys::sentinel::register_trackers(&mut state.ecs_mut()); + state.ecs_mut().add_resource(DeletedEntities::default()); + let this = Self { state, world: Arc::new(world), @@ -309,8 +311,6 @@ impl Server { let server_settings = &self.server_settings; - let mut todo_remove = None; - match event { ServerEvent::Explosion { pos, radius } => { const RAYS: usize = 500; @@ -380,19 +380,20 @@ impl Server { } ServerEvent::Destroy { entity, cause } => { - let ecs = state.ecs(); // Chat message - if let Some(player) = ecs.read_storage::().get(entity) { + if let Some(player) = state.ecs().read_storage::().get(entity) { let msg = if let comp::HealthSource::Attack { by } = cause { - ecs.entity_from_uid(by.into()).and_then(|attacker| { - ecs.read_storage::().get(attacker).map( - |attacker_alias| { + state.ecs().entity_from_uid(by.into()).and_then(|attacker| { + state + .ecs() + .read_storage::() + .get(attacker) + .map(|attacker_alias| { format!( "{} was killed by {}", &player.alias, &attacker_alias.alias ) - }, - ) + }) }) } else { None @@ -402,28 +403,44 @@ impl Server { state.notify_registered_clients(ServerMsg::kill(msg)); } - // Give EXP to the killer if entity had stats - let mut stats = ecs.write_storage::(); - - if let Some(entity_stats) = stats.get(entity).cloned() { - if let comp::HealthSource::Attack { by } = cause { - ecs.entity_from_uid(by.into()).map(|attacker| { - if let Some(attacker_stats) = stats.get_mut(attacker) { - // TODO: Discuss whether we should give EXP by Player Killing or not. - attacker_stats - .exp - .change_by((entity_stats.level.level() * 10) as i64); - } - }); + { + // Give EXP to the killer if entity had stats + let mut stats = state.ecs().write_storage::(); + if let Some(entity_stats) = stats.get(entity).cloned() { + if let comp::HealthSource::Attack { by } = cause { + state.ecs().entity_from_uid(by.into()).map(|attacker| { + if let Some(attacker_stats) = stats.get_mut(attacker) { + // TODO: Discuss whether we should give EXP by Player Killing or not. + attacker_stats + .exp + .change_by((entity_stats.level.level() * 10) as i64); + } + }); + } } } - if let Some(client) = ecs.write_storage::().get_mut(entity) { - let _ = ecs.write_storage().insert(entity, comp::Vel(Vec3::zero())); - let _ = ecs.write_storage().insert(entity, comp::ForceUpdate); + // This sucks + let mut remove = false; + + if let Some(client) = state.ecs().write_storage::().get_mut(entity) { + let _ = state + .ecs() + .write_storage() + .insert(entity, comp::Vel(Vec3::zero())); + let _ = state + .ecs() + .write_storage() + .insert(entity, comp::ForceUpdate); client.force_state(ClientState::Dead); } else { - todo_remove = Some(entity.clone()); + remove = true; + } + + if remove { + if let Err(err) = state.delete_entity_recorded(entity) { + error!("Failed to delete destroyed entity: {:?}", err); + } } } @@ -457,7 +474,9 @@ impl Server { }; if let Some(item_entity) = item_entity { - let _ = state.ecs_mut().delete_entity(item_entity); + if let Err(err) = state.delete_entity_recorded(item_entity) { + error!("Failed to delete picked up item entity: {:?}", err); + } } state.write_component(entity, comp::InventoryUpdate); @@ -728,7 +747,7 @@ impl Server { main, &server_settings, ); - Self::initialize_region_subscription(state, entity); + sys::subscription::initialize_region_subscription(state.ecs(), entity); } ServerEvent::CreateNpc { @@ -746,8 +765,8 @@ impl Server { } ServerEvent::ClientDisconnect(entity) => { - if let Err(err) = state.ecs_mut().delete_entity(entity) { - debug!("Failed to delete disconnected client: {:?}", err); + if let Err(err) = state.delete_entity_recorded(entity) { + error!("Failed to delete disconnected client: {:?}", err); } frontend_events.push(Event::ClientDisconnected { entity }); @@ -761,11 +780,6 @@ impl Server { chat_commands.push((entity, cmd)); } } - - // TODO: is this needed? - if let Some(entity) = todo_remove { - let _ = state.ecs_mut().delete_entity(entity); - } } // Generate requested chunks. @@ -825,7 +839,7 @@ impl Server { frontend_events.append(&mut self.handle_new_connections()?); let before_tick_4 = Instant::now(); - // 4) Tick the client's LocalState. + // 4) Tick the server's LocalState. self.state.tick(dt, sys::add_server_systems); let before_handle_events = Instant::now(); @@ -856,7 +870,9 @@ impl Server { .collect::>() }; for entity in to_delete { - let _ = self.state.ecs_mut().delete_entity(entity); + if let Err(err) = self.state.delete_entity_recorded(entity) { + error!("Failed to delete agent outside the terrain: {:?}", err); + } } let before_tick_7 = Instant::now(); @@ -1004,83 +1020,6 @@ impl Server { Ok(frontend_events) } - /// Initialize region subscription - fn initialize_region_subscription(state: &mut State, entity: specs::Entity) { - let mut subscription = None; - - if let (Some(client_pos), Some(client_vd), Some(client)) = ( - state.ecs().read_storage::().get(entity), - state - .ecs() - .read_storage::() - .get(entity) - .map(|pl| pl.view_distance) - .and_then(|v| v), - state.ecs().write_storage::().get_mut(entity), - ) { - use common::region::RegionMap; - - let fuzzy_chunk = (Vec2::::from(client_pos.0)) - .map2(TerrainChunkSize::RECT_SIZE, |e, sz| e as i32 / sz as i32); - let chunk_size = TerrainChunkSize::RECT_SIZE.reduce_max() as f32; - let regions = common::region::regions_in_vd( - client_pos.0, - (client_vd as f32 * chunk_size) as f32 - + (client::CHUNK_FUZZ as f32 + chunk_size) * 2.0f32.sqrt(), - ); - - for (_, region) in state - .ecs() - .read_resource::() - .iter() - .filter(|(key, _)| regions.contains(key)) - { - // Sync physics of all entities in this region - for (&uid, &pos, vel, ori, character_state, _) in ( - &state.ecs().read_storage::(), - &state.ecs().read_storage::(), // We assume all these entities have a position - state.ecs().read_storage::().maybe(), - state.ecs().read_storage::().maybe(), - state.ecs().read_storage::().maybe(), - region.entities(), - ) - .join() - { - client.notify(ServerMsg::EntityPos { - entity: uid.into(), - pos, - }); - if let Some(vel) = vel.copied() { - client.notify(ServerMsg::EntityVel { - entity: uid.into(), - vel, - }); - } - if let Some(ori) = ori.copied() { - client.notify(ServerMsg::EntityOri { - entity: uid.into(), - ori, - }); - } - if let Some(character_state) = character_state.copied() { - client.notify(ServerMsg::EntityCharacterState { - entity: uid.into(), - character_state, - }); - } - } - } - - subscription = Some(RegionSubscription { - fuzzy_chunk, - regions, - }); - } - if let Some(subscription) = subscription { - state.write_component(entity, subscription); - } - } - pub fn notify_client(&self, entity: EcsEntity, msg: ServerMsg) { if let Some(client) = self.state.ecs().write_storage::().get_mut(entity) { client.notify(msg) @@ -1142,6 +1081,10 @@ trait StateExt { stats: comp::Stats, body: comp::Body, ) -> EcsEntityBuilder; + fn delete_entity_recorded( + &mut self, + entity: EcsEntity, + ) -> Result<(), specs::error::WrongGeneration>; } impl StateExt for State { @@ -1202,4 +1145,28 @@ impl StateExt for State { client.notify(msg.clone()) } } + + fn delete_entity_recorded( + &mut self, + entity: EcsEntity, + ) -> Result<(), specs::error::WrongGeneration> { + let (maybe_uid, maybe_pos) = ( + self.ecs().read_storage::().get(entity).copied(), + self.ecs().read_storage::().get(entity).copied(), + ); + let res = self.ecs_mut().delete_entity(entity); + if res.is_ok() { + if let (Some(uid), Some(pos)) = (maybe_uid, maybe_pos) { + let region_key = self + .ecs() + .read_resource::() + .find_region(entity, pos.0) + .expect("Failed to find region containing entity during entity deletion"); + self.ecs() + .write_resource::() + .record_deleted_entity(uid, region_key); + } + } + res + } } diff --git a/server/src/sys/entity_sync.rs b/server/src/sys/entity_sync.rs index 05611359b6..7bb94fb3a5 100644 --- a/server/src/sys/entity_sync.rs +++ b/server/src/sys/entity_sync.rs @@ -1,5 +1,5 @@ use super::{ - sentinel::{ReadTrackers, TrackedComps, TrackedResources}, + sentinel::{DeletedEntities, ReadTrackers, TrackedComps, TrackedResources}, SysTimer, }; use crate::{ @@ -38,6 +38,7 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Client>, WriteStorage<'a, ForceUpdate>, WriteStorage<'a, InventoryUpdate>, + Write<'a, DeletedEntities>, TrackedComps<'a>, ReadTrackers<'a>, TrackedResources<'a>, @@ -64,8 +65,9 @@ impl<'a> System<'a> for Sys { mut clients, mut force_updates, mut inventory_updates, + mut deleted_entities, tracked_comps, - read_trackers, + trackers, tracked_resources, ): Self::SystemData, ) { @@ -86,6 +88,8 @@ impl<'a> System<'a> for Sys { // Sync physics // via iterating through regions for (key, region) in region_map.iter() { + // Assemble subscriber list for this region by iterating through clients and checking + // if they are subscribed to this region let mut subscribers = (&mut clients, &entities, &subscriptions, &positions) .join() .filter_map(|(client, entity, subscription, pos)| { @@ -100,6 +104,10 @@ impl<'a> System<'a> for Sys { for event in region.events() { match event { RegionEvent::Entered(id, maybe_key) => { + // Don't process newly created entities here (redundant network messages) + if trackers.uid.inserted().contains(*id) { + continue; + } let entity = entities.entity(*id); if let Some((uid, pos, vel, ori, character_state)) = uids.get(entity).and_then(|uid| { @@ -156,8 +164,15 @@ impl<'a> System<'a> for Sys { } // Sync tracked components + // Get deleted entities in this region from DeletedEntities let sync_msg = ServerMsg::EcsSync( - read_trackers.create_sync_package(&tracked_comps, region.entities()), + trackers.create_sync_package( + &tracked_comps, + region.entities(), + deleted_entities + .take_deleted_in_region(key) + .unwrap_or_else(|| Vec::new()), + ), ); for (client, _, _, _) in &mut subscribers { client.notify(sync_msg.clone()); @@ -169,9 +184,9 @@ impl<'a> System<'a> for Sys { force_update: Option<&ForceUpdate>, throttle: bool| { for (client, _, client_entity, client_pos) in &mut subscribers { - let update = if client_entity == &entity && force_update.is_none() { - // Don't send client physics update about itself - false + let update = if client_entity == &entity { + // Don't send client physics updates about itself unless force update is set + force_update.is_some() } else if !throttle { // Update rate not thottled by distance true @@ -283,6 +298,25 @@ impl<'a> System<'a> for Sys { } } + // Handle entity deletion in regions that don't exist in RegionMap (theoretically none) + for (region_key, deleted) in deleted_entities.take_remaining_deleted() { + for client in + (&mut clients, &subscriptions) + .join() + .filter_map(|(client, subscription)| { + if client.is_ingame() && subscription.regions.contains(®ion_key) { + Some(client) + } else { + None + } + }) + { + for uid in &deleted { + client.notify(ServerMsg::DeleteEntity(*uid)); + } + } + } + // TODO: Sync clients that don't have a position? // Sync inventories diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs index e4cdde00b2..6537e4e01a 100644 --- a/server/src/sys/message.rs +++ b/server/src/sys/message.rs @@ -1,7 +1,7 @@ use super::SysTimer; use crate::{auth_provider::AuthProvider, client::Client, CLIENT_TIMEOUT}; use common::{ - comp::{Admin, Body, CanBuild, Controller, Ori, Player, Pos, Vel}, + comp::{Admin, Body, CanBuild, Controller, ForceUpdate, Ori, Player, Pos, Vel}, event::{EventBus, ServerEvent}, msg::{validate_chat_msg, ChatMsgValidationError, MAX_BYTES_CHAT_MSG}, msg::{ClientMsg, ClientState, RequestStateError, ServerMsg}, @@ -25,6 +25,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Body>, ReadStorage<'a, CanBuild>, ReadStorage<'a, Admin>, + ReadStorage<'a, ForceUpdate>, WriteExpect<'a, AuthProvider>, Write<'a, BlockChange>, WriteStorage<'a, Pos>, @@ -46,6 +47,7 @@ impl<'a> System<'a> for Sys { bodies, can_build, admins, + force_updates, mut accounts, mut block_changes, mut positions, @@ -218,9 +220,11 @@ impl<'a> System<'a> for Sys { }, ClientMsg::PlayerPhysics { pos, vel, ori } => match client.client_state { ClientState::Character => { - let _ = positions.insert(entity, pos); - let _ = velocities.insert(entity, vel); - let _ = orientations.insert(entity, ori); + if force_updates.get(entity).is_none() { + let _ = positions.insert(entity, pos); + let _ = velocities.insert(entity, vel); + let _ = orientations.insert(entity, ori); + } } // Only characters can send positions. _ => client.error_state(RequestStateError::Impossible), diff --git a/server/src/sys/sentinel.rs b/server/src/sys/sentinel.rs index a3a0f3ba33..381f567ea7 100644 --- a/server/src/sys/sentinel.rs +++ b/server/src/sys/sentinel.rs @@ -5,17 +5,19 @@ use common::{ Projectile, Scale, Stats, Sticky, }, msg::{EcsCompPacket, EcsResPacket}, - state::{Time, TimeOfDay}, + state::TimeOfDay, sync::{ CompPacket, EntityPackage, ResSyncPackage, StatePackage, SyncPackage, Uid, UpdateTracker, WorldSyncExt, }, }; +use hashbrown::HashMap; use shred_derive::SystemData; use specs::{ Entity as EcsEntity, Join, ReadExpect, ReadStorage, System, World, Write, WriteExpect, }; use std::ops::Deref; +use vek::*; /// Always watching /// This system will monitor specific components for insertion, removal, and modification @@ -39,20 +41,20 @@ impl<'a> System<'a> for Sys { // Probably more difficult than it needs to be :p #[derive(SystemData)] pub struct TrackedComps<'a> { - uid: ReadStorage<'a, Uid>, - body: ReadStorage<'a, Body>, - player: ReadStorage<'a, Player>, - stats: ReadStorage<'a, Stats>, - can_build: ReadStorage<'a, CanBuild>, - light_emitter: ReadStorage<'a, LightEmitter>, - item: ReadStorage<'a, Item>, - scale: ReadStorage<'a, Scale>, - mounting: ReadStorage<'a, Mounting>, - mount_state: ReadStorage<'a, MountState>, - mass: ReadStorage<'a, Mass>, - sticky: ReadStorage<'a, Sticky>, - gravity: ReadStorage<'a, Gravity>, - projectile: ReadStorage<'a, Projectile>, + pub uid: ReadStorage<'a, Uid>, + pub body: ReadStorage<'a, Body>, + pub player: ReadStorage<'a, Player>, + pub stats: ReadStorage<'a, Stats>, + pub can_build: ReadStorage<'a, CanBuild>, + pub light_emitter: ReadStorage<'a, LightEmitter>, + pub item: ReadStorage<'a, Item>, + pub scale: ReadStorage<'a, Scale>, + pub mounting: ReadStorage<'a, Mounting>, + pub mount_state: ReadStorage<'a, MountState>, + pub mass: ReadStorage<'a, Mass>, + pub sticky: ReadStorage<'a, Sticky>, + pub gravity: ReadStorage<'a, Gravity>, + pub projectile: ReadStorage<'a, Projectile>, } impl<'a> TrackedComps<'a> { pub fn create_entity_package(&self, entity: EcsEntity) -> EntityPackage { @@ -121,115 +123,53 @@ impl<'a> TrackedComps<'a> { } #[derive(SystemData)] pub struct ReadTrackers<'a> { - uid: ReadExpect<'a, UpdateTracker>, - body: ReadExpect<'a, UpdateTracker>, - player: ReadExpect<'a, UpdateTracker>, - stats: ReadExpect<'a, UpdateTracker>, - can_build: ReadExpect<'a, UpdateTracker>, - light_emitter: ReadExpect<'a, UpdateTracker>, - item: ReadExpect<'a, UpdateTracker>, - scale: ReadExpect<'a, UpdateTracker>, - mounting: ReadExpect<'a, UpdateTracker>, - mount_state: ReadExpect<'a, UpdateTracker>, - mass: ReadExpect<'a, UpdateTracker>, - sticky: ReadExpect<'a, UpdateTracker>, - gravity: ReadExpect<'a, UpdateTracker>, - projectile: ReadExpect<'a, UpdateTracker>, + pub uid: ReadExpect<'a, UpdateTracker>, + pub body: ReadExpect<'a, UpdateTracker>, + pub player: ReadExpect<'a, UpdateTracker>, + pub stats: ReadExpect<'a, UpdateTracker>, + pub can_build: ReadExpect<'a, UpdateTracker>, + pub light_emitter: ReadExpect<'a, UpdateTracker>, + pub item: ReadExpect<'a, UpdateTracker>, + pub scale: ReadExpect<'a, UpdateTracker>, + pub mounting: ReadExpect<'a, UpdateTracker>, + pub mount_state: ReadExpect<'a, UpdateTracker>, + pub mass: ReadExpect<'a, UpdateTracker>, + pub sticky: ReadExpect<'a, UpdateTracker>, + pub gravity: ReadExpect<'a, UpdateTracker>, + pub projectile: ReadExpect<'a, UpdateTracker>, } impl<'a> ReadTrackers<'a> { pub fn create_sync_package( &self, comps: &TrackedComps, filter: impl Join + Copy, + deleted_entities: Vec, ) -> SyncPackage { - SyncPackage::new(&comps.uid, &self.uid, filter) + SyncPackage::new(&comps.uid, &self.uid, filter, deleted_entities) + .with_component(&comps.uid, self.body.deref(), &comps.body, filter) + .with_component(&comps.uid, self.player.deref(), &comps.player, filter) + .with_component(&comps.uid, self.stats.deref(), &comps.stats, filter) + .with_component(&comps.uid, self.can_build.deref(), &comps.can_build, filter) .with_component( &comps.uid, - &self.uid, - self.body.deref(), - &comps.body, - filter, - ) - .with_component( - &comps.uid, - &self.uid, - self.player.deref(), - &comps.player, - filter, - ) - .with_component( - &comps.uid, - &self.uid, - self.stats.deref(), - &comps.stats, - filter, - ) - .with_component( - &comps.uid, - &self.uid, - self.can_build.deref(), - &comps.can_build, - filter, - ) - .with_component( - &comps.uid, - &self.uid, self.light_emitter.deref(), &comps.light_emitter, filter, ) + .with_component(&comps.uid, self.item.deref(), &comps.item, filter) + .with_component(&comps.uid, self.scale.deref(), &comps.scale, filter) + .with_component(&comps.uid, self.mounting.deref(), &comps.mounting, filter) .with_component( &comps.uid, - &self.uid, - self.item.deref(), - &comps.item, - filter, - ) - .with_component( - &comps.uid, - &self.uid, - self.scale.deref(), - &comps.scale, - filter, - ) - .with_component( - &comps.uid, - &self.uid, - self.mounting.deref(), - &comps.mounting, - filter, - ) - .with_component( - &comps.uid, - &self.uid, self.mount_state.deref(), &comps.mount_state, filter, ) + .with_component(&comps.uid, self.mass.deref(), &comps.mass, filter) + .with_component(&comps.uid, self.sticky.deref(), &comps.sticky, filter) + .with_component(&comps.uid, self.gravity.deref(), &comps.gravity, filter) .with_component( &comps.uid, - &self.uid, - self.mass.deref(), - &comps.mass, - filter, - ) - .with_component( - &comps.uid, - &self.uid, - self.sticky.deref(), - &comps.sticky, - filter, - ) - .with_component( - &comps.uid, - &self.uid, - self.gravity.deref(), - &comps.gravity, - filter, - ) - .with_component( - &comps.uid, - &self.uid, self.projectile.deref(), &comps.projectile, filter, @@ -292,19 +232,46 @@ pub fn register_trackers(world: &mut World) { #[derive(SystemData)] pub struct TrackedResources<'a> { - time: ReadExpect<'a, Time>, time_of_day: ReadExpect<'a, TimeOfDay>, } impl<'a> TrackedResources<'a> { pub fn create_res_sync_package(&self) -> ResSyncPackage { - ResSyncPackage::new() - .with_res(self.time.deref()) - .with_res(self.time_of_day.deref()) + ResSyncPackage::new().with_res(self.time_of_day.deref()) } /// Create state package with resources included pub fn state_package(&self) -> StatePackage { - StatePackage::new() - .with_res(self.time.deref()) - .with_res(self.time_of_day.deref()) + StatePackage::new().with_res(self.time_of_day.deref()) + } +} + +/// Deleted entities grouped by region +pub struct DeletedEntities { + map: HashMap, Vec>, +} + +impl Default for DeletedEntities { + fn default() -> Self { + Self { + map: HashMap::new(), + } + } +} + +impl DeletedEntities { + pub fn record_deleted_entity(&mut self, uid: Uid, region_key: Vec2) { + self.map + .entry(region_key) + .or_insert(Vec::new()) + .push(uid.into()); + } + pub fn take_deleted_in_region(&mut self, key: Vec2) -> Option> { + self.map.remove(&key) + } + pub fn get_deleted_in_region(&mut self, key: Vec2) -> Option<&Vec> { + self.map.get(&key) + } + pub fn take_remaining_deleted(&mut self) -> Vec<(Vec2, Vec)> { + // TODO: don't allocate + self.map.drain().collect() } } diff --git a/server/src/sys/subscription.rs b/server/src/sys/subscription.rs index d4617fd499..a37d4057b7 100644 --- a/server/src/sys/subscription.rs +++ b/server/src/sys/subscription.rs @@ -1,4 +1,7 @@ -use super::{sentinel::TrackedComps, SysTimer}; +use super::{ + sentinel::{DeletedEntities, TrackedComps}, + SysTimer, +}; use crate::client::{self, Client, RegionSubscription}; use common::{ comp::{CharacterState, Ori, Player, Pos, Vel}, @@ -8,7 +11,10 @@ use common::{ terrain::TerrainChunkSize, vol::RectVolSize, }; -use specs::{Entities, Join, ReadExpect, ReadStorage, System, Write, WriteStorage}; +use log::{debug, error}; +use specs::{ + Entities, Join, ReadExpect, ReadStorage, System, SystemData, World, Write, WriteStorage, +}; use vek::*; /// This system will update region subscriptions based on client positions @@ -26,6 +32,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Player>, WriteStorage<'a, Client>, WriteStorage<'a, RegionSubscription>, + Write<'a, DeletedEntities>, TrackedComps<'a>, ); @@ -43,6 +50,7 @@ impl<'a> System<'a> for Sys { players, mut clients, mut subscriptions, + mut deleted_entities, tracked_comps, ): Self::SystemData, ) { @@ -68,8 +76,15 @@ impl<'a> System<'a> for Sys { &entities, ) .join() - .filter_map(|(c, s, pos, player, e)| player.view_distance.map(|v| (c, s, pos, v, e))) + .filter_map(|(client, s, pos, player, e)| { + if client.is_ingame() { + player.view_distance.map(|v| (client, s, pos, v, e)) + } else { + None + } + }) { + // Calculate current chunk let chunk = (Vec2::::from(pos.0)) .map2(TerrainChunkSize::RECT_SIZE, |e, sz| e as i32 / sz as i32); // Only update regions when moving to a new chunk @@ -87,7 +102,7 @@ impl<'a> System<'a> for Sys { .reduce_or() { // Update current chunk - subscription.fuzzy_chunk = (Vec2::::from(pos.0)) + subscription.fuzzy_chunk = dbg!(Vec2::::from(pos.0)) .map2(TerrainChunkSize::RECT_SIZE, |e, sz| e as i32 / sz as i32); // Use the largest side length as our chunk size let chunk_size = TerrainChunkSize::RECT_SIZE.reduce_max() as f32; @@ -108,19 +123,24 @@ impl<'a> System<'a> for Sys { // Iterate through regions to remove for key in regions_to_remove.drain(..) { - // Remove region from this clients set of subscribed regions + // Remove region from this client's set of subscribed regions subscription.regions.remove(&key); // Tell the client to delete the entities in that region if it exists in the RegionMap if let Some(region) = region_map.get(key) { - // Process entity left events since they won't be processed during phsyics sync because this region is no longer subscribed to + // Process entity left events since they won't be processed during entity sync because this region is no longer subscribed to + // TODO: consider changing system ordering?? for event in region.events() { match event { RegionEvent::Entered(_, _) => {} // These don't need to be processed because this region is being thrown out anyway RegionEvent::Left(id, maybe_key) => { // Lookup UID for entity + // Doesn't overlap with entity deletion in sync packages + // because the uid would not be available if the entity was + // deleted if let Some(&uid) = uids.get(entities.entity(*id)) { if !maybe_key .as_ref() + // Don't need to check that this isn't also in the regions to remove since the entity will be removed when we get to that one .map(|key| subscription.regions.contains(key)) .unwrap_or(false) { @@ -130,10 +150,19 @@ impl<'a> System<'a> for Sys { } } } + // Tell client to delete entities in the region for (&uid, _) in (&uids, region.entities()).join() { - client.notify(ServerMsg::DeleteEntity(uid.into())) + client.notify(ServerMsg::DeleteEntity(uid.into())); } } + // Send deleted entities since they won't be processed for this client in entity sync + for uid in deleted_entities + .get_deleted_in_region(key) + .iter() + .flat_map(|v| v.iter()) + { + client.notify(ServerMsg::DeleteEntity(*uid)); + } } for key in regions_in_vd( @@ -141,8 +170,10 @@ impl<'a> System<'a> for Sys { (vd as f32 * chunk_size) + (client::CHUNK_FUZZ as f32 + chunk_size) * 2.0f32.sqrt(), ) { - // Send client intial info about the entities in this region - if subscription.regions.insert(key) { + // Send client intial info about the entities in this region if it was not + // already within the set of subscribed regions + if subscription.regions.insert(key.clone()) { + let mut counter = 0; if let Some(region) = region_map.get(key) { for (uid, pos, vel, ori, character_state, _, entity) in ( &uids, @@ -156,6 +187,7 @@ impl<'a> System<'a> for Sys { .join() .filter(|(_, _, _, _, _, _, e)| *e != client_entity) { + counter += 1; // Send message to create entity and tracked components client.notify(ServerMsg::CreateEntity( tracked_comps.create_entity_package(entity), @@ -179,3 +211,69 @@ impl<'a> System<'a> for Sys { timer.end(); } } + +/// Initialize region subscription +pub fn initialize_region_subscription(world: &World, entity: specs::Entity) { + if let (Some(client_pos), Some(client_vd), Some(client)) = ( + world.read_storage::().get(entity), + world + .read_storage::() + .get(entity) + .map(|pl| pl.view_distance) + .and_then(|v| v), + world.write_storage::().get_mut(entity), + ) { + let fuzzy_chunk = (Vec2::::from(client_pos.0)) + .map2(TerrainChunkSize::RECT_SIZE, |e, sz| e as i32 / sz as i32); + let chunk_size = TerrainChunkSize::RECT_SIZE.reduce_max() as f32; + let regions = common::region::regions_in_vd( + client_pos.0, + (client_vd as f32 * chunk_size) as f32 + + (client::CHUNK_FUZZ as f32 + chunk_size) * 2.0f32.sqrt(), + ); + + let region_map = world.read_resource::(); + let tracked_comps = TrackedComps::fetch(&world.res); + for key in ®ions { + if let Some(region) = region_map.get(*key) { + for (uid, pos, vel, ori, character_state, _, entity) in ( + &tracked_comps.uid, + &world.read_storage::(), // We assume all these entities have a position + world.read_storage::().maybe(), + world.read_storage::().maybe(), + world.read_storage::().maybe(), + region.entities(), + &world.entities(), + ) + .join() + { + // Send message to create entity and tracked components + client.notify(ServerMsg::CreateEntity( + tracked_comps.create_entity_package(entity), + )); + // Send message to create physics components + super::entity_sync::send_initial_unsynced_components( + client, + uid, + pos, + vel, + ori, + character_state, + ); + } + } + } + + if let Err(err) = world.write_storage().insert( + entity, + RegionSubscription { + fuzzy_chunk, + regions, + }, + ) { + error!("Failed to insert region subscription component: {:?}", err); + } + } else { + debug!("Failed to initialize region subcription. Couldn't retrieve all the neccesary components on the provided entity"); + } +} diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 3c4308356e..da4b978067 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -112,6 +112,7 @@ widget_ids! { velocity, loaded_distance, time, + entity_count, // Game Version version, @@ -872,11 +873,19 @@ impl Hud { .font_id(self.fonts.cyri) .font_size(14) .set(self.ids.time, ui_widgets); + // Number of entities + let entity_count = client.state().ecs().entities().join().count(); + Text::new(&format!("Entity count: {}", entity_count)) + .color(TEXT_COLOR) + .down_from(self.ids.time, 5.0) + .font_id(self.fonts.cyri) + .font_size(14) + .set(self.ids.entity_count, ui_widgets); // Help Window Text::new("Press 'F1' to show Keybindings") .color(TEXT_COLOR) - .down_from(self.ids.time, 5.0) + .down_from(self.ids.entity_count, 5.0) .font_id(self.fonts.cyri) .font_size(14) .set(self.ids.help_info, ui_widgets); diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index ce7a484676..2ade596ad0 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -324,7 +324,7 @@ impl WorldSim { let logistic_cdf = |x: f64| (x / logistic_2_base).tanh() * 0.5 + 0.5; let erosion_pow = 2.0; - let n_steps = 100; + let n_steps = 7; let erosion_factor = |x: f64| logistic_cdf(erosion_pow * logit(x)); let alt = do_erosion( 0.0,