loadwise-core 0.1.0

Core traits, strategies, and in-memory stores for loadwise
Documentation
use super::{SelectionContext, Strategy};
use crate::LoadMetric;

/// Selects the node with the lowest load score.
#[derive(Debug)]
pub struct LeastLoad;

impl LeastLoad {
    pub fn new() -> Self {
        Self
    }
}

impl Default for LeastLoad {
    fn default() -> Self {
        Self
    }
}

impl<N: LoadMetric> Strategy<N> for LeastLoad {
    fn select(&self, candidates: &[N], ctx: &SelectionContext) -> Option<usize> {
        if candidates.is_empty() {
            return None;
        }

        let mut best_idx = None;
        let mut best_score = f64::INFINITY;

        for (i, node) in candidates.iter().enumerate() {
            if ctx.is_excluded(i) {
                continue;
            }
            let score = node.load_score();
            if score < best_score {
                best_score = score;
                best_idx = Some(i);
            }
        }

        best_idx
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    struct L(f64);
    impl LoadMetric for L {
        fn load_score(&self) -> f64 {
            self.0
        }
    }

    #[test]
    fn picks_lowest_load() {
        let ll = LeastLoad::new();
        let nodes = [L(3.0), L(1.0), L(2.0)];
        assert_eq!(ll.select(&nodes, &SelectionContext::default()), Some(1));
    }

    #[test]
    fn picks_first_on_tie() {
        let ll = LeastLoad::new();
        let nodes = [L(1.0), L(1.0), L(2.0)];
        assert_eq!(ll.select(&nodes, &SelectionContext::default()), Some(0));
    }

    #[test]
    fn skips_excluded_picks_next_best() {
        let ll = LeastLoad::new();
        let nodes = [L(1.0), L(2.0), L(3.0)];
        // Exclude the best node (index 0)
        let ctx = SelectionContext::builder().exclude(vec![0]).build();
        assert_eq!(ll.select(&nodes, &ctx), Some(1));
    }
}