1use core::fmt::Debug;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub struct PredictedInput<A>
32where
33 A: Copy,
34{
35 pub tick: u64,
37 pub action: A,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq)]
43pub struct Snapshot<S>
44where
45 S: Copy,
46{
47 pub tick: u64,
49 pub state: S,
51}
52
53#[derive(Debug, Clone)]
57pub struct PredictionBuffer<A, S>
58where
59 A: Copy,
60 S: Copy,
61{
62 inputs: Vec<PredictedInput<A>>,
63 snapshots: Vec<Snapshot<S>>,
64 capacity: usize,
65}
66
67impl<A, S> PredictionBuffer<A, S>
68where
69 A: Copy + Debug,
70 S: Copy + Debug,
71{
72 #[must_use]
78 pub fn new(capacity: usize) -> Self {
79 assert!(capacity > 0, "capacity must be > 0");
80 Self {
81 inputs: Vec::with_capacity(capacity),
82 snapshots: Vec::with_capacity(capacity),
83 capacity,
84 }
85 }
86
87 #[must_use]
89 pub fn len(&self) -> usize {
90 self.inputs.len()
91 }
92
93 #[must_use]
95 pub fn is_empty(&self) -> bool {
96 self.inputs.is_empty()
97 }
98
99 pub fn push(&mut self, input: PredictedInput<A>, snapshot: Snapshot<S>) {
104 debug_assert_eq!(
105 input.tick, snapshot.tick,
106 "input and snapshot ticks must match"
107 );
108 if self.inputs.len() == self.capacity {
109 self.inputs.remove(0);
110 self.snapshots.remove(0);
111 }
112 self.inputs.push(input);
113 self.snapshots.push(snapshot);
114 }
115
116 #[must_use]
118 pub fn head_snapshot(&self) -> Option<Snapshot<S>> {
119 self.snapshots.last().copied()
120 }
121
122 #[must_use]
124 pub fn inputs(&self) -> &[PredictedInput<A>] {
125 &self.inputs
126 }
127
128 pub fn drop_acknowledged(&mut self, authoritative_tick: u64) {
131 let cutoff = self
132 .inputs
133 .iter()
134 .position(|i| i.tick > authoritative_tick)
135 .unwrap_or(self.inputs.len());
136 self.inputs.drain(..cutoff);
137 self.snapshots.drain(..cutoff);
138 }
139}
140
141pub fn reconcile<A, S, F>(
158 authoritative: Snapshot<S>,
159 buffer: &mut PredictionBuffer<A, S>,
160 mut step: F,
161) -> Snapshot<S>
162where
163 A: Copy + Debug,
164 S: Copy + Debug,
165 F: FnMut(S, PredictedInput<A>) -> S,
166{
167 buffer.drop_acknowledged(authoritative.tick);
168 if buffer.is_empty() {
169 return authoritative;
170 }
171 let mut state = authoritative.state;
172 let mut new_snapshots = Vec::with_capacity(buffer.len());
173 let inputs_copy: Vec<_> = buffer.inputs.to_vec();
174 for input in &inputs_copy {
175 state = step(state, *input);
176 new_snapshots.push(Snapshot {
177 tick: input.tick,
178 state,
179 });
180 }
181 buffer.snapshots = new_snapshots;
182 buffer
183 .head_snapshot()
184 .expect("buffer non-empty by construction")
185}
186
187#[derive(Debug, Clone, Copy, PartialEq, Eq)]
189#[non_exhaustive]
190pub enum ReconcileError {
191 MissingInputs {
196 needed: u64,
198 oldest: u64,
200 },
201}
202
203pub fn reconcile_checked<A, S, F>(
220 authoritative: Snapshot<S>,
221 buffer: &mut PredictionBuffer<A, S>,
222 step: F,
223) -> Result<Snapshot<S>, ReconcileError>
224where
225 A: Copy + Debug,
226 S: Copy + Debug,
227 F: FnMut(S, PredictedInput<A>) -> S,
228{
229 let mut needed = authoritative.tick.saturating_add(1);
230 for input in buffer.inputs.iter().filter(|i| i.tick > authoritative.tick) {
231 if input.tick != needed {
232 return Err(ReconcileError::MissingInputs {
233 needed,
234 oldest: input.tick,
235 });
236 }
237 needed = needed.saturating_add(1);
238 }
239 Ok(reconcile(authoritative, buffer, step))
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245
246 fn step(state: i32, input: PredictedInput<i32>) -> i32 {
249 state + input.action
250 }
251
252 #[test]
253 fn buffer_rejects_zero_capacity() {
254 let result = std::panic::catch_unwind(|| PredictionBuffer::<i32, i32>::new(0));
255 assert!(result.is_err());
256 }
257
258 #[test]
259 fn buffer_pushes_up_to_capacity_and_drops_oldest() {
260 let mut buffer: PredictionBuffer<i32, i32> = PredictionBuffer::new(3);
261 for t in 0..5 {
262 buffer.push(
263 PredictedInput { tick: t, action: 1 },
264 Snapshot { tick: t, state: 0 },
265 );
266 }
267 assert_eq!(buffer.len(), 3);
268 assert_eq!(buffer.inputs()[0].tick, 2);
270 }
271
272 #[test]
273 fn drop_acknowledged_removes_stale_entries() {
274 let mut buffer: PredictionBuffer<i32, i32> = PredictionBuffer::new(8);
275 for t in 0..5 {
276 buffer.push(
277 PredictedInput { tick: t, action: 1 },
278 Snapshot { tick: t, state: 0 },
279 );
280 }
281 buffer.drop_acknowledged(2);
282 assert_eq!(buffer.len(), 2);
283 assert_eq!(buffer.inputs()[0].tick, 3);
284 }
285
286 #[test]
287 fn reconcile_with_empty_buffer_returns_authoritative() {
288 let mut buffer: PredictionBuffer<i32, i32> = PredictionBuffer::new(4);
289 let authoritative = Snapshot {
290 tick: 10,
291 state: 99,
292 };
293 let result = reconcile(authoritative, &mut buffer, step);
294 assert_eq!(result, authoritative);
295 }
296
297 #[test]
298 fn reconcile_replays_buffered_inputs() {
299 let mut buffer: PredictionBuffer<i32, i32> = PredictionBuffer::new(8);
300 for t in 5..10 {
301 buffer.push(
302 PredictedInput { tick: t, action: 1 },
303 Snapshot { tick: t, state: 0 },
304 );
305 }
306 let authoritative = Snapshot { tick: 4, state: 0 };
307 let head = reconcile(authoritative, &mut buffer, step);
308 assert_eq!(head.state, 5);
310 assert_eq!(head.tick, 9);
311 }
312
313 #[test]
314 fn reconcile_discards_acknowledged_before_replay() {
315 let mut buffer: PredictionBuffer<i32, i32> = PredictionBuffer::new(8);
316 for t in 0..10 {
317 buffer.push(
318 PredictedInput { tick: t, action: 1 },
319 Snapshot { tick: t, state: 0 },
320 );
321 }
322 let authoritative = Snapshot {
323 tick: 5,
324 state: 100,
325 };
326 let head = reconcile(authoritative, &mut buffer, step);
327 assert_eq!(head.state, 104);
329 assert_eq!(head.tick, 9);
330 assert_eq!(buffer.len(), 4);
331 }
332
333 #[test]
334 fn buffer_head_snapshot_returns_newest() {
335 let mut buffer: PredictionBuffer<i32, i32> = PredictionBuffer::new(4);
336 buffer.push(
337 PredictedInput { tick: 1, action: 0 },
338 Snapshot { tick: 1, state: 11 },
339 );
340 buffer.push(
341 PredictedInput { tick: 2, action: 0 },
342 Snapshot { tick: 2, state: 22 },
343 );
344 let head = buffer.head_snapshot().unwrap();
345 assert_eq!(head.tick, 2);
346 assert_eq!(head.state, 22);
347 }
348}