use crate::types::Height;
use commonware_utils::{Acknowledgement, futures::OptionFuture};
use futures::FutureExt;
use pin_project::pin_project;
use std::{
collections::VecDeque,
future::Future,
pin::Pin,
task::{Context, Poll},
};
#[pin_project]
pub(super) struct PendingAck<A: Acknowledgement> {
pub(super) height: Height,
#[pin]
pub(super) receiver: A::Waiter,
}
impl<A: Acknowledgement> Future for PendingAck<A> {
type Output = <A::Waiter as Future>::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.project().receiver.poll(cx)
}
}
pub(super) struct PendingAcks<A: Acknowledgement> {
current: OptionFuture<PendingAck<A>>,
queue: VecDeque<PendingAck<A>>,
max: usize,
}
impl<A: Acknowledgement> PendingAcks<A> {
pub(super) fn new(max: usize) -> Self {
Self {
current: None.into(),
queue: VecDeque::with_capacity(max),
max,
}
}
pub(super) fn clear(&mut self) {
self.current = None.into();
self.queue.clear();
}
pub(super) const fn current(&mut self) -> &mut OptionFuture<PendingAck<A>> {
&mut self.current
}
pub(super) fn has_capacity(&self) -> bool {
let reserved = usize::from(self.current.is_some());
self.queue.len() < self.max - reserved
}
pub(super) fn next_dispatch_height(&self, start_height: Height) -> Height {
self.queue
.back()
.map(|ack| ack.height.next())
.or_else(|| self.current.as_ref().map(|ack| ack.height.next()))
.unwrap_or(start_height)
}
pub(super) fn enqueue(&mut self, ack: PendingAck<A>) {
if self.current.is_none() {
self.current.replace(ack);
return;
}
self.queue.push_back(ack);
}
pub(super) fn complete_current(
&mut self,
result: <A::Waiter as Future>::Output,
) -> (Height, <A::Waiter as Future>::Output) {
let PendingAck { height, .. } = self.current.take().expect("ack state must be present");
if let Some(next) = self.queue.pop_front() {
self.current.replace(next);
}
(height, result)
}
pub(super) fn pop_ready(&mut self) -> Option<(Height, <A::Waiter as Future>::Output)> {
let pending = self.current.as_mut()?;
let result = Pin::new(&mut pending.receiver).now_or_never()?;
Some(self.complete_current(result))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::Height;
use commonware_utils::acknowledgement::Exact;
fn pending_ack(height: u64) -> (PendingAck<Exact>, Exact) {
let (ack, receiver) = Exact::handle();
(
PendingAck {
height: Height::new(height),
receiver,
},
ack,
)
}
#[test]
fn enqueue_tracks_capacity_and_fifo_ready_order() {
let mut pending = PendingAcks::<Exact>::new(2);
assert!(pending.has_capacity());
assert_eq!(pending.next_dispatch_height(Height::new(8)), Height::new(8));
let (first, first_ack) = pending_ack(8);
pending.enqueue(first);
assert!(pending.has_capacity());
assert_eq!(pending.next_dispatch_height(Height::new(8)), Height::new(9));
let (second, second_ack) = pending_ack(9);
pending.enqueue(second);
assert!(!pending.has_capacity());
assert_eq!(
pending.next_dispatch_height(Height::new(8)),
Height::new(10)
);
second_ack.acknowledge();
assert!(pending.pop_ready().is_none());
first_ack.acknowledge();
let (height, result) = pending.pop_ready().expect("first ack should be ready");
assert_eq!(height, Height::new(8));
assert!(result.is_ok());
let (height, result) = pending
.pop_ready()
.expect("queued ready ack should be armed next");
assert_eq!(height, Height::new(9));
assert!(result.is_ok());
assert!(pending.has_capacity());
}
#[test]
fn clear_drops_all_pending_acks() {
let mut pending = PendingAcks::<Exact>::new(2);
let (first, first_ack) = pending_ack(3);
let (second, second_ack) = pending_ack(4);
pending.enqueue(first);
pending.enqueue(second);
assert!(!pending.has_capacity());
pending.clear();
first_ack.acknowledge();
second_ack.acknowledge();
assert!(pending.pop_ready().is_none());
assert!(pending.has_capacity());
assert_eq!(
pending.next_dispatch_height(Height::new(10)),
Height::new(10)
);
}
}