Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiplayer: Handle peer left events #733

Merged
merged 1 commit into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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