use std::time::Duration;
use tokio::time::{
Instant,
sleep_until
};
use tracing::{
debug_span,
Span,
Instrument
};
pub struct Sleeper {
span: Span,
pushed: Duration,
until: Option<Instant>
}
impl Sleeper {
pub fn start() -> Self {
Self {
span: debug_span!("sleep"),
pushed: Duration::ZERO,
until: None
}
}
pub fn push(&mut self, amount: Duration) {
match Instant::now().checked_add(amount) {
Some(until) => {
self.pushed = amount;
self.until = Some(until);
debug!(@self, "next tick in {amount:?}");
},
None => {
self.pushed = Duration::ZERO;
self.until = None;
warn!(@self, "Instant overflowed, not pushed!");
}
}
}
pub async fn sleep(&mut self) -> Option<()> {
sleep_until(self.until?).in_current_span().await;
self.until.take();
debug!(@self, "slept for {:?}", self.pushed);
self.pushed = Duration::ZERO;
Some(())
}
}
#[test_log::test(tokio::test(start_paused = true))]
async fn sleep() {
use futures::FutureExt;
const PUSH: Duration = Duration::from_secs(3);
let mut sleeper = Sleeper::start();
let not_pushed = sleeper.sleep().now_or_never();
assert_eq!(
not_pushed,
Some(None),
"not pushed, should be ready immediately"
);
let before = Instant::now();
sleeper.push(PUSH);
sleeper.sleep().await.unwrap();
let elapsed = Instant::now() - before;
assert!(elapsed >= PUSH, "should sleep for at least {PUSH:?}");
let cleared = sleeper.sleep().now_or_never();
assert_eq!(
cleared,
Some(None),
"cleared, should be ready immediately"
);
sleeper.push(Duration::MAX);
let cleared = sleeper.sleep().now_or_never();
assert_eq!(
cleared,
Some(None),
"cleared because of overflow, should be ready immediately"
);
}