use std::sync::atomic::{AtomicUsize, Ordering};
use super::{SelectionContext, Strategy};
#[derive(Debug)]
pub struct RoundRobin {
counter: AtomicUsize,
}
impl RoundRobin {
pub fn new() -> Self {
Self {
counter: AtomicUsize::new(0),
}
}
}
impl Default for RoundRobin {
fn default() -> Self {
Self::new()
}
}
impl<N> Strategy<N> for RoundRobin {
fn select(&self, candidates: &[N], ctx: &SelectionContext) -> Option<usize> {
if candidates.is_empty() {
return None;
}
let len = candidates.len();
let start = self.counter.fetch_add(1, Ordering::Relaxed);
for i in 0..len {
let idx = (start + i) % len;
if !ctx.is_excluded(idx) {
return Some(idx);
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cycles_through_candidates() {
let rr = RoundRobin::new();
let nodes = [1, 2, 3];
let ctx = SelectionContext::default();
assert_eq!(rr.select(&nodes, &ctx), Some(0));
assert_eq!(rr.select(&nodes, &ctx), Some(1));
assert_eq!(rr.select(&nodes, &ctx), Some(2));
assert_eq!(rr.select(&nodes, &ctx), Some(0));
}
#[test]
fn empty_returns_none() {
let rr = RoundRobin::new();
let nodes: [i32; 0] = [];
assert_eq!(rr.select(&nodes, &SelectionContext::default()), None);
}
#[test]
fn skips_excluded() {
let rr = RoundRobin::new();
let nodes = [1, 2, 3];
let ctx = SelectionContext::builder().exclude(vec![1]).build();
assert_eq!(rr.select(&nodes, &ctx), Some(0));
assert_eq!(rr.select(&nodes, &ctx), Some(2));
assert_eq!(rr.select(&nodes, &ctx), Some(2));
assert_eq!(rr.select(&nodes, &ctx), Some(0));
}
#[test]
fn all_excluded_returns_none() {
let rr = RoundRobin::new();
let nodes = [1, 2];
let ctx = SelectionContext::builder().exclude(vec![0, 1]).build();
assert_eq!(rr.select(&nodes, &ctx), None);
}
}