prns-runtime 0.3.4

Runtime-neutral Personal Reticulum node contracts and manifold kernel
Documentation
use crate::engine::InstantMillis;

pub(crate) struct WindowRing<const BUCKETS: usize> {
    buckets: [u32; BUCKETS],
    bucket_ms: u64,
    head_ordinal: u64,
    total: u64,
}

impl<const BUCKETS: usize> WindowRing<BUCKETS> {
    pub(crate) const fn new(bucket_ms: u64) -> Self {
        Self {
            buckets: [0; BUCKETS],
            bucket_ms,
            head_ordinal: 0,
            total: 0,
        }
    }

    pub(crate) fn record(&mut self, now: InstantMillis, amount: u64) {
        self.advance(now);
        let slot = (self.head_ordinal % BUCKETS as u64) as usize;
        let capped = u32::try_from(amount).unwrap_or(u32::MAX);
        let grown = self.buckets[slot].saturating_add(capped);
        self.total += u64::from(grown - self.buckets[slot]);
        self.buckets[slot] = grown;
    }

    pub(crate) fn total(&mut self, now: InstantMillis) -> u64 {
        self.advance(now);
        debug_assert_eq!(
            self.total,
            self.buckets
                .iter()
                .map(|&amount| u64::from(amount))
                .sum::<u64>(),
            "the running total desynced from the buckets"
        );
        self.total
    }

    fn advance(&mut self, now: InstantMillis) {
        let ordinal = now.0 / self.bucket_ms;
        let skipped = ordinal.saturating_sub(self.head_ordinal);
        if skipped >= BUCKETS as u64 {
            self.buckets = [0; BUCKETS];
            self.total = 0;
        } else {
            for stale in 1..=skipped {
                let slot = ((self.head_ordinal + stale) % BUCKETS as u64) as usize;
                self.total -= u64::from(self.buckets[slot]);
                self.buckets[slot] = 0;
            }
        }
        self.head_ordinal = ordinal.max(self.head_ordinal);
    }
}