use std::hash::{DefaultHasher, Hash, Hasher};
use std::time::Duration;
use futures::StreamExt;
use futures::stream::BoxStream;
use tokio::time::MissedTickBehavior;
use tokio::time::interval;
use tokio_stream::wrappers::IntervalStream;
use super::{SubscriptionId, SubscriptionSource};
#[derive(Debug, Clone)]
pub enum Message {
Tick,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Timer {
interval_ms: u64,
}
impl Timer {
#[must_use]
pub const fn new(interval_ms: u64) -> Self {
Self { interval_ms }
}
}
impl SubscriptionSource for Timer {
type Output = Message;
fn stream(&self) -> BoxStream<'static, Message> {
let duration = Duration::from_millis(self.interval_ms);
let mut interval = interval(duration);
interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
IntervalStream::new(interval)
.skip(1) .map(|_| Message::Tick)
.boxed()
}
fn id(&self) -> SubscriptionId {
let mut hasher = DefaultHasher::new();
self.interval_ms.hash(&mut hasher);
SubscriptionId::of::<Self>(hasher.finish())
}
}
impl Hash for Timer {
fn hash<H: Hasher>(&self, state: &mut H) {
self.interval_ms.hash(state);
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures::StreamExt;
use tokio::time::{Duration, timeout};
#[test]
fn test_timer_new() {
let timer = Timer::new(1000);
assert_eq!(timer.interval_ms, 1000);
}
#[test]
fn test_timer_id_consistency() {
let timer1 = Timer::new(1000);
let timer2 = Timer::new(1000);
assert_eq!(timer1.id(), timer2.id());
}
#[test]
fn test_timer_id_different_intervals() {
let timer1 = Timer::new(1000);
let timer2 = Timer::new(2000);
assert_ne!(timer1.id(), timer2.id());
}
#[test]
fn test_timer_hash_consistency() {
let timer1 = Timer::new(1000);
let timer2 = Timer::new(1000);
let mut hasher1 = DefaultHasher::new();
timer1.hash(&mut hasher1);
let hash1 = hasher1.finish();
let mut hasher2 = DefaultHasher::new();
timer2.hash(&mut hasher2);
let hash2 = hasher2.finish();
assert_eq!(hash1, hash2);
}
#[test]
fn test_timer_hash_different_intervals() {
let timer1 = Timer::new(1000);
let timer2 = Timer::new(2000);
let mut hasher1 = DefaultHasher::new();
timer1.hash(&mut hasher1);
let hash1 = hasher1.finish();
let mut hasher2 = DefaultHasher::new();
timer2.hash(&mut hasher2);
let hash2 = hasher2.finish();
assert_ne!(hash1, hash2);
}
#[tokio::test]
async fn test_timer_stream_produces_ticks() {
let timer = Timer::new(10); let mut stream = timer.stream();
let result = timeout(Duration::from_millis(100), stream.next()).await;
assert!(matches!(result, Ok(Some(Message::Tick))));
}
#[tokio::test]
async fn test_timer_stream_multiple_ticks() {
let timer = Timer::new(10); let mut stream = timer.stream();
let mut count = 0;
for _ in 0..3 {
let result = timeout(Duration::from_millis(100), stream.next()).await;
if matches!(result, Ok(Some(Message::Tick))) {
count += 1;
}
}
assert_eq!(count, 3);
}
#[tokio::test]
async fn test_timer_interval_accuracy() {
use tokio::time::Instant;
let timer = Timer::new(50); let mut stream = timer.stream();
let start = Instant::now();
let result = timeout(Duration::from_millis(300), stream.next()).await;
assert!(matches!(result, Ok(Some(Message::Tick))));
let first_tick = start.elapsed();
let result = timeout(Duration::from_millis(300), stream.next()).await;
assert!(matches!(result, Ok(Some(Message::Tick))));
let second_tick = start.elapsed();
assert!(
first_tick >= Duration::from_millis(30) && first_tick <= Duration::from_millis(100),
"First tick was {first_tick:?}, expected between 30-100ms",
);
assert!(
second_tick >= Duration::from_millis(80) && second_tick <= Duration::from_millis(150),
"Second tick was {second_tick:?}, expected between 80-150ms",
);
}
#[tokio::test]
async fn test_timer_no_immediate_tick() {
use tokio::time::Instant;
let timer = Timer::new(100); let mut stream = timer.stream();
let start = Instant::now();
let result = timeout(Duration::from_millis(70), stream.next()).await;
assert!(
result.is_err(),
"Timer should not tick immediately (within 70ms)"
);
let result = timeout(Duration::from_millis(200), stream.next()).await;
assert!(
matches!(result, Ok(Some(Message::Tick))),
"Timer should tick after interval"
);
let elapsed = start.elapsed();
assert!(
elapsed >= Duration::from_millis(70),
"Timer ticked too early: {elapsed:?}",
);
}
#[tokio::test]
async fn test_timer_different_intervals() {
let fast_timer = Timer::new(20);
let slow_timer = Timer::new(200);
let mut fast_stream = fast_timer.stream();
let mut slow_stream = slow_timer.stream();
let fast_result = timeout(Duration::from_millis(100), fast_stream.next()).await;
let slow_result = timeout(Duration::from_millis(100), slow_stream.next()).await;
assert!(
fast_result.is_ok(),
"Fast timer (20ms) should tick within 100ms"
);
assert!(
slow_result.is_err(),
"Slow timer (200ms) should not tick within 100ms"
);
}
}