use crate::{
Reducer,
fixed_frequency::storage::{FixedStorage, FixedWriter, LevelMetadata},
};
use alloc::vec::Vec;
use core::num::NonZeroUsize;
#[derive(Clone, Copy)]
struct LevelInfo<T> {
capacity: NonZeroUsize,
aggregation_window: NonZeroUsize,
reducer: Reducer<T>,
}
pub struct Builder<T> {
raw_capacity: NonZeroUsize,
levels: Vec<LevelInfo<T>>,
terminal: Option<NonZeroUsize>,
}
impl<T> Builder<T> {
pub fn new(raw_capacity: NonZeroUsize) -> Self {
Self {
raw_capacity,
levels: Vec::new(),
terminal: None,
}
}
pub fn level(mut self, capacity: NonZeroUsize, aggregation_window: NonZeroUsize, reducer: Reducer<T>) -> Self {
self.levels.push(LevelInfo {
capacity,
aggregation_window,
reducer,
});
self
}
pub fn terminal(mut self, aggregation_window: NonZeroUsize) -> Self {
self.terminal = Some(aggregation_window);
self
}
pub fn build(self) -> FixedWriter<T>
where
T: Default,
{
let mut levels = Vec::with_capacity(self.levels.len() + 1);
let mut reducers = Vec::with_capacity(self.levels.len());
let mut start = 0;
let mut capacity = self.raw_capacity.get();
for info in self.levels {
let aggregation_window = info.aggregation_window.get();
let storage_capacity = capacity + aggregation_window;
let end = start + storage_capacity;
levels.push(LevelMetadata::new(start, end, aggregation_window));
reducers.push(info.reducer);
start = end;
capacity = info.capacity.get();
}
let terminal_window = self.terminal.map(NonZeroUsize::get).unwrap_or(0);
let end = start + capacity + terminal_window;
levels.push(LevelMetadata::new(start, end, terminal_window));
let mut data = Vec::with_capacity(end);
for _ in 0..end {
data.push(T::default());
}
FixedWriter {
storage: FixedStorage { data, levels },
reducers,
}
}
}
#[cfg(all(test, feature = "reducers"))]
mod tests {
use super::*;
use crate::reducers;
const fn nz(value: usize) -> NonZeroUsize { NonZeroUsize::new(value).expect("expected non-zero value") }
#[test]
fn construct_simple() {
let s: FixedWriter<f32> = Builder::new(nz(100))
.level(nz(59), nz(100), reducers::average_f32())
.level(nz(59), nz(60), reducers::average_f32())
.level(nz(23), nz(60), reducers::average_f32())
.build();
assert_eq!(s.storage.level_count(), 4);
let raw = s.storage.level_metadata(0).unwrap();
assert_eq!(raw.capacity(), 100);
assert_eq!(raw.aggregation_window(), 100);
assert_eq!(raw.storage_capacity(), 200);
let seconds = s.storage.level_metadata(1).unwrap();
assert_eq!(seconds.capacity(), 59);
assert_eq!(seconds.aggregation_window(), 60);
assert_eq!(seconds.storage_capacity(), 119);
let minutes = s.storage.level_metadata(2).unwrap();
assert_eq!(minutes.capacity(), 59);
assert_eq!(minutes.aggregation_window(), 60);
assert_eq!(minutes.storage_capacity(), 119);
let hours = s.storage.level_metadata(3).unwrap();
assert_eq!(hours.capacity(), 23);
assert_eq!(hours.aggregation_window(), 0);
assert_eq!(hours.storage_capacity(), 23);
}
#[test]
fn construct_with_terminal_aggregation() {
let s: FixedWriter<u32> = Builder::new(nz(4))
.level(nz(3), nz(2), reducers::average_u32())
.terminal(nz(3))
.build();
assert_eq!(s.storage.level_count(), 2);
let raw = s.storage.level_metadata(0).unwrap();
assert_eq!(raw.capacity(), 4);
assert_eq!(raw.aggregation_window(), 2);
assert_eq!(raw.storage_capacity(), 6);
let terminal = s.storage.level_metadata(1).unwrap();
assert_eq!(terminal.capacity(), 3);
assert_eq!(terminal.aggregation_window(), 3);
assert_eq!(terminal.storage_capacity(), 6);
}
#[test]
fn construct_use_simple() {
let mut s: FixedWriter<f32> = Builder::new(nz(100))
.level(nz(59), nz(100), reducers::average_f32())
.level(nz(59), nz(60), reducers::average_f32())
.level(nz(23), nz(60), reducers::average_f32())
.build();
for _ in 0..1000 {
s.push(0.0);
}
}
}