pub(crate) struct VoiceSlots<H> {
voices: Vec<Voice<H>>,
cap: usize,
next_seq: u64,
}
struct Voice<H> {
handle: H,
priority: i32,
seq: u64,
}
pub(crate) enum Admission<H> {
Available,
Steal(H),
Refused,
}
impl<H> VoiceSlots<H> {
pub(crate) fn new(cap: usize) -> Self {
Self {
voices: Vec::new(),
cap,
next_seq: 0,
}
}
#[cfg(test)]
pub(crate) fn len(&self) -> usize {
self.voices.len()
}
pub(crate) fn reap(&mut self, finished: impl Fn(&H) -> bool) {
self.voices.retain(|v| !finished(&v.handle));
}
pub(crate) fn make_room(&mut self, priority: i32) -> Admission<H> {
if self.voices.len() < self.cap {
return Admission::Available;
}
let victim = self
.voices
.iter()
.enumerate()
.min_by_key(|(_, v)| (v.priority, v.seq))
.map(|(i, _)| i);
match victim {
Some(i) if self.voices[i].priority <= priority => {
Admission::Steal(self.voices.swap_remove(i).handle)
}
_ => Admission::Refused,
}
}
pub(crate) fn admit(&mut self, priority: i32, handle: H) {
debug_assert!(self.voices.len() < self.cap);
let seq = self.next_seq;
self.next_seq += 1;
self.voices.push(Voice {
handle,
priority,
seq,
});
}
}
#[cfg(test)]
mod tests {
use super::*;
fn filled(cap: usize, priorities: &[i32]) -> VoiceSlots<u32> {
let mut slots = VoiceSlots::new(cap);
for (i, &p) in priorities.iter().enumerate() {
assert!(matches!(slots.make_room(p), Admission::Available));
slots.admit(p, i as u32);
}
slots
}
#[test]
fn below_cap_always_has_room() {
let mut slots = filled(4, &[0, 0, 0]);
assert!(matches!(slots.make_room(i32::MIN), Admission::Available));
assert_eq!(slots.len(), 3);
}
#[test]
fn full_pool_steals_the_lowest_priority_voice() {
let mut slots = filled(3, &[5, 1, 3]);
match slots.make_room(2) {
Admission::Steal(handle) => assert_eq!(handle, 1, "voice with priority 1 stolen"),
_ => panic!("expected a steal"),
}
assert_eq!(slots.len(), 2);
}
#[test]
fn equal_priorities_steal_the_oldest() {
let mut slots = filled(3, &[2, 2, 2]);
match slots.make_room(2) {
Admission::Steal(handle) => assert_eq!(handle, 0, "first-admitted voice stolen"),
_ => panic!("expected a steal"),
}
}
#[test]
fn outranked_sound_is_refused() {
let mut slots = filled(2, &[4, 6]);
assert!(matches!(slots.make_room(3), Admission::Refused));
assert_eq!(slots.len(), 2, "refusal removes nothing");
}
#[test]
fn reap_frees_slots_for_new_sounds() {
let mut slots = filled(2, &[9, 9]);
assert!(matches!(slots.make_room(0), Admission::Refused));
slots.reap(|&h| h == 0);
assert_eq!(slots.len(), 1);
assert!(matches!(slots.make_room(0), Admission::Available));
}
#[test]
fn zero_cap_refuses_everything() {
let mut slots: VoiceSlots<u32> = VoiceSlots::new(0);
assert!(matches!(slots.make_room(i32::MAX), Admission::Refused));
}
}