use std::{
ops::{Deref, DerefMut},
sync::atomic::{AtomicI64, Ordering},
time::{Duration, Instant},
};
use parking_lot::Mutex;
use tracy_client::plot;
struct TimekeeperInner {
error: i64,
smoothed_error: i64,
last_frame_time: Instant,
}
const MAX_SLEW_UP: f64 = 0.001;
const MAX_SLEW_DOWN: f64 = 0.00025;
#[repr(align(64))]
struct CachelineAligned<T>(T);
impl<T> Deref for CachelineAligned<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for CachelineAligned<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[derive(Default)]
struct Skew {
smoothed: AtomicI64,
raw: AtomicI64,
}
pub(crate) struct Timekeeper {
initial_timebase_estimate: Instant,
initial_timebase_tick: u64,
inner: CachelineAligned<Mutex<TimekeeperInner>>,
current_skew: CachelineAligned<Skew>,
}
impl Timekeeper {
pub(crate) fn new(initial_tick: u64) -> Self {
let now = Instant::now();
Self {
initial_timebase_estimate: now,
initial_timebase_tick: initial_tick,
inner: CachelineAligned(Mutex::new(TimekeeperInner {
error: 0,
smoothed_error: 0,
last_frame_time: now,
})),
current_skew: CachelineAligned(Skew::default()),
}
}
pub(crate) fn now(&self) -> u64 {
self.time_to_tick_estimate(Instant::now())
}
pub(crate) fn update_error(&self, server_tick: u64) {
if server_tick < self.initial_timebase_tick {
panic!(
"Server time ran backwards: {:?} vs {:?}",
server_tick, self.initial_timebase_tick
);
}
let now = Instant::now();
let new_timebase_estimate =
now - Duration::from_nanos(server_tick - self.initial_timebase_tick);
let error = new_timebase_estimate - self.initial_timebase_estimate;
self.inner.lock().error = error.as_nanos().try_into().unwrap();
}
pub(crate) fn update_frame(&self) {
let mut lock = self.inner.lock();
let now = Instant::now();
let last_frame_nanos: i64 = (now - lock.last_frame_time).as_nanos().try_into().unwrap();
let last_frame_nanos = last_frame_nanos as f64;
plot!("lock_error", lock.error as f64);
plot!("lock_sm_error", lock.smoothed_error as f64);
if lock.error > lock.smoothed_error {
let diff_nanos = lock.error - lock.smoothed_error;
let cap = (last_frame_nanos * MAX_SLEW_UP) as i64;
let new_smoothed_error = lock.smoothed_error + std::cmp::min(diff_nanos, cap);
lock.smoothed_error = new_smoothed_error;
self.current_skew.raw.store(lock.error, Ordering::Relaxed);
self.current_skew
.smoothed
.store(new_smoothed_error, Ordering::Relaxed);
} else if lock.error < lock.smoothed_error {
let diff_nanos = lock.smoothed_error - lock.error;
let cap = (last_frame_nanos * MAX_SLEW_DOWN) as i64;
let new_smoothed_error = lock.smoothed_error - std::cmp::min(diff_nanos, cap);
lock.smoothed_error = new_smoothed_error;
self.current_skew.raw.store(lock.error, Ordering::Relaxed);
self.current_skew
.smoothed
.store(new_smoothed_error, Ordering::Relaxed);
}
lock.last_frame_time = now;
}
pub(crate) fn get_smoothed_offset(&self) -> i64 {
self.current_skew.smoothed.load(Ordering::Relaxed)
}
pub(crate) fn get_raw_offset(&self) -> i64 {
self.current_skew.raw.load(Ordering::Relaxed)
}
pub(crate) fn adjust_server_tick(&self, server_tick: u64) -> u64 {
(server_tick as i128 + self.get_smoothed_offset() as i128)
.try_into()
.unwrap()
}
#[allow(dead_code)]
pub(crate) fn estimated_send_time(&self, server_tick: u64) -> Instant {
if server_tick < self.initial_timebase_tick {
panic!(
"Server time ran backwards: {:?} vs {:?}",
server_tick, self.initial_timebase_tick
);
}
self.initial_timebase_estimate
+ Duration::from_nanos(
self.adjust_server_tick(server_tick - self.initial_timebase_tick),
)
}
pub(crate) fn time_to_tick_estimate(&self, time: Instant) -> u64 {
let raw_nanos: i128 = (time - self.initial_timebase_estimate).as_nanos() as i128
+ self.initial_timebase_tick as i128;
(raw_nanos - self.get_smoothed_offset() as i128) as u64
}
}