From 82c73c3eb3d26de40c498b0928756e0bf6bb34df Mon Sep 17 00:00:00 2001 From: Zak Stucke Date: Fri, 29 Aug 2025 17:37:47 +0300 Subject: [PATCH 1/4] Standardize ScopedFuture::new_untracked like untrack() and untrack_with_diagnostics() --- .../src/computed/async_derived/mod.rs | 50 ++++++------------- 1 file changed, 15 insertions(+), 35 deletions(-) diff --git a/reactive_graph/src/computed/async_derived/mod.rs b/reactive_graph/src/computed/async_derived/mod.rs index 8d911e9389..da79412ef8 100644 --- a/reactive_graph/src/computed/async_derived/mod.rs +++ b/reactive_graph/src/computed/async_derived/mod.rs @@ -25,6 +25,7 @@ pin_project! { pub struct ScopedFuture { pub owner: Owner, pub observer: Option, + pub diagnostics: bool, #[pin] pub fut: Fut, } @@ -39,6 +40,7 @@ impl ScopedFuture { Self { owner, observer, + diagnostics: true, fut, } } @@ -51,19 +53,19 @@ impl ScopedFuture { Self { owner, observer: None, + diagnostics: false, fut, } } #[doc(hidden)] #[track_caller] - pub fn new_untracked_with_diagnostics( - fut: Fut, - ) -> ScopedFutureUntrackedWithDiagnostics { + pub fn new_untracked_with_diagnostics(fut: Fut) -> Self { let owner = Owner::current().unwrap_or_default(); - ScopedFutureUntrackedWithDiagnostics { + Self { owner, observer: None, + diagnostics: true, fut, } } @@ -75,41 +77,19 @@ impl Future for ScopedFuture { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); this.owner.with(|| { - #[cfg(debug_assertions)] - let _maybe_guard = if this.observer.is_none() { - Some(crate::diagnostics::SpecialNonReactiveZone::enter()) - } else { - None - }; - this.observer.with_observer(|| this.fut.poll(cx)) + this.observer.with_observer(|| { + #[cfg(debug_assertions)] + let _maybe_guard = if *this.diagnostics { + None + } else { + Some(crate::diagnostics::SpecialNonReactiveZone::enter()) + }; + this.fut.poll(cx) + }) }) } } -pin_project! { - /// A [`Future`] wrapper that sets the [`Owner`] and [`Observer`] before polling the inner - /// `Future`, output of [`ScopedFuture::new_untracked_with_diagnostics`]. - /// - /// In leptos 0.9 this will be replaced with `ScopedFuture` itself. - #[derive(Clone)] - pub struct ScopedFutureUntrackedWithDiagnostics { - owner: Owner, - observer: Option, - #[pin] - fut: Fut, - } -} - -impl Future for ScopedFutureUntrackedWithDiagnostics { - type Output = Fut::Output; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - this.owner - .with(|| this.observer.with_observer(|| this.fut.poll(cx))) - } -} - /// Utilities used to track whether asynchronous computeds are currently loading. pub mod suspense { use crate::{ From 804ea818c3e5f54ce0cca762424ec372be9f93a6 Mon Sep 17 00:00:00 2001 From: Zak Stucke Date: Fri, 29 Aug 2025 22:20:39 +0300 Subject: [PATCH 2/4] CI --- reactive_graph/src/computed/async_derived/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reactive_graph/src/computed/async_derived/mod.rs b/reactive_graph/src/computed/async_derived/mod.rs index da79412ef8..a48594b490 100644 --- a/reactive_graph/src/computed/async_derived/mod.rs +++ b/reactive_graph/src/computed/async_derived/mod.rs @@ -25,7 +25,7 @@ pin_project! { pub struct ScopedFuture { pub owner: Owner, pub observer: Option, - pub diagnostics: bool, + diagnostics: bool, #[pin] pub fut: Fut, } From 0adf57cd3110d192fe8b113057b9f8deaa1d8a91 Mon Sep 17 00:00:00 2001 From: Zak Stucke Date: Sun, 31 Aug 2025 09:24:39 +0300 Subject: [PATCH 3/4] Don't break semver --- .../src/computed/async_derived/mod.rs | 55 +++++++++++-------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/reactive_graph/src/computed/async_derived/mod.rs b/reactive_graph/src/computed/async_derived/mod.rs index a48594b490..6ceeb430dd 100644 --- a/reactive_graph/src/computed/async_derived/mod.rs +++ b/reactive_graph/src/computed/async_derived/mod.rs @@ -25,7 +25,6 @@ pin_project! { pub struct ScopedFuture { pub owner: Owner, pub observer: Option, - diagnostics: bool, #[pin] pub fut: Fut, } @@ -40,53 +39,61 @@ impl ScopedFuture { Self { owner, observer, - diagnostics: true, fut, } } - /// Wraps the given `Future` by taking the current [`Owner`] re-setting it as the - /// active owner every time the inner `Future` is polled. Always untracks, i.e., clears - /// the active [`Observer`] when polled. - pub fn new_untracked(fut: Fut) -> Self { + #[doc(hidden)] + #[track_caller] + pub fn new_untracked_with_diagnostics(fut: Fut) -> Self { let owner = Owner::current().unwrap_or_default(); Self { owner, observer: None, - diagnostics: false, fut, } } +} + +impl Future for ScopedFuture { + type Output = Fut::Output; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + this.owner + .with(|| this.observer.with_observer(|| this.fut.poll(cx))) + } +} +pin_project! { #[doc(hidden)] - #[track_caller] - pub fn new_untracked_with_diagnostics(fut: Fut) -> Self { + pub struct __NonReactiveZoneFut { + #[pin] + fut: Fut, + } +} + +impl ScopedFuture<__NonReactiveZoneFut> { + /// Wraps the given `Future` by taking the current [`Owner`] re-setting it as the + /// active owner every time the inner `Future` is polled. Always untracks, i.e., clears + /// the active [`Observer`] when polled. + pub fn new_untracked(fut: Fut) -> Self { let owner = Owner::current().unwrap_or_default(); Self { owner, observer: None, - diagnostics: true, - fut, + fut: __NonReactiveZoneFut { fut }, } } } -impl Future for ScopedFuture { +impl Future for __NonReactiveZoneFut { type Output = Fut::Output; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - this.owner.with(|| { - this.observer.with_observer(|| { - #[cfg(debug_assertions)] - let _maybe_guard = if *this.diagnostics { - None - } else { - Some(crate::diagnostics::SpecialNonReactiveZone::enter()) - }; - this.fut.poll(cx) - }) - }) + #[cfg(debug_assertions)] + let _guard = crate::diagnostics::SpecialNonReactiveZone::enter(); + self.project().fut.poll(cx) } } From 8e64cb36fa6e04c1d6d74609c0d5a4eb1200e4d7 Mon Sep 17 00:00:00 2001 From: Zak Stucke Date: Sun, 31 Aug 2025 09:27:49 +0300 Subject: [PATCH 4/4] Make internals of ScopedFuture private --- .../src/computed/async_derived/mod.rs | 61 ++++++++----------- 1 file changed, 27 insertions(+), 34 deletions(-) diff --git a/reactive_graph/src/computed/async_derived/mod.rs b/reactive_graph/src/computed/async_derived/mod.rs index 6ceeb430dd..06d2a3c375 100644 --- a/reactive_graph/src/computed/async_derived/mod.rs +++ b/reactive_graph/src/computed/async_derived/mod.rs @@ -23,10 +23,11 @@ pin_project! { #[derive(Clone)] #[allow(missing_docs)] pub struct ScopedFuture { - pub owner: Owner, - pub observer: Option, + owner: Owner, + observer: Option, + diagnostics: bool, #[pin] - pub fut: Fut, + fut: Fut, } } @@ -39,61 +40,53 @@ impl ScopedFuture { Self { owner, observer, + diagnostics: true, fut, } } - #[doc(hidden)] - #[track_caller] - pub fn new_untracked_with_diagnostics(fut: Fut) -> Self { + /// Wraps the given `Future` by taking the current [`Owner`] re-setting it as the + /// active owner every time the inner `Future` is polled. Always untracks, i.e., clears + /// the active [`Observer`] when polled. + pub fn new_untracked(fut: Fut) -> Self { let owner = Owner::current().unwrap_or_default(); Self { owner, observer: None, + diagnostics: false, fut, } } -} - -impl Future for ScopedFuture { - type Output = Fut::Output; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - this.owner - .with(|| this.observer.with_observer(|| this.fut.poll(cx))) - } -} -pin_project! { #[doc(hidden)] - pub struct __NonReactiveZoneFut { - #[pin] - fut: Fut, - } -} - -impl ScopedFuture<__NonReactiveZoneFut> { - /// Wraps the given `Future` by taking the current [`Owner`] re-setting it as the - /// active owner every time the inner `Future` is polled. Always untracks, i.e., clears - /// the active [`Observer`] when polled. - pub fn new_untracked(fut: Fut) -> Self { + #[track_caller] + pub fn new_untracked_with_diagnostics(fut: Fut) -> Self { let owner = Owner::current().unwrap_or_default(); Self { owner, observer: None, - fut: __NonReactiveZoneFut { fut }, + diagnostics: true, + fut, } } } -impl Future for __NonReactiveZoneFut { +impl Future for ScopedFuture { type Output = Fut::Output; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - #[cfg(debug_assertions)] - let _guard = crate::diagnostics::SpecialNonReactiveZone::enter(); - self.project().fut.poll(cx) + let this = self.project(); + this.owner.with(|| { + this.observer.with_observer(|| { + #[cfg(debug_assertions)] + let _maybe_guard = if *this.diagnostics { + None + } else { + Some(crate::diagnostics::SpecialNonReactiveZone::enter()) + }; + this.fut.poll(cx) + }) + }) } }