use crate::types::SequenceNumber;
use std::collections::VecDeque;
use std::fmt::Debug;
#[derive(Debug, Clone)]
pub struct BufferedInput<Op, PredictedStateSnapshot>
where
Op: Clone + Debug,
PredictedStateSnapshot: Clone + Debug,
{
pub sequence_number: SequenceNumber,
pub op: Op,
pub state_before_op_predicted: PredictedStateSnapshot,
}
#[derive(Debug, Clone)]
pub struct ClientInputBuffer<Op, PredictedStateSnapshot>
where
Op: Clone + Debug,
PredictedStateSnapshot: Clone + Debug,
{
inputs: VecDeque<BufferedInput<Op, PredictedStateSnapshot>>,
max_size: usize,
overflowed: u64,
predicted_through: SequenceNumber,
}
impl<Op, PredictedStateSnapshot> ClientInputBuffer<Op, PredictedStateSnapshot>
where
Op: Clone + Debug,
PredictedStateSnapshot: Clone + Debug,
{
pub fn new(max_size: usize) -> Self {
if max_size == 0 {
panic!("ClientInputBuffer max_size must be greater than 0");
}
ClientInputBuffer {
inputs: VecDeque::with_capacity(max_size),
max_size,
overflowed: 0,
predicted_through: 0,
}
}
pub fn record_input(
&mut self,
sequence_number: SequenceNumber,
op: Op,
state_before_op_predicted: PredictedStateSnapshot,
) {
if self.inputs.len() == self.max_size
&& let Some(discarded) = self.inputs.pop_front() {
self.overflowed += 1;
tracing::warn!(
seq = discarded.sequence_number,
"ClientInputBuffer full (size {}), discarding oldest input.",
self.max_size
);
}
self.inputs.push_back(BufferedInput {
sequence_number,
op,
state_before_op_predicted,
});
tracing::trace!(seq = sequence_number, "Recorded input into ClientInputBuffer.");
}
pub fn acknowledge_inputs_up_to(&mut self, ack_sequence_number: SequenceNumber) {
let mut ack_count = 0;
while let Some(input) = self.inputs.front() {
if input.sequence_number <= ack_sequence_number {
self.inputs.pop_front();
ack_count += 1;
} else {
break;
}
}
if ack_count > 0 {
tracing::debug!(
ack_seq = ack_sequence_number,
count = ack_count,
"Acknowledged and pruned inputs from buffer."
);
}
}
pub fn get_unacknowledged_inputs(
&self,
last_acknowledged_sequence_number: SequenceNumber,
) -> impl DoubleEndedIterator<Item = &BufferedInput<Op, PredictedStateSnapshot>> + ExactSizeIterator {
let start_index = self
.inputs
.iter()
.position(|bi| bi.sequence_number > last_acknowledged_sequence_number);
if let Some(idx) = start_index {
self.inputs.range(idx..)
} else {
self.inputs.range(0..0) }
}
pub fn unacknowledged_with_pass(
&mut self,
last_acknowledged_sequence_number: SequenceNumber,
) -> impl DoubleEndedIterator<Item = (&BufferedInput<Op, PredictedStateSnapshot>, bool)> + ExactSizeIterator {
let through = self.predicted_through;
if let Some(newest) = self.inputs.back() {
self.predicted_through = through.max(newest.sequence_number);
}
self
.get_unacknowledged_inputs(last_acknowledged_sequence_number)
.map(move |buffered| (buffered, buffered.sequence_number > through))
}
pub fn get_predicted_state_before_input(&self, sequence_number: SequenceNumber) -> Option<&PredictedStateSnapshot> {
self
.inputs
.iter()
.find(|buffered_input| buffered_input.sequence_number == sequence_number)
.map(|bi| &bi.state_before_op_predicted)
}
pub fn is_empty(&self) -> bool {
self.inputs.is_empty()
}
pub fn len(&self) -> usize {
self.inputs.len()
}
pub fn clear(&mut self) {
self.inputs.clear();
tracing::debug!("ClientInputBuffer cleared.");
}
pub fn overflowed(&self) -> u64 {
self.overflowed
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone, PartialEq)]
struct TestOp(u64);
#[derive(Debug, Clone, PartialEq)]
struct TestState(u64);
const BUFFER_MAX_SIZE: usize = 3;
#[test]
fn new_buffer_is_empty() {
let buffer = ClientInputBuffer::<TestOp, TestState>::new(BUFFER_MAX_SIZE);
assert!(buffer.is_empty());
assert_eq!(buffer.len(), 0);
}
#[test]
#[should_panic]
fn new_buffer_with_zero_size_panics() {
let _buffer = ClientInputBuffer::<TestOp, TestState>::new(0);
}
#[test]
fn record_and_get_inputs() {
let mut buffer = ClientInputBuffer::<TestOp, TestState>::new(BUFFER_MAX_SIZE);
buffer.record_input(1, TestOp(10), TestState(0));
buffer.record_input(2, TestOp(20), TestState(10));
assert_eq!(buffer.len(), 2);
let unacked: Vec<_> = buffer.get_unacknowledged_inputs(0).collect();
assert_eq!(unacked.len(), 2);
assert_eq!(unacked[0].sequence_number, 1);
assert_eq!(unacked[0].op, TestOp(10));
assert_eq!(unacked[0].state_before_op_predicted, TestState(0));
assert_eq!(unacked[1].sequence_number, 2);
}
#[test]
fn buffer_respects_max_size() {
let mut buffer = ClientInputBuffer::<TestOp, TestState>::new(2); buffer.record_input(1, TestOp(10), TestState(0));
buffer.record_input(2, TestOp(20), TestState(10));
assert_eq!(buffer.len(), 2);
buffer.record_input(3, TestOp(30), TestState(20));
assert_eq!(buffer.len(), 2);
let unacked: Vec<_> = buffer.get_unacknowledged_inputs(0).collect();
assert_eq!(unacked.len(), 2);
assert_eq!(unacked[0].sequence_number, 2); assert_eq!(unacked[1].sequence_number, 3);
assert_eq!(buffer.overflowed(), 1, "and the loss is counted, not only logged");
}
#[test]
fn overflow_is_counted_because_replay_is_no_longer_complete() {
let mut buffer = ClientInputBuffer::<TestOp, TestState>::new(2);
assert_eq!(buffer.overflowed(), 0);
for seq in 1..=5 {
buffer.record_input(seq, TestOp(seq * 10), TestState(seq));
}
assert_eq!(buffer.overflowed(), 3);
assert_eq!(buffer.len(), 2);
}
#[test]
fn acknowledge_inputs() {
let mut buffer = ClientInputBuffer::<TestOp, TestState>::new(BUFFER_MAX_SIZE);
buffer.record_input(1, TestOp(10), TestState(0));
buffer.record_input(2, TestOp(20), TestState(10));
buffer.record_input(3, TestOp(30), TestState(20));
buffer.acknowledge_inputs_up_to(1);
assert_eq!(buffer.len(), 2);
let unacked_after_1: Vec<_> = buffer.get_unacknowledged_inputs(1).collect();
assert_eq!(unacked_after_1.len(), 2);
assert_eq!(unacked_after_1[0].sequence_number, 2);
buffer.acknowledge_inputs_up_to(3);
assert_eq!(buffer.len(), 0);
assert!(buffer.is_empty());
let unacked_after_3: Vec<_> = buffer.get_unacknowledged_inputs(3).collect();
assert!(unacked_after_3.is_empty());
}
#[test]
fn acknowledge_non_existent_sequence() {
let mut buffer = ClientInputBuffer::<TestOp, TestState>::new(BUFFER_MAX_SIZE);
buffer.record_input(10, TestOp(100), TestState(0));
buffer.record_input(12, TestOp(120), TestState(100));
buffer.acknowledge_inputs_up_to(11);
assert_eq!(buffer.len(), 1); assert_eq!(buffer.inputs.front().unwrap().sequence_number, 12);
buffer.acknowledge_inputs_up_to(9);
assert_eq!(buffer.len(), 1); assert_eq!(buffer.inputs.front().unwrap().sequence_number, 12);
}
#[test]
fn get_unacknowledged_inputs_logic() {
let mut buffer = ClientInputBuffer::<TestOp, TestState>::new(5);
buffer.record_input(1, TestOp(10), TestState(0));
buffer.record_input(2, TestOp(20), TestState(10));
buffer.record_input(3, TestOp(30), TestState(20));
buffer.record_input(4, TestOp(40), TestState(30));
let unacked_0: Vec<_> = buffer.get_unacknowledged_inputs(0).collect();
assert_eq!(unacked_0.len(), 4);
let unacked_1: Vec<_> = buffer.get_unacknowledged_inputs(1).collect();
assert_eq!(unacked_1.len(), 3);
assert_eq!(unacked_1[0].sequence_number, 2);
let unacked_3: Vec<_> = buffer.get_unacknowledged_inputs(3).collect();
assert_eq!(unacked_3.len(), 1);
assert_eq!(unacked_3[0].sequence_number, 4);
let unacked_4: Vec<_> = buffer.get_unacknowledged_inputs(4).collect();
assert!(unacked_4.is_empty());
let unacked_5_future: Vec<_> = buffer.get_unacknowledged_inputs(5).collect();
assert!(unacked_5_future.is_empty());
}
#[test]
fn get_predicted_state_before_input_test() {
let mut buffer = ClientInputBuffer::<TestOp, TestState>::new(BUFFER_MAX_SIZE);
let state0 = TestState(0);
let state10 = TestState(10);
let state20 = TestState(20);
buffer.record_input(1, TestOp(10), state0.clone());
buffer.record_input(2, TestOp(20), state10.clone());
buffer.record_input(3, TestOp(30), state20.clone());
assert_eq!(buffer.get_predicted_state_before_input(1), Some(&state0));
assert_eq!(buffer.get_predicted_state_before_input(2), Some(&state10));
assert_eq!(buffer.get_predicted_state_before_input(3), Some(&state20));
assert_eq!(buffer.get_predicted_state_before_input(4), None); }
#[test]
fn clear_buffer() {
let mut buffer = ClientInputBuffer::<TestOp, TestState>::new(BUFFER_MAX_SIZE);
buffer.record_input(1, TestOp(10), TestState(0));
buffer.record_input(2, TestOp(20), TestState(10));
assert!(!buffer.is_empty());
buffer.clear();
assert!(buffer.is_empty());
assert_eq!(buffer.len(), 0);
}
#[test]
fn an_input_is_first_pass_once_however_often_it_is_replayed() {
let mut buffer = ClientInputBuffer::<u32, u32>::new(8);
for seq in 1..=3 {
buffer.record_input(seq, seq as u32 * 10, 0);
}
let passes: Vec<(SequenceNumber, bool)> = buffer.unacknowledged_with_pass(0).map(|(bi, first)| (bi.sequence_number, first)).collect();
assert_eq!(passes, vec![(1, true), (2, true), (3, true)], "never handed out before");
buffer.record_input(4, 40, 0);
let passes: Vec<(SequenceNumber, bool)> = buffer.unacknowledged_with_pass(0).map(|(bi, first)| (bi.sequence_number, first)).collect();
assert_eq!(passes, vec![(1, false), (2, false), (3, false), (4, true)], "only the new one is a first pass");
buffer.acknowledge_inputs_up_to(2);
let passes: Vec<(SequenceNumber, bool)> = buffer.unacknowledged_with_pass(2).map(|(bi, first)| (bi.sequence_number, first)).collect();
assert_eq!(passes, vec![(3, false), (4, false)], "a replay is never a first pass, whatever was acknowledged");
let plain: Vec<SequenceNumber> = buffer.get_unacknowledged_inputs(2).map(|bi| bi.sequence_number).collect();
assert_eq!(plain, vec![3, 4], "the unmarked iterator neither reads nor moves the mark");
}
}