blob: b4b1d50a888960774a4597273fa35c097865c349 [file] [log] [blame] [edit]
use crate::ops::Neg;
use crate::ptr::null;
use crate::sys::pal::c;
use crate::time::Duration;
const NANOS_PER_SEC: u64 = 1_000_000_000;
pub const INTERVALS_PER_SEC: u64 = NANOS_PER_SEC / 100;
pub fn checked_dur2intervals(dur: &Duration) -> Option<i64> {
dur.as_secs()
.checked_mul(INTERVALS_PER_SEC)?
.checked_add(dur.subsec_nanos() as u64 / 100)?
.try_into()
.ok()
}
pub fn intervals2dur(intervals: u64) -> Duration {
Duration::new(intervals / INTERVALS_PER_SEC, ((intervals % INTERVALS_PER_SEC) * 100) as u32)
}
pub mod perf_counter {
use super::NANOS_PER_SEC;
use crate::sync::atomic::{Atomic, AtomicU64, Ordering};
use crate::sys::{c, cvt};
use crate::time::Duration;
pub fn now() -> i64 {
let mut qpc_value: i64 = 0;
cvt(unsafe { c::QueryPerformanceCounter(&mut qpc_value) }).unwrap();
qpc_value
}
pub fn frequency() -> i64 {
// Either the cached result of `QueryPerformanceFrequency` or `0` for
// uninitialized. Storing this as a single `AtomicU64` allows us to use
// `Relaxed` operations, as we are only interested in the effects on a
// single memory location.
static FREQUENCY: Atomic<u64> = AtomicU64::new(0);
let cached = FREQUENCY.load(Ordering::Relaxed);
// If a previous thread has filled in this global state, use that.
if cached != 0 {
return cached as i64;
}
// ... otherwise learn for ourselves ...
let mut frequency = 0;
unsafe {
cvt(c::QueryPerformanceFrequency(&mut frequency)).unwrap();
}
FREQUENCY.store(frequency as u64, Ordering::Relaxed);
frequency
}
// Per microsoft docs, the margin of error for cross-thread time comparisons
// using QueryPerformanceCounter is 1 "tick" -- defined as 1/frequency().
// Reference: https://docs.microsoft.com/en-us/windows/desktop/SysInfo
// /acquiring-high-resolution-time-stamps
pub fn epsilon() -> Duration {
let epsilon = NANOS_PER_SEC / (frequency() as u64);
Duration::from_nanos(epsilon)
}
}
/// A timer you can wait on.
pub(crate) struct WaitableTimer {
handle: c::HANDLE,
}
impl WaitableTimer {
/// Creates a high-resolution timer. Will fail before Windows 10, version 1803.
pub fn high_resolution() -> Result<Self, ()> {
let handle = unsafe {
c::CreateWaitableTimerExW(
null(),
null(),
c::CREATE_WAITABLE_TIMER_HIGH_RESOLUTION,
c::TIMER_ALL_ACCESS,
)
};
if !handle.is_null() { Ok(Self { handle }) } else { Err(()) }
}
pub fn set(&self, duration: Duration) -> Result<(), ()> {
// Convert the Duration to a format similar to FILETIME.
// Negative values are relative times whereas positive values are absolute.
// Therefore we negate the relative duration.
let time = checked_dur2intervals(&duration).ok_or(())?.neg();
let result = unsafe { c::SetWaitableTimer(self.handle, &time, 0, None, null(), c::FALSE) };
if result != 0 { Ok(()) } else { Err(()) }
}
pub fn wait(&self) -> Result<(), ()> {
let result = unsafe { c::WaitForSingleObject(self.handle, c::INFINITE) };
if result != c::WAIT_FAILED { Ok(()) } else { Err(()) }
}
}
impl Drop for WaitableTimer {
fn drop(&mut self) {
unsafe { c::CloseHandle(self.handle) };
}
}