feat: add .by_ref() to create a Future from an AsyncDerived (etc.) that takes a reference, rather than cloning

This commit is contained in:
Greg Johnston 2024-06-02 16:57:14 -04:00
parent 4e4deef144
commit 14b7073863
5 changed files with 124 additions and 27 deletions

View file

@ -87,7 +87,6 @@ pub fn fetch_example() -> impl IntoView {
<ul>
{move || Suspend(async move {
cats.await
.as_ref()
.map(|cats| {
cats.iter()
.map(|s| {
@ -99,7 +98,6 @@ pub fn fetch_example() -> impl IntoView {
})
.collect::<Vec<_>>()
})
.map_err(Clone::clone)
})}
</ul>

View file

@ -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! { <p>"No tasks were found."</p> })
@ -145,7 +144,6 @@ pub fn Todos() -> impl IntoView {
)
}
})
.map_err(Clone::clone)
})
};

View file

@ -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<T, Ser> IntoFuture for ArcResource<T, Ser>
where
T: Clone + 'static,
{
type Output = AsyncDerivedGuard<T>;
type Output = T;
type IntoFuture = ArcAsyncDerivedFuture<T>;
fn into_future(self) -> Self::IntoFuture {
@ -252,6 +252,15 @@ where
}
}
impl<T, Ser> ArcResource<T, Ser>
where
T: 'static,
{
pub fn by_ref(&self) -> ArcAsyncDerivedRefFuture<T> {
self.data.by_ref()
}
}
pub struct Resource<T, Ser>
where
T: Send + Sync + 'static,
@ -433,7 +442,7 @@ impl<T, Ser> IntoFuture for Resource<T, Ser>
where
T: Clone + Send + Sync + 'static,
{
type Output = AsyncDerivedGuard<T>;
type Output = T;
type IntoFuture = AsyncDerivedFuture<T>;
#[track_caller]
@ -441,3 +450,12 @@ where
self.data.into_future()
}
}
impl<T, Ser> Resource<T, Ser>
where
T: Send + Sync + 'static,
{
pub fn by_ref(&self) -> AsyncDerivedRefFuture<T> {
self.data.by_ref()
}
}

View file

@ -18,7 +18,7 @@ pub struct AsyncDerived<T> {
pub(crate) inner: StoredValue<ArcAsyncDerived<T>>,
}
impl<T: Send + Sync + 'static> Dispose for AsyncDerived<T> {
impl<T: 'static> Dispose for AsyncDerived<T> {
fn dispose(self) {
self.inner.dispose()
}
@ -105,15 +105,15 @@ impl<T: Send + Sync + 'static> AsyncDerived<T> {
}
}
impl<T: Send + Sync + 'static> Copy for AsyncDerived<T> {}
impl<T> Copy for AsyncDerived<T> {}
impl<T: Send + Sync + 'static> Clone for AsyncDerived<T> {
impl<T> Clone for AsyncDerived<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: Send + Sync + 'static> Debug for AsyncDerived<T> {
impl<T> Debug for AsyncDerived<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AsyncDerived")
.field("type", &std::any::type_name::<T>())
@ -122,7 +122,7 @@ impl<T: Send + Sync + 'static> Debug for AsyncDerived<T> {
}
}
impl<T: Send + Sync + 'static> DefinedAt for AsyncDerived<T> {
impl<T> DefinedAt for AsyncDerived<T> {
#[inline(always)]
fn defined_at(&self) -> Option<&'static Location<'static>> {
#[cfg(debug_assertions)]

View file

@ -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<T> {
source: AnySource,
value: Arc<async_lock::RwLock<Option<T>>>,
loading: Arc<AtomicBool>,
wakers: Arc<RwLock<Vec<Waker>>>,
}
impl<T> IntoFuture for ArcAsyncDerived<T>
where
T: Clone + 'static,
{
type Output = AsyncDerivedGuard<T>;
type Output = T;
type IntoFuture = ArcAsyncDerivedFuture<T>;
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<T> {
source: AnySource,
value: Arc<async_lock::RwLock<Option<T>>>,
loading: Arc<AtomicBool>,
wakers: Arc<RwLock<Vec<Waker>>>,
}
impl<T> Future for ArcAsyncDerivedFuture<T>
where
T: Clone + 'static,
{
type Output = T;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
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<T: 'static> ArcAsyncDerived<T> {
pub fn by_ref(&self) -> ArcAsyncDerivedRefFuture<T> {
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<T> {
source: AnySource,
value: Arc<async_lock::RwLock<Option<T>>>,
loading: Arc<AtomicBool>,
wakers: Arc<RwLock<Vec<Waker>>>,
}
impl<T> Future for ArcAsyncDerivedRefFuture<T>
where
T: 'static,
{
type Output = AsyncDerivedGuard<T>;
@ -115,7 +157,7 @@ impl<T> IntoFuture for AsyncDerived<T>
where
T: Send + Sync + Clone + 'static,
{
type Output = AsyncDerivedGuard<T>;
type Output = T;
type IntoFuture = AsyncDerivedFuture<T>;
fn into_future(self) -> Self::IntoFuture {
@ -130,7 +172,7 @@ impl<T> Future for AsyncDerivedFuture<T>
where
T: Send + Sync + Clone + 'static,
{
type Output = AsyncDerivedGuard<T>;
type Output = T;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.project();
@ -147,3 +189,44 @@ where
this.inner.as_pin_mut().unwrap().poll(cx)
}
}
impl<T: 'static> AsyncDerived<T> {
pub fn by_ref(&self) -> AsyncDerivedRefFuture<T> {
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<T> {
this: AsyncDerived<T>,
#[pin]
inner: Option<ArcAsyncDerivedRefFuture<T>>,
}
}
impl<T> Future for AsyncDerivedRefFuture<T>
where
T: Send + Sync + 'static,
{
type Output = AsyncDerivedGuard<T>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
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)
}
}