use core::fmt;
use std::collections::VecDeque;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct InputFrame<I> {
pub sequence: u64,
pub tick: u64,
pub input: I,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AuthoritativeSnapshot<S> {
pub tick: u64,
pub last_processed_input: u64,
pub state: S,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PredictionError {
ZeroCapacity,
SequenceExhausted,
HistoryFull,
InvalidAcknowledgement,
}
impl fmt::Display for PredictionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ZeroCapacity => f.write_str("prediction history capacity must be non-zero"),
Self::SequenceExhausted => f.write_str("prediction input sequence is exhausted"),
Self::HistoryFull => f.write_str("prediction input history is full"),
Self::InvalidAcknowledgement => {
f.write_str("authoritative snapshot acknowledges an unknown input sequence")
}
}
}
}
impl std::error::Error for PredictionError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ReconcileOutcome {
Applied { replayed_inputs: usize },
StaleSnapshot,
}
pub struct PredictionBuffer<S, I> {
state: S,
pending: VecDeque<InputFrame<I>>,
capacity: usize,
last_issued: u64,
last_acknowledged: u64,
last_snapshot_tick: Option<u64>,
}
impl<S, I> PredictionBuffer<S, I> {
pub fn new(initial_state: S, capacity: usize) -> Result<Self, PredictionError> {
if capacity == 0 {
return Err(PredictionError::ZeroCapacity);
}
Ok(Self {
state: initial_state,
pending: VecDeque::with_capacity(capacity),
capacity,
last_issued: 0,
last_acknowledged: 0,
last_snapshot_tick: None,
})
}
pub fn state(&self) -> &S {
&self.state
}
pub const fn last_issued_sequence(&self) -> u64 {
self.last_issued
}
pub const fn last_acknowledged_sequence(&self) -> u64 {
self.last_acknowledged
}
pub fn pending_len(&self) -> usize {
self.pending.len()
}
pub const fn capacity(&self) -> usize {
self.capacity
}
pub fn pending_inputs(&self) -> impl ExactSizeIterator<Item = &InputFrame<I>> {
self.pending.iter()
}
pub fn predict(
&mut self,
tick: u64,
input: I,
mut step: impl FnMut(&mut S, &I, u64),
) -> Result<u64, PredictionError> {
if self.pending.len() >= self.capacity {
return Err(PredictionError::HistoryFull);
}
let sequence = self
.last_issued
.checked_add(1)
.ok_or(PredictionError::SequenceExhausted)?;
step(&mut self.state, &input, tick);
self.pending.push_back(InputFrame {
sequence,
tick,
input,
});
self.last_issued = sequence;
Ok(sequence)
}
pub fn reconcile(
&mut self,
snapshot: AuthoritativeSnapshot<S>,
mut step: impl FnMut(&mut S, &I, u64),
) -> Result<ReconcileOutcome, PredictionError> {
if snapshot.last_processed_input > self.last_issued {
return Err(PredictionError::InvalidAcknowledgement);
}
if self
.last_snapshot_tick
.is_some_and(|tick| snapshot.tick < tick)
|| snapshot.last_processed_input < self.last_acknowledged
{
return Ok(ReconcileOutcome::StaleSnapshot);
}
let acknowledged = snapshot.last_processed_input;
self.pending.retain(|frame| frame.sequence > acknowledged);
self.state = snapshot.state;
let replayed_inputs = self.pending.len();
for frame in &self.pending {
step(&mut self.state, &frame.input, frame.tick);
}
self.last_acknowledged = acknowledged;
self.last_snapshot_tick = Some(snapshot.tick);
Ok(ReconcileOutcome::Applied { replayed_inputs })
}
}