use std::{future::Future, task::Poll};
use futures::{FutureExt, StreamExt, channel::mpsc, stream::FuturesUnordered};
#[cfg(not(target_family = "wasm"))]
pub(crate) type MaybeSendBox<'a, T> = futures::future::BoxFuture<'a, T>;
#[cfg(target_family = "wasm")]
pub(crate) type MaybeSendBox<'a, T> = futures::future::LocalBoxFuture<'a, T>;
#[cfg(not(target_family = "wasm"))]
pub(crate) trait MaybeBoxedExt<'a>: Future + Send + Sized + 'a {
fn maybe_boxed(self) -> MaybeSendBox<'a, Self::Output> {
self.boxed()
}
}
#[cfg(not(target_family = "wasm"))]
impl<'a, F: Future + Send + 'a> MaybeBoxedExt<'a> for F {}
#[cfg(target_family = "wasm")]
pub(crate) trait MaybeBoxedExt<'a>: Future + Sized + 'a {
fn maybe_boxed(self) -> MaybeSendBox<'a, Self::Output> {
self.boxed_local()
}
}
#[cfg(target_family = "wasm")]
impl<'a, F: Future + 'a> MaybeBoxedExt<'a> for F {}
pub(crate) async fn err_only<E>(fut: impl Future<Output = Result<(), E>>) -> E {
match fut.await {
Err(err) => err,
Ok(()) => std::future::pending().await,
}
}
#[derive(Clone)]
pub(crate) struct Tasks {
tx: mpsc::UnboundedSender<MaybeSendBox<'static, ()>>,
}
impl Tasks {
pub fn push(&self, task: impl MaybeBoxedExt<'static, Output = ()>) {
let _ = self.tx.unbounded_send(task.maybe_boxed());
}
}
pub(crate) struct TaskSet {
rx: mpsc::UnboundedReceiver<MaybeSendBox<'static, ()>>,
active: FuturesUnordered<MaybeSendBox<'static, ()>>,
}
impl TaskSet {
pub fn new() -> (Tasks, Self) {
let (tx, rx) = mpsc::unbounded();
(
Tasks { tx },
Self {
rx,
active: FuturesUnordered::new(),
},
)
}
pub fn owned() -> Self {
let (_, set) = Self::new();
set
}
pub fn push(&mut self, task: impl MaybeBoxedExt<'static, Output = ()>) {
self.active.push(task.maybe_boxed());
}
pub fn poll(&mut self, waiter: &kio::Waiter) -> Poll<()> {
let mut cx = std::task::Context::from_waker(waiter.waker());
let mut submissions_done = false;
loop {
match self.rx.poll_next_unpin(&mut cx) {
Poll::Ready(Some(task)) => self.active.push(task),
Poll::Ready(None) => {
submissions_done = true;
break;
}
Poll::Pending => break,
}
}
loop {
match self.active.poll_next_unpin(&mut cx) {
Poll::Ready(Some(())) => {}
Poll::Ready(None) if submissions_done => return Poll::Ready(()),
Poll::Ready(None) | Poll::Pending => return Poll::Pending,
}
}
}
pub async fn drive<F: Future>(&mut self, future: F) -> F::Output {
let mut future = std::pin::pin!(future);
kio::wait(|waiter| {
if let Poll::Ready(output) = waiter.poll_future(future.as_mut()) {
return Poll::Ready(output);
}
let _ = self.poll(waiter);
Poll::Pending
})
.await
}
#[cfg(test)]
pub async fn run(mut self) {
kio::wait(|waiter| self.poll(waiter)).await
}
}
#[cfg(test)]
mod tests {
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
use super::*;
#[test]
fn task_set_runs_nested_work_without_a_runtime() {
let (tasks, task_set) = TaskSet::new();
let completed = Arc::new(AtomicUsize::new(0));
let nested_tasks = tasks.clone();
let outer_completed = completed.clone();
tasks.push(async move {
outer_completed.fetch_add(1, Ordering::SeqCst);
let inner_completed = outer_completed.clone();
nested_tasks.push(async move {
inner_completed.fetch_add(1, Ordering::SeqCst);
});
});
drop(tasks);
futures::executor::block_on(task_set.run());
assert_eq!(completed.load(Ordering::SeqCst), 2);
}
#[test]
fn drive_polls_children_alongside_the_accept_future() {
let (tasks, mut set) = TaskSet::new();
let completed = Arc::new(AtomicUsize::new(0));
let child_completed = completed.clone();
tasks.push(async move {
child_completed.fetch_add(1, Ordering::SeqCst);
});
let gate = completed.clone();
let output = futures::executor::block_on(set.drive(std::future::poll_fn(move |cx| {
if gate.load(Ordering::SeqCst) == 1 {
std::task::Poll::Ready(42)
} else {
cx.waker().wake_by_ref();
std::task::Poll::Pending
}
})));
assert_eq!(output, 42);
}
}