use std::sync::mpsc::{Receiver, TryRecvError};
use numr::runtime::Runtime;
use crate::voxcpm::engine::batch::{EncodeJob, Job, Work};
pub(super) struct Slots<T> {
entries: Vec<Option<T>>,
}
impl<T> Slots<T> {
pub(super) fn new(width: usize) -> Self {
Self {
entries: (0..width).map(|_| None).collect(),
}
}
pub(super) fn width(&self) -> usize {
self.entries.len()
}
pub(super) fn free(&self) -> Option<usize> {
self.entries.iter().position(Option::is_none)
}
pub(super) fn free_count(&self) -> usize {
self.entries.iter().filter(|slot| slot.is_none()).count()
}
pub(super) fn any_occupied(&self) -> bool {
self.entries.iter().any(Option::is_some)
}
pub(super) fn seat(&mut self, slot: usize, value: T) {
if let Some(held) = self.entries.get_mut(slot) {
*held = Some(value);
}
}
pub(super) fn take(&mut self, slot: usize) -> Option<T> {
self.entries.get_mut(slot).and_then(Option::take)
}
pub(super) fn get_mut(&mut self, slot: usize) -> Option<&mut T> {
self.entries.get_mut(slot).and_then(Option::as_mut)
}
pub(super) fn drain_all(&mut self) -> Vec<(usize, T)> {
self.entries
.iter_mut()
.enumerate()
.filter_map(|(slot, held)| held.take().map(|row| (slot, row)))
.collect()
}
}
pub(super) fn drain<R: Runtime>(
jobs: &Receiver<Work<R>>,
free: usize,
serve: &mut dyn FnMut(EncodeJob<R>),
) -> (Vec<Job<R>>, bool) {
let mut taken = Vec::new();
while taken.len() < free {
match jobs.try_recv() {
Ok(Work::Render(job)) => taken.push(job),
Ok(Work::Encode(encode)) => serve(encode),
Err(TryRecvError::Empty) => return (taken, false),
Err(TryRecvError::Disconnected) => return (taken, true),
}
}
(taken, false)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::voxcpm::engine::batch::{JobVoice, test_sampler};
use crate::voxcpm::engine::reply::Reply;
use crate::voxcpm::engine::request::Badcase;
use boostr::model::audio::voxcpm::model::PadSide;
use numr::runtime::cpu::CpuRuntime;
use std::sync::mpsc::channel;
fn job() -> Work<CpuRuntime> {
let (tx, rx) = channel();
drop(rx);
Work::Render(Job {
voice: JobVoice::ZeroShot,
prompt: None,
seed: 0,
sampler: test_sampler(10),
text_token_ids: vec![1, 2],
max_len: 22,
seq_len: 2,
target_len: 2,
badcase: Badcase::default(),
attempt: 1,
reply: Reply::Buffered(tx),
})
}
fn encode() -> Work<CpuRuntime> {
let (tx, rx) = channel();
drop(rx);
Work::Encode(EncodeJob {
samples: vec![0.0; 4],
side: PadSide::Left,
reply: tx,
})
}
#[test]
fn slots_track_what_they_hold() {
let mut slots = Slots::<u32>::new(4);
assert_eq!(slots.width(), 4);
assert_eq!(slots.free_count(), 4);
assert!(!slots.any_occupied());
assert_eq!(slots.free(), Some(0));
for value in 0..4u32 {
let slot = slots.free().expect("a free slot");
assert_eq!(slot, value as usize, "the lowest free slot is taken");
slots.seat(slot, value);
}
assert_eq!(slots.free(), None, "a full table offers no slot");
assert_eq!(slots.free_count(), 0);
assert!(slots.any_occupied());
assert_eq!(slots.take(2), Some(2));
assert_eq!(slots.free(), Some(2), "a released slot is offered again");
assert_eq!(slots.free_count(), 1);
assert_eq!(slots.take(2), None, "an empty slot holds nothing");
}
#[test]
fn a_released_slot_is_reseated_without_moving_the_others() {
let mut slots = Slots::<u32>::new(3);
for value in 0..3u32 {
slots.seat(value as usize, value);
}
slots.take(1);
let slot = slots.free().expect("slot 1 is free");
assert_eq!(slot, 1);
slots.seat(slot, 99);
assert_eq!(slots.get_mut(0).copied(), Some(0));
assert_eq!(slots.get_mut(1).copied(), Some(99));
assert_eq!(slots.get_mut(2).copied(), Some(2));
}
#[test]
fn drain_all_empties_every_slot_and_names_it() {
let mut slots = Slots::<u32>::new(3);
slots.seat(0, 7);
slots.seat(2, 9);
assert_eq!(slots.drain_all(), vec![(0, 7), (2, 9)]);
assert!(!slots.any_occupied());
}
#[test]
fn drain_takes_what_fits_and_leaves_the_rest_queued() {
let (tx, rx) = channel();
for _ in 0..5 {
tx.send(job()).expect("queue");
}
let (taken, closed) = drain(&rx, 2, &mut |_| {});
assert_eq!(taken.len(), 2);
assert!(!closed);
let (taken, closed) = drain(&rx, 8, &mut |_| {});
assert_eq!(taken.len(), 3, "an empty queue ends the drain at once");
assert!(!closed);
}
#[test]
fn drain_of_an_empty_queue_or_a_full_table_takes_nothing() {
let (tx, rx) = channel();
let (taken, closed) = drain(&rx, 4, &mut |_| {});
assert!(taken.is_empty());
assert!(!closed);
tx.send(job()).expect("queue");
let (taken, closed) = drain(&rx, 0, &mut |_| {});
assert!(taken.is_empty(), "no free slot takes no job");
assert!(!closed);
}
#[test]
fn drain_reports_a_closed_queue_after_taking_what_was_left() {
let (tx, rx) = channel();
tx.send(job()).expect("queue");
drop(tx);
let (taken, closed) = drain(&rx, 4, &mut |_| {});
assert_eq!(taken.len(), 1);
assert!(closed, "the queue closed");
}
#[test]
fn drain_serves_encodes_without_spending_a_slot() {
let (tx, rx) = channel();
tx.send(job()).expect("queue");
tx.send(encode()).expect("queue");
tx.send(job()).expect("queue");
let mut served = 0;
let (taken, closed) = drain(&rx, 2, &mut |_| served += 1);
assert_eq!(taken.len(), 2);
assert_eq!(served, 1);
assert!(!closed);
}
}