#![feature(div_duration)]
use std::time::{Duration, Instant};
pub mod prelude {
pub use super::{reprint, Observer, Options};
}
#[macro_export]
macro_rules! reprint {
($($tk:tt)*) => {
{
print!("\r{}", format!($($tk)*));
::std::io::Write::flush(&mut ::std::io::stdout()).unwrap();
}
};
}
pub struct Observer {
frequency_target: Duration,
checkpoint_size: u64,
max_checkpoint_size: Option<u64>,
delay: u64,
max_scale_factor: f64,
run_for: Option<Duration>,
next_checkpoint: u64,
last_observation: Instant,
first_observation: Instant,
ticks: u64,
finished: bool,
}
pub struct Options {
pub first_checkpoint: u64,
pub max_checkpoint_size: Option<u64>,
pub delay: u64,
pub max_scale_factor: f64,
pub run_for: Option<Duration>,
}
impl Default for Options {
fn default() -> Self {
Self {
first_checkpoint: 1,
max_checkpoint_size: None,
delay: 0,
max_scale_factor: 2.0,
run_for: None,
}
}
}
impl Observer {
pub fn new_with(
frequency_target: Duration,
Options {
first_checkpoint: checkpoint_size,
max_checkpoint_size,
delay,
max_scale_factor,
run_for,
}: Options,
) -> Self {
if max_scale_factor < 1.0 {
panic!("max_scale_factor of {max_scale_factor} is less than 1.0");
}
Self {
frequency_target,
checkpoint_size,
max_checkpoint_size,
delay,
max_scale_factor,
run_for,
next_checkpoint: checkpoint_size,
last_observation: Instant::now(),
first_observation: Instant::now(),
ticks: 0,
finished: false,
}
}
pub fn new_starting_at(frequency_target: Duration, first_checkpoint: u64) -> Self {
Self::new_with(
frequency_target,
Options {
first_checkpoint,
..Default::default()
},
)
}
pub fn new(frequency_target: Duration) -> Self {
Self::new_with(frequency_target, Options::default())
}
pub fn tick_n(&mut self, mut n: u64) -> bool {
if self.delay > 0 {
let adjustment = n.min(self.delay);
self.delay -= adjustment;
n -= adjustment;
if self.delay > 0 {
return false;
} else {
self.last_observation = Instant::now();
self.first_observation = Instant::now();
}
}
self.ticks += n;
if self.ticks >= self.next_checkpoint {
let observation_time = Instant::now();
if self.run_for.is_some_and(|run_for| {
observation_time.duration_since(self.first_observation) > run_for
}) {
self.finished = true;
}
let time_since_observation = observation_time.duration_since(self.last_observation);
let checkpoint_ratio = time_since_observation.div_duration_f64(self.frequency_target);
let checkpoint_size = self.checkpoint_size as f64;
self.checkpoint_size = ((checkpoint_size / checkpoint_ratio) as u64)
.max(1)
.min((checkpoint_size * self.max_scale_factor) as u64);
if let Some(max_size) = self.max_checkpoint_size {
self.checkpoint_size = self.checkpoint_size.min(max_size);
}
self.next_checkpoint += self.checkpoint_size;
self.last_observation = observation_time;
true
} else {
false
}
}
pub fn tick(&mut self) -> bool {
self.tick_n(1)
}
}
impl Iterator for Observer {
type Item = bool;
fn next(&mut self) -> Option<Self::Item> {
(!self.finished).then(|| self.tick())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reprint() {
let mut count: u64 = 0;
for should_print in Observer::new(Duration::from_secs(1)).take(1_000_000_000) {
count += 1;
if should_print {
reprint!("{count: <20}");
count = 0;
}
}
}
#[test]
fn delay() {
for (i, should_print) in Observer::new_with(
Duration::from_secs(1),
Options {
max_checkpoint_size: Some(2),
delay: 5,
..Default::default()
},
)
.enumerate()
.take(10)
{
println!("{i}: {should_print}");
}
}
#[test]
fn run_for() {
for (i, should_print) in Observer::new_with(
Duration::from_secs_f32(0.1),
Options {
run_for: Some(Duration::from_secs(5)),
..Default::default()
},
)
.enumerate()
{
if should_print {
reprint!("{i}");
}
}
}
}