use std::{
pin::Pin,
task::{Context, Poll, ready},
};
use pin_project_lite::pin_project;
use topcoat_core::{context::Cx, error::Result};
use super::Builder;
use crate::{View, ViewFirst, ViewHandle, ViewSwap};
pin_project! {
pub struct JoinView<'cx, U, F> {
cx: &'cx Cx,
#[pin]
units: U,
burst: Option<F>,
next_swap_index: usize,
}
}
impl<'cx, U, F> JoinView<'cx, U, F>
where
U: JoinUnits,
F: FnOnce(&mut Builder<'_, '_, '_>, U::Contents),
{
#[must_use]
pub fn new(cx: &'cx Cx, units: U, burst: F) -> Self {
Self {
cx,
units,
burst: Some(burst),
next_swap_index: 0,
}
}
}
impl<U, F> View for JoinView<'_, U, F>
where
U: JoinUnits + Send,
F: FnOnce(&mut Builder<'_, '_, '_>, U::Contents) + Send,
{
fn poll_first(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<ViewFirst>> {
let mut this = self.project();
ready!(this.units.as_mut().poll_contents(cx))?;
let contents = this.units.as_mut().take_contents();
let burst = this.burst.take().expect("the burst runs once");
let content = Builder::block(this.cx, |builder| burst(builder, contents));
Poll::Ready(Ok(ViewFirst {
content,
live: this.units.is_live(),
}))
}
fn poll_swap(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<Option<ViewSwap>>> {
let mut this = self.project();
let len = U::LEN;
let start = *this.next_swap_index;
let mut all_done = true;
for (from, to) in [(start, len), (0, start)] {
match this.units.as_mut().poll_swap_range(cx, from, to) {
Poll::Pending => all_done = false,
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
Poll::Ready(Ok(None)) => {}
Poll::Ready(Ok(Some((position, swap)))) => {
*this.next_swap_index = (position + 1) % len;
return Poll::Ready(Ok(Some(swap)));
}
}
}
if all_done {
Poll::Ready(Ok(None))
} else {
Poll::Pending
}
}
}
pub trait JoinUnits {
const LEN: usize;
type Contents;
fn poll_contents(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>>;
fn take_contents(self: Pin<&mut Self>) -> Self::Contents;
fn is_live(&self) -> bool;
fn poll_swap_range(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
from: usize,
to: usize,
) -> Poll<Result<Option<(usize, ViewSwap)>>>;
}
impl JoinUnits for () {
const LEN: usize = 0;
type Contents = ();
fn poll_contents(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<()>> {
Poll::Ready(Ok(()))
}
fn take_contents(self: Pin<&mut Self>) -> Self::Contents {}
fn is_live(&self) -> bool {
false
}
fn poll_swap_range(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
_from: usize,
_to: usize,
) -> Poll<Result<Option<(usize, ViewSwap)>>> {
Poll::Ready(Ok(None))
}
}
pin_project! {
pub struct JoinUnit<V, Rest> {
#[pin]
view: V,
content: Option<ViewHandle>,
done: bool,
#[pin]
rest: Rest,
}
}
impl<V, Rest> JoinUnit<V, Rest>
where
V: View,
Rest: JoinUnits,
{
#[must_use]
pub fn new(view: V, rest: Rest) -> Self {
Self {
view,
content: None,
done: false,
rest,
}
}
}
impl<V, Rest> JoinUnits for JoinUnit<V, Rest>
where
V: View,
Rest: JoinUnits,
{
const LEN: usize = 1 + Rest::LEN;
type Contents = (ViewHandle, Rest::Contents);
fn poll_contents(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
let this = self.project();
let mut ready = true;
if this.content.is_none() {
match this.view.poll_first(cx) {
Poll::Ready(Ok(ViewFirst { content, live })) => {
*this.content = Some(content);
*this.done = !live;
}
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
Poll::Pending => ready = false,
}
}
match this.rest.poll_contents(cx) {
Poll::Ready(Ok(())) => {}
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
Poll::Pending => ready = false,
}
if ready {
Poll::Ready(Ok(()))
} else {
Poll::Pending
}
}
fn take_contents(self: Pin<&mut Self>) -> Self::Contents {
let this = self.project();
let content = this
.content
.take()
.expect("every unit resolved its content");
(content, this.rest.take_contents())
}
fn is_live(&self) -> bool {
!self.done || self.rest.is_live()
}
fn poll_swap_range(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
from: usize,
to: usize,
) -> Poll<Result<Option<(usize, ViewSwap)>>> {
if to == 0 {
return Poll::Ready(Ok(None));
}
let this = self.project();
let mut pending = false;
if from == 0 && !*this.done {
match this.view.poll_swap(cx) {
Poll::Ready(Ok(Some(swap))) => return Poll::Ready(Ok(Some((0, swap)))),
Poll::Ready(Ok(None)) => *this.done = true,
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
Poll::Pending => pending = true,
}
}
match this
.rest
.poll_swap_range(cx, from.saturating_sub(1), to - 1)
{
Poll::Ready(Ok(Some((position, swap)))) => Poll::Ready(Ok(Some((position + 1, swap)))),
Poll::Ready(Ok(None)) if pending => Poll::Pending,
other => other,
}
}
}
#[cfg(test)]
mod tests {
use std::task::Waker;
use super::*;
use crate::{RegionId, region::RegionScope};
struct Ticker {
region: RegionId,
remaining: usize,
}
impl View for Ticker {
fn poll_first(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<ViewFirst>> {
Poll::Ready(Ok(ViewFirst {
content: ViewHandle::empty(),
live: true,
}))
}
fn poll_swap(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Result<Option<ViewSwap>>> {
let this = self.get_mut();
if this.remaining == 0 {
return Poll::Ready(Ok(None));
}
this.remaining -= 1;
Poll::Ready(Ok(Some(ViewSwap {
region: this.region,
replacement: ViewHandle::empty(),
})))
}
}
#[test]
fn swaps_are_collected_round_robin() {
let mut counter = 1;
let _regions = RegionScope::new(&mut counter);
let (a, b, c) = (RegionId::next(), RegionId::next(), RegionId::next());
let ticker = |region, remaining| Ticker { region, remaining };
let units = JoinUnit::new(
ticker(a, 3),
JoinUnit::new(ticker(b, 1), JoinUnit::new(ticker(c, 2), ())),
);
let cx = Cx::default();
let mut view = std::pin::pin!(JoinView::new(&cx, units, |_, _| ()));
let mut task_cx = Context::from_waker(Waker::noop());
let mut order = Vec::new();
loop {
match view.as_mut().poll_swap(&mut task_cx) {
Poll::Ready(Ok(Some(swap))) => order.push(swap.region),
Poll::Ready(Ok(None)) => break,
Poll::Ready(Err(_)) => panic!("the tickers never fail"),
Poll::Pending => panic!("the tickers are always ready"),
}
}
assert_eq!(order, [a, b, c, a, c, a]);
}
}