use std::time::Duration;
use derive_builder::Builder;
use fastrand::Rng;
#[doc(hidden)]
#[derive(Debug, Builder)]
pub struct Strategy {
#[builder(setter(into), default = "Duration::from_secs(2)")]
duration: Duration,
#[builder(setter(into), default)]
duration_max: Option<Duration>,
#[doc(hidden)]
#[builder(field(private), default)]
kind: Kind,
#[builder(default = "0.1")]
jitter: f32,
#[doc(hidden)]
#[builder(field(private), default)]
rng: Rng,
}
pub fn builder() -> StrategyBuilder {
StrategyBuilder::default()
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
enum Kind {
Fixed,
#[default]
Exponential,
}
impl StrategyBuilder {
pub fn fixed(&mut self) -> &mut Self {
self.kind = Some(Kind::Fixed);
self
}
pub fn exponential(&mut self) -> &mut Self {
self.kind = Some(Kind::Exponential);
self
}
}
impl Kind {
pub fn next(&self, durration: Duration) -> Duration {
match self {
Kind::Fixed => durration,
Kind::Exponential => durration.saturating_mul(2),
}
}
}
impl Strategy {
fn j(&mut self, d: Duration) -> Duration {
let j = (d.as_secs_f32() * self.jitter * 1000.0) as i32;
let j = self.rng.i32((-j)..(j + 1));
if 0 <= j {
d.saturating_add(Duration::from_millis(j as u64))
} else {
d.saturating_sub(Duration::from_millis((-j) as u64))
}
}
fn update_duration(&mut self) -> Duration {
let duration = self.duration;
let next_duration = self.kind.next(duration);
if let Some(saturation) = self.duration_max {
self.duration = next_duration.min(saturation);
self.j(duration).min(saturation)
} else {
self.duration = next_duration;
self.j(duration)
}
}
}
impl Iterator for Strategy {
type Item = Duration;
fn next(&mut self) -> Option<Self::Item> {
Some(self.update_duration())
}
fn size_hint(&self) -> (usize, Option<usize>) {
(usize::MAX, None)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let xs = builder().duration(Duration::from_secs(1)).build().unwrap();
let mut p = Duration::new(0, 0);
for x in xs.into_iter().take(10) {
assert!(p <= x);
p = x;
}
println!("fixed");
for x in builder().fixed().build().unwrap().take(10) {
println!("{x:?}");
}
println!("exp");
for x in builder()
.exponential()
.duration_max(Duration::from_secs(120))
.build()
.unwrap()
.take(10)
{
println!("{x:?}");
}
}
}