use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use tokio::sync::Notify;
use crate::frame::Frame;
const DEPTH: usize = 4;
pub(super) struct FrameChannel {
state: Mutex<State>,
notify: Notify,
}
struct State {
frames: VecDeque<Frame>,
closed: bool,
}
impl FrameChannel {
pub(super) fn new() -> Arc<Self> {
Arc::new(Self {
state: Mutex::new(State {
frames: VecDeque::new(),
closed: false,
}),
notify: Notify::new(),
})
}
pub(super) fn push(&self, frame: Frame) {
{
let mut state = self.state.lock().unwrap();
if state.closed {
return;
}
if state.frames.len() >= DEPTH {
state.frames.pop_front();
}
state.frames.push_back(frame);
}
self.notify.notify_one();
}
pub(super) fn close(&self) {
self.state.lock().unwrap().closed = true;
self.notify.notify_waiters();
}
pub(super) async fn recv(&self) -> Option<Frame> {
loop {
let notified = self.notify.notified();
{
let mut state = self.state.lock().unwrap();
if let Some(frame) = state.frames.pop_front() {
return Some(frame);
}
if state.closed {
return None;
}
}
notified.await;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frame::I420;
fn frame(id: u32) -> Frame {
Frame::I420(I420 {
width: id,
height: 2,
data: Vec::new(),
})
}
#[tokio::test]
async fn recv_returns_frames_in_order() {
let chan = FrameChannel::new();
chan.push(frame(1));
chan.push(frame(2));
assert_eq!(chan.recv().await.unwrap().width(), 1);
assert_eq!(chan.recv().await.unwrap().width(), 2);
}
#[tokio::test]
async fn drops_oldest_when_full() {
let chan = FrameChannel::new();
for id in 1..=(DEPTH as u32 + 2) {
chan.push(frame(id));
}
for id in 3..=(DEPTH as u32 + 2) {
assert_eq!(chan.recv().await.unwrap().width(), id);
}
}
#[tokio::test]
async fn close_returns_none_after_draining() {
let chan = FrameChannel::new();
chan.push(frame(1));
chan.close();
assert_eq!(chan.recv().await.unwrap().width(), 1);
assert!(chan.recv().await.is_none());
}
#[tokio::test]
async fn recv_is_cancel_safe() {
let chan = FrameChannel::new();
tokio::select! {
_ = chan.recv() => panic!("no frame pushed yet"),
_ = std::future::ready(()) => {}
}
chan.push(frame(7));
assert_eq!(chan.recv().await.unwrap().width(), 7);
}
}