use std::pin::Pin;
use futures::future::FusedFuture;
use futures::{task, Future};
use pin_project::pin_project;
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
#[pin_project]
pub struct ReplaceValue<Fut: Future<Output = ()>, T> {
#[pin]
future: Fut,
value: Option<T>,
}
pub trait FutureExt: Future<Output = ()> {
fn replace_value<T>(self, value: T) -> ReplaceValue<Self, T>
where
Self: Sized,
{
ReplaceValue::new(self, value)
}
}
impl<Fut: Future<Output = ()>, T> ReplaceValue<Fut, T> {
fn new(future: Fut, value: T) -> Self {
Self { future, value: Some(value) }
}
}
impl<T: ?Sized + Future<Output = ()>> FutureExt for T {}
impl<Fut: Future<Output = ()>, T: Unpin> Future for ReplaceValue<Fut, T> {
type Output = T;
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
let this = self.project();
let () = futures::ready!(this.future.poll(cx));
task::Poll::Ready(
this.value
.take()
.expect("ReplaceValue must not be polled after it returned `Poll::Ready`"),
)
}
}
impl<Fut: Future<Output = ()> + Unpin, T: Unpin> FusedFuture for ReplaceValue<Fut, T> {
fn is_terminated(&self) -> bool {
self.value.is_none()
}
}
#[derive(Default)]
pub struct YieldToExecutorOnce(YieldToExecutorOnceInner);
#[derive(Default)]
enum YieldToExecutorOnceInner {
#[default]
NotPolled,
Ready,
Terminated,
}
impl YieldToExecutorOnce {
pub fn new() -> Self {
Self::default()
}
}
impl Future for YieldToExecutorOnce {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
let Self(inner) = self.get_mut();
match *inner {
YieldToExecutorOnceInner::NotPolled => {
*inner = YieldToExecutorOnceInner::Ready;
cx.waker().wake_by_ref();
task::Poll::Pending
}
YieldToExecutorOnceInner::Ready => {
*inner = YieldToExecutorOnceInner::Terminated;
task::Poll::Ready(())
}
YieldToExecutorOnceInner::Terminated => {
panic!("polled future after completion");
}
}
}
}
impl FusedFuture for YieldToExecutorOnce {
fn is_terminated(&self) -> bool {
let Self(inner) = self;
match inner {
YieldToExecutorOnceInner::Ready | YieldToExecutorOnceInner::NotPolled => false,
YieldToExecutorOnceInner::Terminated => true,
}
}
}
#[cfg(test)]
mod tests {
use fuchsia_async as fasync;
#[fasync::run_singlethreaded(test)]
async fn replace_value_trivial() {
use super::FutureExt as _;
let value = "hello world";
assert_eq!(futures::future::ready(()).replace_value(value).await, value);
}
#[test]
fn replace_value_is_terminated() {
use super::FutureExt as _;
use futures::future::{FusedFuture as _, FutureExt as _};
let fut = &mut futures::future::ready(()).replace_value(());
assert!(!fut.is_terminated());
assert_eq!(fut.now_or_never(), Some(()));
assert!(fut.is_terminated());
}
#[test]
fn yield_to_executor_once() {
use futures::future::FusedFuture as _;
use futures::FutureExt as _;
let (waker, count) = futures_test::task::new_count_waker();
let mut context = std::task::Context::from_waker(&waker);
let mut fut = super::YieldToExecutorOnce::new();
assert!(!fut.is_terminated());
assert_eq!(count, 0);
assert_eq!(fut.poll_unpin(&mut context), std::task::Poll::Pending);
assert!(!fut.is_terminated());
assert_eq!(count, 1);
assert_eq!(fut.poll_unpin(&mut context), std::task::Poll::Ready(()));
assert!(fut.is_terminated());
assert_eq!(count, 1);
}
}