use crate::core::cancel::CancelToken;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
pub(crate) type BranchFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;
pub(crate) type StopPredicate<T> = Box<dyn Fn(&T) -> bool + Send>;
pub(crate) struct BoundedJoin<T> {
futures: Vec<Option<BranchFuture<T>>>,
results: Vec<Option<T>>,
active: Vec<usize>,
next: usize,
limit: usize,
stop_starting: bool,
stop_on: Option<StopPredicate<T>>,
cancel: Option<CancelToken>,
}
impl<T> BoundedJoin<T> {
pub(crate) fn new(
futures: Vec<BranchFuture<T>>,
limit: usize,
stop_on: Option<StopPredicate<T>>,
cancel: Option<CancelToken>,
) -> Self {
assert!(limit > 0, "Orka setup error: fan-out concurrency limit must be at least 1.");
let count = futures.len();
let mut results = Vec::with_capacity(count);
results.resize_with(count, || None);
Self {
futures: futures.into_iter().map(Some).collect(),
results,
active: Vec::new(),
next: 0,
limit,
stop_starting: false,
stop_on,
cancel,
}
}
fn cancelled(&self) -> bool {
self.cancel.as_ref().is_some_and(|c| c.is_cancelled())
}
}
impl<T> Unpin for BoundedJoin<T> {}
impl<T> Future for BoundedJoin<T> {
type Output = Vec<Option<T>>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
loop {
let halted = this.stop_starting || this.cancelled();
while !halted && this.active.len() < this.limit && this.next < this.futures.len() {
this.active.push(this.next);
this.next += 1;
}
let mut completed_any = false;
let mut i = 0;
while i < this.active.len() {
let index = this.active[i];
let branch = this.futures[index]
.as_mut()
.expect("an active slot always holds its future");
match branch.as_mut().poll(cx) {
Poll::Ready(output) => {
if let Some(stop_on) = this.stop_on.as_ref()
&& stop_on(&output)
{
this.stop_starting = true;
}
this.results[index] = Some(output);
this.futures[index] = None;
this.active.swap_remove(i); completed_any = true;
}
Poll::Pending => i += 1,
}
}
let halted = this.stop_starting || this.cancelled();
if this.active.is_empty() && (halted || this.next >= this.futures.len()) {
return Poll::Ready(std::mem::take(&mut this.results));
}
let can_start_more = !halted && this.next < this.futures.len() && this.active.len() < this.limit;
if !(completed_any && can_start_more) {
return Poll::Pending;
}
}
}
}