use std::{
collections::{HashMap, HashSet},
hash::Hash,
};
use naia_shared::{
ComponentKind, EntityAndGlobalEntityConverter, GlobalEntity, Replicate, Tick, WorldRefType,
};
use crate::world::global_world_manager::GlobalWorldManager;
pub type EntitySnapshot = HashMap<ComponentKind, Box<dyn Replicate>>;
pub struct Historian {
max_ticks: u16,
snapshots: std::collections::VecDeque<(Tick, HashMap<GlobalEntity, EntitySnapshot>)>,
component_filter: Option<HashSet<ComponentKind>>,
}
impl Historian {
pub fn new(max_ticks: u16) -> Self {
Self {
max_ticks,
snapshots: std::collections::VecDeque::new(),
component_filter: None,
}
}
pub fn new_filtered(max_ticks: u16, filter: impl IntoIterator<Item = ComponentKind>) -> Self {
Self {
max_ticks,
snapshots: std::collections::VecDeque::new(),
component_filter: Some(filter.into_iter().collect()),
}
}
pub fn record_tick<E: Copy + Eq + Hash + Send + Sync, W: WorldRefType<E>>(
&mut self,
tick: Tick,
global_world_manager: &GlobalWorldManager,
global_entity_map: &impl EntityAndGlobalEntityConverter<E>,
world: &W,
) {
let mut tick_snapshot: HashMap<GlobalEntity, EntitySnapshot> = HashMap::new();
for &global_entity in global_world_manager.all_global_entities() {
let Ok(world_entity) = global_entity_map.global_entity_to_entity(&global_entity) else {
continue;
};
let Some(kinds) = global_world_manager.component_kinds(&global_entity) else {
continue;
};
let mut entity_snapshot = EntitySnapshot::new();
for kind in kinds {
if let Some(ref filter) = self.component_filter {
if !filter.contains(&kind) {
continue;
}
}
if let Some(component_ref) = world.component_of_kind(&world_entity, &kind) {
entity_snapshot.insert(kind, component_ref.copy_to_box());
}
}
if !entity_snapshot.is_empty() {
tick_snapshot.insert(global_entity, entity_snapshot);
}
}
self.snapshots.push_back((tick, tick_snapshot));
let max_ticks = self.max_ticks as u32;
while let Some(&(oldest_tick, _)) = self.snapshots.front() {
let age = (tick as u32).wrapping_sub(oldest_tick as u32);
if age > max_ticks {
self.snapshots.pop_front();
} else {
break;
}
}
}
pub fn snapshot_at_tick(&self, tick: Tick) -> Option<&HashMap<GlobalEntity, EntitySnapshot>> {
for (t, snapshot) in &self.snapshots {
if *t == tick {
return Some(snapshot);
}
}
None
}
pub fn snapshot_at_time_ago_ms(
&self,
time_ago_ms: u32,
current_tick: Tick,
tick_duration_ms: f32,
) -> Option<&HashMap<GlobalEntity, EntitySnapshot>> {
if self.snapshots.is_empty() {
return None;
}
let ticks_ago = (time_ago_ms as f32 / tick_duration_ms).round() as u32;
let target_tick = (current_tick as u32).wrapping_sub(ticks_ago) as u16;
if let Some(snap) = self.snapshot_at_tick(target_tick) {
return Some(snap);
}
self.snapshots.front().map(|(_, s)| s)
}
pub fn len(&self) -> usize {
self.snapshots.len()
}
pub fn is_empty(&self) -> bool {
self.snapshots.is_empty()
}
}