use std::fmt;
use std::ops::{Add, AddAssign, Sub, SubAssign};
use std::time::{Duration, Instant as StdInstant};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct OrengineInstant {
#[cfg(not(unix))]
instant: StdInstant,
#[cfg(unix)]
instant: u64,
}
impl OrengineInstant {
#[cfg(unix)]
pub fn from_u64(instant: u64) -> Self {
assert!(cfg!(unix), "`from_u64` can be called only on UNIX.");
Self { instant }
}
#[cfg(unix)]
pub fn into_u64(self) -> u64 {
assert!(cfg!(unix), "`into_u64` can be called only on UNIX.");
self.instant
}
pub fn now() -> Self {
#[cfg(not(unix))]
return Self {
instant: StdInstant::now(),
};
#[allow(clippy::cast_sign_loss, reason = "It can't be negative")]
#[cfg(unix)]
{
let ts = rustix::time::clock_gettime(rustix::time::ClockId::Monotonic);
Self {
instant: ts.tv_sec as u64 * 1_000_000_000 + ts.tv_nsec as u64,
}
}
}
pub fn checked_duration_since(&self, earlier: impl Into<StdInstant>) -> Option<Duration> {
#[cfg(not(unix))]
{
self.instant.checked_duration_since(earlier.into())
}
#[cfg(unix)]
{
Some(Duration::from_nanos(
self.instant - Self::from(earlier.into()).instant,
))
}
}
pub fn saturating_duration_since(&self, earlier: impl Into<StdInstant>) -> Duration {
self.checked_duration_since(earlier.into())
.unwrap_or_default()
}
pub fn duration_since(&self, earlier: impl Into<StdInstant>) -> Duration {
self.saturating_duration_since(earlier.into())
}
pub fn elapsed(&self) -> Duration {
Self::now() - *self
}
pub fn checked_add(&self, duration: Duration) -> Option<Self> {
#[cfg(not(unix))]
{
Some(Self {
instant: self.instant.checked_add(duration)?,
})
}
#[cfg(unix)]
{
let total_nanos = u64::try_from(duration.as_nanos()).ok()?;
Some(Self {
instant: self.instant.checked_add(total_nanos)?,
})
}
}
pub fn checked_sub(&self, duration: Duration) -> Option<Self> {
#[cfg(not(unix))]
{
Some(Self {
instant: self.instant.checked_sub(duration)?,
})
}
#[cfg(unix)]
{
let total_nanos = u64::try_from(duration.as_nanos()).ok()?;
Some(Self {
instant: self.instant.checked_sub(total_nanos)?,
})
}
}
}
impl Add<Duration> for OrengineInstant {
type Output = Self;
fn add(self, other: Duration) -> Self {
self.checked_add(other)
.expect("overflow when adding duration to instant")
}
}
impl AddAssign<Duration> for OrengineInstant {
fn add_assign(&mut self, other: Duration) {
*self = self
.checked_add(other)
.expect("overflow when adding duration to instant");
}
}
impl Sub<Duration> for OrengineInstant {
type Output = Self;
fn sub(self, other: Duration) -> Self {
self.checked_sub(other)
.expect("overflow when subtracting duration from instant")
}
}
impl SubAssign<Duration> for OrengineInstant {
fn sub_assign(&mut self, other: Duration) {
*self = self
.checked_sub(other)
.expect("overflow when subtracting duration from instant");
}
}
impl Sub<Self> for OrengineInstant {
type Output = Duration;
fn sub(self, other: Self) -> Duration {
self.duration_since(other)
}
}
impl fmt::Debug for OrengineInstant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.instant.fmt(f)
}
}
#[cfg(unix)]
mod unix_time {
pub(crate) struct Nanoseconds(pub(crate) u32);
pub(crate) struct Timespec {
pub(crate) tv_sec: i64,
pub(crate) tv_nsec: Nanoseconds,
}
}
impl From<OrengineInstant> for std::time::Instant {
fn from(val: OrengineInstant) -> Self {
#[cfg(not(unix))]
{
val.instant
}
#[cfg(unix)]
{
let dur = Duration::from_nanos(val.instant);
unsafe {
#[allow(clippy::transmute_undefined_repr, reason = "False positive")]
#[allow(clippy::cast_possible_wrap, reason = "It is fine for our century")]
std::mem::transmute(unix_time::Timespec {
tv_sec: dur.as_secs() as i64,
tv_nsec: unix_time::Nanoseconds(dur.subsec_nanos()),
})
}
}
}
}
impl From<std::time::Instant> for OrengineInstant {
#[allow(clippy::cast_sign_loss, reason = "It is never negative")]
fn from(val: std::time::Instant) -> Self {
#[cfg(not(unix))]
{
Self { instant: val }
}
#[cfg(unix)]
{
#[allow(clippy::transmute_undefined_repr, reason = "False positive")]
let ts: unix_time::Timespec = unsafe { std::mem::transmute(val) };
Self {
instant: ts.tv_sec as u64 * 1_000_000_000 + u64::from(ts.tv_nsec.0),
}
}
}
}
#[cfg(test)]
mod tests {
use super::OrengineInstant;
use std::thread;
use std::time::Duration;
#[test]
fn test_into_std_instant() {
let instant = OrengineInstant::now();
let std_instant: std::time::Instant = instant.into();
let instant_from_std: OrengineInstant = std_instant.into();
assert_eq!(instant, instant_from_std);
thread::sleep(Duration::from_millis(1));
let now = OrengineInstant::now();
assert_eq!(now.duration_since(instant), now.duration_since(std_instant));
}
#[test]
fn test_from_std_instant() {
let std_instant: std::time::Instant = std::time::Instant::now();
let instant: OrengineInstant = std_instant.into();
let std_instant_from_instant: std::time::Instant = instant.into();
assert_eq!(std_instant, std_instant_from_instant);
}
#[test]
fn test_instant_ordering() {
let instant1: OrengineInstant = std::time::Instant::now().into();
let instant2 = instant1;
assert_eq!(instant1, instant2);
let instant3 = instant1 + Duration::from_millis(1);
let instant4 = instant1 + Duration::from_millis(2);
assert!(instant1 < instant3);
assert!(instant3 < instant4);
let mut btree = std::collections::BTreeSet::new();
assert!(btree.insert(instant1));
assert!(btree.insert(instant3));
assert!(btree.insert(instant4));
assert_eq!(btree.pop_first(), Some(instant1));
assert_eq!(btree.pop_first(), Some(instant3));
assert_eq!(btree.pop_first(), Some(instant4));
}
}