use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use crate::Deadline;
#[derive(Debug)]
pub struct Timer {
state: State,
deadline: Deadline,
}
impl Timer {
pub fn new(delay: Duration) -> Self {
Self {
state: State::new(),
deadline: Deadline::repeat(delay),
}
}
pub fn watcher(&self) -> Watcher {
Watcher::new(self.state.clone())
}
pub fn tick(&mut self) {
self.deadline.wait();
self.state.toggle();
}
}
pub struct Watcher {
state: State,
prev_state: bool,
}
impl Watcher {
fn new(state: State) -> Self {
let prev_state = state.value();
Self { state, prev_state }
}
pub fn has_ticked(&mut self) -> bool {
if self.state.value() != self.prev_state {
self.prev_state = !self.prev_state;
return true;
}
false
}
}
impl Clone for Watcher {
fn clone(&self) -> Self {
let state = self.state.clone();
let prev_state = state.value();
Self { state, prev_state }
}
}
#[derive(Debug, Default, Clone)]
struct State(Arc<AtomicBool>);
impl State {
#[inline]
fn new() -> Self {
Self::default()
}
#[inline]
fn toggle(&self) {
self.0.fetch_xor(true, Ordering::Release);
}
#[inline]
fn value(&self) -> bool {
self.0.load(Ordering::Acquire)
}
}
#[cfg(test)]
impl PartialEq<bool> for State {
#[inline]
fn eq(&self, other: &bool) -> bool {
self.value() == *other
}
}
#[cfg(test)]
mod state {
use super::*;
#[test]
fn new() {
let new = State::new();
assert_eq!(new, false);
}
#[test]
fn toggle() {
let new = State::new();
assert_eq!(new, false);
new.toggle();
assert_eq!(new, true);
}
}
#[cfg(test)]
#[allow(clippy::module_inception)]
mod timer {
use std::time::Instant;
use super::*;
#[test]
fn tick_delay() {
let now = Instant::now();
let mut timer = Timer::new(Duration::from_millis(100));
for count in 0..5 {
timer.tick();
let elapsed = now.elapsed();
assert!(
now.elapsed() >= Duration::from_millis(100 * count),
"elapsed = {elapsed:?}"
)
}
}
}
#[cfg(test)]
mod watcher {
use std::time::Instant;
use super::*;
#[test]
fn new() {
let mut timer = Timer::new(Duration::from_millis(100));
let mut watcher = timer.watcher();
assert!(
!watcher.has_ticked(),
"watcher shouldn't have been notified yet"
);
timer.tick();
assert!(watcher.has_ticked(), "watcher should have been notified");
assert!(
!watcher.has_ticked(),
"watcher shouldn't have been notified instantly"
);
}
#[test]
fn cloned() {
let mut timer = Timer::new(Duration::from_millis(100));
let mut watcher = timer.watcher();
assert!(
!watcher.has_ticked(),
"watcher shouldn't have been notified yet"
);
let mut watcher_clone = watcher.clone();
assert!(
!watcher_clone.has_ticked(),
"watcher clone shouldn't have been notified yet"
);
timer.tick();
assert!(watcher.has_ticked(), "watcher should have been notified");
assert!(
watcher_clone.has_ticked(),
"watcher clone should have been notified"
);
let mut watcher_clone = watcher.clone();
assert!(
!watcher_clone.has_ticked(),
"2dn watcher clone shouldn't have been notified yet"
);
}
#[test]
fn thread_sync() {
let stop = Arc::new(AtomicBool::default());
let now = Instant::now();
let mut timer = Timer::new(Duration::from_millis(100));
let mut watcher = timer.watcher();
let stop_clone = Arc::clone(&stop);
let watcher_thread = std::thread::spawn(move || {
let mut loops = 1;
while !stop_clone.load(Ordering::Acquire) {
if watcher.has_ticked() {
let elapsed = now.elapsed();
let expected = Duration::from_millis(100 * loops);
if now.elapsed() < expected {
return Some((elapsed, expected));
}
loops += 1;
}
}
None
});
for _ in 0..5 {
timer.tick();
}
stop.store(true, Ordering::Release);
let test_result = watcher_thread.join().unwrap();
assert_eq!(
test_result, None,
"watcher detected a tick before the expected time"
);
}
}