use core::{cmp::min, num::NonZeroUsize};
use alloc::{boxed::Box, vec::Vec};
use arbitrary::Arbitrary;
use crate::{prelude::*, queues::Queue};
#[derive(Debug, PartialEq, Eq, Arbitrary, Clone, Copy)]
pub enum BulkProducerOperation {
Produce,
Slurp,
BulkProduce(NonZeroUsize),
}
fn do_operations_make_progress(ops: &[BulkProducerOperation]) -> bool {
ops.iter()
.any(|op| !matches!(op, BulkProducerOperation::Slurp))
}
#[derive(Debug)]
pub struct BulkScrambled<P, Q, Final, Error> {
inner: P,
buffer: Q,
last: Option<Result<Final, Error>>,
ops: Box<[BulkProducerOperation]>,
op_index: usize,
}
impl<P, Q, Final, Error> BulkScrambled<P, Q, Final, Error> {
pub(crate) fn new(inner: P, buffer: Q, mut ops: Vec<BulkProducerOperation>) -> Self {
if !do_operations_make_progress(&ops) {
ops.push(BulkProducerOperation::Produce)
}
Self {
inner,
buffer,
last: None,
ops: ops.into_boxed_slice(),
op_index: 0,
}
}
pub fn into_inner(self) -> P {
self.inner
}
}
impl<P, Q, Final, Error> AsRef<P> for BulkScrambled<P, Q, Final, Error> {
fn as_ref(&self) -> &P {
&self.inner
}
}
impl<P, Q, Final, Error> BulkScrambled<P, Q, Final, Error>
where
P: BulkProducer<Item: Clone, Final = Final, Error = Error>,
Q: Queue<Item = P::Item>,
{
async fn fill_buffer_from_inner(&mut self) {
while self.last.is_none() && !self.buffer.is_full() {
match self.ops[self.op_index] {
BulkProducerOperation::Produce => match self.inner.produce().await {
Ok(Left(item)) => {
let res = self.buffer.enqueue(item);
debug_assert!(
res.is_none(),
"Enqueueing into a non-full queue must always succeed."
);
}
Ok(Right(fin)) => {
self.last = Some(Ok(fin));
}
Err(err) => {
self.last = Some(Err(err));
}
},
BulkProducerOperation::Slurp => match self.inner.slurp().await {
Ok(()) => { }
Err(err) => self.last = Some(Err(err)),
},
BulkProducerOperation::BulkProduce(buffer_len) => {
let result = self.buffer.expose_slots(async |slots| {
let slots_len = slots.len();
debug_assert!(slots_len > 0, "A non-full queue must not call the callback in expose_slots with an empty buffer");
match self.inner.bulk_produce(&mut slots[..min(slots_len, buffer_len.into())]).await {
Ok(Left(amount)) => (amount, Ok(Left(()))),
Ok(Right(fin)) => (0, Ok(Right(fin))),
Err(err) => (0, Err(err)),
}
}).await;
match result {
Ok(Left(())) => { }
Ok(Right(fin)) => {
self.last = Some(Ok(fin));
}
Err(err) => {
self.last = Some(Err(err));
}
}
}
}
if self.op_index == self.ops.len() - 1 {
self.op_index = 0;
} else {
self.op_index += 1;
}
}
debug_assert!(self.last.is_some() || !self.buffer.is_empty());
}
fn check_last(&mut self) -> Option<Result<Final, Error>> {
if !self.buffer.is_empty() {
None
} else {
self.last.take()
}
}
}
impl<P, Q, Final, Error> Producer for BulkScrambled<P, Q, Final, Error>
where
P: BulkProducer<Item: Clone, Final = Final, Error = Error>,
Q: Queue<Item = P::Item>,
{
type Item = P::Item;
type Final = P::Final;
type Error = P::Error;
async fn produce(&mut self) -> Result<Either<Self::Item, Self::Final>, Self::Error> {
match self.check_last() {
Some(Ok(fin)) => Ok(Right(fin)),
Some(Err(err)) => Err(err),
None => match self.buffer.dequeue() {
Some(item) => Ok(Left(item)),
None => {
self.fill_buffer_from_inner().await;
match self.check_last() {
Some(Ok(fin)) => Ok(Right(fin)),
Some(Err(err)) => Err(err),
None => {
Ok(Left(self.buffer.dequeue().expect(
"Dequeueing from a non-empty queue must always suceed.",
)))
}
}
}
},
}
}
async fn slurp(&mut self) -> Result<(), Self::Error> {
match self.check_last() {
Some(Ok(fin)) => {
self.last = Some(Ok(fin));
Ok(())
}
Some(Err(err)) => {
debug_assert!(self.buffer.is_empty());
Err(err)
}
None => {
self.fill_buffer_from_inner().await;
if self.last.is_none() {
match self.inner.slurp().await {
Ok(()) => Ok(()),
Err(err) => {
if self.buffer.is_empty() {
Err(err)
} else {
self.last = Some(Err(err));
Ok(())
}
}
}
} else {
Ok(())
}
}
}
}
}
impl<P, Q, Final, Error> BulkProducer for BulkScrambled<P, Q, Final, Error>
where
P: BulkProducer<Item: Clone, Final = Final, Error = Error>,
Q: Queue<Item = P::Item>,
{
async fn expose_items_gracefully<F, R>(
&mut self,
f: F,
) -> Result<Either<R, (F, Self::Final)>, (F, Self::Error)>
where
F: AsyncFnOnce(&[Self::Item]) -> (usize, R),
{
match self.check_last() {
Some(Ok(fin)) => Ok(Right((f, fin))),
Some(Err(err)) => Err((f, err)),
None => {
if self.buffer.is_empty() {
self.fill_buffer_from_inner().await;
match self.check_last() {
Some(Ok(fin)) => return Ok(Right((f, fin))),
Some(Err(err)) => return Err((f, err)),
None => { }
}
}
Ok(Left(self.buffer.expose_items(async |buffer_items| {
debug_assert!(!buffer_items.is_empty(), "A non-empty queue must expose at least one item when expose_items is invoked");
f(buffer_items).await
}).await))
}
}
}
}