use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use ringbuf::traits::{Consumer, Observer, RingBuffer};
use ringbuf::HeapRb;
use crate::types::AudioChunk;
type Shared = Arc<Mutex<HeapRb<AudioChunk>>>;
pub fn chunk_ring(capacity_chunks: usize) -> (ChunkProducer, ChunkConsumer) {
let cap = capacity_chunks.max(1);
let rb: Shared = Arc::new(Mutex::new(HeapRb::<AudioChunk>::new(cap)));
let dropped = Arc::new(AtomicU64::new(0));
(
ChunkProducer {
rb: rb.clone(),
dropped: dropped.clone(),
},
ChunkConsumer { rb, dropped },
)
}
pub struct ChunkProducer {
rb: Shared,
dropped: Arc<AtomicU64>,
}
impl ChunkProducer {
pub fn push(&mut self, mut chunk: AudioChunk) -> Option<u64> {
let mut rb = self.rb.lock().unwrap_or_else(|e| e.into_inner());
let will_evict = rb.is_full();
if will_evict {
self.dropped.fetch_add(1, Ordering::Relaxed);
}
let total = self.dropped.load(Ordering::Relaxed);
chunk.dropped_before = u32::try_from(total).unwrap_or(u32::MAX);
let evicted = rb.push_overwrite(chunk);
drop(rb);
debug_assert_eq!(evicted.is_some(), will_evict);
if will_evict {
Some(total)
} else {
None
}
}
pub fn dropped_count(&self) -> u64 {
self.dropped.load(Ordering::Relaxed)
}
}
pub struct ChunkConsumer {
rb: Shared,
dropped: Arc<AtomicU64>,
}
impl ChunkConsumer {
pub fn try_pop(&mut self) -> Option<AudioChunk> {
let mut rb = self.rb.lock().unwrap_or_else(|e| e.into_inner());
rb.try_pop()
}
pub fn len(&self) -> usize {
self.rb
.lock()
.unwrap_or_else(|e| e.into_inner())
.occupied_len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn dropped_count(&self) -> u64 {
self.dropped.load(Ordering::Relaxed)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::ChunkFlags;
fn chunk(seq: u64) -> AudioChunk {
AudioChunk {
data: vec![0.0; 1920],
frames: 960,
pts_ns: seq as i64 * 20_000_000,
seq,
flags: ChunkFlags::empty(),
dropped_before: 0,
peak: 0.0,
rms: 0.0,
}
}
#[test]
fn fifo_order_when_not_full() {
let (mut p, mut c) = chunk_ring(4);
assert!(c.is_empty());
for s in 0..3 {
assert_eq!(p.push(chunk(s)), None);
}
assert_eq!(c.len(), 3);
assert_eq!(c.try_pop().unwrap().seq, 0);
assert_eq!(c.try_pop().unwrap().seq, 1);
assert_eq!(c.try_pop().unwrap().seq, 2);
assert!(c.try_pop().is_none());
}
#[test]
fn drop_oldest_when_full_and_counts() {
let (mut p, mut c) = chunk_ring(2);
assert_eq!(p.push(chunk(0)), None);
assert_eq!(p.push(chunk(1)), None);
assert_eq!(p.dropped_count(), 0);
let dropped_total = p.push(chunk(2));
assert_eq!(dropped_total, Some(1));
assert_eq!(p.dropped_count(), 1);
let dropped_total = p.push(chunk(3));
assert_eq!(dropped_total, Some(2));
assert_eq!(p.dropped_count(), 2);
let first = c.try_pop().unwrap();
assert_eq!(first.seq, 2);
assert_eq!(first.dropped_before, 1);
let second = c.try_pop().unwrap();
assert_eq!(second.seq, 3);
assert_eq!(second.dropped_before, 2);
assert!(c.try_pop().is_none());
}
#[test]
fn dropped_before_is_cumulative() {
let (mut p, mut c) = chunk_ring(1);
p.push(chunk(0)); p.push(chunk(1)); c.try_pop(); let r = p.push(chunk(2)); assert_eq!(r, None);
let got = c.try_pop().unwrap();
assert_eq!(got.seq, 2);
assert_eq!(got.dropped_before, 1);
assert_eq!(p.dropped_count(), 1);
}
#[test]
fn zero_capacity_is_clamped_to_one() {
let (mut p, mut c) = chunk_ring(0);
assert!(c.is_empty());
assert_eq!(p.push(chunk(0)), None); assert_eq!(c.len(), 1);
assert_eq!(p.push(chunk(1)), Some(1));
let got = c.try_pop().unwrap();
assert_eq!(got.seq, 1);
}
#[test]
fn len_tracks_occupancy_and_is_capped() {
let (mut p, mut c) = chunk_ring(3);
assert!(c.is_empty());
for s in 0..3 {
p.push(chunk(s));
}
assert_eq!(c.len(), 3);
assert!(!c.is_empty());
p.push(chunk(3));
p.push(chunk(4));
assert_eq!(c.len(), 3, "占有数は容量 3 を超えない");
assert_eq!(c.try_pop().unwrap().seq, 2);
assert_eq!(c.try_pop().unwrap().seq, 3);
assert_eq!(c.try_pop().unwrap().seq, 4);
assert!(c.try_pop().is_none());
assert!(c.is_empty());
}
#[test]
fn empty_pop_is_none_and_dropped_is_shared() {
let (mut p, mut c) = chunk_ring(2);
assert!(c.try_pop().is_none());
p.push(chunk(0));
p.push(chunk(1));
p.push(chunk(2)); assert_eq!(p.dropped_count(), 1);
assert_eq!(c.dropped_count(), 1, "consumer 側も同じ dropped を観測する");
}
}