use std::collections::VecDeque;
use std::time::Duration;
use tokio::time::Instant;
pub(super) struct ConnectionEvaluator {
scores: VecDeque<(Instant, f64)>,
window_duration: Duration,
}
impl ConnectionEvaluator {
pub fn new(window_duration: Duration) -> Self {
ConnectionEvaluator {
scores: VecDeque::new(),
window_duration,
}
}
pub fn record_only_with_current_time(&mut self, score: f64, current_time: Instant) {
self.remove_outdated_scores(current_time);
self.scores.push_back((current_time, score));
}
pub fn record_and_eval_with_current_time(&mut self, score: f64, current_time: Instant) -> bool {
self.remove_outdated_scores(current_time);
let is_better = self.scores.is_empty() || self.scores.iter().all(|&(_, s)| score > s);
self.record_only_with_current_time(score, current_time);
is_better
}
fn remove_outdated_scores(&mut self, current_time: Instant) {
while let Some(&(time, _)) = self.scores.front() {
if current_time.duration_since(time) > self.window_duration {
self.scores.pop_front();
} else {
break;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_record_first_score() {
let mut evaluator = ConnectionEvaluator::new(Duration::from_secs(10));
let current_time = Instant::now();
assert!(evaluator.record_and_eval_with_current_time(5.0, current_time));
assert_eq!(evaluator.scores.len(), 1);
assert_eq!(evaluator.scores[0].1, 5.0);
assert_eq!(evaluator.scores[0].0, current_time);
}
#[test]
fn test_not_best_in_time_window() {
let mut evaluator = ConnectionEvaluator::new(Duration::from_secs(10));
let start_time = Instant::now();
evaluator.record_and_eval_with_current_time(5.0, start_time);
assert!(
!evaluator.record_and_eval_with_current_time(4.0, start_time + Duration::from_secs(5)),
);
}
#[test]
fn test_best_in_time_window() {
let mut evaluator = ConnectionEvaluator::new(Duration::from_secs(10));
let start_time = Instant::now();
evaluator.record_and_eval_with_current_time(5.0, start_time);
assert!(
evaluator.record_and_eval_with_current_time(4.0, start_time + Duration::from_secs(11)),
);
}
#[test]
fn test_remove_outdated_scores() {
let mut evaluator = ConnectionEvaluator::new(Duration::from_secs(10));
let start_time = Instant::now();
evaluator.record_and_eval_with_current_time(5.0, start_time);
evaluator.record_and_eval_with_current_time(6.0, start_time + Duration::from_secs(5));
evaluator.record_and_eval_with_current_time(4.5, start_time + Duration::from_secs(11));
assert_eq!(evaluator.scores.len(), 2);
}
#[test]
fn test_empty_window_duration() {
let mut evaluator = ConnectionEvaluator::new(Duration::from_secs(0));
let current_time = Instant::now();
assert!(evaluator.record_and_eval_with_current_time(5.0, current_time));
assert!(!evaluator.record_and_eval_with_current_time(4.0, current_time));
}
#[test]
fn test_multiple_scores_same_timestamp() {
let mut evaluator = ConnectionEvaluator::new(Duration::from_secs(10));
let current_time = Instant::now();
evaluator.record_only_with_current_time(5.0, current_time);
evaluator.record_only_with_current_time(6.0, current_time);
assert_eq!(evaluator.scores.len(), 2);
assert!(
!evaluator
.record_and_eval_with_current_time(4.0, current_time + Duration::from_secs(5)),
);
}
#[test]
fn test_negative_scores() {
let mut evaluator = ConnectionEvaluator::new(Duration::from_secs(10));
let start_time = Instant::now();
assert!(evaluator.record_and_eval_with_current_time(-5.0, start_time),);
assert!(
evaluator.record_and_eval_with_current_time(-4.0, start_time + Duration::from_secs(5)),
);
assert!(
!evaluator.record_and_eval_with_current_time(-6.0, start_time + Duration::from_secs(5)),
);
}
#[test]
fn test_large_number_of_scores() {
let mut evaluator = ConnectionEvaluator::new(Duration::from_secs(10));
let start_time = Instant::now();
for i in 0..1000 {
evaluator.record_only_with_current_time(i as f64, start_time + Duration::from_secs(i));
}
assert!(
evaluator
.record_and_eval_with_current_time(1000.0, start_time + Duration::from_secs(1001)),
);
}
}