1use std::collections::VecDeque;
2use std::time::Duration;
3
4use serde::{Deserialize, Serialize};
5
6use crate::{NetcodeError, NetcodeResult};
7
8#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
10#[serde(default, deny_unknown_fields)]
11#[non_exhaustive]
12pub struct InterpolationConfig {
13 pub tick_rate: u32,
15 pub capacity: usize,
17 pub base_delay_ticks: f64,
19 pub min_delay_ticks: f64,
21 pub max_delay_ticks: f64,
23 pub smoothing: f64,
25 pub jitter_multiplier: f64,
27 pub late_sample_penalty_ticks: f64,
29 pub max_adjustment_per_sample_ticks: f64,
31}
32
33impl Default for InterpolationConfig {
34 fn default() -> Self {
35 Self {
36 tick_rate: 30,
37 capacity: 128,
38 base_delay_ticks: 2.0,
39 min_delay_ticks: 1.0,
40 max_delay_ticks: 8.0,
41 smoothing: 0.1,
42 jitter_multiplier: 2.0,
43 late_sample_penalty_ticks: 2.0,
44 max_adjustment_per_sample_ticks: 0.25,
45 }
46 }
47}
48
49impl InterpolationConfig {
50 pub fn validate(&self) -> NetcodeResult<()> {
52 let values = [
53 self.base_delay_ticks,
54 self.min_delay_ticks,
55 self.max_delay_ticks,
56 self.smoothing,
57 self.jitter_multiplier,
58 self.late_sample_penalty_ticks,
59 self.max_adjustment_per_sample_ticks,
60 ];
61 if !(1..=240).contains(&self.tick_rate) || self.capacity < 2 {
62 return Err(NetcodeError::InvalidConfig(
63 "interpolation Tick rate or capacity is invalid",
64 ));
65 }
66 if values
67 .iter()
68 .any(|value| !value.is_finite() || *value < 0.0)
69 || self.smoothing <= 0.0
70 || self.smoothing > 1.0
71 || self.min_delay_ticks > self.base_delay_ticks
72 || self.base_delay_ticks > self.max_delay_ticks
73 || self.max_adjustment_per_sample_ticks == 0.0
74 {
75 return Err(NetcodeError::InvalidConfig(
76 "interpolation delay parameters are invalid",
77 ));
78 }
79 Ok(())
80 }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub enum InterpolationInsert {
86 Newest,
88 Late,
90 Replaced,
92}
93
94#[derive(Debug, Clone, Copy, PartialEq)]
96pub struct InterpolationStats {
97 pub jitter_ticks: f64,
99 pub late_sample_pressure: f64,
101 pub delay_ticks: f64,
103 pub late_samples: u64,
105 pub replaced_samples: u64,
107}
108
109#[derive(Debug, Clone, Copy)]
111pub struct InterpolationSample<'a, S> {
112 pub render_tick: f64,
114 pub previous_tick: u64,
116 pub previous: &'a S,
118 pub next_tick: u64,
120 pub next: &'a S,
122 pub alpha: f64,
124 pub holding_newest: bool,
126}
127
128#[derive(Debug, Clone)]
130pub struct InterpolationBuffer<S> {
131 config: InterpolationConfig,
132 states: VecDeque<(u64, S)>,
133 last_arrival: Option<Duration>,
134 last_newest: Option<(u64, Duration)>,
135 jitter_ticks: f64,
136 late_pressure: f64,
137 delay_ticks: f64,
138 late_samples: u64,
139 replaced_samples: u64,
140}
141
142impl<S> InterpolationBuffer<S> {
143 pub fn new(config: InterpolationConfig) -> NetcodeResult<Self> {
145 config.validate()?;
146 Ok(Self {
147 config,
148 states: VecDeque::with_capacity(config.capacity),
149 last_arrival: None,
150 last_newest: None,
151 jitter_ticks: 0.0,
152 late_pressure: 0.0,
153 delay_ticks: config.base_delay_ticks,
154 late_samples: 0,
155 replaced_samples: 0,
156 })
157 }
158
159 pub fn insert(
161 &mut self,
162 tick: u64,
163 state: S,
164 arrived_at: Duration,
165 ) -> NetcodeResult<InterpolationInsert> {
166 if tick == 0 {
167 return Err(NetcodeError::InvalidInput(
168 "interpolation state Tick must be positive",
169 ));
170 }
171 if self
172 .last_arrival
173 .is_some_and(|last_arrival| arrived_at < last_arrival)
174 {
175 return Err(NetcodeError::InvalidSample(
176 "interpolation arrival time moved backwards",
177 ));
178 }
179 let newest = self.states.back().map(|(tick, _)| *tick);
180 let state_index = self
181 .states
182 .binary_search_by_key(&tick, |(buffered_tick, _)| *buffered_tick);
183 let disposition = match state_index {
184 Ok(_) => {
185 self.replaced_samples = self.replaced_samples.saturating_add(1);
186 InterpolationInsert::Replaced
187 }
188 Err(_) if newest.is_some_and(|newest| tick < newest) => {
189 self.late_samples = self.late_samples.saturating_add(1);
190 InterpolationInsert::Late
191 }
192 Err(_) => InterpolationInsert::Newest,
193 };
194
195 let late_observation = matches!(disposition, InterpolationInsert::Late);
196 self.late_pressure +=
197 ((u8::from(late_observation) as f64) - self.late_pressure) * self.config.smoothing;
198 if matches!(disposition, InterpolationInsert::Newest) {
199 if let Some((previous_tick, previous_arrival)) = self.last_newest {
200 let tick_gap = tick.saturating_sub(previous_tick);
201 if tick_gap > 0 {
202 let actual = arrived_at.saturating_sub(previous_arrival).as_secs_f64();
203 let expected = tick_gap as f64 / f64::from(self.config.tick_rate);
204 let variation_ticks =
205 (actual - expected).abs() * f64::from(self.config.tick_rate);
206 self.jitter_ticks +=
207 (variation_ticks - self.jitter_ticks) * self.config.smoothing;
208 }
209 }
210 self.last_newest = Some((tick, arrived_at));
211 }
212 self.last_arrival = Some(arrived_at);
213
214 let target_delay = (self.config.base_delay_ticks
215 + self.jitter_ticks * self.config.jitter_multiplier
216 + self.late_pressure * self.config.late_sample_penalty_ticks)
217 .clamp(self.config.min_delay_ticks, self.config.max_delay_ticks);
218 let adjustment = (target_delay - self.delay_ticks).clamp(
219 -self.config.max_adjustment_per_sample_ticks,
220 self.config.max_adjustment_per_sample_ticks,
221 );
222 self.delay_ticks += adjustment;
223
224 match state_index {
225 Ok(index) => self.states[index].1 = state,
226 Err(index) => self.states.insert(index, (tick, state)),
227 }
228 if self.states.len() > self.config.capacity {
229 self.states.pop_front();
230 }
231 Ok(disposition)
232 }
233
234 pub fn sample(&self, estimated_server_tick: f64) -> NetcodeResult<InterpolationSample<'_, S>> {
236 if !estimated_server_tick.is_finite() || estimated_server_tick < 0.0 {
237 return Err(NetcodeError::InvalidSample(
238 "estimated server Tick must be finite and non-negative",
239 ));
240 }
241 let Some((oldest_tick, oldest)) = self.states.front() else {
242 return Err(NetcodeError::InterpolationBufferEmpty);
243 };
244 let (newest_tick, newest) = self.states.back().unwrap();
245 let render_tick = (estimated_server_tick - self.delay_ticks).max(0.0);
246 if render_tick <= *oldest_tick as f64 {
247 return Ok(InterpolationSample {
248 render_tick,
249 previous_tick: *oldest_tick,
250 previous: oldest,
251 next_tick: *oldest_tick,
252 next: oldest,
253 alpha: 0.0,
254 holding_newest: false,
255 });
256 }
257 if render_tick >= *newest_tick as f64 {
258 return Ok(InterpolationSample {
259 render_tick,
260 previous_tick: *newest_tick,
261 previous: newest,
262 next_tick: *newest_tick,
263 next: newest,
264 alpha: 0.0,
265 holding_newest: render_tick > *newest_tick as f64,
266 });
267 }
268
269 let previous_bound = render_tick.floor() as u64;
270 let next_bound = render_tick.ceil() as u64;
271 let previous_index = self
272 .states
273 .binary_search_by_key(&previous_bound, |(tick, _)| *tick)
274 .unwrap_or_else(|index| index - 1);
275 let next_index = self
276 .states
277 .binary_search_by_key(&next_bound, |(tick, _)| *tick)
278 .unwrap_or_else(|index| index);
279 let (previous_tick, previous) = &self.states[previous_index];
280 let (next_tick, next) = &self.states[next_index];
281 let span = next_tick.saturating_sub(*previous_tick);
282 let alpha = if span == 0 {
283 0.0
284 } else {
285 ((render_tick - *previous_tick as f64) / span as f64).clamp(0.0, 1.0)
286 };
287 Ok(InterpolationSample {
288 render_tick,
289 previous_tick: *previous_tick,
290 previous,
291 next_tick: *next_tick,
292 next,
293 alpha,
294 holding_newest: false,
295 })
296 }
297
298 pub fn stats(&self) -> InterpolationStats {
300 InterpolationStats {
301 jitter_ticks: self.jitter_ticks,
302 late_sample_pressure: self.late_pressure,
303 delay_ticks: self.delay_ticks,
304 late_samples: self.late_samples,
305 replaced_samples: self.replaced_samples,
306 }
307 }
308
309 pub fn len(&self) -> usize {
311 self.states.len()
312 }
313
314 pub fn is_empty(&self) -> bool {
316 self.states.is_empty()
317 }
318
319 pub fn reset(&mut self) {
321 self.states.clear();
322 self.last_arrival = None;
323 self.last_newest = None;
324 self.jitter_ticks = 0.0;
325 self.late_pressure = 0.0;
326 self.delay_ticks = self.config.base_delay_ticks;
327 self.late_samples = 0;
328 self.replaced_samples = 0;
329 }
330}