use std::collections::VecDeque;
use serde::{Deserialize, Serialize};
use crate::{NetcodeError, NetcodeResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
#[non_exhaustive]
pub struct PredictionConfig {
pub history_capacity: usize,
pub max_replay_steps: usize,
}
impl Default for PredictionConfig {
fn default() -> Self {
Self {
history_capacity: 256,
max_replay_steps: 64,
}
}
}
impl PredictionConfig {
pub fn validate(&self) -> NetcodeResult<()> {
if self.history_capacity == 0 {
return Err(NetcodeError::InvalidConfig(
"prediction history capacity must be positive",
));
}
if self.max_replay_steps == 0 || self.max_replay_steps > self.history_capacity {
return Err(NetcodeError::InvalidConfig(
"prediction replay limit must be within history capacity",
));
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PredictionFrame<I, S> {
pub tick: u64,
pub input: I,
pub predicted_state: S,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReconciliationReport<S> {
pub authoritative_tick: u64,
pub predicted_state_at_authoritative_tick: Option<S>,
pub corrected_state: S,
pub replayed_inputs: usize,
pub current_tick: u64,
}
#[derive(Debug, Clone)]
pub struct PredictionBuffer<I, S> {
config: PredictionConfig,
frames: VecDeque<PredictionFrame<I, S>>,
confirmed_tick: u64,
}
impl<I, S> PredictionBuffer<I, S> {
pub fn new(config: PredictionConfig) -> NetcodeResult<Self> {
config.validate()?;
Ok(Self {
config,
frames: VecDeque::with_capacity(config.history_capacity),
confirmed_tick: 0,
})
}
pub fn record(&mut self, tick: u64, input: I, predicted_state: S) -> NetcodeResult<()> {
let previous = self
.frames
.back()
.map_or(self.confirmed_tick, |frame| frame.tick);
if tick == 0 || tick <= previous {
return Err(NetcodeError::InvalidInput(
"prediction Tick must increase and be positive",
));
}
if self.frames.len() >= self.config.history_capacity {
return Err(NetcodeError::PredictionHistoryFull);
}
self.frames.push_back(PredictionFrame {
tick,
input,
predicted_state,
});
Ok(())
}
pub fn confirmed_tick(&self) -> u64 {
self.confirmed_tick
}
pub fn pending_len(&self) -> usize {
self.frames.len()
}
pub fn is_empty(&self) -> bool {
self.frames.is_empty()
}
pub fn frames(&self) -> impl Iterator<Item = &PredictionFrame<I, S>> {
self.frames.iter()
}
}
impl<I, S> PredictionBuffer<I, S>
where
S: Clone,
{
pub fn reconcile<Simulate>(
&mut self,
authoritative_tick: u64,
authoritative_state: S,
mut simulate: Simulate,
) -> NetcodeResult<ReconciliationReport<S>>
where
Simulate: FnMut(&mut S, u64, &I),
{
if authoritative_tick < self.confirmed_tick {
return Err(NetcodeError::InvalidInput(
"authoritative prediction Tick moved backwards",
));
}
let confirmed_frames = self
.frames
.iter()
.position(|frame| frame.tick > authoritative_tick)
.unwrap_or(self.frames.len());
let replayed_inputs = self.frames.len() - confirmed_frames;
if replayed_inputs > self.config.max_replay_steps {
return Err(NetcodeError::ReplayLimitExceeded);
}
let predicted_state_at_authoritative_tick = confirmed_frames
.checked_sub(1)
.and_then(|index| self.frames.get(index))
.filter(|frame| frame.tick == authoritative_tick)
.map(|frame| frame.predicted_state.clone());
let mut corrected_state = authoritative_state;
self.frames.drain(..confirmed_frames);
for frame in &mut self.frames {
simulate(&mut corrected_state, frame.tick, &frame.input);
frame.predicted_state = corrected_state.clone();
}
let current_tick = self
.frames
.back()
.map_or(authoritative_tick, |frame| frame.tick);
self.confirmed_tick = authoritative_tick;
Ok(ReconciliationReport {
authoritative_tick,
predicted_state_at_authoritative_tick,
corrected_state,
replayed_inputs,
current_tick,
})
}
pub fn reset(&mut self, confirmed_tick: u64) {
self.frames.clear();
self.confirmed_tick = confirmed_tick;
}
}