From 14b7073863a8da77bb990639ee023fdee2d98ee2 Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Sun, 2 Jun 2024 16:57:14 -0400 Subject: [PATCH] feat: add `.by_ref()` to create a `Future` from an `AsyncDerived` (etc.) that takes a reference, rather than cloning --- examples/fetch/src/lib.rs | 2 - examples/todo_app_sqlite_axum/src/todo.rs | 2 - leptos_server/src/resource.rs | 26 +++- .../computed/async_derived/async_derived.rs | 10 +- .../computed/async_derived/future_impls.rs | 111 +++++++++++++++--- 5 files changed, 124 insertions(+), 27 deletions(-) diff --git a/examples/fetch/src/lib.rs b/examples/fetch/src/lib.rs index d648788eb..3f9368daf 100644 --- a/examples/fetch/src/lib.rs +++ b/examples/fetch/src/lib.rs @@ -87,7 +87,6 @@ pub fn fetch_example() -> impl IntoView { diff --git a/examples/todo_app_sqlite_axum/src/todo.rs b/examples/todo_app_sqlite_axum/src/todo.rs index 15ddbeb85..eaad0042a 100644 --- a/examples/todo_app_sqlite_axum/src/todo.rs +++ b/examples/todo_app_sqlite_axum/src/todo.rs @@ -121,7 +121,6 @@ pub fn Todos() -> impl IntoView { Suspend(async move { todos .await - .as_ref() .map(|todos| { if todos.is_empty() { Either::Left(view! {

"No tasks were found."

}) @@ -145,7 +144,6 @@ pub fn Todos() -> impl IntoView { ) } }) - .map_err(Clone::clone) }) }; diff --git a/leptos_server/src/resource.rs b/leptos_server/src/resource.rs index 073c7e2db..71d8c4c19 100644 --- a/leptos_server/src/resource.rs +++ b/leptos_server/src/resource.rs @@ -12,8 +12,8 @@ use futures::Future; use hydration_context::SerializedDataId; use reactive_graph::{ computed::{ - ArcAsyncDerived, ArcAsyncDerivedFuture, ArcMemo, AsyncDerived, - AsyncDerivedFuture, AsyncDerivedGuard, + ArcAsyncDerived, ArcAsyncDerivedFuture, ArcAsyncDerivedRefFuture, + ArcMemo, AsyncDerived, AsyncDerivedFuture, AsyncDerivedRefFuture, }, graph::{Source, ToAnySubscriber}, owner::Owner, @@ -244,7 +244,7 @@ impl IntoFuture for ArcResource where T: Clone + 'static, { - type Output = AsyncDerivedGuard; + type Output = T; type IntoFuture = ArcAsyncDerivedFuture; fn into_future(self) -> Self::IntoFuture { @@ -252,6 +252,15 @@ where } } +impl ArcResource +where + T: 'static, +{ + pub fn by_ref(&self) -> ArcAsyncDerivedRefFuture { + self.data.by_ref() + } +} + pub struct Resource where T: Send + Sync + 'static, @@ -433,7 +442,7 @@ impl IntoFuture for Resource where T: Clone + Send + Sync + 'static, { - type Output = AsyncDerivedGuard; + type Output = T; type IntoFuture = AsyncDerivedFuture; #[track_caller] @@ -441,3 +450,12 @@ where self.data.into_future() } } + +impl Resource +where + T: Send + Sync + 'static, +{ + pub fn by_ref(&self) -> AsyncDerivedRefFuture { + self.data.by_ref() + } +} diff --git a/reactive_graph/src/computed/async_derived/async_derived.rs b/reactive_graph/src/computed/async_derived/async_derived.rs index 25df86541..a8a1d0094 100644 --- a/reactive_graph/src/computed/async_derived/async_derived.rs +++ b/reactive_graph/src/computed/async_derived/async_derived.rs @@ -18,7 +18,7 @@ pub struct AsyncDerived { pub(crate) inner: StoredValue>, } -impl Dispose for AsyncDerived { +impl Dispose for AsyncDerived { fn dispose(self) { self.inner.dispose() } @@ -105,15 +105,15 @@ impl AsyncDerived { } } -impl Copy for AsyncDerived {} +impl Copy for AsyncDerived {} -impl Clone for AsyncDerived { +impl Clone for AsyncDerived { fn clone(&self) -> Self { *self } } -impl Debug for AsyncDerived { +impl Debug for AsyncDerived { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("AsyncDerived") .field("type", &std::any::type_name::()) @@ -122,7 +122,7 @@ impl Debug for AsyncDerived { } } -impl DefinedAt for AsyncDerived { +impl DefinedAt for AsyncDerived { #[inline(always)] fn defined_at(&self) -> Option<&'static Location<'static>> { #[cfg(debug_assertions)] diff --git a/reactive_graph/src/computed/async_derived/future_impls.rs b/reactive_graph/src/computed/async_derived/future_impls.rs index 787cda430..98e5fcf46 100644 --- a/reactive_graph/src/computed/async_derived/future_impls.rs +++ b/reactive_graph/src/computed/async_derived/future_impls.rs @@ -47,20 +47,11 @@ impl Future for ArcAsyncDerivedReadyFuture { } } -/// A [`Future`] that is ready when an [`ArcAsyncDerived`] is finished loading or reloading, -/// and contains its value. -pub struct ArcAsyncDerivedFuture { - source: AnySource, - value: Arc>>, - loading: Arc, - wakers: Arc>>, -} - impl IntoFuture for ArcAsyncDerived where T: Clone + 'static, { - type Output = AsyncDerivedGuard; + type Output = T; type IntoFuture = ArcAsyncDerivedFuture; fn into_future(self) -> Self::IntoFuture { @@ -73,11 +64,62 @@ where } } -// this is implemented to output T by cloning it because read guards should not be held across -// .await points, and it's way too easy to trip up by doing that! +/// A [`Future`] that is ready when an [`ArcAsyncDerived`] is finished loading or reloading, +/// and contains its value. `.await`ing this clones the value `T`. +pub struct ArcAsyncDerivedFuture { + source: AnySource, + value: Arc>>, + loading: Arc, + wakers: Arc>>, +} + impl Future for ArcAsyncDerivedFuture where T: Clone + 'static, +{ + type Output = T; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let waker = cx.waker(); + self.source.track(); + let value = self.value.read_arc(); + pin_mut!(value); + match (self.loading.load(Ordering::Relaxed), value.poll(cx)) { + (true, _) => { + self.wakers.write().or_poisoned().push(waker.clone()); + Poll::Pending + } + (_, Poll::Pending) => Poll::Pending, + (_, Poll::Ready(guard)) => { + Poll::Ready(guard.as_ref().unwrap().clone()) + } + } + } +} + +impl ArcAsyncDerived { + pub fn by_ref(&self) -> ArcAsyncDerivedRefFuture { + ArcAsyncDerivedRefFuture { + source: self.to_any_source(), + value: Arc::clone(&self.value), + loading: Arc::clone(&self.loading), + wakers: Arc::clone(&self.wakers), + } + } +} + +/// A [`Future`] that is ready when an [`ArcAsyncDerived`] is finished loading or reloading, +/// and yields an [`AsyncDerivedGuard`] that dereferences to its value. +pub struct ArcAsyncDerivedRefFuture { + source: AnySource, + value: Arc>>, + loading: Arc, + wakers: Arc>>, +} + +impl Future for ArcAsyncDerivedRefFuture +where + T: 'static, { type Output = AsyncDerivedGuard; @@ -115,7 +157,7 @@ impl IntoFuture for AsyncDerived where T: Send + Sync + Clone + 'static, { - type Output = AsyncDerivedGuard; + type Output = T; type IntoFuture = AsyncDerivedFuture; fn into_future(self) -> Self::IntoFuture { @@ -130,7 +172,7 @@ impl Future for AsyncDerivedFuture where T: Send + Sync + Clone + 'static, { - type Output = AsyncDerivedGuard; + type Output = T; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut this = self.project(); @@ -147,3 +189,44 @@ where this.inner.as_pin_mut().unwrap().poll(cx) } } + +impl AsyncDerived { + pub fn by_ref(&self) -> AsyncDerivedRefFuture { + AsyncDerivedRefFuture { + this: *self, + inner: None, + } + } +} + +pin_project! { + /// A [`Future`] that is ready when an [`AsyncDerived`] is finished loading or reloading, + /// and contains its value. + pub struct AsyncDerivedRefFuture { + this: AsyncDerived, + #[pin] + inner: Option>, + } +} + +impl Future for AsyncDerivedRefFuture +where + T: Send + Sync + 'static, +{ + type Output = AsyncDerivedGuard; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); + if this.inner.is_none() { + let stored = *this.this; + this.inner.set(Some( + stored + .inner + .get() + .unwrap_or_else(unwrap_signal!(stored)) + .by_ref(), + )); + } + this.inner.as_pin_mut().unwrap().poll(cx) + } +}