use std::{
cell::Cell,
pin::Pin,
task::{Context, Poll},
};
use pin_project_lite::pin_project;
use crate::buffer::ViewBuffer;
thread_local! {
static CURRENT: Cell<Option<Box<ViewBuffer>>> = const { Cell::new(None) };
}
pub struct ViewBufferScope<'a> {
slot: &'a mut Option<Box<ViewBuffer>>,
}
impl<'a> ViewBufferScope<'a> {
fn swap(slot: &'a mut Option<Box<ViewBuffer>>) -> Self {
*slot = CURRENT.replace(slot.take());
Self { slot }
}
fn buffer(&mut self) -> Option<&mut ViewBuffer> {
self.slot.as_deref_mut()
}
fn is_active() -> bool {
let buffer = CURRENT.take();
let active = buffer.is_some();
CURRENT.set(buffer);
active
}
pub fn scope<F: Future>(fut: F) -> impl Future<Output = (F::Output, Option<ViewBuffer>)> {
ScopeFuture {
fut,
buffer: None,
role: Role::Undecided,
}
}
pub fn scope_sync<R>(f: impl FnOnce() -> R) -> (R, Option<ViewBuffer>) {
if Self::is_active() {
return (f(), None);
}
let mut slot = Some(Box::new(ViewBuffer::new()));
let output = {
let _scope = ViewBufferScope::swap(&mut slot);
f()
};
let buffer = slot.expect("the buffer was swapped back on exit");
(output, Some(*buffer))
}
pub fn with<R>(f: impl FnOnce(&mut ViewBuffer) -> R) -> R {
let mut slot = None;
let mut scope = ViewBufferScope::swap(&mut slot);
let buffer = scope.buffer().unwrap_or_else(|| {
panic!(
"no view is building on the current task: build views with `view!`, \
on the task that runs the outermost invocation"
)
});
f(buffer)
}
}
impl Drop for ViewBufferScope<'_> {
fn drop(&mut self) {
*self.slot = CURRENT.replace(self.slot.take());
}
}
pin_project! {
struct ScopeFuture<F> {
#[pin]
fut: F,
buffer: Option<Box<ViewBuffer>>,
role: Role,
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Role {
Undecided,
Root,
Nested,
}
impl<F: Future> Future for ScopeFuture<F> {
type Output = (F::Output, Option<ViewBuffer>);
fn poll(self: Pin<&mut Self>, task_cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
if *this.role == Role::Undecided {
*this.role = if ViewBufferScope::is_active() {
Role::Nested
} else {
*this.buffer = Some(Box::new(ViewBuffer::new()));
Role::Root
};
}
let output = if *this.role == Role::Root {
let _scope = ViewBufferScope::swap(this.buffer);
this.fut.poll(task_cx)
} else {
this.fut.poll(task_cx)
};
match output {
Poll::Ready(output) => Poll::Ready((output, this.buffer.take().map(|buffer| *buffer))),
Poll::Pending => Poll::Pending,
}
}
}