pub const ACCUMULATORS: usize = 5;
pub const SUBSTREAMS: usize = crate::process::MAX_PRESENTATIONS;
const RING: usize = 128;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(usize)]
pub enum Accumulator {
Substream0,
Sixch,
Eightch,
Sixteench,
WholeStream,
}
const FBB_SUBSTREAM0_CAP: [usize; 10] = [90_000, 30_000, 0, 30_000, 0, 0, 0, 0, 0, 30_000];
const FBB_STREAM_CAP: [usize; 10] = [90_000, 30_000, 0, 30_000, 0, 0, 0, 0, 0, 90_000];
impl Accumulator {
pub const ALL: [Accumulator; ACCUMULATORS] = [
Accumulator::Substream0,
Accumulator::Sixch,
Accumulator::Eightch,
Accumulator::Sixteench,
Accumulator::WholeStream,
];
pub const fn fba_cap(&self) -> usize {
match self {
Accumulator::Substream0 => 30_000,
Accumulator::Sixch => 90_000,
_ => 120_000,
}
}
pub fn fbb_cap(&self, substream_info: u8) -> Option<usize> {
let index = (substream_info as usize & 0xF).wrapping_sub(4);
match self {
Accumulator::Substream0 => Some(FBB_SUBSTREAM0_CAP.get(index).copied().unwrap_or(0)),
Accumulator::Sixch | Accumulator::WholeStream => {
Some(FBB_STREAM_CAP.get(index).copied().unwrap_or(0))
}
Accumulator::Eightch | Accumulator::Sixteench => None,
}
}
pub const fn group(&self) -> &'static str {
match self {
Accumulator::Substream0 => "substream 0",
Accumulator::Sixch => "the 6-channel decoder",
Accumulator::Eightch => "the 8-channel decoder",
Accumulator::Sixteench => "the 16-channel decoder",
Accumulator::WholeStream => "the whole stream",
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct FifoDepthReport {
pub depths: [usize; ACCUMULATORS],
pub underrun: Option<usize>,
}
#[derive(Clone, Copy, Debug, Default)]
pub struct FifoContribution {
pub total: [usize; ACCUMULATORS],
pub stream: [usize; ACCUMULATORS],
pub substream: [usize; SUBSTREAMS],
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct FifoPeak {
pub total: usize,
pub stream: usize,
pub overhead: usize,
}
#[derive(Clone, Copy, Debug)]
pub struct FifoDepthState {
contribution: [[u32; RING]; ACCUMULATORS],
stream: [[u32; RING]; ACCUMULATORS],
substream: [[u32; RING]; SUBSTREAMS],
removal: [usize; RING],
read: usize,
write: usize,
depth: [usize; ACCUMULATORS],
depth_stream: [usize; ACCUMULATORS],
depth_substream: [usize; SUBSTREAMS],
peak: [FifoPeak; ACCUMULATORS],
peak_substream: [usize; SUBSTREAMS],
}
impl Default for FifoDepthState {
fn default() -> Self {
Self {
contribution: [[0; RING]; ACCUMULATORS],
stream: [[0; RING]; ACCUMULATORS],
substream: [[0; RING]; SUBSTREAMS],
removal: [0; RING],
read: 0,
write: 0,
depth: [0; ACCUMULATORS],
depth_stream: [0; ACCUMULATORS],
depth_substream: [0; SUBSTREAMS],
peak: [FifoPeak::default(); ACCUMULATORS],
peak_substream: [0; SUBSTREAMS],
}
}
}
impl FifoDepthState {
pub fn push(
&mut self,
playhead: usize,
removal: usize,
contribution: FifoContribution,
) -> FifoDepthReport {
let mut report = FifoDepthReport::default();
let slot = self.write;
self.removal[slot] = removal;
for k in 0..ACCUMULATORS {
self.contribution[k][slot] = contribution.total[k] as u32;
self.stream[k][slot] = contribution.stream[k] as u32;
}
for i in 0..SUBSTREAMS {
self.substream[i][slot] = contribution.substream[i] as u32;
}
let mut drained = 0;
while playhead > self.removal[self.read] && drained < RING {
let read = self.read;
for k in 0..ACCUMULATORS {
let leaving = self.contribution[k][read] as usize;
match self.depth[k].checked_sub(leaving) {
Some(remaining) => {
self.depth[k] = remaining;
self.depth_stream[k] =
self.depth_stream[k].saturating_sub(self.stream[k][read] as usize);
}
None => {
self.depth[k] = 0;
self.depth_stream[k] = 0;
report.underrun = Some(k);
}
}
self.sample_peak(k);
}
for i in 0..SUBSTREAMS {
self.depth_substream[i] =
self.depth_substream[i].saturating_sub(self.substream[i][read] as usize);
self.peak_substream[i] = self.peak_substream[i].max(self.depth_substream[i]);
}
self.read = (read + 1) & (RING - 1);
drained += 1;
if report.underrun.is_some() {
break;
}
}
for k in 0..ACCUMULATORS {
self.depth[k] += contribution.total[k];
self.depth_stream[k] += contribution.stream[k];
self.sample_peak(k);
}
for i in 0..SUBSTREAMS {
self.depth_substream[i] += contribution.substream[i];
self.peak_substream[i] = self.peak_substream[i].max(self.depth_substream[i]);
}
self.write = (slot + 1) & (RING - 1);
report.depths = self.depth;
report
}
fn sample_peak(&mut self, k: usize) {
if self.depth[k] <= self.peak[k].total {
return;
}
self.peak[k] = FifoPeak {
total: self.depth[k],
stream: self.depth_stream[k],
overhead: self.depth[k] - self.depth_stream[k].min(self.depth[k]),
};
}
pub fn peaks(&self) -> [usize; ACCUMULATORS] {
let mut peaks = [0; ACCUMULATORS];
for (peak, record) in peaks.iter_mut().zip(self.peak) {
*peak = record.total;
}
peaks
}
pub fn peak_records(&self) -> [FifoPeak; ACCUMULATORS] {
self.peak
}
pub fn substream_peaks(&self) -> [usize; SUBSTREAMS] {
self.peak_substream
}
pub fn restart(&mut self) {
self.read = 0;
self.write = 0;
self.depth = [0; ACCUMULATORS];
self.depth_stream = [0; ACCUMULATORS];
self.depth_substream = [0; SUBSTREAMS];
}
pub fn buffered(&self) -> usize {
self.write.wrapping_sub(self.read) & (RING - 1)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn unit(total: [usize; ACCUMULATORS]) -> FifoContribution {
let mut substream = [0; SUBSTREAMS];
substream[0] = total[4] / 4;
substream[1] = total[4] / 4;
FifoContribution {
total,
stream: total.map(|bytes| bytes / 2),
substream,
}
}
const UNIT: [usize; ACCUMULATORS] = [10, 20, 30, 40, 50];
#[test]
fn a_two_substream_fbb_stream_has_real_caps() {
assert_eq!(Accumulator::Substream0.fbb_cap(0x0D), Some(30_000));
assert_eq!(Accumulator::Sixch.fbb_cap(0x0D), Some(90_000));
assert_eq!(Accumulator::WholeStream.fbb_cap(0x0D), Some(90_000));
assert_eq!(Accumulator::Substream0.fbb_cap(0x04), Some(90_000));
assert_eq!(Accumulator::Substream0.fbb_cap(0x05), Some(30_000));
assert_eq!(Accumulator::Substream0.fbb_cap(0x07), Some(30_000));
assert_eq!(Accumulator::Sixch.fbb_cap(0x07), Some(30_000));
assert_eq!(Accumulator::Substream0.fbb_cap(0x2D), Some(30_000));
}
#[test]
fn a_record_stays_until_playback_strictly_passes_its_removal_time() {
let mut fifo = FifoDepthState::default();
for i in 0..3 {
let arrival = i * 40;
fifo.push(arrival, arrival + 100, unit(UNIT));
}
assert_eq!(fifo.buffered(), 3);
assert_eq!(fifo.peaks(), [30, 60, 90, 120, 150]);
let report = fifo.push(100, 220, unit(UNIT));
assert_eq!(fifo.buffered(), 4);
assert_eq!(report.depths, [40, 80, 120, 160, 200]);
let report = fifo.push(101, 221, unit(UNIT));
assert_eq!(fifo.buffered(), 4);
assert_eq!(report.depths, [40, 80, 120, 160, 200]);
assert!(report.underrun.is_none());
}
#[test]
fn the_drain_runs_before_the_add_so_the_peak_includes_the_new_unit() {
let mut fifo = FifoDepthState::default();
fifo.push(0, 50, unit(UNIT));
fifo.push(50, 100, unit(UNIT));
assert_eq!(fifo.peaks(), [20, 40, 60, 80, 100]);
fifo.push(51, 150, unit(UNIT));
assert_eq!(fifo.peaks(), [20, 40, 60, 80, 100]);
}
#[test]
fn an_underrun_clamps_to_zero_and_stops_the_drain_pass() {
let mut fifo = FifoDepthState::default();
fifo.push(0, 10, unit(UNIT));
fifo.push(1, 11, unit(UNIT));
let mut broken = FifoDepthState::default();
broken.push(0, 10, unit([100, 100, 100, 100, 100]));
broken.push(1, 11, unit(UNIT));
broken.contribution.iter_mut().for_each(|c| c[0] = 200);
let report = broken.push(100, 200, unit([2, 2, 2, 2, 2]));
assert!(report.underrun.is_some());
assert_eq!(report.depths, [2, 2, 2, 2, 2]);
assert_eq!(broken.buffered(), 2);
}
#[test]
fn the_stream_and_overhead_parts_of_a_peak_add_up_to_it() {
let mut fifo = FifoDepthState::default();
for i in 0..40 {
let arrival = i * 40;
fifo.push(arrival, arrival + 300, unit(UNIT));
}
for record in fifo.peak_records() {
assert_eq!(record.stream + record.overhead, record.total);
assert_eq!(record.stream, record.total / 2);
}
assert_eq!(fifo.peak_records().map(|record| record.total), fifo.peaks());
}
#[test]
fn a_substream_peak_carries_no_overhead() {
let mut fifo = FifoDepthState::default();
for i in 0..8 {
let arrival = i * 40;
fifo.push(arrival, arrival + 300, unit(UNIT));
}
let peaks = fifo.substream_peaks();
assert_eq!(peaks, [8 * (UNIT[4] / 4), 8 * (UNIT[4] / 4), 0, 0]);
let whole = fifo.peak_records()[Accumulator::WholeStream as usize];
assert!(peaks.iter().sum::<usize>() < whole.total);
}
#[test]
fn the_ring_overwrites_rather_than_evicts_past_128_records() {
let mut fifo = FifoDepthState::default();
for i in 0..(RING * 2) {
fifo.push(i, usize::MAX, unit([2, 2, 2, 2, 2]));
}
assert_eq!(fifo.peaks(), [RING * 4; ACCUMULATORS]);
}
}