twsnap 0.7.4

Common snapshot format between TwGpu and TwGame
Documentation
//! Timing for ddnet

use serde::{Deserialize, Serialize};
use std::fmt;
use std::ops::{Add, Sub};
use vek::num_traits::SaturatingSub;

// TODO: make it i64?
pub type SnapTick = i32;

#[derive(
    Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
)]
/// Time measured in Ticks (1/50 of a second = 20ms)
pub struct Instant(i64);

impl Add<Duration> for Instant {
    type Output = Instant;
    fn add(self, rhs: Duration) -> Self::Output {
        Instant(self.0 + rhs.0 as i64)
    }
}

impl Sub<Duration> for Instant {
    type Output = Instant;
    fn sub(self, rhs: Duration) -> Self::Output {
        Instant(self.0 - rhs.0 as i64)
    }
}

impl Instant {
    /// returns whether the duration has passed since the other instant
    pub fn duration_passed_since(self, past_time: Instant, duration: Duration) -> bool {
        past_time.0 + duration.0 as i64 <= self.0
    }

    pub fn duration_since(self, past_time: Instant) -> Option<Duration> {
        Some(Duration(self.0.checked_sub(past_time.0)? as u32))
    }

    pub fn advance(&self) -> Self {
        Self(self.0 + 1)
    }

    pub const fn zero() -> Instant {
        Instant(0)
    }

    pub fn seconds(&self) -> f32 {
        self.0 as f32 / 50.0
    }

    pub fn snap_tick(self) -> SnapTick {
        self.0.try_into().unwrap()
    }

    pub fn from_snap_tick(t: SnapTick) -> Self {
        Self(t.into())
    }
}

impl fmt::Display for Duration {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let days = self.0 / 50 / 60 / 60 / 24;
        let hours = self.0 / 50 / 60 / 60 % 24;
        let minutes = self.0 / 50 / 60 % 60;
        let seconds = self.0 / 50 % 60;
        let sub_seconds = (self.0 % 50) * 2;
        let ticks = self.0;
        if days > 0 {
            write!(
                f,
                "{days}d {hours:02}:{minutes:02}:{seconds:02}.{sub_seconds:02} ({ticks})"
            )
        } else if hours > 0 {
            write!(
                f,
                "{hours:02}:{minutes:02}:{seconds:02}.{sub_seconds:02} ({ticks})"
            )
        } else {
            write!(f, "{minutes:02}:{seconds:02}.{sub_seconds:02} ({ticks})",)
        }
    }
}

impl fmt::Display for Instant {
    // This trait requires `fmt` with this exact signature.
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let days = self.0 / 50 / 60 / 60 / 24;
        let hours = self.0 / 50 / 60 / 60 % 24;
        let minutes = self.0 / 50 / 60 % 60;
        let seconds = self.0 / 50 % 60;
        let ticks = self.0;
        if days > 0 {
            write!(f, "{days}d {hours:02}:{minutes:02}:{seconds:02} ({ticks})")
        } else if hours > 0 {
            write!(f, "{hours:02}:{minutes:02}:{seconds:02} ({ticks})")
        } else {
            write!(f, "{minutes:02}:{seconds:02} ({ticks})",)
        }
    }
}

/// Duration in Ticks (1/50 of a second = 20ms)
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Duration(u32);

impl Add<Duration> for Duration {
    type Output = Duration;
    fn add(self, rhs: Duration) -> Self::Output {
        Duration(self.0 + rhs.0)
    }
}

impl Sub<Self> for Duration {
    type Output = Duration;

    fn sub(self, rhs: Self) -> Self::Output {
        Duration(self.0.sub(rhs.0))
    }
}

impl SaturatingSub for Duration {
    fn saturating_sub(&self, rhs: &Self) -> Self {
        Duration(self.0.saturating_sub(rhs.0))
    }
}

impl Duration {
    pub fn decrement(self) -> Option<Duration> {
        self.0.checked_sub(1).map(Duration)
    }

    pub fn seconds(self) -> f32 {
        self.0 as f32 / 50.0
    }

    pub fn ticks(self) -> SnapTick {
        self.0.try_into().unwrap()
    }

    /// Must only be called for loading from save code
    pub fn from_ticks(n: SnapTick) -> Duration {
        Duration(if n.is_negative() { 0 } else { n as u32 })
    }

    pub const fn from_min(n: u32) -> Duration {
        Duration(n * 50 * 60)
    }

    pub const fn from_secs(n: u32) -> Duration {
        Duration(n * 50)
    }

    pub fn from_secs_f32(s: f32) -> Duration {
        Duration((s * 50.0) as u32)
    }

    pub fn from_ms_f32(ms: f32) -> Duration {
        Duration(ms as u32 / 20)
    }

    pub const T0MS: Duration = Duration(0);
    pub const T20MS: Duration = Duration(1);
    pub const T40MS: Duration = Duration(2);
    pub const T60MS: Duration = Duration(3);
    pub const T80MS: Duration = Duration(4);
    pub const T100MS: Duration = Duration(5);
    pub const T120MS: Duration = Duration(6);
    pub const T140MS: Duration = Duration(7);
    pub const T160MS: Duration = Duration(8);
    pub const T180MS: Duration = Duration(9);
    pub const T200MS: Duration = Duration(10);
    pub const T220MS: Duration = Duration(11);
    pub const T240MS: Duration = Duration(12);
    pub const T260MS: Duration = Duration(13);
    pub const T280MS: Duration = Duration(14);
    pub const T300MS: Duration = Duration(15);
    pub const T320MS: Duration = Duration(16);
    pub const T340MS: Duration = Duration(17);
    pub const T360MS: Duration = Duration(18);
    pub const T380MS: Duration = Duration(19);
    pub const T400MS: Duration = Duration(20);
    pub const T420MS: Duration = Duration(21);
    pub const T440MS: Duration = Duration(22);
    pub const T460MS: Duration = Duration(23);
    pub const T480MS: Duration = Duration(24);
    pub const T500MS: Duration = Duration(25);
    pub const T520MS: Duration = Duration(26);
    pub const T540MS: Duration = Duration(27);
    pub const T560MS: Duration = Duration(28);
    pub const T580MS: Duration = Duration(29);
    pub const T600MS: Duration = Duration(30);
    pub const T620MS: Duration = Duration(31);
    pub const T640MS: Duration = Duration(32);
    pub const T660MS: Duration = Duration(33);
    pub const T680MS: Duration = Duration(34);
    pub const T700MS: Duration = Duration(35);
    pub const T720MS: Duration = Duration(36);
    pub const T740MS: Duration = Duration(37);
    pub const T760MS: Duration = Duration(38);
    pub const T780MS: Duration = Duration(39);
    pub const T800MS: Duration = Duration(40);
    pub const T820MS: Duration = Duration(41);
    pub const T840MS: Duration = Duration(42);
    pub const T860MS: Duration = Duration(43);
    pub const T880MS: Duration = Duration(44);
    pub const T900MS: Duration = Duration(45);
    pub const T920MS: Duration = Duration(46);
    pub const T940MS: Duration = Duration(47);
    pub const T960MS: Duration = Duration(48);
    pub const T980MS: Duration = Duration(49);
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn simple() {
        let a = Instant(150);
        let b = Instant(50);
        assert!(a.duration_passed_since(b, Duration::from_secs(1)));
        assert!(a.duration_passed_since(b, Duration::from_secs(2)));
        assert!(!a.duration_passed_since(b, Duration::from_secs(3)));
        assert!(!a.duration_passed_since(b, Duration::from_secs(5)));
    }
}