1use std::collections::VecDeque;
30use std::fmt::Debug;
31
32pub type Frame = u64;
35
36#[derive(Debug, Clone)]
44pub struct StateHistory<State: Clone> {
45 ring: VecDeque<State>,
46 base_frame: Frame,
47 capacity: usize,
48 resets: u64,
49}
50
51impl<State: Clone> StateHistory<State> {
52 pub fn new(capacity: usize) -> Self {
57 if capacity == 0 {
58 panic!("StateHistory capacity must be greater than 0");
59 }
60 Self {
61 ring: VecDeque::with_capacity(capacity),
62 base_frame: 0,
63 capacity,
64 resets: 0,
65 }
66 }
67
68 pub fn save(&mut self, frame: Frame, state: State) {
74 if self.ring.is_empty() {
75 self.base_frame = frame;
76 self.ring.push_back(state);
77 return;
78 }
79
80 let end = self.base_frame + self.ring.len() as u64; if frame == end {
82 self.ring.push_back(state);
83 while self.ring.len() > self.capacity {
84 self.ring.pop_front();
85 self.base_frame += 1;
86 }
87 } else if frame >= self.base_frame && frame < end {
88 let idx = (frame - self.base_frame) as usize;
89 self.ring[idx] = state;
90 } else {
91 self.resets += 1;
92 tracing::warn!(?frame, base = self.base_frame, len = self.ring.len(), "StateHistory: non-contiguous save, resetting the window");
93 self.ring.clear();
94 self.base_frame = frame;
95 self.ring.push_back(state);
96 }
97 }
98
99 pub fn restore(&self, frame: Frame) -> Option<State> {
102 if self.ring.is_empty() || frame < self.base_frame {
103 return None;
104 }
105 let idx = (frame - self.base_frame) as usize;
106 self.ring.get(idx).cloned()
107 }
108
109 pub fn oldest_frame(&self) -> Option<Frame> {
111 (!self.ring.is_empty()).then_some(self.base_frame)
112 }
113
114 pub fn latest_frame(&self) -> Option<Frame> {
116 (!self.ring.is_empty()).then(|| self.base_frame + self.ring.len() as u64 - 1)
117 }
118
119 pub fn len(&self) -> usize {
120 self.ring.len()
121 }
122
123 pub fn is_empty(&self) -> bool {
124 self.ring.is_empty()
125 }
126
127 pub fn clear(&mut self) {
128 self.ring.clear();
129 self.base_frame = 0;
130 }
131
132 pub fn resets(&self) -> u64 {
139 self.resets
140 }
141}
142
143#[derive(Debug, Clone)]
152pub struct InputTimeline<Input: Clone + Debug> {
153 slots: VecDeque<Option<Input>>,
154 base_frame: Frame,
155 last_confirmed: Option<Frame>,
156 capacity: usize,
157}
158
159impl<Input: Clone + Debug> InputTimeline<Input> {
160 pub fn new(capacity: usize) -> Self {
165 if capacity == 0 {
166 panic!("InputTimeline capacity must be greater than 0");
167 }
168 Self {
169 slots: VecDeque::with_capacity(capacity),
170 base_frame: 0,
171 last_confirmed: None,
172 capacity,
173 }
174 }
175
176 pub fn confirm(&mut self, frame: Frame, input: Input) {
183 if self.slots.is_empty() {
184 self.base_frame = frame;
185 self.slots.push_back(Some(input));
186 } else if frame < self.base_frame {
187 return; } else {
189 let end = self.base_frame + self.slots.len() as u64;
190 if frame < end {
191 self.slots[(frame - self.base_frame) as usize] = Some(input);
192 } else {
193 while self.base_frame + (self.slots.len() as u64) < frame {
194 self.slots.push_back(None);
195 }
196 self.slots.push_back(Some(input));
197 }
198 while self.slots.len() > self.capacity {
199 self.slots.pop_front();
200 self.base_frame += 1;
201 }
202 }
203 self.last_confirmed = Some(self.last_confirmed.map_or(frame, |c| c.max(frame)));
204 }
205
206 pub fn confirmed_at(&self, frame: Frame) -> Option<&Input> {
209 if self.slots.is_empty() || frame < self.base_frame {
210 return None;
211 }
212 let idx = (frame - self.base_frame) as usize;
213 self.slots.get(idx).and_then(|s| s.as_ref())
214 }
215
216 pub fn last_confirmed_at_or_before(&self, frame: Frame) -> Option<&Input> {
219 if self.slots.is_empty() || frame < self.base_frame {
220 return None;
221 }
222 let end = self.base_frame + self.slots.len() as u64;
223 let mut f = frame.min(end - 1);
224 loop {
225 let idx = (f - self.base_frame) as usize;
226 if let Some(input) = self.slots[idx].as_ref() {
227 return Some(input);
228 }
229 if f == self.base_frame {
230 return None;
231 }
232 f -= 1;
233 }
234 }
235
236 pub fn last_confirmed_frame(&self) -> Option<Frame> {
238 self.last_confirmed
239 }
240}
241
242#[derive(Debug, Clone, Copy)]
244pub struct RollbackConfig {
245 pub max_rollback_frames: usize,
249}
250
251impl Default for RollbackConfig {
252 fn default() -> Self {
253 Self { max_rollback_frames: 240 }
254 }
255}
256
257pub fn repeat_last_input<Input: Clone>(last: &Input, _frame: Frame) -> Input {
263 last.clone()
264}
265
266pub struct RollbackSession<State: Clone + Debug, Input: Clone + Debug + PartialEq> {
291 state_history: StateHistory<State>,
292 used: Vec<StateHistory<Input>>,
295 timelines: Vec<InputTimeline<Input>>,
296 neutral: Vec<Input>,
298
299 current_state: State,
300 head_frame: Frame,
301
302 advance: fn(&State, &[Input]) -> State,
303 predictor: fn(&Input, Frame) -> Input,
304
305 rollback_enabled: bool,
306 earliest_incorrect: Option<Frame>,
307 last_rollback_len: usize,
308 max_rollback_len: usize,
309 rollback_count: u64,
310}
311
312impl<State: Clone + Debug, Input: Clone + Debug + PartialEq> RollbackSession<State, Input> {
313 pub fn new(initial_state: State, neutral_inputs: Vec<Input>, config: RollbackConfig, advance: fn(&State, &[Input]) -> State) -> Self {
319 let cap = config.max_rollback_frames.max(1);
320 let players = neutral_inputs.len();
321 Self {
322 state_history: StateHistory::new(cap),
323 used: (0..players).map(|_| StateHistory::new(cap)).collect(),
324 timelines: (0..players).map(|_| InputTimeline::new(cap)).collect(),
325 neutral: neutral_inputs,
326 current_state: initial_state,
327 head_frame: 0,
328 advance,
329 predictor: repeat_last_input,
330 rollback_enabled: true,
331 earliest_incorrect: None,
332 last_rollback_len: 0,
333 max_rollback_len: 0,
334 rollback_count: 0,
335 }
336 }
337
338 pub fn with_predictor(mut self, predictor: fn(&Input, Frame) -> Input) -> Self {
342 self.predictor = predictor;
343 self
344 }
345
346 pub fn num_players(&self) -> usize {
347 self.timelines.len()
348 }
349
350 pub fn current_frame(&self) -> Frame {
353 self.head_frame
354 }
355
356 pub fn state(&self) -> &State {
359 &self.current_state
360 }
361
362 pub fn state_at(&self, frame: Frame) -> Option<State> {
367 if frame == self.head_frame {
368 return Some(self.current_state.clone());
369 }
370 self.state_history.restore(frame)
371 }
372
373 pub fn set_rollback_enabled(&mut self, enabled: bool) {
380 self.rollback_enabled = enabled;
381 }
382
383 pub fn queue_local_input(&mut self, player: usize, input: Input) {
387 self.timelines[player].confirm(self.head_frame, input);
388 }
389
390 pub fn confirm_remote_input(&mut self, player: usize, frame: Frame, input: Input) {
395 self.timelines[player].confirm(frame, input.clone());
396
397 if frame < self.head_frame
398 && let Some(used) = self.used[player].restore(frame)
399 && used != input
400 {
401 self.earliest_incorrect = Some(self.earliest_incorrect.map_or(frame, |cur| cur.min(frame)));
402 }
403 }
404
405 pub fn advance_frame(&mut self) {
409 self.resolve_rollback();
410 let f = self.head_frame;
411 self.simulate_frame(f);
412 self.head_frame += 1;
413 }
414
415 pub fn is_frame_confirmed(&self, frame: Frame) -> bool {
419 self.timelines.iter().all(|t| t.confirmed_at(frame).is_some())
420 }
421
422 pub fn confirmed_frame(&self, player: usize) -> Option<Frame> {
424 self.timelines[player].last_confirmed_frame()
425 }
426
427 pub fn prediction_horizon(&self) -> usize {
431 if self.head_frame == 0 {
432 return 0;
433 }
434 let last_simulated = self.head_frame - 1;
435 (0..self.timelines.len())
436 .map(|p| match self.timelines[p].last_confirmed_frame() {
437 Some(c) if c >= last_simulated => 0,
438 Some(c) => (last_simulated - c) as usize,
439 None => self.head_frame as usize,
440 })
441 .max()
442 .unwrap_or(0)
443 }
444
445 pub fn last_rollback_frames(&self) -> usize {
448 self.last_rollback_len
449 }
450
451 pub fn max_rollback_frames(&self) -> usize {
453 self.max_rollback_len
454 }
455
456 pub fn rollback_count(&self) -> u64 {
458 self.rollback_count
459 }
460
461 fn resolve_rollback(&mut self) {
463 self.last_rollback_len = 0;
464 let Some(mut earliest) = self.earliest_incorrect.take() else {
465 return;
466 };
467 if !self.rollback_enabled {
468 return; }
470 if earliest >= self.head_frame {
471 return; }
473 match self.state_history.oldest_frame() {
477 Some(oldest) if earliest < oldest => earliest = oldest,
478 None => return,
479 _ => {}
480 }
481 let Some(restored) = self.state_history.restore(earliest) else {
482 return;
483 };
484
485 let target = self.head_frame; self.current_state = restored;
487 let mut f = earliest;
488 while f < target {
489 self.simulate_frame(f);
490 f += 1;
491 }
492
493 let len = (target - earliest) as usize;
494 self.last_rollback_len = len;
495 self.max_rollback_len = self.max_rollback_len.max(len);
496 self.rollback_count += 1;
497 }
498
499 fn simulate_frame(&mut self, frame: Frame) {
502 self.state_history.save(frame, self.current_state.clone());
503
504 let mut inputs = Vec::with_capacity(self.timelines.len());
505 for p in 0..self.timelines.len() {
506 let input = self.input_for(p, frame);
507 self.used[p].save(frame, input.clone());
508 inputs.push(input);
509 }
510 self.current_state = (self.advance)(&self.current_state, &inputs);
511 }
512
513 fn input_for(&self, p: usize, frame: Frame) -> Input {
517 if let Some(confirmed) = self.timelines[p].confirmed_at(frame) {
518 return confirmed.clone();
519 }
520 let basis = self.timelines[p].last_confirmed_at_or_before(frame).cloned().unwrap_or_else(|| self.neutral[p].clone());
521 (self.predictor)(&basis, frame)
522 }
523}
524
525#[cfg(test)]
526mod tests {
527 use super::*;
528
529 #[derive(Clone, Copy, Debug, PartialEq)]
532 struct World {
533 pos: [i64; 2],
534 }
535
536 #[derive(Clone, Copy, Debug, PartialEq)]
537 struct In(i64);
538
539 const NEUTRAL: In = In(0);
540
541 fn step(state: &World, inputs: &[In]) -> World {
542 let mut next = *state;
543 next.pos[0] += inputs[0].0;
544 next.pos[1] += inputs[1].0;
545 next
546 }
547
548 fn session() -> RollbackSession<World, In> {
549 RollbackSession::new(World { pos: [0, 0] }, vec![NEUTRAL, NEUTRAL], RollbackConfig { max_rollback_frames: 64 }, step)
550 }
551
552 mod the_predictor {
559 use super::*;
560
561 fn always_neutral(_last: &In, _frame: Frame) -> In {
564 NEUTRAL
565 }
566
567 fn rollbacks_with(predictor: Option<fn(&In, Frame) -> In>) -> u64 {
570 let mut s = session();
571 if let Some(predictor) = predictor {
572 s = s.with_predictor(predictor);
573 }
574 for frame in 0..20u64 {
575 s.queue_local_input(0, In(1));
576 if frame >= 2 {
579 s.confirm_remote_input(1, frame - 2, In(1));
580 }
581 s.advance_frame();
582 }
583 s.rollback_count()
584 }
585
586 #[test]
587 fn a_predictor_that_guesses_right_costs_fewer_rollbacks() {
588 let repeating = rollbacks_with(None);
591 let neutral = rollbacks_with(Some(always_neutral));
592 assert!(
593 neutral > repeating,
594 "the wrong guess re-simulates more: {neutral} against {repeating}"
595 );
596 }
597
598 #[test]
599 fn the_frame_counters_agree_with_what_was_driven() {
600 let mut s = session();
603 assert_eq!(s.num_players(), 2);
604 assert_eq!(s.current_frame(), 0);
605 assert_eq!(s.confirmed_frame(1), None, "nothing confirmed yet");
606
607 for frame in 0..5u64 {
608 s.queue_local_input(0, In(1));
609 s.confirm_remote_input(1, frame, In(0));
610 s.advance_frame();
611 }
612
613 assert_eq!(s.current_frame(), 5, "five frames driven, five frames on");
614 assert_eq!(s.confirmed_frame(1), Some(4), "and the peer is confirmed through the last");
615 }
616 }
617
618 fn ground_truth(inputs0: &[In], inputs1: &[In]) -> World {
620 let mut w = World { pos: [0, 0] };
621 for f in 0..inputs0.len() {
622 w = step(&w, &[inputs0[f], inputs1[f]]);
623 }
624 w
625 }
626
627 #[test]
628 fn state_history_saves_restores_and_evicts_by_frame() {
629 let mut h = StateHistory::new(3);
630 h.save(0, 10);
631 h.save(1, 11);
632 h.save(2, 12);
633 assert_eq!(h.restore(1), Some(11));
634 assert_eq!(h.oldest_frame(), Some(0));
635 assert_eq!(h.latest_frame(), Some(2));
636
637 h.save(3, 13); assert_eq!(h.restore(0), None);
639 assert_eq!(h.oldest_frame(), Some(1));
640 assert_eq!(h.restore(3), Some(13));
641 }
642
643 #[test]
644 fn state_history_overwrites_a_frame_in_place() {
645 let mut h = StateHistory::new(4);
646 h.save(0, 1);
647 h.save(1, 2);
648 h.save(1, 99); assert_eq!(h.restore(1), Some(99));
650 assert_eq!(h.latest_frame(), Some(1), "overwriting does not extend the window");
651 }
652
653 #[test]
654 fn a_non_contiguous_save_is_counted_because_it_shortens_the_reach() {
655 let mut h = StateHistory::new(4);
659 h.save(0, 1);
660 h.save(1, 2);
661 assert_eq!(h.resets(), 0);
662 h.save(50, 3);
663 assert_eq!(h.resets(), 1);
664 assert_eq!(h.oldest_frame(), Some(50), "and the reach is now one frame");
665 assert_eq!(h.restore(1), None);
666 }
667
668 #[test]
669 fn input_timeline_predicts_the_last_confirmed() {
670 let mut t = InputTimeline::new(8);
671 t.confirm(0, In(5));
672 t.confirm(1, In(5));
673 assert_eq!(t.confirmed_at(1), Some(&In(5)));
674 assert_eq!(t.confirmed_at(2), None, "frame 2 is unconfirmed");
675 assert_eq!(t.last_confirmed_at_or_before(5), Some(&In(5)));
677 assert_eq!(t.last_confirmed_frame(), Some(1));
678 }
679
680 #[test]
681 fn a_correct_prediction_never_rolls_back() {
682 let mut s = session();
687 let inputs = [In(2); 10];
688 s.confirm_remote_input(1, 0, inputs[0]);
689 for f in 0..10u64 {
690 s.queue_local_input(0, In(1));
691 if f >= 3 {
693 s.confirm_remote_input(1, f - 3, inputs[(f - 3) as usize]);
694 }
695 s.advance_frame();
696 }
697 assert_eq!(s.rollback_count(), 0, "a held remote input is predicted exactly");
698 assert_eq!(s.last_rollback_frames(), 0);
699 }
700
701 #[test]
702 fn a_wrong_prediction_rolls_back_and_lands_on_the_truth() {
703 let remote: Vec<In> = (0..10).map(|f| if f < 4 { In(1) } else { In(-3) }).collect();
705 let local = vec![In(1); 10];
706
707 let mut s = session();
708 for f in 0..10u64 {
709 s.queue_local_input(0, local[f as usize]);
710 if f >= 2 {
712 let past = f - 2;
713 s.confirm_remote_input(1, past, remote[past as usize]);
714 }
715 s.advance_frame();
716 }
717 for f in 8..10u64 {
719 s.confirm_remote_input(1, f, remote[f as usize]);
720 }
721 s.resolve_rollback();
722
723 assert!(s.rollback_count() > 0, "the direction change forced at least one rollback");
724 let truth = ground_truth(&local, &remote);
727 assert_eq!(*s.state(), truth, "rollback converged on the ground truth");
728 }
729
730 #[test]
731 fn two_peers_exchanging_inputs_converge_to_the_same_world() {
732 let p0: Vec<In> = (0..40).map(|f| In(((f * 7) % 5) as i64 - 2)).collect();
736 let p1: Vec<In> = (0..40).map(|f| In(((f * 3) % 4) as i64 - 1)).collect();
737
738 let mut a = session(); let mut b = session(); let delay = 2u64;
743 for f in 0..40u64 {
744 a.queue_local_input(0, p0[f as usize]);
745 b.queue_local_input(1, p1[f as usize]);
746
747 if f >= delay {
748 let past = f - delay;
749 a.confirm_remote_input(1, past, p1[past as usize]); b.confirm_remote_input(0, past, p0[past as usize]); }
752 a.advance_frame();
753 b.advance_frame();
754 }
755 for f in 38..40u64 {
757 a.confirm_remote_input(1, f, p1[f as usize]);
758 b.confirm_remote_input(0, f, p0[f as usize]);
759 }
760 a.resolve_rollback();
761 b.resolve_rollback();
762
763 let truth = ground_truth(&p0, &p1);
764 assert_eq!(*a.state(), truth, "peer A converged");
765 assert_eq!(*b.state(), truth, "peer B converged");
766 assert_eq!(a.state(), b.state(), "the two peers agree frame for frame");
767 }
768
769 #[test]
770 fn delay_based_policy_can_tell_when_a_frame_is_fully_known() {
771 let mut s = session();
772 s.queue_local_input(0, In(1));
773 assert!(!s.is_frame_confirmed(0), "the remote input for frame 0 is not in yet");
774 s.confirm_remote_input(1, 0, In(1));
775 assert!(s.is_frame_confirmed(0), "now every player's frame-0 input is known");
776 }
777
778 #[test]
779 fn a_correction_older_than_the_history_does_not_panic() {
780 let mut s = RollbackSession::new(World { pos: [0, 0] }, vec![NEUTRAL, NEUTRAL], RollbackConfig { max_rollback_frames: 4 }, step);
784 for f in 0..20u64 {
785 s.queue_local_input(0, In(1));
786 s.confirm_remote_input(1, f, In(1)); s.advance_frame();
788 }
789 s.confirm_remote_input(1, 0, In(99));
791 s.advance_frame(); assert!(s.state().pos[0].is_positive() || s.state().pos[0] == 0);
793 let _ = s.state();
794 }
795
796 #[test]
797 fn rollback_disabled_keeps_a_wrong_guess_and_diverges_from_the_truth() {
798 let remote: Vec<In> = (0..10).map(|f| if f < 4 { In(1) } else { In(-3) }).collect();
802 let local = vec![In(1); 10];
803
804 let mut s = session();
805 s.set_rollback_enabled(false);
806 for f in 0..10u64 {
807 s.queue_local_input(0, local[f as usize]);
808 if f >= 2 {
809 let past = f - 2;
810 s.confirm_remote_input(1, past, remote[past as usize]);
811 }
812 s.advance_frame();
813 }
814 for f in 8..10u64 {
815 s.confirm_remote_input(1, f, remote[f as usize]);
816 }
817 s.resolve_rollback();
818
819 assert_eq!(s.rollback_count(), 0, "rollback disabled never re-simulates");
820 let truth = ground_truth(&local, &remote);
821 assert_ne!(*s.state(), truth, "without rollback the trusted guess never converges");
822 }
823
824 #[test]
825 fn state_at_a_confirmed_frame_matches_across_two_peers() {
826 let p0: Vec<In> = (0..30).map(|f| In(((f * 5) % 3) as i64 - 1)).collect();
829 let p1: Vec<In> = (0..30).map(|f| In(((f * 2) % 3) as i64 - 1)).collect();
830
831 let mut a = session();
832 let mut b = session();
833 for f in 0..30u64 {
834 a.queue_local_input(0, p0[f as usize]);
835 b.queue_local_input(1, p1[f as usize]);
836 if f >= 3 {
837 a.confirm_remote_input(1, f - 3, p1[(f - 3) as usize]);
838 b.confirm_remote_input(0, f - 3, p0[(f - 3) as usize]);
839 }
840 a.advance_frame();
841 b.advance_frame();
842 }
843 let cf = 20;
846 let sa = a.state_at(cf).expect("A retains the frame");
847 let sb = b.state_at(cf).expect("B retains the frame");
848 assert_eq!(sa, sb, "a fully-confirmed frame is identical on both peers");
849 }
850
851 #[test]
852 fn the_prediction_horizon_reflects_how_far_ahead_of_confirmation_it_is() {
853 let mut s = session();
854 for _ in 0..6u64 {
855 s.queue_local_input(0, In(1));
856 s.advance_frame();
857 }
858 assert_eq!(s.prediction_horizon(), 6);
861 s.confirm_remote_input(1, 4, In(0));
862 assert_eq!(s.prediction_horizon(), 1, "confirmed through frame 4, one frame ahead");
863 }
864}