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: Make non-local entity updates possible #730

Merged
merged 1 commit into from
Sep 16, 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
21 changes: 14 additions & 7 deletions crates/messages/src/players/entity.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
#[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(u32);
pub struct EntityNet {
player: Player,
index: u32,
}

#[cfg(feature = "bevy")]
impl From<Entity> for EntityNet {
fn from(entity: Entity) -> Self {
Self(entity.index())
impl EntityNet {
/// # Arguments
///
/// * `player` - the human player executing the entity simulating game
/// instance.
///
/// * `index` - locally unique index of the entity.
pub fn new(player: Player, index: u32) -> Self {
Self { player, index }
}
}
2 changes: 1 addition & 1 deletion crates/multiplayer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub use crate::{
lifecycle::{MultiplayerShuttingDownEvent, ShutdownMultiplayerEvent, StartMultiplayerEvent},
messages::{MessagesSet, ToPlayersEvent},
netstate::NetState,
playermsg::{GameNetSet, NetRecvDespawnActiveEvent, NetRecvSpawnActiveEvent},
playermsg::{GameNetSet, NetEntities, NetRecvDespawnActiveEvent, NetRecvSpawnActiveEvent},
};
use crate::{netstate::NetStatePlugin, network::NetworkPlugin};

Expand Down
68 changes: 51 additions & 17 deletions crates/multiplayer/src/playermsg.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use ahash::AHashMap;
use bevy::prelude::*;
use de_core::{schedule::PreMovement, state::AppState};
use bevy::{ecs::system::SystemParam, prelude::*};
use de_core::{gconfig::GameConfig, schedule::PreMovement, state::AppState};
use de_messages::{EntityNet, ToPlayers};
use de_types::{objects::ActiveObjectType, player::Player};

Expand Down Expand Up @@ -96,39 +96,65 @@ impl NetRecvDespawnActiveEvent {
}
}

/// Mapping between remote and local entity IDs.
#[derive(SystemParam)]
pub struct NetEntities<'w> {
config: Res<'w, GameConfig>,
map: Res<'w, EntityIdMapRes>,
}

impl<'w> NetEntities<'w> {
/// Translates a local entity ID to a remote entity ID. This works for both
/// locally simulated and non-local entities.
///
/// It is assumed that the entity exists.
pub fn net_id(&self, entity: Entity) -> EntityNet {
match self.map.translate_local(entity) {
Some(id) => id,
None => self.local_net_id(entity),
}
}

/// Translates a local entity ID to a remote entity ID. This works only for
/// locally simulated entities.
///
/// 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())
}
}

/// Mapping between remote and local entity IDs for non-locally simulated
/// entities.
#[derive(Resource)]
struct EntityIdMapRes {
/// Associated player is not the player owning the entity but the only
/// human player co-located with the player of the entity. Thus the player
/// is either the same (if it is a human) or a different player (if the
/// owning player is AI).
remote_to_local: AHashMap<(Player, EntityNet), Entity>,
remote_to_local: AHashMap<EntityNet, Entity>,
local_to_remote: AHashMap<Entity, EntityNet>,
}

