topcoat-view 0.9.0

A modular, batteries-included Rust web framework for server-rendered apps.
Documentation
use std::{
    pin::Pin,
    task::{Context, Poll},
};

use pin_project_lite::pin_project;
use topcoat_core::error::Result;

use crate::{View, ViewBuffer, ViewBufferScope, ViewFirst, ViewSwap};

pin_project! {
    /// Installs a buffer for a [`View`]'s content while it is polled.
    ///
    /// On its first poll, a scope uses the active build's buffer if there
    /// is one. Otherwise, it creates a buffer and returns self-contained
    /// initial content.
    pub struct ScopeView<V> {
        #[pin]
        view: V,
        buffer: Option<Box<ViewBuffer>>,
        polled: bool,
    }
}

impl<V> ScopeView<V> {
    /// Creates a scope that decides on first poll whether it needs a
    /// buffer of its own.
    #[must_use]
    pub fn new(view: V) -> Self {
        Self {
            view,
            buffer: None,
            polled: false,
        }
    }

    /// Creates a scope that owns its buffer from the start.
    ///
    /// The buffer is installed while `f` builds the view, so everything the
    /// build pushes lands in the same buffer the polls install again.
    #[must_use]
    pub fn self_contained(f: impl FnOnce() -> V) -> Self {
        let mut buffer = Some(Box::new(ViewBuffer::new()));
        let view = {
            let _buffer = ViewBufferScope::new(&mut buffer);
            f()
        };
        Self {
            view,
            buffer,
            polled: false,
        }
    }
}

impl<V> View for ScopeView<V>
where
    V: View,
{
    fn poll_first(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<ViewFirst>> {
        let this = self.project();

        if !*this.polled && !this.buffer.is_some() && !ViewBufferScope::is_active() {
            *this.buffer = Some(Box::new(ViewBuffer::new()));
        }
        *this.polled = true;

        let poll = if this.buffer.is_some() {
            let _buffer = ViewBufferScope::new(this.buffer);
            this.view.poll_first(cx)
        } else {
            this.view.poll_first(cx)
        };

        match poll {
            Poll::Pending => Poll::Pending,
            Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
            Poll::Ready(Ok(ViewFirst { content, live })) => {
                let content = if let Some(buffer) = this.buffer.take() {
                    content.seal(*buffer)
                } else {
                    content
                };
                Poll::Ready(Ok(ViewFirst { content, live }))
            }
        }
    }

    fn poll_swap(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<Option<ViewSwap>>> {
        let this = self.project();
        this.view.poll_swap(cx)
    }
}