Compare commits

...

5 Commits

Author SHA1 Message Date
Greg Johnston
ca903c1053 fix: missing Copy/Clone implementations for OnceResource 2024-10-09 13:43:12 -04:00
Greg Johnston
eef88ac180 chore: update tests 2024-10-06 19:42:42 -04:00
Greg Johnston
c594c0e523 chore: clippy 2024-10-06 19:23:03 -04:00
Greg Johnston
b8f553c3a1 remove local prop to simplify 2024-10-06 13:04:50 -04:00
Greg Johnston
55873e9a10 feat: add an optimized OnceResource and use it to rebuild Await 2024-10-05 12:51:06 -04:00
10 changed files with 807 additions and 84 deletions

View File

@@ -1,10 +1,8 @@
use crate::Suspense;
use leptos_dom::IntoView;
use crate::{prelude::Suspend, suspense_component::Suspense, IntoView};
use leptos_macro::{component, view};
use leptos_reactive::{
create_blocking_resource, create_local_resource, create_resource,
store_value, Serializable,
};
use leptos_server::ArcOnceResource;
use reactive_graph::prelude::ReadUntracked;
use serde::{de::DeserializeOwned, Serialize};
#[component]
/// Allows you to inline the data loading for an `async` block or
@@ -15,11 +13,8 @@ use leptos_reactive::{
/// Adding `let:{variable name}` to the props makes the data available in the children
/// that variable name, when resolved.
/// ```
/// # use leptos_reactive::*;
/// # use leptos_macro::*;
/// # use leptos_dom::*; use leptos::*;
/// # use leptos::prelude::*;
/// # if false {
/// # let runtime = create_runtime();
/// async fn fetch_monkeys(monkey: i32) -> i32 {
/// // do some expensive work
/// 3
@@ -27,29 +22,23 @@ use leptos_reactive::{
///
/// view! {
/// <Await
/// future=|| fetch_monkeys(3)
/// future=fetch_monkeys(3)
/// let:data
/// >
/// <p>{*data} " little monkeys, jumping on the bed."</p>
/// </Await>
/// }
/// # ;
/// # runtime.dispose();
/// # }
/// ```
pub fn Await<T, Fut, FF, VF, V>(
/// A function that returns the [`Future`](std::future::Future) that
/// will the component will `.await` before rendering.
future: FF,
/// If `true`, the component will use [`create_blocking_resource`], preventing
pub fn Await<T, Fut, Chil, V>(
/// A [`Future`](std::future::Future) that will the component will `.await`
/// before rendering.
future: Fut,
/// If `true`, the component will create a blocking resource, preventing
/// the HTML stream from returning anything before `future` has resolved.
#[prop(optional)]
blocking: bool,
/// If `true`, the component will use [`create_local_resource`], this will
/// always run on the local system and therefore its result type does not
/// need to be `Serializable`.
#[prop(optional)]
local: bool,
/// A function that takes a reference to the resolved data from the `future`
/// renders a view.
///
@@ -58,65 +47,58 @@ pub fn Await<T, Fut, FF, VF, V>(
/// `let:` syntax to specify the name for the data variable.
///
/// ```rust
/// # use leptos::*;
/// # use leptos::prelude::*;
/// # if false {
/// # let runtime = create_runtime();
/// # async fn fetch_monkeys(monkey: i32) -> i32 {
/// # 3
/// # }
/// view! {
/// <Await
/// future=|| fetch_monkeys(3)
/// future=fetch_monkeys(3)
/// let:data
/// >
/// <p>{*data} " little monkeys, jumping on the bed."</p>
/// </Await>
/// }
/// # ;
/// # runtime.dispose();
/// # }
/// ```
/// is the same as
/// ```rust
/// # use leptos::*;
/// # use leptos::prelude::*;
/// # if false {
/// # let runtime = create_runtime();
/// # async fn fetch_monkeys(monkey: i32) -> i32 {
/// # 3
/// # }
/// view! {
/// <Await
/// future=|| fetch_monkeys(3)
/// future=fetch_monkeys(3)
/// children=|data| view! {
/// <p>{*data} " little monkeys, jumping on the bed."</p>
/// }
/// />
/// }
/// # ;
/// # runtime.dispose();
/// # }
/// ```
children: VF,
children: Chil,
) -> impl IntoView
where
Fut: std::future::Future<Output = T> + 'static,
FF: Fn() -> Fut + 'static,
T: Send + Sync + Serialize + DeserializeOwned + 'static,
Fut: std::future::Future<Output = T> + Send + 'static,
Chil: FnOnce(&T) -> V + Send + 'static,
V: IntoView,
VF: Fn(&T) -> V + 'static,
T: Serializable + 'static,
{
let res = if blocking {
create_blocking_resource(|| (), move |_| future())
} else if local {
create_local_resource(|| (), move |_| future())
} else {
create_resource(|| (), move |_| future())
};
let view = store_value(children);
let res = ArcOnceResource::<T>::new_with_options(future, blocking);
let ready = res.ready();
view! {
<Suspense fallback=|| ()>
{move || res.map(|data| view.with_value(|view| view(data)))}
{Suspend::new(async move {
ready.await;
children(res.read_untracked().as_ref().unwrap())
})}
</Suspense>
}
}

View File

@@ -200,10 +200,11 @@ pub mod error {
pub use throw_error::*;
}
/// Control-flow components like `<Show>` and `<For>`.
/// Control-flow components like `<Show>`, `<For>`, and `<Await>`.
pub mod control_flow {
pub use crate::{for_loop::*, show::*};
pub use crate::{await_::*, for_loop::*, show::*};
}
mod await_;
mod for_loop;
mod show;
@@ -328,7 +329,6 @@ pub use web_sys;
/*mod additional_attributes;
pub use additional_attributes::*;
mod await_;
pub use await_::*;
pub use leptos_config::{self, get_configuration, LeptosOptions};
#[cfg(not(all(

View File

@@ -19,6 +19,7 @@ tracing = { version = "0.1.40", optional = true }
futures = "0.3.30"
any_spawner = { workspace = true }
or_poisoned = { workspace = true }
tachys = { workspace = true, optional = true, features = ["reactive_graph"] }
send_wrapper = "0.6"

View File

@@ -8,6 +8,8 @@ mod local_resource;
pub use local_resource::*;
mod multi_action;
pub use multi_action::*;
mod once_resource;
pub use once_resource::*;
mod resource;
pub use resource::*;
mod shared;

View File

@@ -0,0 +1,708 @@
use crate::{
initial_value, FromEncodedStr, IntoEncodedString,
IS_SUPPRESSING_RESOURCE_LOAD,
};
#[cfg(feature = "rkyv")]
use codee::binary::RkyvCodec;
#[cfg(feature = "serde-wasm-bindgen")]
use codee::string::JsonSerdeWasmCodec;
#[cfg(feature = "miniserde")]
use codee::string::MiniserdeCodec;
#[cfg(feature = "serde-lite")]
use codee::SerdeLite;
use codee::{
string::{FromToStringCodec, JsonSerdeCodec},
Decoder, Encoder,
};
use core::{fmt::Debug, marker::PhantomData};
use futures::Future;
use or_poisoned::OrPoisoned;
use reactive_graph::{
computed::{
suspense::SuspenseContext, AsyncDerivedReadyFuture, ScopedFuture,
},
diagnostics::{SpecialNonReactiveFuture, SpecialNonReactiveZone},
graph::{AnySource, ToAnySource},
owner::{use_context, ArenaItem, Owner},
prelude::*,
signal::{
guards::{Plain, ReadGuard},
ArcTrigger,
},
unwrap_signal,
};
use std::{
future::IntoFuture,
mem,
panic::Location,
pin::Pin,
sync::{
atomic::{AtomicBool, Ordering},
Arc, RwLock,
},
task::{Context, Poll, Waker},
};
#[derive(Debug)]
pub struct ArcOnceResource<T, Ser = JsonSerdeCodec> {
trigger: ArcTrigger,
value: Arc<RwLock<Option<T>>>,
wakers: Arc<RwLock<Vec<Waker>>>,
suspenses: Arc<RwLock<Vec<SuspenseContext>>>,
loading: Arc<AtomicBool>,
ser: PhantomData<fn() -> Ser>,
#[cfg(debug_assertions)]
defined_at: &'static Location<'static>,
}
impl<T, Ser> Clone for ArcOnceResource<T, Ser> {
fn clone(&self) -> Self {
Self {
trigger: self.trigger.clone(),
value: self.value.clone(),
wakers: self.wakers.clone(),
suspenses: self.suspenses.clone(),
loading: self.loading.clone(),
ser: self.ser,
#[cfg(debug_assertions)]
defined_at: self.defined_at,
}
}
}
impl<T, Ser> ArcOnceResource<T, Ser>
where
T: Send + Sync + 'static,
Ser: Encoder<T> + Decoder<T>,
<Ser as Encoder<T>>::Error: Debug,
<Ser as Decoder<T>>::Error: Debug,
<<Ser as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError: Debug,
<Ser as Encoder<T>>::Encoded: IntoEncodedString,
<Ser as Decoder<T>>::Encoded: FromEncodedStr,
{
#[track_caller]
pub fn new_with_options(
fut: impl Future<Output = T> + Send + 'static,
#[allow(unused)] // this is used with `feature = "ssr"`
blocking: bool,
) -> Self {
let shared_context = Owner::current_shared_context();
let id = shared_context
.as_ref()
.map(|sc| sc.next_id())
.unwrap_or_default();
let initial = initial_value::<T, Ser>(&id, shared_context.as_ref());
let is_ready = initial.is_some();
let value = Arc::new(RwLock::new(initial));
let wakers = Arc::new(RwLock::new(Vec::<Waker>::new()));
let suspenses = Arc::new(RwLock::new(Vec::<SuspenseContext>::new()));
let loading = Arc::new(AtomicBool::new(!is_ready));
let trigger = ArcTrigger::new();
let fut = ScopedFuture::new(fut);
if !is_ready && !IS_SUPPRESSING_RESOURCE_LOAD.load(Ordering::Relaxed) {
let value = Arc::clone(&value);
let wakers = Arc::clone(&wakers);
let loading = Arc::clone(&loading);
let trigger = trigger.clone();
reactive_graph::spawn(async move {
let loaded = fut.await;
*value.write().or_poisoned() = Some(loaded);
loading.store(false, Ordering::Relaxed);
for waker in mem::take(&mut *wakers.write().or_poisoned()) {
waker.wake();
}
trigger.notify();
});
}
let data = Self {
trigger,
value: value.clone(),
loading,
wakers,
suspenses,
ser: PhantomData,
#[cfg(debug_assertions)]
defined_at: Location::caller(),
};
#[cfg(feature = "ssr")]
if let Some(shared_context) = shared_context {
let value = Arc::clone(&value);
let ready_fut = data.ready();
if blocking {
shared_context.defer_stream(Box::pin(data.ready()));
}
if shared_context.get_is_hydrating() {
shared_context.write_async(
id,
Box::pin(async move {
ready_fut.await;
let value = value.read().or_poisoned();
let value = value.as_ref().unwrap();
Ser::encode(value).unwrap().into_encoded_string()
}),
);
}
}
data
}
}
impl<T, Ser> ArcOnceResource<T, Ser> {
/// Returns a `Future` that is ready when this resource has next finished loading.
pub fn ready(&self) -> AsyncDerivedReadyFuture {
AsyncDerivedReadyFuture::new(
self.to_any_source(),
&self.loading,
&self.wakers,
)
}
}
impl<T, Ser> DefinedAt for ArcOnceResource<T, Ser> {
fn defined_at(&self) -> Option<&'static Location<'static>> {
#[cfg(not(debug_assertions))]
{
None
}
#[cfg(debug_assertions)]
{
Some(self.defined_at)
}
}
}
impl<T, Ser> IsDisposed for ArcOnceResource<T, Ser> {
#[inline(always)]
fn is_disposed(&self) -> bool {
false
}
}
impl<T, Ser> ToAnySource for ArcOnceResource<T, Ser> {
fn to_any_source(&self) -> AnySource {
self.trigger.to_any_source()
}
}
impl<T, Ser> Track for ArcOnceResource<T, Ser> {
fn track(&self) {
self.trigger.track();
}
}
impl<T, Ser> ReadUntracked for ArcOnceResource<T, Ser>
where
T: 'static,
{
type Value = ReadGuard<Option<T>, Plain<Option<T>>>;
fn try_read_untracked(&self) -> Option<Self::Value> {
if let Some(suspense_context) = use_context::<SuspenseContext>() {
if self.value.read().or_poisoned().is_none() {
let handle = suspense_context.task_id();
let ready = SpecialNonReactiveFuture::new(self.ready());
reactive_graph::spawn(async move {
ready.await;
drop(handle);
});
self.suspenses.write().or_poisoned().push(suspense_context);
}
}
Plain::try_new(Arc::clone(&self.value)).map(ReadGuard::new)
}
}
impl<T, Ser> IntoFuture for ArcOnceResource<T, Ser>
where
T: Clone + 'static,
{
type Output = T;
type IntoFuture = OnceResourceFuture<T>;
fn into_future(self) -> Self::IntoFuture {
OnceResourceFuture {
source: self.to_any_source(),
value: Arc::clone(&self.value),
loading: Arc::clone(&self.loading),
wakers: Arc::clone(&self.wakers),
suspenses: Arc::clone(&self.suspenses),
}
}
}
/// 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 OnceResourceFuture<T> {
source: AnySource,
value: Arc<RwLock<Option<T>>>,
loading: Arc<AtomicBool>,
wakers: Arc<RwLock<Vec<Waker>>>,
suspenses: Arc<RwLock<Vec<SuspenseContext>>>,
}
impl<T> Future for OnceResourceFuture<T>
where
T: Clone + 'static,
{
type Output = T;
#[track_caller]
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
#[cfg(debug_assertions)]
let _guard = SpecialNonReactiveZone::enter();
let waker = cx.waker();
self.source.track();
if let Some(suspense_context) = use_context::<SuspenseContext>() {
self.suspenses.write().or_poisoned().push(suspense_context);
}
if self.loading.load(Ordering::Relaxed) {
self.wakers.write().or_poisoned().push(waker.clone());
Poll::Pending
} else {
Poll::Ready(
self.value.read().or_poisoned().as_ref().unwrap().clone(),
)
}
}
}
impl<T> ArcOnceResource<T, JsonSerdeCodec>
where
T: Send + Sync + 'static,
JsonSerdeCodec: Encoder<T> + Decoder<T>,
<JsonSerdeCodec as Encoder<T>>::Error: Debug,
<JsonSerdeCodec as Decoder<T>>::Error: Debug,
<<JsonSerdeCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
Debug,
<JsonSerdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
<JsonSerdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
{
#[track_caller]
pub fn new(fut: impl Future<Output = T> + Send + 'static) -> Self {
ArcOnceResource::new_with_options(fut, false)
}
#[track_caller]
pub fn new_blocking(fut: impl Future<Output = T> + Send + 'static) -> Self {
ArcOnceResource::new_with_options(fut, true)
}
}
impl<T> ArcOnceResource<T, FromToStringCodec>
where
T: Send + Sync + 'static,
FromToStringCodec: Encoder<T> + Decoder<T>,
<FromToStringCodec as Encoder<T>>::Error: Debug, <FromToStringCodec as Decoder<T>>::Error: Debug,
<<FromToStringCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError: Debug,
<FromToStringCodec as Encoder<T>>::Encoded: IntoEncodedString,
<FromToStringCodec as Decoder<T>>::Encoded: FromEncodedStr,
{
pub fn new_str(
fut: impl Future<Output = T> + Send + 'static
) -> Self
{
ArcOnceResource::new_with_options(fut, false)
}
pub fn new_str_blocking(
fut: impl Future<Output = T> + Send + 'static
) -> Self
{
ArcOnceResource::new_with_options(fut, true)
}
}
#[cfg(feature = "serde-wasm-bindgen")]
impl<T> ArcOnceResource<T, JsonSerdeWasmCodec>
where
T: Send + Sync + 'static,
JsonSerdeWasmCodec: Encoder<T> + Decoder<T>,
<JsonSerdeWasmCodec as Encoder<T>>::Error: Debug, <JsonSerdeWasmCodec as Decoder<T>>::Error: Debug,
<<JsonSerdeWasmCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError: Debug,
<JsonSerdeWasmCodec as Encoder<T>>::Encoded: IntoEncodedString,
<JsonSerdeWasmCodec as Decoder<T>>::Encoded: FromEncodedStr,
{
#[track_caller]
pub fn new_serde_wb(
fut: impl Future<Output = T> + Send + 'static
) -> Self
{
ArcOnceResource::new_with_options(fut, false)
}
#[track_caller]
pub fn new_serde_wb_blocking(
fut: impl Future<Output = T> + Send + 'static
) -> Self
{
ArcOnceResource::new_with_options(fut, true)
}
}
#[cfg(feature = "miniserde")]
impl<T> ArcOnceResource<T, MiniserdeCodec>
where
T: Send + Sync + 'static,
MiniserdeCodec: Encoder<T> + Decoder<T>,
<MiniserdeCodec as Encoder<T>>::Error: Debug,
<MiniserdeCodec as Decoder<T>>::Error: Debug,
<<MiniserdeCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
Debug,
<MiniserdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
<MiniserdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
{
#[track_caller]
pub fn new_miniserde(
fut: impl Future<Output = T> + Send + 'static,
) -> Self {
ArcOnceResource::new_with_options(fut, false)
}
#[track_caller]
pub fn new_miniserde_blocking(
fut: impl Future<Output = T> + Send + 'static,
) -> Self {
ArcOnceResource::new_with_options(fut, true)
}
}
#[cfg(feature = "serde-lite")]
impl<T> ArcOnceResource<T, SerdeLite<JsonSerdeCodec>>
where
T: Send + Sync + 'static,
SerdeLite<JsonSerdeCodec>: Encoder<T> + Decoder<T>,
<SerdeLite<JsonSerdeCodec> as Encoder<T>>::Error: Debug, <SerdeLite<JsonSerdeCodec> as Decoder<T>>::Error: Debug,
<<SerdeLite<JsonSerdeCodec> as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError: Debug,
<SerdeLite<JsonSerdeCodec> as Encoder<T>>::Encoded: IntoEncodedString,
<SerdeLite<JsonSerdeCodec> as Decoder<T>>::Encoded: FromEncodedStr,
{
#[track_caller]
pub fn new_serde_lite(
fut: impl Future<Output = T> + Send + 'static
) -> Self
{
ArcOnceResource::new_with_options(fut, false)
}
#[track_caller]
pub fn new_serde_lite_blocking(
fut: impl Future<Output = T> + Send + 'static
) -> Self
{
ArcOnceResource::new_with_options(fut, true)
}
}
#[cfg(feature = "rkyv")]
impl<T> ArcOnceResource<T, RkyvCodec>
where
T: Send + Sync + 'static,
RkyvCodec: Encoder<T> + Decoder<T>,
<RkyvCodec as Encoder<T>>::Error: Debug,
<RkyvCodec as Decoder<T>>::Error: Debug,
<<RkyvCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
Debug,
<RkyvCodec as Encoder<T>>::Encoded: IntoEncodedString,
<RkyvCodec as Decoder<T>>::Encoded: FromEncodedStr,
{
#[track_caller]
pub fn new_rkyv(fut: impl Future<Output = T> + Send + 'static) -> Self {
ArcOnceResource::new_with_options(fut, false)
}
#[track_caller]
pub fn new_rkyv_blocking(
fut: impl Future<Output = T> + Send + 'static,
) -> Self {
ArcOnceResource::new_with_options(fut, true)
}
}
#[derive(Debug)]
pub struct OnceResource<T, Ser = JsonSerdeCodec> {
inner: ArenaItem<ArcOnceResource<T, Ser>>,
#[cfg(debug_assertions)]
defined_at: &'static Location<'static>,
}
impl<T, Ser> Clone for OnceResource<T, Ser> {
fn clone(&self) -> Self {
*self
}
}
impl<T, Ser> Copy for OnceResource<T, Ser> {}
impl<T, Ser> OnceResource<T, Ser>
where
T: Send + Sync + 'static,
Ser: Encoder<T> + Decoder<T>,
<Ser as Encoder<T>>::Error: Debug,
<Ser as Decoder<T>>::Error: Debug,
<<Ser as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError: Debug,
<Ser as Encoder<T>>::Encoded: IntoEncodedString,
<Ser as Decoder<T>>::Encoded: FromEncodedStr,
{
#[track_caller]
pub fn new_with_options(
fut: impl Future<Output = T> + Send + 'static,
blocking: bool,
) -> Self {
#[cfg(debug_assertions)]
let defined_at = Location::caller();
Self {
inner: ArenaItem::new(ArcOnceResource::new_with_options(
fut, blocking,
)),
#[cfg(debug_assertions)]
defined_at,
}
}
}
impl<T, Ser> OnceResource<T, Ser>
where
T: Send + Sync + 'static,
Ser: 'static,
{
/// Returns a `Future` that is ready when this resource has next finished loading.
pub fn ready(&self) -> AsyncDerivedReadyFuture {
self.inner
.try_with_value(|inner| inner.ready())
.unwrap_or_else(unwrap_signal!(self))
}
}
impl<T, Ser> DefinedAt for OnceResource<T, Ser> {
fn defined_at(&self) -> Option<&'static Location<'static>> {
#[cfg(not(debug_assertions))]
{
None
}
#[cfg(debug_assertions)]
{
Some(self.defined_at)
}
}
}
impl<T, Ser> IsDisposed for OnceResource<T, Ser> {
#[inline(always)]
fn is_disposed(&self) -> bool {
false
}
}
impl<T, Ser> ToAnySource for OnceResource<T, Ser>
where
T: Send + Sync + 'static,
Ser: 'static,
{
fn to_any_source(&self) -> AnySource {
self.inner
.try_with_value(|inner| inner.to_any_source())
.unwrap_or_else(unwrap_signal!(self))
}
}
impl<T, Ser> Track for OnceResource<T, Ser>
where
T: Send + Sync + 'static,
Ser: 'static,
{
fn track(&self) {
if let Some(inner) = self.inner.try_get_value() {
inner.track();
}
}
}
impl<T, Ser> ReadUntracked for OnceResource<T, Ser>
where
T: Send + Sync + 'static,
Ser: 'static,
{
type Value = ReadGuard<Option<T>, Plain<Option<T>>>;
fn try_read_untracked(&self) -> Option<Self::Value> {
self.inner
.try_with_value(|inner| inner.try_read_untracked())
.flatten()
}
}
impl<T, Ser> IntoFuture for OnceResource<T, Ser>
where
T: Clone + Send + Sync + 'static,
Ser: 'static,
{
type Output = T;
type IntoFuture = OnceResourceFuture<T>;
fn into_future(self) -> Self::IntoFuture {
self.inner
.try_get_value()
.unwrap_or_else(unwrap_signal!(self))
.into_future()
}
}
impl<T> OnceResource<T, JsonSerdeCodec>
where
T: Send + Sync + 'static,
JsonSerdeCodec: Encoder<T> + Decoder<T>,
<JsonSerdeCodec as Encoder<T>>::Error: Debug,
<JsonSerdeCodec as Decoder<T>>::Error: Debug,
<<JsonSerdeCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
Debug,
<JsonSerdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
<JsonSerdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
{
#[track_caller]
pub fn new(fut: impl Future<Output = T> + Send + 'static) -> Self {
OnceResource::new_with_options(fut, false)
}
#[track_caller]
pub fn new_blocking(fut: impl Future<Output = T> + Send + 'static) -> Self {
OnceResource::new_with_options(fut, true)
}
}
impl<T> OnceResource<T, FromToStringCodec>
where
T: Send + Sync + 'static,
FromToStringCodec: Encoder<T> + Decoder<T>,
<FromToStringCodec as Encoder<T>>::Error: Debug, <FromToStringCodec as Decoder<T>>::Error: Debug,
<<FromToStringCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError: Debug,
<FromToStringCodec as Encoder<T>>::Encoded: IntoEncodedString,
<FromToStringCodec as Decoder<T>>::Encoded: FromEncodedStr,
{
pub fn new_str(
fut: impl Future<Output = T> + Send + 'static
) -> Self
{
OnceResource::new_with_options(fut, false)
}
pub fn new_str_blocking(
fut: impl Future<Output = T> + Send + 'static
) -> Self
{
OnceResource::new_with_options(fut, true)
}
}
#[cfg(feature = "serde-wasm-bindgen")]
impl<T> OnceResource<T, JsonSerdeWasmCodec>
where
T: Send + Sync + 'static,
JsonSerdeWasmCodec: Encoder<T> + Decoder<T>,
<JsonSerdeWasmCodec as Encoder<T>>::Error: Debug, <JsonSerdeWasmCodec as Decoder<T>>::Error: Debug,
<<JsonSerdeWasmCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError: Debug,
<JsonSerdeWasmCodec as Encoder<T>>::Encoded: IntoEncodedString,
<JsonSerdeWasmCodec as Decoder<T>>::Encoded: FromEncodedStr,
{
#[track_caller]
pub fn new_serde_wb(
fut: impl Future<Output = T> + Send + 'static
) -> Self
{
OnceResource::new_with_options(fut, false)
}
#[track_caller]
pub fn new_serde_wb_blocking(
fut: impl Future<Output = T> + Send + 'static
) -> Self
{
OnceResource::new_with_options(fut, true)
}
}
#[cfg(feature = "miniserde")]
impl<T> OnceResource<T, MiniserdeCodec>
where
T: Send + Sync + 'static,
MiniserdeCodec: Encoder<T> + Decoder<T>,
<MiniserdeCodec as Encoder<T>>::Error: Debug,
<MiniserdeCodec as Decoder<T>>::Error: Debug,
<<MiniserdeCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
Debug,
<MiniserdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
<MiniserdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
{
#[track_caller]
pub fn new_miniserde(
fut: impl Future<Output = T> + Send + 'static,
) -> Self {
OnceResource::new_with_options(fut, false)
}
#[track_caller]
pub fn new_miniserde_blocking(
fut: impl Future<Output = T> + Send + 'static,
) -> Self {
OnceResource::new_with_options(fut, true)
}
}
#[cfg(feature = "serde-lite")]
impl<T> OnceResource<T, SerdeLite<JsonSerdeCodec>>
where
T: Send + Sync + 'static,
SerdeLite<JsonSerdeCodec>: Encoder<T> + Decoder<T>,
<SerdeLite<JsonSerdeCodec> as Encoder<T>>::Error: Debug, <SerdeLite<JsonSerdeCodec> as Decoder<T>>::Error: Debug,
<<SerdeLite<JsonSerdeCodec> as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError: Debug,
<SerdeLite<JsonSerdeCodec> as Encoder<T>>::Encoded: IntoEncodedString,
<SerdeLite<JsonSerdeCodec> as Decoder<T>>::Encoded: FromEncodedStr,
{
#[track_caller]
pub fn new_serde_lite(
fut: impl Future<Output = T> + Send + 'static
) -> Self
{
OnceResource::new_with_options(fut, false)
}
#[track_caller]
pub fn new_serde_lite_blocking(
fut: impl Future<Output = T> + Send + 'static
) -> Self
{
OnceResource::new_with_options(fut, true)
}
}
#[cfg(feature = "rkyv")]
impl<T> OnceResource<T, RkyvCodec>
where
T: Send + Sync + 'static,
RkyvCodec: Encoder<T> + Decoder<T>,
<RkyvCodec as Encoder<T>>::Error: Debug,
<RkyvCodec as Decoder<T>>::Error: Debug,
<<RkyvCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
Debug,
<RkyvCodec as Encoder<T>>::Encoded: IntoEncodedString,
<RkyvCodec as Decoder<T>>::Encoded: FromEncodedStr,
{
#[track_caller]
pub fn new_rkyv(fut: impl Future<Output = T> + Send + 'static) -> Self {
OnceResource::new_with_options(fut, false)
}
#[track_caller]
pub fn new_rkyv_blocking(
fut: impl Future<Output = T> + Send + 'static,
) -> Self {
OnceResource::new_with_options(fut, true)
}
}

View File

@@ -13,7 +13,7 @@ use codee::{
};
use core::{fmt::Debug, marker::PhantomData};
use futures::Future;
use hydration_context::SerializedDataId;
use hydration_context::{SerializedDataId, SharedContext};
use reactive_graph::{
computed::{
ArcAsyncDerived, ArcMemo, AsyncDerived, AsyncDerivedFuture,
@@ -28,10 +28,14 @@ use std::{
future::{pending, IntoFuture},
ops::Deref,
panic::Location,
sync::atomic::{AtomicBool, Ordering},
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
static IS_SUPPRESSING_RESOURCE_LOAD: AtomicBool = AtomicBool::new(false);
pub(crate) static IS_SUPPRESSING_RESOURCE_LOAD: AtomicBool =
AtomicBool::new(false);
pub struct SuppressResourceLoad;
@@ -175,7 +179,7 @@ where
.map(|sc| sc.next_id())
.unwrap_or_default();
let initial = Self::initial_value(&id);
let initial = initial_value::<T, Ser>(&id, shared_context.as_ref());
let is_ready = initial.is_some();
let refetch = ArcRwSignal::new(0);
@@ -253,43 +257,53 @@ where
pub fn refetch(&self) {
*self.refetch.write() += 1;
}
}
#[inline(always)]
#[allow(unused)]
fn initial_value(id: &SerializedDataId) -> Option<T> {
#[cfg(feature = "hydration")]
{
use std::borrow::Borrow;
#[inline(always)]
#[allow(unused)]
pub(crate) fn initial_value<T, Ser>(
id: &SerializedDataId,
shared_context: Option<&Arc<dyn SharedContext + Send + Sync>>,
) -> Option<T>
where
Ser: Encoder<T> + Decoder<T>,
<Ser as Encoder<T>>::Error: Debug,
<Ser as Decoder<T>>::Error: Debug,
<<Ser as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError: Debug,
<Ser as Encoder<T>>::Encoded: IntoEncodedString,
<Ser as Decoder<T>>::Encoded: FromEncodedStr,
{
#[cfg(feature = "hydration")]
{
use std::borrow::Borrow;
let shared_context = Owner::current_shared_context();
if let Some(shared_context) = shared_context {
let value = shared_context.read_data(id);
if let Some(value) = value {
let encoded =
match <Ser as Decoder<T>>::Encoded::from_encoded_str(
&value,
) {
Ok(value) => value,
Err(e) => {
#[cfg(feature = "tracing")]
tracing::error!("couldn't deserialize: {e:?}");
return None;
}
};
let encoded = encoded.borrow();
match Ser::decode(encoded) {
Ok(value) => return Some(value),
#[allow(unused)]
let shared_context = Owner::current_shared_context();
if let Some(shared_context) = shared_context {
let value = shared_context.read_data(id);
if let Some(value) = value {
let encoded =
match <Ser as Decoder<T>>::Encoded::from_encoded_str(&value)
{
Ok(value) => value,
Err(e) => {
#[cfg(feature = "tracing")]
tracing::error!("couldn't deserialize: {e:?}");
return None;
}
};
let encoded = encoded.borrow();
match Ser::decode(encoded) {
Ok(value) => return Some(value),
#[allow(unused)]
Err(e) => {
#[cfg(feature = "tracing")]
tracing::error!("couldn't deserialize: {e:?}");
}
}
}
}
None
}
None
}
impl<T, E, Ser> ArcResource<Result<T, E>, Ser>

View File

@@ -534,11 +534,11 @@ impl<T: 'static> ArcAsyncDerived<T> {
/// Returns a `Future` that is ready when this resource has next finished loading.
pub fn ready(&self) -> AsyncDerivedReadyFuture {
AsyncDerivedReadyFuture {
source: self.to_any_source(),
loading: Arc::clone(&self.loading),
wakers: Arc::clone(&self.wakers),
}
AsyncDerivedReadyFuture::new(
self.to_any_source(),
&self.loading,
&self.wakers,
)
}
}

View File

@@ -34,6 +34,21 @@ pub struct AsyncDerivedReadyFuture {
pub(crate) wakers: Arc<RwLock<Vec<Waker>>>,
}
impl AsyncDerivedReadyFuture {
/// Creates a new [`Future`] that will be ready when the given resource is ready.
pub fn new(
source: AnySource,
loading: &Arc<AtomicBool>,
wakers: &Arc<RwLock<Vec<Waker>>>,
) -> Self {
AsyncDerivedReadyFuture {
source,
loading: Arc::clone(loading),
wakers: Arc::clone(wakers),
}
}
}
impl Future for AsyncDerivedReadyFuture {
type Output = ();

View File

@@ -53,7 +53,8 @@ impl Drop for SpecialNonReactiveZoneGuard {
}
pin_project! {
pub(crate) struct SpecialNonReactiveFuture<Fut> {
#[doc(hidden)]
pub struct SpecialNonReactiveFuture<Fut> {
#[pin]
inner: Fut
}

View File

@@ -125,7 +125,7 @@ pub fn log_warning(text: Arguments) {
/// Calls [`Executor::spawn`], but ensures that the task also runs in the current arena, if
/// multithreaded arena sandboxing is enabled.
pub(crate) fn spawn(task: impl Future<Output = ()> + Send + 'static) {
pub fn spawn(task: impl Future<Output = ()> + Send + 'static) {
#[cfg(feature = "sandboxed-arenas")]
let task = owner::Sandboxed::new(task);