impl EntityIdMapRes {
fn new() -> Self {
Self {
remote_to_local: AHashMap::new(),
local_to_remote: AHashMap::new(),
}
}

/// Registers a new remote entity.
///
/// # Arguments
///
/// * `source` - human player executing the remote side (not necessarily
/// the player of the registered entity which may be AI simulated).
///
/// * `remote` - remote entity identification.
///
/// * `local` - local entity (present in the local ECS).
///
/// # Panics
///
/// Panics if the remote entity is already registered.
fn register(&mut self, source: Player, remote: EntityNet, local: Entity) {
let result = self.remote_to_local.insert((source, remote), local);
fn register(&mut self, remote: EntityNet, local: Entity) {
let result = self.remote_to_local.insert(remote, local);
assert!(result.is_none());
let result = self.local_to_remote.insert(local, remote);
assert!(result.is_none());
}

Expand All @@ -139,8 +165,16 @@ impl EntityIdMapRes {
/// # Panics
///
/// Panics if the entity is not registered.
fn deregister(&mut self, source: Player, remote: EntityNet) -> Entity {
self.remote_to_local.remove(&(source, remote)).unwrap()
fn deregister(&mut self, remote: EntityNet) -> Entity {
let local = self.remote_to_local.remove(&remote).unwrap();
self.local_to_remote.remove(&local).unwrap();
local
}

/// Translates local entity ID to a remote entity ID in case the entity is
/// not locally simulated.
fn translate_local(&self, local: Entity) -> Option<EntityNet> {
self.local_to_remote.get(&local).copied()
}
}

Expand Down Expand Up @@ -168,7 +202,7 @@ fn recv_messages(
transform,
} => {
let local = commands.spawn_empty().id();
map.register(input.source(), *entity, local);
map.register(*entity, local);

spawn_events.send(NetRecvSpawnActiveEvent::new(
*player,
Expand All @@ -178,7 +212,7 @@ fn recv_messages(
));
}
ToPlayers::Despawn { entity } => {
let local = map.deregister(input.source(), *entity);
let local = map.deregister(*entity);
despawn_events.send(NetRecvDespawnActiveEvent::new(local));
}
_ => (),
Expand Down
5 changes: 3 additions & 2 deletions crates/spawner/src/despawner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use de_core::{
state::AppState,
};
use de_messages::ToPlayers;
use de_multiplayer::{NetRecvDespawnActiveEvent, ToPlayersEvent};
use de_multiplayer::{NetEntities, NetRecvDespawnActiveEvent, ToPlayersEvent};
use de_objects::Health;
use de_types::objects::{ActiveObjectType, ObjectType};

Expand Down Expand Up @@ -77,6 +77,7 @@ fn find_dead(

fn despawn_active_local(
config: Res<GameConfig>,
net_entities: NetEntities,
mut event_reader: EventReader<DespawnActiveLocalEvent>,
mut event_writer: EventWriter<DespawnActiveEvent>,
mut net_events: EventWriter<ToPlayersEvent>,
Expand All @@ -86,7 +87,7 @@ fn despawn_active_local(

if config.multiplayer() {
net_events.send(ToPlayersEvent::new(ToPlayers::Despawn {
entity: event.0.into(),
entity: net_entities.local_net_id(event.0),
}));
}
}
Expand Down
5 changes: 3 additions & 2 deletions crates/spawner/src/spawner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use de_core::{
};
use de_energy::Battery;
use de_messages::ToPlayers;
use de_multiplayer::{NetRecvSpawnActiveEvent, ToPlayersEvent};
use de_multiplayer::{NetEntities, NetRecvSpawnActiveEvent, ToPlayersEvent};
use de_objects::{AssetCollection, InitialHealths, SceneType, Scenes, SolidObjects};
use de_pathing::{PathTarget, UpdateEntityPathEvent};
use de_terrain::{CircleMarker, MarkerVisibility, RectangleMarker};
Expand Down Expand Up @@ -141,6 +141,7 @@ impl SpawnEvent {
fn spawn_local_active(
mut commands: Commands,
config: Res<GameConfig>,
net_entities: NetEntities,
mut event_reader: EventReader<SpawnLocalActiveEvent>,
mut event_writer: EventWriter<SpawnActiveEvent>,
mut path_events: EventWriter<UpdateEntityPathEvent>,
Expand All @@ -167,7 +168,7 @@ fn spawn_local_active(

if config.multiplayer() {
net_events.send(ToPlayersEvent::new(ToPlayers::Spawn {
entity: entity.into(),
entity: net_entities.local_net_id(entity),
player: event.player,
object_type: event.object_type,
transform: event.transform.into(),
Expand Down