elura_netcode/
prediction.rs1use std::collections::VecDeque;
2
3use serde::{Deserialize, Serialize};
4
5use crate::{NetcodeError, NetcodeResult};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(default, deny_unknown_fields)]
10#[non_exhaustive]
11pub struct PredictionConfig {
12 pub history_capacity: usize,
14 pub max_replay_steps: usize,
16}
17
18impl Default for PredictionConfig {
19 fn default() -> Self {
20 Self {
21 history_capacity: 256,
22 max_replay_steps: 64,
23 }
24 }
25}
26
27impl PredictionConfig {
28 pub fn validate(&self) -> NetcodeResult<()> {
30 if self.history_capacity == 0 {
31 return Err(NetcodeError::InvalidConfig(
32 "prediction history capacity must be positive",
33 ));
34 }
35 if self.max_replay_steps == 0 || self.max_replay_steps > self.history_capacity {
36 return Err(NetcodeError::InvalidConfig(
37 "prediction replay limit must be within history capacity",
38 ));
39 }
40 Ok(())
41 }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct PredictionFrame<I, S> {
47 pub tick: u64,
49 pub input: I,
51 pub predicted_state: S,
53}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
57pub struct ReconciliationReport<S> {
58 pub authoritative_tick: u64,
60 pub predicted_state_at_authoritative_tick: Option<S>,
62 pub corrected_state: S,
64 pub replayed_inputs: usize,
66 pub current_tick: u64,
68}
69
70#[derive(Debug, Clone)]
72pub struct PredictionBuffer<I, S> {
73 config: PredictionConfig,
74 frames: VecDeque<PredictionFrame<I, S>>,
75 confirmed_tick: u64,
76}
77
78impl<I, S> PredictionBuffer<I, S> {
79 pub fn new(config: PredictionConfig) -> NetcodeResult<Self> {
81 config.validate()?;
82 Ok(Self {
83 config,
84 frames: VecDeque::with_capacity(config.history_capacity),
85 confirmed_tick: 0,
86 })
87 }
88
89 pub fn record(&mut self, tick: u64, input: I, predicted_state: S) -> NetcodeResult<()> {
91 let previous = self
92 .frames
93 .back()
94 .map_or(self.confirmed_tick, |frame| frame.tick);
95 if tick == 0 || tick <= previous {
96 return Err(NetcodeError::InvalidInput(
97 "prediction Tick must increase and be positive",
98 ));
99 }
100 if self.frames.len() >= self.config.history_capacity {
101 return Err(NetcodeError::PredictionHistoryFull);
102 }
103 self.frames.push_back(PredictionFrame {
104 tick,
105 input,
106 predicted_state,
107 });
108 Ok(())
109 }
110
111 pub fn confirmed_tick(&self) -> u64 {
113 self.confirmed_tick
114 }
115
116 pub fn pending_len(&self) -> usize {
118 self.frames.len()
119 }
120
121 pub fn is_empty(&self) -> bool {
123 self.frames.is_empty()
124 }
125
126 pub fn frames(&self) -> impl Iterator<Item = &PredictionFrame<I, S>> {
128 self.frames.iter()
129 }
130}
131
132impl<I, S> PredictionBuffer<I, S>
133where
134 S: Clone,
135{
136 pub fn reconcile<Simulate>(
141 &mut self,
142 authoritative_tick: u64,
143 authoritative_state: S,
144 mut simulate: Simulate,
145 ) -> NetcodeResult<ReconciliationReport<S>>
146 where
147 Simulate: FnMut(&mut S, u64, &I),
148 {
149 if authoritative_tick < self.confirmed_tick {
150 return Err(NetcodeError::InvalidInput(
151 "authoritative prediction Tick moved backwards",
152 ));
153 }
154 let confirmed_frames = self
155 .frames
156 .iter()
157 .position(|frame| frame.tick > authoritative_tick)
158 .unwrap_or(self.frames.len());
159 let replayed_inputs = self.frames.len() - confirmed_frames;
160 if replayed_inputs > self.config.max_replay_steps {
161 return Err(NetcodeError::ReplayLimitExceeded);
162 }
163
164 let predicted_state_at_authoritative_tick = confirmed_frames
165 .checked_sub(1)
166 .and_then(|index| self.frames.get(index))
167 .filter(|frame| frame.tick == authoritative_tick)
168 .map(|frame| frame.predicted_state.clone());
169 let mut corrected_state = authoritative_state;
170 self.frames.drain(..confirmed_frames);
171 for frame in &mut self.frames {
172 simulate(&mut corrected_state, frame.tick, &frame.input);
173 frame.predicted_state = corrected_state.clone();
174 }
175 let current_tick = self
176 .frames
177 .back()
178 .map_or(authoritative_tick, |frame| frame.tick);
179
180 self.confirmed_tick = authoritative_tick;
181 Ok(ReconciliationReport {
182 authoritative_tick,
183 predicted_state_at_authoritative_tick,
184 corrected_state,
185 replayed_inputs,
186 current_tick,
187 })
188 }
189
190 pub fn reset(&mut self, confirmed_tick: u64) {
192 self.frames.clear();
193 self.confirmed_tick = confirmed_tick;
194 }
195}