use anyhow::Context;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use bevy::prelude::{Entity, EntityWorldMut, World};
#[derive(Default, Debug)]
pub struct EntityMap {
remote_to_local: HashMap<Entity, Entity>,
local_to_remote: HashMap<Entity, Entity>,
}
impl EntityMap {
#[inline]
pub fn insert(&mut self, remote_entity: Entity, local_entity: Entity) {
self.remote_to_local.insert(remote_entity, local_entity);
self.local_to_remote.insert(local_entity, remote_entity);
}
pub(crate) fn get_local(&self, remote_entity: Entity) -> Option<&Entity> {
self.remote_to_local.get(&remote_entity)
}
pub(crate) fn get_remote(&self, local_entity: Entity) -> Option<&Entity> {
self.local_to_remote.get(&local_entity)
}
pub(super) fn get_by_remote<'a>(
&mut self,
world: &'a mut World,
remote_entity: Entity,
) -> anyhow::Result<EntityWorldMut<'a>> {
self.get_local(remote_entity)
.and_then(|e| world.get_entity_mut(*e))
.context("Failed to get local entity")
}
pub(super) fn get_by_remote_or_spawn<'a>(
&mut self,
world: &'a mut World,
remote_entity: Entity,
) -> EntityWorldMut<'a> {
match self.remote_to_local.entry(remote_entity) {
Entry::Occupied(entry) => world.entity_mut(*entry.get()),
Entry::Vacant(entry) => {
let local_entity = world.spawn_empty();
entry.insert(local_entity.id());
self.local_to_remote
.insert(local_entity.id(), remote_entity);
local_entity
}
}
}
pub(super) fn remove_by_remote(&mut self, remote_entity: Entity) -> Option<Entity> {
let local_entity = self.remote_to_local.remove(&remote_entity);
if let Some(local_entity) = local_entity {
self.local_to_remote.remove(&local_entity);
}
local_entity
}
#[inline]
pub fn to_local(&self) -> &HashMap<Entity, Entity> {
&self.remote_to_local
}
#[inline]
pub fn to_remote(&self) -> &HashMap<Entity, Entity> {
&self.local_to_remote
}
fn clear(&mut self) {
self.local_to_remote.clear();
self.remote_to_local.clear();
}
}
pub trait MapEntities {
fn map_entities(&mut self, entity_map: &EntityMap);
}
impl MapEntities for Entity {
fn map_entities(&mut self, entity_map: &EntityMap) {
if let Some(local) = entity_map.get_local(*self) {
*self = *local;
}
}
}
#[cfg(test)]
mod tests {
use crate::prelude::client::*;
use crate::prelude::*;
use crate::tests::protocol::*;
use crate::tests::stepper::{BevyStepper, Step};
use std::time::Duration;
#[test]
fn test_replicated_entity_mapping() -> anyhow::Result<()> {
let frame_duration = Duration::from_millis(10);
let tick_duration = Duration::from_millis(10);
let shared_config = SharedConfig {
enable_replication: true,
tick: TickConfig::new(tick_duration),
..Default::default()
};
let link_conditioner = LinkConditionerConfig {
incoming_latency: Duration::from_millis(0),
incoming_jitter: Duration::from_millis(0),
incoming_loss: 0.0,
};
let sync_config = SyncConfig::default().speedup_factor(1.0);
let prediction_config = PredictionConfig::default().disable(false);
let interpolation_config = InterpolationConfig::default();
let mut stepper = BevyStepper::new(
shared_config,
sync_config,
prediction_config,
interpolation_config,
link_conditioner,
frame_duration,
);
stepper.client_mut().connect();
stepper.client_mut().set_synced();
for _ in 0..20 {
stepper.frame_step();
}
let server_entity = stepper
.server_app
.world
.spawn((Component1(0.0), Replicate::default()))
.id();
stepper.frame_step();
stepper.frame_step();
let client_entity = *stepper
.client()
.connection()
.base()
.replication_manager
.entity_map
.get_local(server_entity)
.unwrap();
assert_eq!(
stepper
.client_app
.world
.entity(client_entity)
.get::<Component1>()
.unwrap(),
&Component1(0.0)
);
let server_entity_2 = stepper
.server_app
.world
.spawn((Component4(server_entity), Replicate::default()))
.id();
stepper.frame_step();
stepper.frame_step();
let client_entity_2 = *stepper
.client()
.connection()
.base()
.replication_manager
.entity_map
.get_local(server_entity_2)
.unwrap();
assert_eq!(
stepper
.client_app
.world
.entity(client_entity_2)
.get::<Component4>()
.unwrap(),
&Component4(client_entity)
);
Ok(())
}
}