use alloc::collections::VecDeque;
use alloc::{
format,
string::{String, ToString},
vec::Vec,
};
use bevy_ecs::component::Component;
use bevy_reflect::Reflect;
use bevy_utils::prelude::DebugName;
use core::convert::TryFrom;
use core::fmt::{Debug, Formatter};
use lightyear_core::tick::Tick;
use serde::{Deserialize, Serialize};
#[allow(unused_imports)]
use tracing::{info, trace};
#[derive(Component, Reflect)]
pub struct InputBuffer<S, M> {
pub start_tick: Option<Tick>,
pub buffer: VecDeque<Compressed<S>>,
pub last_remote_tick: Option<Tick>,
#[reflect(ignore)]
pub marker: core::marker::PhantomData<M>,
}
impl<S: Debug, M> Debug for InputBuffer<S, M> {
#[inline]
fn fmt(&self, f: &mut Formatter) -> ::core::fmt::Result {
f.debug_struct("InputBuffer")
.field("start_tick", &self.start_tick)
.field("buffer", &self.buffer)
.field("last_remote_tick", &self.last_remote_tick)
.finish()
}
}
impl<T: Debug, M> core::fmt::Display for InputBuffer<T, M> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let ty = DebugName::type_name::<T>();
let Some(tick) = self.start_tick else {
return write!(f, "EmptyInputBuffer");
};
let buffer_str = self
.buffer
.iter()
.enumerate()
.map(|(i, item)| {
let str = match item {
Compressed::Absent => "Absent".to_string(),
Compressed::SameAsPrecedent => "SameAsPrecedent".to_string(),
Compressed::Input(data) => format!("{data:?}"),
};
format!("{:?}: {}\n", tick + i as i32, str)
})
.collect::<Vec<String>>()
.join("");
write!(f, "InputBuffer<{ty:?}>:\n {buffer_str}")
}
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Reflect)]
pub enum Compressed<T> {
Absent,
SameAsPrecedent,
Input(T),
}
impl<T> From<Option<T>> for Compressed<T> {
fn from(value: Option<T>) -> Self {
match value {
Some(value) => Compressed::Input(value),
_ => Compressed::Absent,
}
}
}
impl<T, M> Default for InputBuffer<T, M> {
fn default() -> Self {
Self {
buffer: VecDeque::new(),
start_tick: None,
last_remote_tick: None,
marker: Default::default(),
}
}
}
impl<T: Clone + PartialEq, M> InputBuffer<T, M> {
pub fn len(&self) -> usize {
self.buffer.len()
}
pub fn clip_after(&mut self, tick: Tick) {
let Some(end_tick) = self.end_tick() else {
return;
};
let start_tick = self.start_tick.unwrap();
if tick < start_tick {
self.start_tick = None;
self.buffer.clear();
return;
}
if tick >= end_tick {
return;
}
let new_end = usize::try_from(tick - start_tick + 1).unwrap();
self.buffer.drain(new_end..);
}
pub fn extend_to_range(&mut self, start_tick: Tick, end_tick: Tick) {
if self.start_tick.is_none() {
self.start_tick = Some(start_tick);
}
let mut current_start = self.start_tick.unwrap();
if start_tick < current_start {
let prepend_count = (current_start - start_tick) as usize;
for _ in 0..prepend_count {
self.buffer.push_front(Compressed::Absent);
}
self.start_tick = Some(start_tick);
current_start = start_tick;
}
let current_end = current_start + (self.buffer.len() as i32 - 1);
if end_tick > current_end {
let append_count = (end_tick - current_end) as usize;
for _ in 0..append_count {
self.buffer.push_back(Compressed::Absent);
}
}
}
pub fn set(&mut self, tick: Tick, value: T) {
if let Some(precedent) = self.get(tick - 1)
&& precedent == &value
{
self.set_raw(tick, Compressed::SameAsPrecedent);
return;
}
self.set_raw(tick, Compressed::Input(value));
}
pub fn set_empty(&mut self, tick: Tick) {
self.set_raw(tick, Compressed::Absent);
}
pub fn set_raw(&mut self, tick: Tick, value: Compressed<T>) {
let Some(start_tick) = self.start_tick else {
self.start_tick = Some(tick);
self.buffer.push_back(value);
return;
};
if tick < start_tick {
return;
}
let end_tick = start_tick + (self.buffer.len() as i32 - 1);
if tick > end_tick {
for _ in 0..(tick - end_tick - 1) {
trace!("fill ticks");
self.buffer.push_back(Compressed::SameAsPrecedent);
}
self.buffer.push_back(Compressed::Absent);
}
let entry = self.buffer.get_mut((tick - start_tick) as usize).unwrap();
*entry = value;
}
pub fn pop_keeping_last(&mut self, tick: Tick) -> Option<T> {
let Some((last_tick, _)) = self.get_last_with_tick() else {
return self.pop(tick);
};
let clamped = if tick >= last_tick {
last_tick - 1
} else {
tick
};
self.pop(clamped)
}
pub fn pop(&mut self, tick: Tick) -> Option<T> {
let start_tick = self.start_tick?;
if tick < start_tick {
return None;
}
if tick > start_tick + (self.buffer.len() as i32 - 1) {
self.buffer = VecDeque::new();
self.start_tick = Some(tick + 1);
return None;
}
let mut popped = Compressed::Absent;
for _ in 0..(tick + 1 - start_tick) {
let data = self.buffer.pop_front().unwrap();
match data {
Compressed::Absent | Compressed::Input(_) => {
popped = data;
}
_ => {}
}
}
self.start_tick = Some(tick + 1);
if let Some(Compressed::SameAsPrecedent) = self.buffer.front() {
*self.buffer.front_mut().unwrap() = popped.clone();
}
match popped {
Compressed::Input(value) => Some(value),
_ => None,
}
}
pub fn get_raw(&self, tick: Tick) -> &Compressed<T> {
let Some(start_tick) = self.start_tick else {
return &Compressed::Absent;
};
if self.buffer.is_empty() {
return &Compressed::Absent;
}
if tick < start_tick || tick > start_tick + (self.buffer.len() as i32 - 1) {
return &Compressed::Absent;
}
self.buffer.get((tick - start_tick) as usize).unwrap()
}
pub fn get(&self, tick: Tick) -> Option<&T> {
let start_tick = self.start_tick?;
if self.buffer.is_empty() {
return None;
}
if tick < start_tick || tick > start_tick + (self.buffer.len() as i32 - 1) {
return None;
}
let data = self.buffer.get((tick - start_tick) as usize).unwrap();
match data {
Compressed::Absent => None,
Compressed::SameAsPrecedent => {
self.get(tick - 1)
}
Compressed::Input(data) => Some(data),
}
}
pub fn get_predict(&self, tick: Tick) -> Option<&T> {
let start_tick = self.start_tick?;
if self.buffer.is_empty() {
return None;
}
if tick < start_tick {
return None;
}
if tick > start_tick + (self.buffer.len() as i32 - 1) {
return self.get_last();
}
let data = self.buffer.get((tick - start_tick) as usize).unwrap();
match data {
Compressed::Absent => None,
Compressed::SameAsPrecedent => {
self.get(tick - 1)
}
Compressed::Input(data) => Some(data),
}
}
pub fn get_last(&self) -> Option<&T> {
let start_tick = self.start_tick?;
if self.buffer.is_empty() {
return None;
}
self.get(start_tick + (self.buffer.len() as i32 - 1))
}
pub fn get_last_with_tick(&self) -> Option<(Tick, &T)> {
let start_tick = self.start_tick?;
if self.buffer.is_empty() {
return None;
}
let end_tick = start_tick + (self.buffer.len() as i32 - 1);
self.get(end_tick)
.map(|action_state| (end_tick, action_state))
}
#[inline(always)]
pub fn end_tick(&self) -> Option<Tick> {
self.start_tick
.map(|start_tick| start_tick + (self.buffer.len() as i32 - 1))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_set_pop() {
let mut input_buffer = InputBuffer::<i32, i32>::default();
input_buffer.set(Tick(4), 0);
input_buffer.set(Tick(6), 1);
input_buffer.set(Tick(7), 1);
input_buffer.set(Tick(8), 1);
assert_eq!(input_buffer.get(Tick(4)), Some(&0));
assert_eq!(input_buffer.get(Tick(5)), Some(&0));
assert_eq!(input_buffer.get_raw(Tick(5)), &Compressed::SameAsPrecedent);
assert_eq!(input_buffer.get(Tick(6)), Some(&1));
assert_eq!(input_buffer.get_raw(Tick(7)), &Compressed::SameAsPrecedent);
assert_eq!(input_buffer.get_raw(Tick(8)), &Compressed::SameAsPrecedent);
assert_eq!(input_buffer.get(Tick(9)), None);
assert_eq!(input_buffer.pop(Tick(5)), Some(0));
assert_eq!(input_buffer.start_tick, Some(Tick(6)));
assert_eq!(input_buffer.pop(Tick(7)), Some(1));
assert_eq!(input_buffer.start_tick, Some(Tick(8)));
assert_eq!(input_buffer.get(Tick(8)), Some(&1));
assert_eq!(input_buffer.get_raw(Tick(8)), &Compressed::Input(1));
assert_eq!(input_buffer.buffer.len(), 1);
}
#[test]
fn test_extend_to_range_empty() {
let mut input_buffer: InputBuffer<i32, i32> = InputBuffer::default();
input_buffer.extend_to_range(Tick(5), Tick(7));
assert_eq!(input_buffer.start_tick, Some(Tick(5)));
assert_eq!(input_buffer.buffer.len(), 3);
assert_eq!(input_buffer.get_raw(Tick(5)), &Compressed::Absent);
assert_eq!(input_buffer.get_raw(Tick(6)), &Compressed::Absent);
assert_eq!(input_buffer.get_raw(Tick(7)), &Compressed::Absent);
}
#[test]
fn test_extend_to_range_right() {
let mut input_buffer: InputBuffer<i32, i32> = InputBuffer::default();
input_buffer.set(Tick(10), 42);
input_buffer.extend_to_range(Tick(10), Tick(13));
assert_eq!(input_buffer.start_tick, Some(Tick(10)));
assert_eq!(input_buffer.buffer.len(), 4);
assert_eq!(input_buffer.get_raw(Tick(10)), &Compressed::Input(42));
assert_eq!(input_buffer.get_raw(Tick(11)), &Compressed::Absent);
assert_eq!(input_buffer.get_raw(Tick(12)), &Compressed::Absent);
assert_eq!(input_buffer.get_raw(Tick(13)), &Compressed::Absent);
}
#[test]
fn test_extend_to_range_left() {
let mut input_buffer: InputBuffer<i32, i32> = InputBuffer::default();
input_buffer.set(Tick(10), 42);
input_buffer.extend_to_range(Tick(8), Tick(10));
assert_eq!(input_buffer.start_tick, Some(Tick(8)));
assert_eq!(input_buffer.buffer.len(), 3);
assert_eq!(input_buffer.get_raw(Tick(8)), &Compressed::Absent);
assert_eq!(input_buffer.get_raw(Tick(9)), &Compressed::Absent);
assert_eq!(input_buffer.get_raw(Tick(10)), &Compressed::Input(42));
}
#[test]
fn test_extend_to_range_both_sides() {
let mut input_buffer: InputBuffer<i32, i32> = InputBuffer::default();
input_buffer.set(Tick(5), 1);
input_buffer.set(Tick(6), 2);
input_buffer.extend_to_range(Tick(3), Tick(8));
assert_eq!(input_buffer.start_tick, Some(Tick(3)));
assert_eq!(input_buffer.buffer.len(), 6);
assert_eq!(input_buffer.get_raw(Tick(3)), &Compressed::Absent);
assert_eq!(input_buffer.get_raw(Tick(4)), &Compressed::Absent);
assert_eq!(input_buffer.get_raw(Tick(5)), &Compressed::Input(1));
assert_eq!(input_buffer.get_raw(Tick(6)), &Compressed::Input(2));
assert_eq!(input_buffer.get_raw(Tick(7)), &Compressed::Absent);
assert_eq!(input_buffer.get_raw(Tick(8)), &Compressed::Absent);
}
#[test]
fn test_set_empty_and_get_raw() {
let mut input_buffer: InputBuffer<i32, i32> = InputBuffer::default();
input_buffer.set_empty(Tick(3));
assert_eq!(input_buffer.get_raw(Tick(3)), &Compressed::Absent);
assert_eq!(input_buffer.get(Tick(3)), None);
}
#[test]
fn test_set_raw_and_get() {
let mut input_buffer: InputBuffer<i32, i32> = InputBuffer::default();
input_buffer.set_raw(Tick(2), Compressed::Input(7));
assert_eq!(input_buffer.get(Tick(2)), Some(&7));
input_buffer.set_raw(Tick(3), Compressed::SameAsPrecedent);
assert_eq!(input_buffer.get(Tick(3)), Some(&7));
}
#[test]
fn test_get_last_and_get_last_with_tick() {
let mut input_buffer: InputBuffer<i32, i32> = InputBuffer::default();
assert_eq!(input_buffer.get_last(), None);
assert_eq!(input_buffer.get_last_with_tick(), None);
input_buffer.set(Tick(1), 10);
input_buffer.set(Tick(2), 20);
assert_eq!(input_buffer.get_last(), Some(&20));
assert_eq!(input_buffer.get_last_with_tick(), Some((Tick(2), &20)));
}
#[test]
fn test_end_tick() {
let mut input_buffer: InputBuffer<i32, i32> = InputBuffer::default();
assert_eq!(input_buffer.end_tick(), None);
input_buffer.set(Tick(5), 1);
assert_eq!(input_buffer.end_tick(), Some(Tick(5)));
input_buffer.set(Tick(7), 2);
assert_eq!(input_buffer.end_tick(), Some(Tick(7)));
}
#[test]
fn test_pop_with_absent() {
let mut input_buffer: InputBuffer<i32, i32> = InputBuffer::default();
input_buffer.set(Tick(1), 1);
input_buffer.set(Tick(2), 2);
input_buffer.set_empty(Tick(3));
input_buffer.set(Tick(4), 2);
assert_eq!(input_buffer.pop(Tick(2)), Some(2));
assert_eq!(input_buffer.pop(Tick(3)), None);
assert_eq!(input_buffer.pop(Tick(4)), Some(2));
}
#[test]
fn test_pop_out_of_range() {
let mut input_buffer: InputBuffer<i32, i32> = InputBuffer::default();
input_buffer.set(Tick(10), 5);
assert_eq!(input_buffer.pop(Tick(5)), None);
assert_eq!(input_buffer.pop(Tick(20)), None);
assert_eq!(input_buffer.buffer.len(), 0);
assert_eq!(input_buffer.start_tick, Some(Tick(21)));
}
#[test]
fn test_server_pop_pattern_preserves_get_predict_fallback() {
let mut buf: InputBuffer<i32, i32> = InputBuffer::default();
buf.set(Tick(10), 42);
assert_eq!(buf.get_predict(Tick(13)), Some(&42));
buf.pop_keeping_last(Tick(11));
assert_eq!(
buf.get_predict(Tick(13)),
Some(&42),
"advancing the floor past the last entry must preserve the fallback",
);
}
#[test]
fn test_pop_same_absent_in_gap() {
let mut input_buffer: InputBuffer<i32, i32> = InputBuffer::default();
input_buffer.set(Tick(9), 5);
input_buffer.set(Tick(10), 5);
input_buffer.set_empty(Tick(11));
input_buffer.set_empty(Tick(12));
input_buffer.set_empty(Tick(13));
assert_eq!(input_buffer.pop(Tick(12)), None);
assert_eq!(input_buffer.get(Tick(13)), None);
assert_eq!(input_buffer.buffer.len(), 1);
}
#[test]
fn test_len() {
let mut input_buffer: InputBuffer<i32, i32> = InputBuffer::default();
assert_eq!(input_buffer.len(), 0);
input_buffer.set(Tick(1), 1);
assert_eq!(input_buffer.len(), 1);
input_buffer.set(Tick(2), 2);
assert_eq!(input_buffer.len(), 2);
}
#[test]
fn test_clip_after() {
let mut input_buffer: InputBuffer<i32, i32> = InputBuffer::default();
input_buffer.set(Tick(1), 1);
input_buffer.set(Tick(2), 2);
input_buffer.set(Tick(3), 2);
input_buffer.clip_after(Tick(3));
assert_eq!(input_buffer.len(), 3);
input_buffer.clip_after(Tick(1));
assert_eq!(input_buffer.len(), 1);
assert_eq!(input_buffer.get(Tick(1)), Some(&1));
assert_eq!(input_buffer.get(Tick(2)), None);
}
#[test]
fn test_get_vs_get_predict_past_buffer_end() {
let mut input_buffer = InputBuffer::<i32, i32>::default();
input_buffer.set(Tick(10), 42);
input_buffer.set(Tick(12), 99);
assert_eq!(input_buffer.get(Tick(10)), Some(&42));
assert_eq!(input_buffer.get_predict(Tick(10)), Some(&42));
assert_eq!(input_buffer.get(Tick(12)), Some(&99));
assert_eq!(input_buffer.get_predict(Tick(12)), Some(&99));
assert_eq!(input_buffer.get(Tick(15)), None);
assert_eq!(
input_buffer.get_predict(Tick(15)),
Some(&99),
"get_predict should fall back to the last known input"
);
assert_eq!(input_buffer.get(Tick(5)), None);
assert_eq!(input_buffer.get_predict(Tick(5)), None);
}
}