fix: require `Suspend::new()` to ensure the `Future` is properly scoped at creation time, not at render time

This commit is contained in:
Greg Johnston 2024-07-16 14:09:59 -04:00
parent b24ae7a5e3
commit 93e6456e19
3 changed files with 36 additions and 18 deletions

View File

@ -204,11 +204,11 @@ mod view_implementations {
type State = RenderEffectState<SuspendState<T, R>>; type State = RenderEffectState<SuspendState<T, R>>;
fn build(self) -> Self::State { fn build(self) -> Self::State {
(move || Suspend(async move { self.await })).build() (move || Suspend::new(async move { self.await })).build()
} }
fn rebuild(self, state: &mut Self::State) { fn rebuild(self, state: &mut Self::State) {
(move || Suspend(async move { self.await })).rebuild(state) (move || Suspend::new(async move { self.await })).rebuild(state)
} }
} }
@ -239,7 +239,7 @@ mod view_implementations {
where where
Self::Output<NewAttr>: RenderHtml<R>, Self::Output<NewAttr>: RenderHtml<R>,
{ {
(move || Suspend(async move { self.await })).add_any_attr(attr) (move || Suspend::new(async move { self.await })).add_any_attr(attr)
} }
} }
@ -258,7 +258,7 @@ mod view_implementations {
} }
fn resolve(self) -> impl Future<Output = Self::AsyncOutput> + Send { fn resolve(self) -> impl Future<Output = Self::AsyncOutput> + Send {
(move || Suspend(async move { self.await })).resolve() (move || Suspend::new(async move { self.await })).resolve()
} }
fn to_html_with_buf( fn to_html_with_buf(
@ -267,7 +267,7 @@ mod view_implementations {
position: &mut Position, position: &mut Position,
escape: bool, escape: bool,
) { ) {
(move || Suspend(async move { self.await })) (move || Suspend::new(async move { self.await }))
.to_html_with_buf(buf, position, escape); .to_html_with_buf(buf, position, escape);
} }
@ -279,7 +279,7 @@ mod view_implementations {
) where ) where
Self: Sized, Self: Sized,
{ {
(move || Suspend(async move { self.await })) (move || Suspend::new(async move { self.await }))
.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape); .to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape);
} }
@ -288,7 +288,7 @@ mod view_implementations {
cursor: &Cursor<R>, cursor: &Cursor<R>,
position: &PositionState, position: &PositionState,
) -> Self::State { ) -> Self::State {
(move || Suspend(async move { self.await })) (move || Suspend::new(async move { self.await }))
.hydrate::<FROM_SERVER>(cursor, position) .hydrate::<FROM_SERVER>(cursor, position)
} }
} }

View File

@ -20,11 +20,13 @@ use std::{
pin_project! { pin_project! {
/// A [`Future`] wrapper that sets the [`Owner`] and [`Observer`] before polling the inner /// A [`Future`] wrapper that sets the [`Owner`] and [`Observer`] before polling the inner
/// `Future`. /// `Future`.
#[derive(Clone)]
#[allow(missing_docs)]
pub struct ScopedFuture<Fut> { pub struct ScopedFuture<Fut> {
owner: Option<Owner>, pub owner: Option<Owner>,
observer: Option<AnySubscriber>, pub observer: Option<AnySubscriber>,
#[pin] #[pin]
fut: Fut, pub fut: Fut,
} }
} }

View File

@ -21,7 +21,14 @@ use std::{cell::RefCell, fmt::Debug, future::Future, pin::Pin, rc::Rc};
/// A suspended `Future`, which can be used in the view. /// A suspended `Future`, which can be used in the view.
#[derive(Clone)] #[derive(Clone)]
pub struct Suspend<Fut>(pub Fut); pub struct Suspend<Fut>(pub ScopedFuture<Fut>); // TODO probably shouldn't be pub
impl<Fut> Suspend<Fut> {
/// Creates a new suspended view.
pub fn new(fut: Fut) -> Self {
Self(ScopedFuture::new(fut))
}
}
impl<Fut> Debug for Suspend<Fut> { impl<Fut> Debug for Suspend<Fut> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@ -69,7 +76,7 @@ where
// poll the future once immediately // poll the future once immediately
// if it's already available, start in the ready state // if it's already available, start in the ready state
// otherwise, start with the fallback // otherwise, start with the fallback
let mut fut = Box::pin(ScopedFuture::new(self.0)); let mut fut = Box::pin(self.0);
let initial = fut.as_mut().now_or_never(); let initial = fut.as_mut().now_or_never();
let initially_pending = initial.is_none(); let initially_pending = initial.is_none();
let inner = Rc::new(RefCell::new(initial.build())); let inner = Rc::new(RefCell::new(initial.build()));
@ -96,7 +103,7 @@ where
fn rebuild(self, state: &mut Self::State) { fn rebuild(self, state: &mut Self::State) {
// get a unique ID if there's a SuspenseContext // get a unique ID if there's a SuspenseContext
let fut = ScopedFuture::new(self.0); let fut = self.0;
let id = use_context::<SuspenseContext>().map(|sc| sc.task_id()); let id = use_context::<SuspenseContext>().map(|sc| sc.task_id());
// spawn the future, and rebuild the state when it resolves // spawn the future, and rebuild the state when it resolves
@ -137,10 +144,19 @@ where
Self::Output<NewAttr>: RenderHtml<Rndr>, Self::Output<NewAttr>: RenderHtml<Rndr>,
{ {
let attr = attr.into_cloneable_owned(); let attr = attr.into_cloneable_owned();
Suspend(Box::pin(async move { let ScopedFuture {
let this = self.0.await; owner,
this.add_any_attr(attr) observer,
})) fut,
} = self.0;
Suspend(ScopedFuture {
owner,
observer,
fut: Box::pin(async move {
let this = fut.await;
this.add_any_attr(attr)
}),
})
} }
} }
@ -234,7 +250,7 @@ where
// poll the future once immediately // poll the future once immediately
// if it's already available, start in the ready state // if it's already available, start in the ready state
// otherwise, start with the fallback // otherwise, start with the fallback
let mut fut = Box::pin(ScopedFuture::new(self.0)); let mut fut = Box::pin(self.0);
let initial = fut.as_mut().now_or_never(); let initial = fut.as_mut().now_or_never();
let initially_pending = initial.is_none(); let initially_pending = initial.is_none();
let inner = Rc::new(RefCell::new( let inner = Rc::new(RefCell::new(