use bevy_ecs::prelude::*;
use std::collections::VecDeque;
use std::time::Duration;
#[derive(Component, Clone, Copy, Debug, Default)]
pub struct Predicted;
pub fn predict_only(world: &mut World, hero: Entity) {
let others: Vec<Entity> = world
.query_filtered::<Entity, With<Predicted>>()
.iter(world)
.filter(|entity| *entity != hero)
.collect();
for entity in others {
world.entity_mut(entity).remove::<Predicted>();
}
world.entity_mut(hero).insert(Predicted);
}
pub const MAX_PENDING: usize = 240;
#[derive(Clone, Debug)]
pub struct Prediction<I> {
pending: VecDeque<(u64, I)>,
}
impl<I> Default for Prediction<I> {
fn default() -> Self {
Self {
pending: VecDeque::new(),
}
}
}
impl<I> Prediction<I> {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, tick: u64, input: I) {
if self.pending.len() == MAX_PENDING {
self.pending.pop_front();
}
self.pending.push_back((tick, input));
}
pub fn reconcile<S>(
&mut self,
acked_tick: u64,
mut state: S,
mut step: impl FnMut(&mut S, &I),
) -> S {
while self
.pending
.front()
.is_some_and(|(tick, _)| *tick <= acked_tick)
{
self.pending.pop_front();
}
for (_, input) in &self.pending {
step(&mut state, input);
}
state
}
pub fn pending(&self) -> impl Iterator<Item = &(u64, I)> {
self.pending.iter()
}
}
#[derive(Clone, Copy, Debug)]
pub struct Smoothing {
offset: [f32; 3],
half_life: Duration,
snap: f32,
}
impl Smoothing {
pub fn new(half_life: Duration, snap: f32) -> Self {
Self {
offset: [0.0; 3],
half_life,
snap,
}
}
pub fn correct(&mut self, shown: [f32; 3], corrected: [f32; 3]) {
let offset: [f32; 3] = std::array::from_fn(|i| shown[i] - corrected[i]);
let length = offset.iter().map(|x| x * x).sum::<f32>().sqrt();
self.offset = if length > self.snap { [0.0; 3] } else { offset };
}
pub fn shown(
&mut self,
simulated: [f32; 3],
elapsed: Duration,
) -> [f32; 3] {
if !self.half_life.is_zero() {
let decay = 0.5f32
.powf(elapsed.as_secs_f32() / self.half_life.as_secs_f32());
self.offset = self.offset.map(|x| x * decay);
} else {
self.offset = [0.0; 3];
}
std::array::from_fn(|i| simulated[i] + self.offset[i])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reconcile_replays_unconfirmed_inputs_on_the_host_state() {
fn step(x: &mut f32, input: &f32) {
*x += input;
}
let mut prediction = Prediction::new();
let mut client = 0.0;
for tick in 1..=5 {
prediction.push(tick, 1.0);
step(&mut client, &1.0);
}
assert_eq!(prediction.reconcile(3, 3.0, step), client);
assert_eq!(prediction.pending().count(), 2);
assert_eq!(prediction.reconcile(4, 2.0, step), 3.0);
assert_eq!(
prediction.pending().map(|(t, _)| *t).collect::<Vec<_>>(),
[5]
);
assert_eq!(prediction.reconcile(2, 2.0, step), 3.0);
for tick in 6..6 + MAX_PENDING as u64 + 10 {
prediction.push(tick, 0.0);
}
assert_eq!(prediction.pending().count(), MAX_PENDING);
}
#[test]
fn smoothing_fades_a_correction_without_touching_the_simulation() {
let mut prediction = Prediction::new();
let mut position = [0.0f32; 3];
let step = |p: &mut [f32; 3], dx: &f32| p[0] += dx;
for tick in 1..=3 {
prediction.push(tick, 1.0);
step(&mut position, &1.0);
}
let mut smoothing = Smoothing::new(Duration::from_millis(100), 2.0);
let shown = smoothing.shown(position, Duration::ZERO);
position = prediction.reconcile(1, [0.5, 0.0, 0.0], step);
assert_eq!(position, [2.5, 0.0, 0.0], "simulation takes it whole");
smoothing.correct(shown, position);
assert_eq!(smoothing.shown(position, Duration::ZERO), shown);
let later = smoothing.shown(position, Duration::from_millis(100));
assert!((later[0] - 2.75).abs() < 1e-5, "half the error left");
assert_eq!(position, [2.5, 0.0, 0.0], "never fed back");
smoothing.correct(position, [50.0, 0.0, 0.0]);
assert_eq!(smoothing.shown([50.0, 0.0, 0.0], Duration::ZERO)[0], 50.0);
}
}