use std::time::Duration;
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct StreamInstant {
secs: u64,
nanos: u32,
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub struct InputStreamTimestamp {
pub callback: StreamInstant,
pub capture: StreamInstant,
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub struct OutputStreamTimestamp {
pub callback: StreamInstant,
pub playback: StreamInstant,
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub struct InputCallbackInfo {
pub(crate) timestamp: InputStreamTimestamp,
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub struct OutputCallbackInfo {
pub(crate) timestamp: OutputStreamTimestamp,
}
impl StreamInstant {
pub const ZERO: Self = Self { secs: 0, nanos: 0 };
pub fn checked_duration_since(&self, earlier: StreamInstant) -> Option<Duration> {
if self < &earlier {
return None;
}
let delta = self.as_nanos() - earlier.as_nanos();
let secs = u64::try_from(delta / 1_000_000_000).ok()?;
let subsec_nanos = (delta % 1_000_000_000) as u32;
Some(Duration::new(secs, subsec_nanos))
}
pub fn saturating_duration_since(&self, earlier: StreamInstant) -> Duration {
self.checked_duration_since(earlier).unwrap_or_default()
}
pub fn duration_since(&self, earlier: StreamInstant) -> Duration {
self.saturating_duration_since(earlier)
}
pub fn checked_add(&self, duration: Duration) -> Option<Self> {
let total = self.as_nanos().checked_add(duration.as_nanos())?;
let secs = u64::try_from(total / 1_000_000_000).ok()?;
let nanos = (total % 1_000_000_000) as u32;
Some(Self { secs, nanos })
}
pub fn checked_sub(&self, duration: Duration) -> Option<Self> {
let total = self.as_nanos().checked_sub(duration.as_nanos())?;
let secs = u64::try_from(total / 1_000_000_000).ok()?;
let nanos = (total % 1_000_000_000) as u32;
Some(Self { secs, nanos })
}
pub fn as_nanos(&self) -> u128 {
self.secs as u128 * 1_000_000_000 + self.nanos as u128
}
pub fn from_nanos(nanos: u64) -> Self {
let secs = nanos / 1_000_000_000;
let subsec_nanos = (nanos % 1_000_000_000) as u32;
Self::new(secs, subsec_nanos)
}
pub fn from_millis(millis: u64) -> Self {
Self::new(millis / 1_000, (millis % 1_000 * 1_000_000) as u32)
}
pub fn from_micros(micros: u64) -> Self {
Self::new(micros / 1_000_000, (micros % 1_000_000 * 1_000) as u32)
}
pub fn from_secs_f64(secs: f64) -> Self {
const NANOS_PER_SEC: u128 = 1_000_000_000;
const MAX_NANOS: f64 = ((u64::MAX as u128 + 1) * NANOS_PER_SEC) as f64;
let nanos = secs * NANOS_PER_SEC as f64;
if !(0.0..MAX_NANOS).contains(&nanos) {
panic!("StreamInstant::from_secs_f64 called with invalid value: {secs}");
}
let nanos = nanos as u128;
Self::new(
(nanos / NANOS_PER_SEC) as u64,
(nanos % NANOS_PER_SEC) as u32,
)
}
pub fn new(secs: u64, nanos: u32) -> Self {
let carry = nanos / 1_000_000_000;
let subsec_nanos = nanos % 1_000_000_000;
let secs = secs
.checked_add(carry as u64)
.expect("overflow in StreamInstant::new");
Self {
secs,
nanos: subsec_nanos,
}
}
}
impl std::ops::Add<Duration> for StreamInstant {
type Output = Self;
#[inline]
fn add(self, rhs: Duration) -> Self::Output {
self.checked_add(rhs)
.expect("overflow when adding duration to stream instant")
}
}
impl std::ops::AddAssign<Duration> for StreamInstant {
#[inline]
fn add_assign(&mut self, rhs: Duration) {
*self = *self + rhs;
}
}
impl std::ops::Sub<Duration> for StreamInstant {
type Output = Self;
#[inline]
fn sub(self, rhs: Duration) -> Self::Output {
self.checked_sub(rhs)
.expect("underflow when subtracting duration from stream instant")
}
}
impl std::ops::SubAssign<Duration> for StreamInstant {
#[inline]
fn sub_assign(&mut self, rhs: Duration) {
*self = *self - rhs;
}
}
impl std::ops::Sub<StreamInstant> for StreamInstant {
type Output = Duration;
#[inline]
fn sub(self, rhs: StreamInstant) -> Self::Output {
self.saturating_duration_since(rhs)
}
}
impl InputCallbackInfo {
pub fn new(timestamp: InputStreamTimestamp) -> Self {
Self { timestamp }
}
pub fn timestamp(&self) -> InputStreamTimestamp {
self.timestamp
}
}
impl OutputCallbackInfo {
pub fn new(timestamp: OutputStreamTimestamp) -> Self {
Self { timestamp }
}
pub fn timestamp(&self) -> OutputStreamTimestamp {
self.timestamp
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stream_instant() {
let z = StreamInstant::ZERO; let a = StreamInstant::new(2, 0);
let max = StreamInstant::new(u64::MAX, 999_999_999);
assert_eq!(
a.checked_sub(Duration::from_secs(1)),
Some(StreamInstant::new(1, 0))
);
assert_eq!(
a.checked_sub(Duration::from_secs(2)),
Some(StreamInstant::ZERO)
);
assert_eq!(a.checked_sub(Duration::from_secs(3)), None); assert_eq!(z.checked_sub(Duration::from_nanos(1)), None);
assert_eq!(
a.checked_add(Duration::from_secs(1)),
Some(StreamInstant::new(3, 0))
);
assert_eq!(max.checked_add(Duration::from_nanos(1)), None);
assert_eq!(a.duration_since(z), Duration::from_secs(2));
assert_eq!(z.duration_since(a), Duration::ZERO); assert_eq!(a.checked_duration_since(z), Some(Duration::from_secs(2)));
assert_eq!(z.checked_duration_since(a), None);
assert_eq!(a.saturating_duration_since(z), Duration::from_secs(2));
assert_eq!(z.saturating_duration_since(a), Duration::ZERO);
assert_eq!(z + Duration::from_secs(2), a);
assert_eq!(a - Duration::from_secs(2), z);
assert_eq!(a - z, Duration::from_secs(2));
assert_eq!(z - a, Duration::ZERO); let mut c = z;
c += Duration::from_secs(2);
assert_eq!(c, a);
let mut d = a;
d -= Duration::from_secs(2);
assert_eq!(d, z);
assert_eq!(
StreamInstant::new(1, 1_500_000_000),
StreamInstant::new(2, 500_000_000)
);
assert_eq!(
StreamInstant::new(0, 1_000_000_000),
StreamInstant::new(1, 0)
);
assert_eq!(
StreamInstant::from_secs_f64(1.5),
StreamInstant::new(1, 500_000_000)
);
assert_eq!(StreamInstant::from_secs_f64(0.0), z);
}
#[test]
#[should_panic]
fn test_stream_instant_new_overflow() {
StreamInstant::new(u64::MAX, 1_000_000_000); }
#[test]
#[should_panic]
fn test_stream_instant_from_secs_f64_negative() {
StreamInstant::from_secs_f64(-1.0);
}
#[test]
#[should_panic]
fn test_stream_instant_from_secs_f64_nan() {
StreamInstant::from_secs_f64(f64::NAN);
}
#[test]
#[should_panic]
fn test_stream_instant_from_secs_f64_infinite() {
StreamInstant::from_secs_f64(f64::INFINITY);
}
}