Skip to content

Commit

Permalink
Multiplayer: Handle peer left events (#733)
Browse files Browse the repository at this point in the history
  • Loading branch information
Indy2222 authored Sep 19, 2023
1 parent ab57a13 commit c373022
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 17 deletions.
2 changes: 1 addition & 1 deletion crates/messages/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
pub use game::{FromGame, JoinError, Readiness, ToGame};
pub use players::{
BorrowedFromPlayers, ChatMessage, ChatMessageError, EntityNet, FromPlayers, HealthDelta,
ToPlayers, MAX_CHAT_LEN,
NetEntityIndex, ToPlayers, MAX_CHAT_LEN,
};
pub use server::{FromServer, GameOpenError, ToServer};

Expand Down
28 changes: 25 additions & 3 deletions crates/messages/src/players/entity.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#[cfg(feature = "bevy")]
use bevy::ecs::entity::Entity;
use bincode::{Decode, Encode};
use de_types::player::Player;

/// Bevy ECS Entity derived identification of an entity.
#[derive(Clone, Copy, Debug, Encode, Decode, Hash, PartialEq, Eq)]
pub struct EntityNet {
player: Player,
index: u32,
index: NetEntityIndex,
}

impl EntityNet {
Expand All @@ -15,11 +17,31 @@ impl EntityNet {
/// instance.
///
/// * `index` - locally unique index of the entity.
pub fn new(player: Player, index: u32) -> Self {
pub fn new(player: Player, index: NetEntityIndex) -> Self {
Self { player, index }
}

pub fn index(&self) -> u32 {
pub fn player(&self) -> Player {
self.player
}

pub fn index(&self) -> NetEntityIndex {
self.index
}
}

#[derive(Clone, Copy, Debug, Encode, Decode, Hash, PartialEq, Eq)]
pub struct NetEntityIndex(u32);

impl From<NetEntityIndex> for u32 {
fn from(index: NetEntityIndex) -> u32 {
index.0
}
}

#[cfg(feature = "bevy")]
impl From<Entity> for NetEntityIndex {
fn from(entity: Entity) -> Self {
Self(entity.index())
}
}
2 changes: 1 addition & 1 deletion crates/messages/src/players/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use bincode::{Decode, Encode};
pub use chat::{ChatMessage, ChatMessageError, MAX_CHAT_LEN};
use de_types::{objects::ActiveObjectType, player::Player};
pub use entity::EntityNet;
pub use entity::{EntityNet, NetEntityIndex};
pub use geom::{TransformNet, Vec2Net, Vec3Net, Vec4Net};
pub use path::{PathError, PathNet};

Expand Down
2 changes: 1 addition & 1 deletion crates/multiplayer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub use crate::{
messages::{MessagesSet, ToPlayersEvent},
netstate::NetState,
playermsg::{
GameNetSet, NetEntities, NetRecvDespawnActiveEvent, NetRecvHealthEvent,
GameNetSet, NetEntities, NetEntityCommands, NetRecvDespawnActiveEvent, NetRecvHealthEvent,
NetRecvSpawnActiveEvent,
},
};
Expand Down
73 changes: 65 additions & 8 deletions crates/multiplayer/src/playermsg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use bevy::{
prelude::*,
};
use de_core::{gconfig::GameConfig, schedule::PreMovement, state::AppState};
use de_messages::{EntityNet, ToPlayers};
use de_messages::{EntityNet, NetEntityIndex, ToPlayers};
use de_types::{objects::ActiveObjectType, player::Player};

use crate::messages::{FromPlayersEvent, MessagesSet};
Expand Down Expand Up @@ -148,7 +148,7 @@ impl<'w> NetEntities<'w> {
/// It is assumed that the entity exists.
pub fn local_net_id(&self, entity: Entity) -> EntityNet {
let player = self.config.locals().playable();
EntityNet::new(player, entity.index())
EntityNet::new(player, entity.into())
}
}

Expand All @@ -159,6 +159,10 @@ pub struct NetEntityCommands<'w> {
}

impl<'w> NetEntityCommands<'w> {
pub fn remove_player(&mut self, player: Player) -> Option<PlayerNetToLocal> {
self.map.remove_player(player)
}

fn register(&mut self, remote: EntityNet, local: Entity) {
self.map.register(remote, local)
}
Expand All @@ -170,15 +174,15 @@ impl<'w> NetEntityCommands<'w> {
fn local_id(&self, entity: EntityNet) -> Option<Entity> {
self.map
.translate_remote(entity)
.or_else(|| self.entities.resolve_from_id(entity.index()))
.or_else(|| self.entities.resolve_from_id(entity.index().into()))
}
}

/// Mapping between remote and local entity IDs for non-locally simulated
/// entities.
#[derive(Resource)]
struct EntityIdMapRes {
remote_to_local: AHashMap<EntityNet, Entity>,
remote_to_local: AHashMap<Player, PlayerNetToLocal>,
local_to_remote: AHashMap<Entity, EntityNet>,
}

Expand All @@ -202,8 +206,10 @@ impl EntityIdMapRes {
///
/// Panics if the remote entity is already registered.
fn register(&mut self, remote: EntityNet, local: Entity) {
let result = self.remote_to_local.insert(remote, local);
assert!(result.is_none());
self.remote_to_local
.entry(remote.player())
.or_default()
.insert(remote.index(), local);
let result = self.local_to_remote.insert(local, remote);
assert!(result.is_none());
}
Expand All @@ -216,7 +222,8 @@ impl EntityIdMapRes {
///
/// Panics if the entity is not registered.
fn deregister(&mut self, remote: EntityNet) -> Entity {
let local = self.remote_to_local.remove(&remote).unwrap();
let player_entities = self.remote_to_local.get_mut(&remote.player()).unwrap();
let local = player_entities.remove(remote.index()).unwrap();
self.local_to_remote.remove(&local).unwrap();
local
}
Expand All @@ -230,7 +237,57 @@ impl EntityIdMapRes {
/// Translates remote entity ID to a local entity ID in case the entity is
/// not locally simulated.
fn translate_remote(&self, remote: EntityNet) -> Option<Entity> {
self.remote_to_local.get(&remote).copied()
self.remote_to_local
.get(&remote.player())
.and_then(|h| h.translate(remote.index()))
}

/// Removes entity mapping for the player.
///
/// This should not be called unless the player leaves the multiplayer
/// game.
fn remove_player(&mut self, player: Player) -> Option<PlayerNetToLocal> {
let Some(map) = self.remote_to_local.remove(&player) else {
return None;
};

for local in map.locals() {
self.local_to_remote.remove(&local).unwrap();
}

Some(map)
}
}

/// Mapping from remote entity indices to local ECS entities.
#[derive(Default)]
pub struct PlayerNetToLocal(AHashMap<NetEntityIndex, Entity>);

impl PlayerNetToLocal {
/// Inserts a new remote to local entity link.
///
/// # Panics
///
/// Panics if the remote entity is already registered.
fn insert(&mut self, remote: NetEntityIndex, local: Entity) {
let result = self.0.insert(remote, local);
debug_assert!(result.is_none());
}

/// Removes a remote to local entity link and returns the local entity if
/// it was registered.
fn remove(&mut self, index: NetEntityIndex) -> Option<Entity> {
self.0.remove(&index)
}

/// Translates a remote entity to a local entity.
fn translate(&self, remote: NetEntityIndex) -> Option<Entity> {
self.0.get(&remote).copied()
}

/// Returns an iterator over all local entities from the mapping.
pub fn locals(&self) -> impl Iterator<Item = Entity> + '_ {
self.0.values().copied()
}
}

Expand Down
27 changes: 24 additions & 3 deletions crates/spawner/src/despawner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ use de_audio::spatial::{PlaySpatialAudioEvent, Sound};
use de_core::gconfig::GameConfig;
use de_core::{objects::ObjectTypeComponent, player::PlayerComponent, state::AppState};
use de_messages::ToPlayers;
use de_multiplayer::{NetEntities, NetRecvDespawnActiveEvent, ToPlayersEvent};
use de_multiplayer::{
NetEntities, NetEntityCommands, NetRecvDespawnActiveEvent, PeerLeftEvent, ToPlayersEvent,
};
use de_types::objects::{ActiveObjectType, ObjectType};

use crate::ObjectCounter;
use crate::{ObjectCounter, SpawnerSet};

pub(crate) struct DespawnerPlugin;

Expand All @@ -22,11 +24,16 @@ impl Plugin for DespawnerPlugin {
despawn_active_remote
.run_if(on_event::<NetRecvDespawnActiveEvent>())
.before(despawn_active),
despawn_active_peer_left
.run_if(on_event::<PeerLeftEvent>())
.after(despawn_active_remote)
.before(despawn_active),
despawn_active.before(despawn),
despawn,
)
.run_if(in_state(AppState::InGame))
.in_set(DespawnerSet::Despawn),
.in_set(DespawnerSet::Despawn)
.after(SpawnerSet::Spawner),
)
.add_event::<DespawnActiveLocalEvent>()
.add_event::<DespawnActiveEvent>()
Expand Down Expand Up @@ -84,6 +91,20 @@ fn despawn_active_remote(
}
}

fn despawn_active_peer_left(
mut net_commands: NetEntityCommands,
mut peer_left_events: EventReader<PeerLeftEvent>,
mut event_writer: EventWriter<DespawnActiveEvent>,
) {
for event in peer_left_events.iter() {
if let Some(entity_map) = net_commands.remove_player(event.id()) {
for entity in entity_map.locals() {
event_writer.send(DespawnActiveEvent(entity));
}
}
}
}

fn despawn_active(
mut counter: ResMut<ObjectCounter>,
entities: Query<(&PlayerComponent, &ObjectTypeComponent, &Transform)>,
Expand Down

1 comment on commit c373022

@JackCrumpLeys
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

commit 1000 🎉

Please sign in to comment.