use core::{cmp::min, fmt};
use alloc::collections::VecDeque;
use crate::queues::{BoundedQueue, Queue};
pub struct Elastic<T> {
minimum_capacity: usize,
maximum_capacity: usize,
pub(super) buffer: VecDeque<T>,
amount: usize,
initialise_memory: fn() -> T,
}
impl<T> Elastic<T> {
pub(crate) fn new(
minimum_capacity: usize,
maximum_capacity: usize,
initialise_memory: fn() -> T,
) -> Self {
debug_assert!(
minimum_capacity != 0,
"The minimum capacity of an elastic queue must be greater than 0."
);
assert!(minimum_capacity <= maximum_capacity, "The maximum capacity of an elastic queue must be at least as large as its minimum capacity, but got minimum = {minimum_capacity}, maximum = {maximum_capacity}");
Self {
minimum_capacity,
maximum_capacity,
buffer: VecDeque::with_capacity(minimum_capacity),
amount: 0,
initialise_memory,
}
}
fn readable_slice(&mut self) -> &[T] {
let (fst, snd) = self.buffer.as_slices();
if fst.is_empty() {
&snd[..min(snd.len(), self.amount)]
} else {
&fst[..min(fst.len(), self.amount)]
}
}
fn writeable_slice(&mut self) -> &mut [T] {
let (fst, snd) = self.buffer.as_mut_slices();
let fst_len = fst.len();
if fst_len > self.amount {
&mut fst[self.amount..]
} else {
let snd_len = snd.len();
&mut snd[min(snd_len, self.amount - fst_len)..]
}
}
fn try_capacity(&mut self) -> bool {
let expandable = self.buffer.len() < self.maximum_capacity;
let full = self.buffer.len() == self.amount;
if full && expandable {
let new_len = (self.buffer.capacity() * 2).min(self.maximum_capacity);
self.buffer.resize_with(new_len, self.initialise_memory);
true
} else {
!full
}
}
fn shrink_if_possible(&mut self) {
if self.amount < self.buffer.capacity() / 2 {
self.buffer
.shrink_to((self.buffer.capacity() / 2).max(self.minimum_capacity));
}
}
}
impl<T> Queue for Elastic<T> {
type Item = T;
fn len(&self) -> usize {
self.amount
}
fn is_full(&self) -> bool {
self.amount == self.maximum_capacity
}
fn max_capacity(&self) -> Option<usize> {
Some(self.maximum_capacity)
}
fn enqueue(&mut self, item: T) -> Option<T> {
if self.try_capacity() {
self.buffer[self.amount] = item;
self.amount += 1;
None
} else {
Some(item)
}
}
async fn expose_slots<F, R>(&mut self, f: F) -> R
where
F: AsyncFnOnce(&mut [T]) -> (usize, R),
{
self.try_capacity();
let (amount, returned) = f(self.writeable_slice()).await;
self.amount += amount;
returned
}
fn dequeue(&mut self) -> Option<T> {
if self.amount == 0 {
None
} else {
let tmp = self.buffer.pop_front();
self.amount -= 1;
self.shrink_if_possible();
Some(tmp.expect("amount == 0 if queue is empty"))
}
}
async fn expose_items<F, R>(&mut self, f: F) -> R
where
F: AsyncFnOnce(&[T]) -> (usize, R),
{
let (amount, returned) = f(self.readable_slice()).await;
self.amount -= amount;
let rotate_by = amount;
self.buffer.rotate_left(rotate_by);
self.buffer.truncate(self.amount);
self.shrink_if_possible();
returned
}
}
impl<T: fmt::Debug> fmt::Debug for Elastic<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Elastic")
.field("minimum_capacity", &self.minimum_capacity)
.field("maximum_capacity", &self.maximum_capacity)
.field("len", &self.amount)
.field("data", &DataDebugger(self))
.finish()
}
}
impl<T> BoundedQueue for Elastic<T> {}
struct DataDebugger<'q, T>(&'q Elastic<T>);
impl<T: fmt::Debug> fmt::Debug for DataDebugger<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut list = f.debug_list();
for item in self.0.buffer.iter().take(self.0.amount) {
list.entry(item);
}
list.finish()
}
}
#[cfg(test)]
mod tests {
extern crate alloc;
use super::*;
use crate::queues::QueueExt;
use alloc::format;
#[test]
fn enqueues_and_dequeues_with_correct_amount() {
let mut queue: Elastic<u8> = Elastic::new(1, 5, Default::default);
assert_eq!(queue.enqueue(2), None);
assert_eq!(queue.enqueue(3), None);
assert_eq!(queue.enqueue(5), None);
assert_eq!(queue.enqueue(7), None);
assert_eq!(queue.len(), 4);
assert_eq!(queue.enqueue(11), None);
assert_eq!(queue.len(), 5);
assert_eq!(queue.enqueue(13), Some(13));
assert_eq!(queue.dequeue(), Some(2));
assert_eq!(queue.len(), 4);
assert_eq!(queue.enqueue(13), None);
}
#[test]
fn returns_none_on_dequeue_when_queue_is_empty() {
let mut queue: Elastic<u8> = Elastic::new(1, 5, Default::default);
queue.enqueue(2);
queue.dequeue();
assert!(queue.dequeue().is_none());
}
#[test]
fn bulk_enqueues_and_dequeues_with_correct_amount() {
pollster::block_on(async {
let mut queue: Elastic<u8> = Elastic::new(1, 7, Default::default);
let mut buf = [0; 8];
let enqueue_amount = queue.bulk_enqueue(b"ufotofu").await;
let dequeue_amount = queue.bulk_dequeue(&mut buf).await;
assert_eq!(enqueue_amount, dequeue_amount);
})
}
#[test]
fn test_debug_impl() {
let mut queue: Elastic<u8> = Elastic::new(1, 8, Default::default);
assert_eq!(queue.enqueue(2), None);
assert_eq!(queue.enqueue(3), None);
assert_eq!(queue.enqueue(5), None);
assert_eq!(
format!("{queue:?}"),
"Elastic { minimum_capacity: 1, maximum_capacity: 8, len: 3, data: [2, 3, 5] }"
);
assert_eq!(queue.dequeue(), Some(2));
assert_eq!(
format!("{queue:?}"),
"Elastic { minimum_capacity: 1, maximum_capacity: 8, len: 2, data: [3, 5] }"
);
assert_eq!(queue.dequeue(), Some(3));
assert_eq!(
format!("{queue:?}"),
"Elastic { minimum_capacity: 1, maximum_capacity: 8, len: 1, data: [5] }"
);
assert_eq!(queue.enqueue(7), None);
assert_eq!(
format!("{queue:?}"),
"Elastic { minimum_capacity: 1, maximum_capacity: 8, len: 2, data: [5, 7] }"
);
assert_eq!(queue.enqueue(11), None);
assert_eq!(queue.enqueue(13), None);
assert_eq!(queue.enqueue(17), None);
assert_eq!(
format!("{queue:?}"),
"Elastic { minimum_capacity: 1, maximum_capacity: 8, len: 5, data: [5, 7, 11, 13, 17] }"
);
}
}