use core::ops::{Add, AddAssign, Sub, SubAssign};
use crate::{
Duration, FloatScalar, FrameIndex, FrameRate, conversion,
macros::{impl_approx_forwarding, impl_min_max_forwarding, impl_zero_forwarding},
};
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct Time<T: FloatScalar>(T);
impl<T: FloatScalar> Time<T> {
#[inline]
pub const fn from_seconds_unchecked(seconds: T) -> Self {
Self(seconds)
}
#[inline]
pub fn from_seconds(seconds: T) -> Self {
assert!(seconds.is_finite(), "Time cannot be created from NaN or infinite values");
Self(seconds)
}
#[inline]
pub fn seconds(&self) -> T {
self.0
}
#[inline]
pub fn to_frame(self, rate: FrameRate<T>) -> FrameIndex {
conversion::frame_from_time(self, rate)
}
#[inline]
pub fn from_frame(frame: FrameIndex, rate: FrameRate<T>) -> Self {
conversion::time_from_frame(frame, rate)
}
#[inline]
pub fn normalize_between(&self, start: Self, end: Self) -> T {
let total = end - start;
if total.seconds() > T::zero() { (*self - start) / total } else { T::zero() }
}
impl_min_max_forwarding!(T, 0);
impl_zero_forwarding!(T, 0);
}
impl<T: FloatScalar> Default for Time<T> {
#[inline]
fn default() -> Self {
Self::zero()
}
}
impl<T: FloatScalar> From<T> for Time<T> {
#[inline]
fn from(value: T) -> Self {
Self::from_seconds(value)
}
}
impl From<Time<f32>> for f32 {
#[inline]
fn from(time: Time<f32>) -> Self {
time.seconds()
}
}
impl From<Time<f64>> for f64 {
#[inline]
fn from(time: Time<f64>) -> Self {
time.seconds()
}
}
impl_approx_forwarding!(Time<T>, 0);
impl<T: FloatScalar> Sub for Time<T> {
type Output = Duration<T>;
#[inline]
fn sub(self, rhs: Self) -> Duration<T> {
Duration::from_seconds(self.seconds() - rhs.seconds())
}
}
impl<T: FloatScalar> Sub<T> for Time<T> {
type Output = Self;
#[inline]
fn sub(self, rhs: T) -> Self {
Self::from_seconds(self.seconds() - rhs)
}
}
impl<T: FloatScalar> Add<Duration<T>> for Time<T> {
type Output = Time<T>;
#[inline]
fn add(self, rhs: Duration<T>) -> Time<T> {
Self::from_seconds(self.seconds() + rhs.seconds())
}
}
impl<T: FloatScalar> AddAssign<Duration<T>> for Time<T> {
#[inline]
fn add_assign(&mut self, rhs: Duration<T>) {
*self = *self + rhs;
}
}
impl<T: FloatScalar> Sub<Duration<T>> for Time<T> {
type Output = Time<T>;
#[inline]
fn sub(self, rhs: Duration<T>) -> Time<T> {
Self::from_seconds(self.seconds() - rhs.seconds())
}
}
impl<T: FloatScalar> SubAssign<Duration<T>> for Time<T> {
#[inline]
fn sub_assign(&mut self, rhs: Duration<T>) {
*self = *self - rhs;
}
}
#[cfg(test)]
mod tests {
use approx::assert_abs_diff_eq;
use super::*;
#[test]
fn from_seconds_and_seconds_roundtrip() {
let t = Time::from_seconds(1.25_f64);
assert_eq!(t.seconds(), 1.25);
}
#[test]
#[should_panic(expected = "Time cannot be created from NaN or infinite values")]
fn from_seconds_panics_on_non_finite() {
let _ = Time::from_seconds(f64::INFINITY);
}
#[test]
fn frame_conversion_roundtrip() {
let rate = FrameRate::from_fps(24.0_f64);
let t = Time::from_seconds(2.5);
let frame = t.to_frame(rate);
let t2 = Time::from_frame(frame, rate);
assert_eq!(frame, FrameIndex::new(60));
assert_eq!(t2, Time::from_seconds(2.5));
}
#[test]
fn normalize_between_handles_normal_and_degenerate_ranges() {
let t = Time::from_seconds(5.0_f64);
assert_abs_diff_eq!(
t.normalize_between(Time::from_seconds(0.0), Time::from_seconds(10.0)),
0.5,
epsilon = 1e-12
);
assert_abs_diff_eq!(
t.normalize_between(Time::from_seconds(10.0), Time::from_seconds(0.0)),
0.0,
epsilon = 1e-12
);
assert_abs_diff_eq!(
t.normalize_between(Time::from_seconds(4.0), Time::from_seconds(4.0)),
0.0,
epsilon = 1e-12
);
}
#[test]
fn min_max_zero_and_default_work() {
let a = Time::from_seconds(1.0_f64);
let b = Time::from_seconds(3.0_f64);
assert_eq!(a.min(b), a);
assert_eq!(a.max(b), b);
let z = Time::<f64>::zero();
assert_eq!(Time::<f64>::default(), z);
assert!(z.is_zero());
}
#[test]
fn raw_conversions_work() {
let t: Time<f64> = 2.0_f64.into();
let raw: f64 = t.into();
assert_eq!(raw, 2.0);
}
#[test]
fn time_duration_arithmetic_and_assign_work() {
let mut t = Time::from_seconds(10.0_f64);
let d = Duration::from_seconds(2.5_f64);
assert_eq!(t - Time::from_seconds(4.0), Duration::from_seconds(6.0));
assert_eq!(t + d, Time::from_seconds(12.5));
assert_eq!(t - d, Time::from_seconds(7.5));
t += d;
assert_eq!(t, Time::from_seconds(12.5));
t -= d;
assert_eq!(t, Time::from_seconds(10.0));
}
}