use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::Sender;
use crate::error::{Error, Result};
use crate::voxcpm::engine::request::Rendered;
pub(super) enum Reply {
Buffered(Sender<Result<Rendered>>),
Streaming {
chunks: Sender<Result<Vec<f32>>>,
cancelled: Arc<AtomicBool>,
},
}
impl Reply {
pub(super) fn stream(&self) -> Option<(&Sender<Result<Vec<f32>>>, &AtomicBool)> {
match self {
Self::Buffered(_) => None,
Self::Streaming { chunks, cancelled } => Some((chunks, cancelled)),
}
}
pub(super) fn send_chunk(&self, samples: Vec<f32>) -> bool {
let Some((chunks, cancelled)) = self.stream() else {
return false;
};
!cancelled.load(Ordering::Relaxed) && chunks.send(Ok(samples)).is_ok()
}
pub(super) fn send_samples(&self, samples: Vec<f32>, attempts: usize) {
if let Self::Buffered(done) = self {
let _ = done.send(Ok(Rendered { samples, attempts }));
}
}
pub(super) fn fail(&self, err: Error) {
match self {
Self::Buffered(done) => {
let _ = done.send(Err(err));
}
Self::Streaming { chunks, .. } => {
let _ = chunks.send(Err(err));
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::mpsc::{Receiver, channel};
fn buffered() -> (Reply, Receiver<Result<Rendered>>) {
let (tx, rx) = channel();
(Reply::Buffered(tx), rx)
}
fn streaming() -> (Reply, Receiver<Result<Vec<f32>>>, Arc<AtomicBool>) {
let (tx, rx) = channel();
let cancelled = Arc::new(AtomicBool::new(false));
(
Reply::Streaming {
chunks: tx,
cancelled: Arc::clone(&cancelled),
},
rx,
cancelled,
)
}
#[test]
fn a_cancelled_streaming_row_receives_no_further_chunks() {
let (reply, rx, cancelled) = streaming();
assert!(reply.send_chunk(vec![0.5, 0.25]));
cancelled.store(true, Ordering::Relaxed);
assert!(!reply.send_chunk(vec![1.0]));
let first = rx.recv().expect("first chunk").expect("ok");
assert_eq!(first, [0.5, 0.25]);
drop(reply);
assert!(rx.recv().is_err(), "a cancelled row sent a second chunk");
}
#[test]
fn a_disconnected_streaming_row_stops_receiving_chunks() {
let (reply, rx, _cancelled) = streaming();
drop(rx);
assert!(!reply.send_chunk(vec![1.0]));
}
#[test]
fn a_buffered_reply_takes_samples_not_chunks() {
let (reply, rx) = buffered();
assert!(!reply.send_chunk(vec![1.0]));
reply.send_samples(vec![0.5], 2);
let got = rx.recv().expect("reply").expect("ok");
assert_eq!((got.samples, got.attempts), (vec![0.5], 2));
}
#[test]
fn a_failure_reaches_either_kind_of_caller() {
let (reply, rx) = buffered();
reply.fail(Error::ModelError {
reason: "boom".into(),
});
let err = rx.recv().expect("reply").expect_err("error");
assert!(err.to_string().contains("boom"), "{err}");
let (reply, rx, _cancelled) = streaming();
reply.fail(Error::ModelError {
reason: "bang".into(),
});
let err = rx.recv().expect("reply").expect_err("error");
assert!(err.to_string().contains("bang"), "{err}");
}
}