mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-27 16:54:41 -05:00
Compare commits
5 Commits
4385
...
once-resou
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca903c1053 | ||
|
|
eef88ac180 | ||
|
|
c594c0e523 | ||
|
|
b8f553c3a1 | ||
|
|
55873e9a10 |
@@ -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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
708
leptos_server/src/once_resource.rs
Normal file
708
leptos_server/src/once_resource.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = ();
|
||||
|
||||
|
||||
@@ -53,7 +53,8 @@ impl Drop for SpecialNonReactiveZoneGuard {
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
pub(crate) struct SpecialNonReactiveFuture<Fut> {
|
||||
#[doc(hidden)]
|
||||
pub struct SpecialNonReactiveFuture<Fut> {
|
||||
#[pin]
|
||||
inner: Fut
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user