use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::time::{Duration, Instant};
pub trait Clock: Send + Sync {
fn now(&self) -> std::time::Duration;
fn sleep(
&self,
duration: std::time::Duration,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + 'static>>;
fn sleep_because(
&self,
duration: std::time::Duration,
cause: WaitCause,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + 'static>> {
let _ = cause;
self.sleep(duration)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct WaitCause(&'static str);
impl WaitCause {
#[must_use]
pub const fn new(word: &'static str) -> Self {
assert!(
Self::is_word(word),
"a wait cause is one word of printable ASCII"
);
Self(word)
}
#[must_use]
pub const fn is_word(text: &str) -> bool {
let bytes = text.as_bytes();
if bytes.is_empty() {
return false;
}
let mut index = 0;
while index < bytes.len() {
if !bytes[index].is_ascii_graphic() {
return false;
}
index += 1;
}
true
}
#[must_use]
pub const fn as_str(self) -> &'static str {
self.0
}
}
impl std::fmt::Display for WaitCause {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(self.0)
}
}
pub const SIMULATED_CLOCK_VARIABLE: &str = "OTG_SIMULATED_CLOCK";
pub type SharedClock = std::sync::Arc<dyn Clock>;
#[must_use]
pub fn system_clock() -> SharedClock {
Arc::new(SystemClock {
origin: Instant::now(),
})
}
struct SystemClock {
origin: Instant,
}
impl Clock for SystemClock {
fn now(&self) -> Duration {
self.origin.elapsed()
}
fn sleep(&self, duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send + 'static>> {
Box::pin(tokio::time::sleep(duration))
}
}
#[cfg(test)]
mod tests {
use super::WaitCause;
#[test]
fn a_word_of_printable_ascii_is_a_cause_and_nothing_else_is() {
const FILING: WaitCause = WaitCause::new("board-filing");
assert_eq!(FILING.as_str(), "board-filing");
assert_eq!(FILING.to_string(), "board-filing");
for refused in [
"",
" ",
"two words",
"tab\there",
"line\n",
"café",
"\u{a0}nbsp",
] {
assert!(!WaitCause::is_word(refused), "{refused:?} is not one word");
}
}
#[test]
#[should_panic(expected = "a wait cause is one word of printable ASCII")]
fn a_cause_that_is_not_one_word_is_refused_where_it_is_made() {
let words = String::from("two words");
let _ = WaitCause::new(Box::leak(words.into_boxed_str()));
}
}