use std::sync::{Arc, Mutex};
use tokio::sync::Notify;
use crate::Error;
use crate::frame::Surface;
pub(super) struct FrameChannel {
state: Mutex<State>,
notify: Notify,
}
struct State {
frame: Option<Surface>,
closed: bool,
error: Option<Error>,
}
impl FrameChannel {
pub(super) fn new() -> Arc<Self> {
Arc::new(Self {
state: Mutex::new(State {
frame: None,
closed: false,
error: None,
}),
notify: Notify::new(),
})
}
pub(super) fn push(&self, frame: Surface) {
{
let mut state = self.state.lock().unwrap();
if state.closed {
return;
}
state.frame = Some(frame);
}
self.notify.notify_one();
}
pub(super) fn close(&self) {
let mut state = self.state.lock().unwrap();
state.closed = true;
drop(state);
self.wake();
}
pub(super) fn fail(&self, error: Error) {
let mut state = self.state.lock().unwrap();
if state.closed {
return;
}
state.frame = None;
state.error = Some(error);
state.closed = true;
drop(state);
self.wake();
}
fn wake(&self) {
self.notify.notify_one();
}
pub(super) async fn recv(&self) -> Result<Option<Surface>, Error> {
loop {
let notified = self.notify.notified();
{
let mut state = self.state.lock().unwrap();
if let Some(error) = state.error.take() {
return Err(error);
}
if let Some(frame) = state.frame.take() {
return Ok(Some(frame));
}
if state.closed {
return Ok(None);
}
}
notified.await;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frame::I420;
fn frame(id: u32) -> Surface {
Surface::I420(I420 {
width: id,
height: 2,
data: Vec::new(),
color: None,
})
}
#[tokio::test]
async fn recv_returns_frames_in_order() {
let chan = FrameChannel::new();
chan.push(frame(1));
assert_eq!(chan.recv().await.unwrap().unwrap().width(), 1);
chan.push(frame(2));
assert_eq!(chan.recv().await.unwrap().unwrap().width(), 2);
}
#[tokio::test]
async fn slow_consumer_receives_only_the_latest_frame() {
let chan = FrameChannel::new();
for id in 1..=6 {
chan.push(frame(id));
}
assert_eq!(chan.recv().await.unwrap().unwrap().width(), 6);
}
#[tokio::test]
async fn close_returns_none_after_the_pending_frame() {
let chan = FrameChannel::new();
chan.push(frame(1));
chan.close();
assert_eq!(chan.recv().await.unwrap().unwrap().width(), 1);
assert!(chan.recv().await.unwrap().is_none());
}
#[tokio::test]
async fn failure_discards_a_pending_frame_and_surfaces_the_cause() {
let chan = FrameChannel::new();
chan.push(frame(1));
chan.fail(Error::SourceUnavailable("window closed".to_string()));
assert!(matches!(
chan.recv().await,
Err(Error::SourceUnavailable(reason)) if reason == "window closed"
));
assert!(chan.recv().await.unwrap().is_none());
}
#[tokio::test]
async fn closing_retains_a_wakeup_for_a_consumer_that_has_not_parked() {
let chan = FrameChannel::new();
chan.close();
chan.notify.notified().await;
let chan = FrameChannel::new();
chan.fail(Error::SourceUnavailable("stream stopped".to_string()));
chan.notify.notified().await;
}
#[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().unwrap().width(), 7);
}
}