use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use crate::{NetcodeError, NetcodeResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct PredictionKey(pub u64);
#[derive(Debug, Clone)]
pub struct PredictionKeyGenerator {
next: u64,
}
impl Default for PredictionKeyGenerator {
fn default() -> Self {
Self { next: 1 }
}
}
impl PredictionKeyGenerator {
pub fn with_next(next: u64) -> NetcodeResult<Self> {
if next == 0 {
return Err(NetcodeError::InvalidConfig(
"next prediction key must be positive",
));
}
Ok(Self { next })
}
pub fn generate(&mut self) -> NetcodeResult<PredictionKey> {
let key = self.next;
self.next = self
.next
.checked_add(1)
.ok_or(NetcodeError::SequenceExhausted)?;
Ok(PredictionKey(key))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
#[non_exhaustive]
pub struct PredictedEntityConfig {
pub capacity: usize,
pub timeout_ticks: u64,
}
impl Default for PredictedEntityConfig {
fn default() -> Self {
Self {
capacity: 128,
timeout_ticks: 120,
}
}
}
impl PredictedEntityConfig {
pub fn validate(&self) -> NetcodeResult<()> {
if self.capacity == 0 || self.timeout_ticks == 0 {
return Err(NetcodeError::InvalidConfig(
"predicted entity limits must be positive",
));
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PredictedEntity<I> {
pub key: PredictionKey,
pub temporary_entity: I,
pub spawned_tick: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EntityMatch<I> {
pub key: PredictionKey,
pub temporary_entity: I,
pub authoritative_entity: I,
pub age_ticks: u64,
}
#[derive(Debug, Clone)]
pub struct PredictedEntityMatcher<I> {
config: PredictedEntityConfig,
pending: BTreeMap<PredictionKey, PredictedEntity<I>>,
temporary: BTreeMap<I, PredictionKey>,
}
impl<I> PredictedEntityMatcher<I>
where
I: Ord + Clone,
{
pub fn new(config: PredictedEntityConfig) -> NetcodeResult<Self> {
config.validate()?;
Ok(Self {
config,
pending: BTreeMap::new(),
temporary: BTreeMap::new(),
})
}
pub fn register(
&mut self,
key: PredictionKey,
temporary_entity: I,
spawned_tick: u64,
) -> NetcodeResult<()> {
if key.0 == 0 || spawned_tick == 0 {
return Err(NetcodeError::InvalidInput(
"prediction key and spawn Tick must be positive",
));
}
if self.pending.contains_key(&key) || self.temporary.contains_key(&temporary_entity) {
return Err(NetcodeError::InvalidInput(
"prediction key or temporary entity is already registered",
));
}
if self.pending.len() >= self.config.capacity {
return Err(NetcodeError::PredictedEntityLimit);
}
self.temporary.insert(temporary_entity.clone(), key);
self.pending.insert(
key,
PredictedEntity {
key,
temporary_entity,
spawned_tick,
},
);
Ok(())
}
pub fn resolve(
&mut self,
key: PredictionKey,
authoritative_entity: I,
current_tick: u64,
) -> NetcodeResult<Option<EntityMatch<I>>> {
let Some(predicted) = self.pending.get(&key) else {
return Ok(None);
};
if current_tick < predicted.spawned_tick {
return Err(NetcodeError::InvalidInput(
"authoritative match Tick precedes predicted spawn",
));
}
if current_tick - predicted.spawned_tick > self.config.timeout_ticks {
let predicted = self.pending.remove(&key).unwrap();
self.temporary.remove(&predicted.temporary_entity);
return Ok(None);
}
let predicted = self.pending.remove(&key).unwrap();
self.temporary.remove(&predicted.temporary_entity);
Ok(Some(EntityMatch {
key,
temporary_entity: predicted.temporary_entity,
authoritative_entity,
age_ticks: current_tick - predicted.spawned_tick,
}))
}
pub fn expire(&mut self, current_tick: u64) -> Vec<PredictedEntity<I>> {
let expired_keys = self
.pending
.iter()
.filter(|(_, predicted)| {
current_tick.saturating_sub(predicted.spawned_tick) > self.config.timeout_ticks
})
.map(|(key, _)| *key)
.collect::<Vec<_>>();
expired_keys
.into_iter()
.filter_map(|key| {
let predicted = self.pending.remove(&key)?;
self.temporary.remove(&predicted.temporary_entity);
Some(predicted)
})
.collect()
}
pub fn len(&self) -> usize {
self.pending.len()
}
pub fn is_empty(&self) -> bool {
self.pending.is_empty()
}
pub fn get(&self, key: PredictionKey) -> Option<&PredictedEntity<I>> {
self.pending.get(&key)
}
pub fn clear(&mut self) {
self.pending.clear();
self.temporary.clear();
}
}