use std::{
ops::{Range as StdRange, RangeInclusive},
time::Duration,
};
use rand::{
distr::{uniform::Error as UniformError, Distribution, Uniform},
random,
rngs::ThreadRng,
};
#[derive(Debug)]
pub struct Range {
distribution: Uniform<u64>,
rng: ThreadRng,
}
impl Range {
#[must_use]
pub fn from_millis_exclusive(minimum: u64, maximum: u64) -> Self {
Range {
distribution: Uniform::new(minimum, maximum)
.expect("minimum must be less than maximum"),
rng: rand::rng(),
}
}
pub fn try_from_millis_exclusive(minimum: u64, maximum: u64) -> Result<Self, UniformError> {
Ok(Range {
distribution: Uniform::new(minimum, maximum)?,
rng: rand::rng(),
})
}
#[must_use]
pub fn from_millis_inclusive(minimum: u64, maximum: u64) -> Self {
Range {
distribution: Uniform::new_inclusive(minimum, maximum)
.expect("minimum must be less than maximum"),
rng: rand::rng(),
}
}
pub fn try_from_millis_inclusive(minimum: u64, maximum: u64) -> Result<Self, UniformError> {
Ok(Range {
distribution: Uniform::new_inclusive(minimum, maximum)?,
rng: rand::rng(),
})
}
}
impl Iterator for Range {
type Item = Duration;
fn next(&mut self) -> Option<Duration> {
Some(Duration::from_millis(
self.distribution.sample(&mut self.rng),
))
}
}
impl From<StdRange<Duration>> for Range {
fn from(range: StdRange<Duration>) -> Self {
Self::from_millis_exclusive(range.start.as_millis() as u64, range.end.as_millis() as u64)
}
}
impl From<RangeInclusive<Duration>> for Range {
fn from(range: RangeInclusive<Duration>) -> Self {
Self::from_millis_inclusive(
range.start().as_millis() as u64,
range.end().as_millis() as u64,
)
}
}
#[must_use]
pub fn jitter(duration: Duration) -> Duration {
duration.mul_f64(random::<f64>())
}
#[test]
fn range_uniform() {
let mut range = Range::from_millis_exclusive(0, 1);
assert_eq!(Duration::from_millis(0), range.next().unwrap());
assert_eq!(Duration::from_millis(0), range.next().unwrap());
assert_eq!(Duration::from_millis(0), range.next().unwrap());
}
#[test]
#[should_panic]
fn range_exclusive_uniform_wrong_input() {
let _panic = Range::from_millis_exclusive(1, 0);
}
#[test]
#[should_panic]
fn range_inclusive_uniform_wrong_input() {
let _panic = Range::from_millis_inclusive(1, 0);
}
#[test]
fn try_range_uniform() {
assert!(Range::try_from_millis_exclusive(0, 1).is_ok());
assert!(Range::try_from_millis_inclusive(0, 1).is_ok());
assert!(Range::try_from_millis_exclusive(1, 0).is_err());
assert!(Range::try_from_millis_inclusive(1, 0).is_err());
}
#[test]
fn test_jitter() {
assert_eq!(Duration::from_millis(0), jitter(Duration::from_millis(0)));
assert!(Duration::from_millis(0) < jitter(Duration::from_millis(2)));
}