#[derive(Clone, Debug)]
pub struct InputCoalescer<Input> {
last_sent: Option<Input>,
last_sent_ms: u64,
keepalive_ms: u64,
enabled: bool,
}
impl<Input: Clone + PartialEq> InputCoalescer<Input> {
pub fn new(keepalive_ms: u64) -> Self {
Self {
last_sent: None,
last_sent_ms: 0,
keepalive_ms,
enabled: true,
}
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn should_send(&mut self, input: &Input, now_ms: u64) -> bool {
let changed = self.last_sent.as_ref() != Some(input);
let keepalive_due = self.last_sent.is_none() || now_ms.saturating_sub(self.last_sent_ms) >= self.keepalive_ms;
if !self.enabled || changed || keepalive_due {
self.last_sent = Some(input.clone());
self.last_sent_ms = now_ms;
return true;
}
false
}
pub fn last_sent(&self) -> Option<&Input> {
self.last_sent.as_ref()
}
pub fn reset(&mut self) {
self.last_sent = None;
self.last_sent_ms = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_unchanged_input_is_not_retransmitted() {
let mut policy = InputCoalescer::new(1000);
assert!(policy.should_send(&1, 0));
let sent = (1..60).filter(|frame| policy.should_send(&1, frame * 16)).count();
assert_eq!(sent, 0, "a held direction went on the wire {sent} more times");
}
#[test]
fn a_change_always_goes_immediately() {
let mut policy = InputCoalescer::new(1000);
policy.should_send(&1, 0);
assert!(policy.should_send(&2, 16));
assert!(policy.should_send(&3, 32));
}
#[test]
fn the_keepalive_bounds_how_long_a_dropped_change_can_persist() {
let mut policy = InputCoalescer::new(120);
policy.should_send(&1, 0);
assert!(!policy.should_send(&1, 100), "not yet due");
assert!(policy.should_send(&1, 120), "the held input is resent");
assert!(!policy.should_send(&1, 200), "and the clock restarts from the resend");
assert!(policy.should_send(&1, 240));
}
#[test]
fn the_first_input_always_transmits() {
let mut policy = InputCoalescer::new(120);
assert!(policy.should_send(&0, 0), "the very first input must reach the server");
}
#[test]
fn turning_coalescing_off_transmits_everything() {
let mut policy = InputCoalescer::new(1000);
policy.set_enabled(false);
let sent = (0..60).filter(|frame| policy.should_send(&1, frame * 16)).count();
assert_eq!(sent, 60, "with coalescing off every input goes");
}
#[test]
fn a_reset_makes_the_next_input_transmit() {
let mut policy = InputCoalescer::new(10_000);
policy.should_send(&1, 0);
assert!(!policy.should_send(&1, 16));
policy.reset();
assert!(policy.should_send(&1, 32), "the server was told again after a reset");
assert_eq!(policy.last_sent(), Some(&1));
}
}