use std::{
cell::Cell,
mem,
pin::Pin,
task::{Context, Poll},
};
use pin_project_lite::pin_project;
use topcoat_core::error::Result;
use crate::{RegionId, View, ViewFirst, ViewSwap};
pub(super) fn poll_body<Fut: Future>(
body: Pin<&mut Fut>,
cx: &mut Context<'_>,
) -> (Poll<Fut::Output>, Option<Yield>) {
let _guard = YieldGuard::new();
(body.poll(cx), YIELD.take())
}
thread_local! {
static YIELD: Cell<Option<Yield>> = const { Cell::new(None) };
}
pub(super) enum Yield {
First(ViewFirst),
Swap(ViewSwap),
}
impl Yield {
pub(super) fn into_swap(self, region: RegionId) -> ViewSwap {
match self {
Self::First(first) => ViewSwap {
region,
replacement: first.content,
},
Self::Swap(swap) => swap,
}
}
fn offer(self) -> Option<Self> {
YIELD.with(|slot| match slot.take() {
None => {
slot.set(Some(self));
None
}
Some(taken) => {
slot.set(Some(taken));
Some(self)
}
})
}
}
struct YieldGuard {
prev: Option<Yield>,
}
impl YieldGuard {
fn new() -> Self {
Self { prev: YIELD.take() }
}
}
impl Drop for YieldGuard {
fn drop(&mut self) {
YIELD.set(mem::take(&mut self.prev));
}
}
pin_project! {
pub(super) struct DriveFuture<V> {
#[pin]
view: V,
first: bool,
deferred: Option<Yield>,
}
}
impl<V: View> DriveFuture<V> {
pub(super) fn new(view: V) -> Self {
Self {
view,
first: true,
deferred: None,
}
}
}
impl<V: View> Future for DriveFuture<V> {
type Output = Result<()>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
let value = match this.deferred.take() {
Some(deferred) => deferred,
None if *this.first => match this.view.poll_first(cx) {
Poll::Ready(Ok(first)) => {
*this.first = false;
Yield::First(first)
}
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
Poll::Pending => return Poll::Pending,
},
None => match this.view.poll_swap(cx) {
Poll::Ready(Ok(Some(swap))) => Yield::Swap(swap),
Poll::Ready(Ok(None)) => return Poll::Ready(Ok(())),
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
Poll::Pending => return Poll::Pending,
},
};
*this.deferred = value.offer();
if this.deferred.is_some() {
cx.waker().wake_by_ref();
}
Poll::Pending
}
}