#![warn(missing_docs)]
use quanta::Clock;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use std::time::Duration;
pub struct AtomicInterval {
inner: AtomicIntervalImpl,
}
impl AtomicInterval {
pub fn new(period: Duration) -> Self {
Self {
inner: AtomicIntervalImpl::new(period),
}
}
pub fn period(&self) -> Duration {
self.inner.period()
}
pub fn set_period(&mut self, period: Duration) {
self.inner.set_period(period)
}
pub fn is_ticked(&self, success: Ordering, failure: Ordering) -> bool {
self.inner.is_ticked::<false>(success, failure).0
}
}
pub struct AtomicIntervalLight {
inner: AtomicIntervalImpl,
}
impl AtomicIntervalLight {
pub fn new(period: Duration) -> Self {
Self {
inner: AtomicIntervalImpl::new(period),
}
}
pub fn period(&self) -> Duration {
self.inner.period()
}
pub fn set_period(&mut self, period: Duration) {
self.inner.set_period(period)
}
pub fn is_ticked(&self) -> bool {
self.inner
.is_ticked::<true>(Ordering::Relaxed, Ordering::Relaxed)
.0
}
}
struct AtomicIntervalImpl {
period: Duration,
clock: Clock,
last_tick: AtomicU64,
}
impl AtomicIntervalImpl {
fn new(period: Duration) -> Self {
let clock = Clock::new();
let last_tick = AtomicU64::new(clock.raw());
Self {
period,
clock,
last_tick,
}
}
#[inline(always)]
fn set_period(&mut self, period: Duration) {
self.period = period
}
#[inline(always)]
fn period(&self) -> Duration {
self.period
}
#[inline(always)]
fn is_ticked<const WEAK_CMP: bool>(
&self,
success: Ordering,
failure: Ordering,
) -> (bool, Duration) {
let current = self.last_tick.load(failure);
let elapsed = self.clock.delta(current, self.clock.raw());
if self.period <= elapsed
&& ((!WEAK_CMP
&& self
.last_tick
.compare_exchange(current, self.clock.raw(), success, failure)
.is_ok())
|| (WEAK_CMP
&& self
.last_tick
.compare_exchange_weak(current, self.clock.raw(), success, failure)
.is_ok()))
{
(true, elapsed)
} else {
(false, elapsed)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ticks() {
utilities::test_ticks_impl::<false>();
utilities::test_ticks_impl::<true>();
}
mod utilities {
use super::*;
pub(super) fn test_ticks_impl<const WEAK_CMP: bool>() {
const NUM_TICKS: usize = 10;
const ERROR_TOLERANCE: f64 = 0.03;
let period = Duration::from_millis(10);
let atomic_interval = AtomicIntervalImpl::new(period);
for _ in 0..NUM_TICKS {
let elapsed = wait_for_atomic_interval::<WEAK_CMP>(&atomic_interval);
assert!(period <= elapsed);
let error = elapsed.as_secs_f64() / period.as_secs_f64() - 1_f64;
assert!(
error <= ERROR_TOLERANCE,
"Delay error {:.1}% (max: {:.1}%)",
error * 100_f64,
ERROR_TOLERANCE * 100_f64
);
}
}
fn wait_for_atomic_interval<const WEAK_CMP: bool>(
atomic_interval: &AtomicIntervalImpl,
) -> Duration {
loop {
let (ticked, elapsed) =
atomic_interval.is_ticked::<WEAK_CMP>(Ordering::Relaxed, Ordering::Relaxed);
if ticked {
break elapsed;
}
}
}
}
}