Sync entities for group members regardless of distance.

This makes them visible on the map and makes their health visible on the left.
This commit is contained in:
Avi Weinstock 2021-04-29 14:05:32 -04:00
parent 287a1a9b94
commit 8242a49681
3 changed files with 62 additions and 26 deletions

View File

@ -50,6 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- You can now block and parry with melee weapons - You can now block and parry with melee weapons
- Lift is now calculated for gliders based on dimensions (currently same for all) - Lift is now calculated for gliders based on dimensions (currently same for all)
- Specific music tracks can now play exclusively in towns. - Specific music tracks can now play exclusively in towns.
- Group members are now visible on the map regardless of distance.
### Changed ### Changed

View File

@ -15,7 +15,7 @@ use tracing::{error, warn};
// - clients don't know which pets are theirs (could be easy to solve by // - clients don't know which pets are theirs (could be easy to solve by
// putting owner uid in Role::Pet) // putting owner uid in Role::Pet)
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Hash, Eq, Serialize, Deserialize)]
pub struct Group(u32); pub struct Group(u32);
// TODO: Hack // TODO: Hack

View File

@ -5,7 +5,7 @@ use crate::{
Tick, Tick,
}; };
use common::{ use common::{
comp::{Collider, ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Player, Pos, Vel}, comp::{Collider, ForceUpdate, Group, Inventory, InventoryUpdate, Last, Ori, Player, Pos, Vel},
outcome::Outcome, outcome::Outcome,
region::{Event as RegionEvent, RegionMap}, region::{Event as RegionEvent, RegionMap},
resources::{PlayerPhysicsSettings, TimeOfDay}, resources::{PlayerPhysicsSettings, TimeOfDay},
@ -15,8 +15,9 @@ use common::{
}; };
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
use common_net::{msg::ServerGeneral, sync::CompSyncPackage}; use common_net::{msg::ServerGeneral, sync::CompSyncPackage};
use hashbrown::HashMap;
use itertools::Either; use itertools::Either;
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, Write, WriteStorage}; use specs::{Entities, Entity, Join, Read, ReadExpect, ReadStorage, Write, WriteStorage};
use vek::*; use vek::*;
/// This system will send physics updates to the client /// This system will send physics updates to the client
@ -37,6 +38,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, RegionSubscription>, ReadStorage<'a, RegionSubscription>,
ReadStorage<'a, Presence>, ReadStorage<'a, Presence>,
ReadStorage<'a, Collider>, ReadStorage<'a, Collider>,
ReadStorage<'a, Group>,
WriteStorage<'a, Last<Pos>>, WriteStorage<'a, Last<Pos>>,
WriteStorage<'a, Last<Vel>>, WriteStorage<'a, Last<Vel>>,
WriteStorage<'a, Last<Ori>>, WriteStorage<'a, Last<Ori>>,
@ -70,6 +72,7 @@ impl<'a> System<'a> for Sys {
subscriptions, subscriptions,
presences, presences,
colliders, colliders,
groups,
mut last_pos, mut last_pos,
mut last_vel, mut last_vel,
mut last_ori, mut last_ori,
@ -86,11 +89,12 @@ impl<'a> System<'a> for Sys {
) { ) {
let tick = tick.0; let tick = tick.0;
// To send entity updates // To send entity updates
// 1. Iterate through regions // 1. Build an efficient index for group membership
// 2. Iterate through region subscribers (ie clients) // 2. Iterate through regions
// 3. Iterate through region subscribers (ie clients)
// - Collect a list of entity ids for clients who are subscribed to this // - Collect a list of entity ids for clients who are subscribed to this
// region (hash calc to check each) // region (hash calc to check each)
// 3. Iterate through events from that region // 4. Iterate through events from that region
// - For each entity entered event, iterate through the client list and // - For each entity entered event, iterate through the client list and
// check if they are subscribed to the source (hash calc per subscribed // check if they are subscribed to the source (hash calc per subscribed
// client per entity event), if not subscribed to the source send a entity // client per entity event), if not subscribed to the source send a entity
@ -98,9 +102,18 @@ impl<'a> System<'a> for Sys {
// - For each entity left event, iterate through the client list and check // - For each entity left event, iterate through the client list and check
// if they are subscribed to the destination (hash calc per subscribed // if they are subscribed to the destination (hash calc per subscribed
// client per entity event) // client per entity event)
// 4. Iterate through entities in that region // 5. Iterate through clients in that region
// 5. Inform clients of the component changes for that entity // 6. Inform clients of component changes for group members
// - Throttle update rate base on distance to each client // 7. Inform clients of the component changes for each entity within the region
// - Throttle update rate base on distance to each entity
// Build group index
let mut group_index: HashMap<Group, Vec<Entity>> = HashMap::new();
// Only allocate for entities with clients, to avoid adding NPC groups to the
// index
for (entity, group, _) in (&entities, &groups, &clients).join() {
group_index.entry(*group).or_default().push(entity);
}
// Sync physics // Sync physics
// via iterating through regions // via iterating through regions
@ -113,11 +126,12 @@ impl<'a> System<'a> for Sys {
presences.maybe(), presences.maybe(),
&subscriptions, &subscriptions,
&positions, &positions,
groups.maybe(),
) )
.join() .join()
.filter_map(|(client, entity, presence, subscription, pos)| { .filter_map(|(client, entity, presence, subscription, pos, group)| {
if presence.is_some() && subscription.regions.contains(&key) { if presence.is_some() && subscription.regions.contains(&key) {
Some((client, &subscription.regions, entity, *pos)) Some((client, &subscription.regions, entity, *pos, group))
} else { } else {
None None
} }
@ -145,7 +159,7 @@ impl<'a> System<'a> for Sys {
}) })
{ {
let create_msg = ServerGeneral::CreateEntity(pkg); let create_msg = ServerGeneral::CreateEntity(pkg);
for (client, regions, client_entity, _) in &mut subscribers { for (client, regions, client_entity, _, _) in &mut subscribers {
if maybe_key if maybe_key
.as_ref() .as_ref()
.map(|key| !regions.contains(key)) .map(|key| !regions.contains(key))
@ -161,7 +175,7 @@ impl<'a> System<'a> for Sys {
RegionEvent::Left(id, maybe_key) => { RegionEvent::Left(id, maybe_key) => {
// Lookup UID for entity // Lookup UID for entity
if let Some(&uid) = uids.get(entities.entity(*id)) { if let Some(&uid) = uids.get(entities.entity(*id)) {
for (client, regions, _, _) in &mut subscribers { for (client, regions, _, _, _) in &mut subscribers {
if maybe_key if maybe_key
.as_ref() .as_ref()
.map(|key| !regions.contains(key)) .map(|key| !regions.contains(key))
@ -187,7 +201,7 @@ impl<'a> System<'a> for Sys {
// We lazily initializethe the synchronization messages in case there are no // We lazily initializethe the synchronization messages in case there are no
// clients. // clients.
let mut entity_comp_sync = Either::Left((entity_sync_package, comp_sync_package)); let mut entity_comp_sync = Either::Left((entity_sync_package, comp_sync_package));
for (client, _, _, _) in &mut subscribers { for (client, _, _, _, _) in &mut subscribers {
let msg = let msg =
entity_comp_sync.right_or_else(|(entity_sync_package, comp_sync_package)| { entity_comp_sync.right_or_else(|(entity_sync_package, comp_sync_package)| {
( (
@ -202,21 +216,42 @@ impl<'a> System<'a> for Sys {
entity_comp_sync = Either::Right(msg); entity_comp_sync = Either::Right(msg);
} }
for (client, _, client_entity, client_pos) in &mut subscribers { for (client, _, client_entity, client_pos, client_group) in &mut subscribers {
let mut comp_sync_package = CompSyncPackage::new(); let mut comp_sync_package = CompSyncPackage::new();
for (_, entity, &uid, (&pos, last_pos), vel, ori, force_update, collider) in ( // Unconditionally send updates for group members, even outside the region.
region.entities(), // Uses `get` instead of `join` to avoid O(n^2) behavior.
&entities, if let Some(members) = client_group.and_then(|g| group_index.get(g)) {
&uids, for entity in members.iter() {
(&positions, last_pos.mask().maybe()), if let Some(package) = tracked_comps.create_entity_package(
(&velocities, last_vel.mask().maybe()).maybe(), *entity,
(&orientations, last_vel.mask().maybe()).maybe(), positions.get(*entity).copied(),
force_updates.mask().maybe(), velocities.get(*entity).copied(),
colliders.maybe(), orientations.get(*entity).copied(),
) ) {
.join() client.send_fallible(ServerGeneral::CreateEntity(package));
}
}
}
for (_, entity, &uid, (&pos, last_pos), vel, ori, force_update, collider, group) in
(
region.entities(),
&entities,
&uids,
(&positions, last_pos.mask().maybe()),
(&velocities, last_vel.mask().maybe()).maybe(),
(&orientations, last_vel.mask().maybe()).maybe(),
force_updates.mask().maybe(),
colliders.maybe(),
groups.maybe(),
)
.join()
{ {
// Skip sending group updates here, since we sent them in the previous loop
if client_group.is_some() && *client_group == group {
continue;
}
// Decide how regularly to send physics updates. // Decide how regularly to send physics updates.
let send_now = if client_entity == &entity { let send_now = if client_entity == &entity {
let player_physics_setting = players let player_physics_setting = players