use std::collections::HashMap;
#[derive(Debug, Default, Clone, Copy)]
struct AccumulatedField {
last_value: u64,
accumulated: u64,
initialised: bool,
}
#[derive(Debug, Default)]
pub struct Accumulator {
state: HashMap<(u16, u8), AccumulatedField>,
}
impl Accumulator {
pub fn new() -> Self {
Self::default()
}
pub fn accumulate(
&mut self,
mesg_num: u16,
field_def_num: u8,
new_value: u64,
bits: u32,
) -> u64 {
let key = (mesg_num, field_def_num);
let state = self.state.entry(key).or_default();
if !state.initialised {
state.last_value = new_value;
state.accumulated = new_value;
state.initialised = true;
return new_value;
}
let bits = bits.clamp(1, 64);
let mask = if bits >= 64 {
u64::MAX
} else {
(1u64 << bits) - 1
};
let delta = new_value.wrapping_sub(state.last_value) & mask;
state.accumulated = state.accumulated.wrapping_add(delta);
state.last_value = new_value;
state.accumulated
}
pub fn clear(&mut self) {
self.state.clear();
}
pub fn tracked_fields(&self) -> usize {
self.state.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn first_observation_initialises_total() {
let mut acc = Accumulator::new();
assert_eq!(acc.accumulate(20, 5, 100, 8), 100);
assert_eq!(acc.tracked_fields(), 1);
}
#[test]
fn monotonic_8bit_increments_pass_through() {
let mut acc = Accumulator::new();
acc.accumulate(20, 5, 10, 8);
assert_eq!(acc.accumulate(20, 5, 30, 8), 30);
assert_eq!(acc.accumulate(20, 5, 50, 8), 50);
}
#[test]
fn wraparound_compensation_8bit() {
let mut acc = Accumulator::new();
let _ = acc.accumulate(20, 5, 250, 8);
assert_eq!(acc.accumulate(20, 5, 5, 8), 250 + 11);
}
#[test]
fn distinct_fields_are_independent() {
let mut acc = Accumulator::new();
acc.accumulate(20, 1, 100, 8);
acc.accumulate(20, 2, 7, 8);
assert_eq!(acc.tracked_fields(), 2);
assert_eq!(acc.accumulate(20, 1, 110, 8), 110);
assert_eq!(acc.accumulate(20, 2, 7, 8), 7);
}
#[test]
fn clear_drops_all_state() {
let mut acc = Accumulator::new();
acc.accumulate(20, 1, 100, 8);
assert_eq!(acc.tracked_fields(), 1);
acc.clear();
assert_eq!(acc.tracked_fields(), 0);
assert_eq!(acc.accumulate(20, 1, 200, 8), 200);
}
#[test]
fn wide_field_no_wrap_behaviour() {
let mut acc = Accumulator::new();
let _ = acc.accumulate(20, 5, 100, 32);
assert_eq!(acc.accumulate(20, 5, 1_000_000, 32), 1_000_000);
}
}