Skip to main content

hopr_transport_session/balancer/
simple.rs

1use crate::balancer::{BalancerControllerBounds, SurbBalancerController};
2
3/// Controller that uses the simple linear formula `limit * min(current / setpoint, 1.0)` to
4/// compute the control output.
5///
6/// Scaling with the *level* rather than the deficit is deliberate: this drives egress (each sent
7/// packet spends a SURB), so an empty buffer must send nothing. Do not invert it -- that is only
8/// correct for a controller driving production.
9#[derive(Clone, Debug, Default)]
10pub struct SimpleBalancerController {
11    bounds: BalancerControllerBounds,
12}
13
14impl SurbBalancerController for SimpleBalancerController {
15    fn bounds(&self) -> BalancerControllerBounds {
16        self.bounds
17    }
18
19    fn set_target_and_limit(&mut self, bounds: BalancerControllerBounds) {
20        self.bounds = bounds;
21    }
22
23    fn next_control_output(&mut self, current_buffer_level: u64) -> u64 {
24        let ratio = current_buffer_level as f64 / self.bounds.target() as f64;
25        (self.bounds.output_limit() as f64 * ratio.clamp(0.0, 1.0)).floor() as u64
26    }
27
28    fn reset(&mut self) {
29        // Nothing to discard: the output is a pure function of the level handed in, so this
30        // controller has no history that could outlive the regime it was accumulated under.
31    }
32}
33
34#[cfg(test)]
35mod tests {
36    use super::*;
37
38    #[test]
39    fn test_simple_balancer() {
40        let mut controller = SimpleBalancerController::default();
41        controller.set_target_and_limit(BalancerControllerBounds::new(100, 100));
42        assert_eq!(100, controller.bounds.target());
43
44        let outputs: Vec<_> = [10, 100, 101]
45            .iter()
46            .map(|&level| controller.next_control_output(level))
47            .collect();
48        assert_eq!(outputs, [10, 100, 100]);
49        assert_eq!(100, controller.bounds.target());
50    }
51}