mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
Merge branch 'main' into more-stores
This commit is contained in:
commit
dc9fbb0585
50 changed files with 578 additions and 214 deletions
3
.github/workflows/get-leptos-changed.yml
vendored
3
.github/workflows/get-leptos-changed.yml
vendored
|
@ -43,11 +43,14 @@ jobs:
|
|||
oco/**
|
||||
or_poisoned/**
|
||||
reactive_graph/**
|
||||
reactive_stores/**
|
||||
reactive_stores_macro/**
|
||||
router/**
|
||||
router_macro/**
|
||||
server_fn/**
|
||||
server_fn/server_fn_macro_default/**
|
||||
server_fn_macro/**
|
||||
tachys/**
|
||||
|
||||
- name: List source files that changed
|
||||
run: echo '${{ steps.changed-source.outputs.all_changed_files }}'
|
||||
|
|
42
Cargo.toml
42
Cargo.toml
|
@ -40,36 +40,36 @@ members = [
|
|||
exclude = ["benchmarks", "examples", "projects"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.7.0-beta4"
|
||||
version = "0.7.0-beta5"
|
||||
edition = "2021"
|
||||
rust-version = "1.76"
|
||||
|
||||
[workspace.dependencies]
|
||||
throw_error = { path = "./any_error/", version = "0.2.0-beta4" }
|
||||
throw_error = { path = "./any_error/", version = "0.2.0-beta5" }
|
||||
any_spawner = { path = "./any_spawner/", version = "0.1.0" }
|
||||
const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1.0" }
|
||||
either_of = { path = "./either_of/", version = "0.1.0" }
|
||||
hydration_context = { path = "./hydration_context", version = "0.2.0-beta4" }
|
||||
leptos = { path = "./leptos", version = "0.7.0-beta4" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.7.0-beta4" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.7.0-beta4" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.0-beta4" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.0-beta4" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.7.0-beta4" }
|
||||
leptos_router = { path = "./router", version = "0.7.0-beta4" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.7.0-beta4" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.7.0-beta4" }
|
||||
leptos_meta = { path = "./meta", version = "0.7.0-beta4" }
|
||||
next_tuple = { path = "./next_tuple", version = "0.1.0-beta4" }
|
||||
hydration_context = { path = "./hydration_context", version = "0.2.0-beta5" }
|
||||
leptos = { path = "./leptos", version = "0.7.0-beta5" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.7.0-beta5" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.7.0-beta5" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.0-beta5" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.0-beta5" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.7.0-beta5" }
|
||||
leptos_router = { path = "./router", version = "0.7.0-beta5" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.7.0-beta5" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.7.0-beta5" }
|
||||
leptos_meta = { path = "./meta", version = "0.7.0-beta5" }
|
||||
next_tuple = { path = "./next_tuple", version = "0.1.0-beta5" }
|
||||
oco_ref = { path = "./oco", version = "0.2.0" }
|
||||
or_poisoned = { path = "./or_poisoned", version = "0.1.0" }
|
||||
reactive_graph = { path = "./reactive_graph", version = "0.1.0-beta4" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.1.0-beta4" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.0-beta4" }
|
||||
server_fn = { path = "./server_fn", version = "0.7.0-beta4" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.7.0-beta4" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.0-beta4" }
|
||||
tachys = { path = "./tachys", version = "0.1.0-beta4" }
|
||||
reactive_graph = { path = "./reactive_graph", version = "0.1.0-beta5" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.1.0-beta5" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.0-beta5" }
|
||||
server_fn = { path = "./server_fn", version = "0.7.0-beta5" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.7.0-beta5" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.0-beta5" }
|
||||
tachys = { path = "./tachys", version = "0.1.0-beta5" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "throw_error"
|
||||
version = "0.2.0-beta4"
|
||||
version = "0.2.0-beta5"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
|
|
@ -32,7 +32,6 @@ pub fn App() -> impl IntoView {
|
|||
// Provides context that manages stylesheets, titles, meta tags, etc.
|
||||
provide_meta_context();
|
||||
let fallback = || view! { "Page not found." }.into_view();
|
||||
let ssr = SsrMode::Async;
|
||||
|
||||
view! {
|
||||
<Stylesheet id="leptos" href="/pkg/axum_js_ssr.css"/>
|
||||
|
@ -79,19 +78,19 @@ pub fn App() -> impl IntoView {
|
|||
<h1>"Leptos JavaScript Integration Demo with SSR in Axum"</h1>
|
||||
<FlatRoutes fallback>
|
||||
<Route path=path!("") view=HomePage/>
|
||||
<Route path=path!("naive") view=Naive ssr/>
|
||||
<Route path=path!("naive-alt") view=|| view! { <NaiveEvent/> } ssr/>
|
||||
<Route path=path!("naive-hook") view=|| view! { <NaiveEvent hook=true/> } ssr/>
|
||||
<Route path=path!("naive") view=Naive ssr=SsrMode::Async/>
|
||||
<Route path=path!("naive-alt") view=|| view! { <NaiveEvent/> } ssr=SsrMode::Async/>
|
||||
<Route path=path!("naive-hook") view=|| view! { <NaiveEvent hook=true/> } ssr=SsrMode::Async/>
|
||||
<Route path=path!("naive-fallback") view=|| view! {
|
||||
<NaiveEvent hook=true fallback=true/>
|
||||
} ssr/>
|
||||
<Route path=path!("signal-effect-script") view=CodeDemoSignalEffect ssr/>
|
||||
<Route path=path!("custom-event") view=CustomEvent ssr/>
|
||||
<Route path=path!("wasm-bindgen-naive") view=WasmBindgenNaive ssr/>
|
||||
<Route path=path!("wasm-bindgen-event") view=WasmBindgenJSHookReadyEvent ssr/>
|
||||
<Route path=path!("wasm-bindgen-effect") view=WasmBindgenEffect ssr/>
|
||||
<Route path=path!("wasm-bindgen-direct") view=WasmBindgenDirect ssr/>
|
||||
<Route path=path!("wasm-bindgen-direct-fixed") view=WasmBindgenDirectFixed ssr/>
|
||||
} ssr=SsrMode::Async/>
|
||||
<Route path=path!("signal-effect-script") view=CodeDemoSignalEffect ssr=SsrMode::Async/>
|
||||
<Route path=path!("custom-event") view=CustomEvent ssr=SsrMode::Async/>
|
||||
<Route path=path!("wasm-bindgen-naive") view=WasmBindgenNaive ssr=SsrMode::Async/>
|
||||
<Route path=path!("wasm-bindgen-event") view=WasmBindgenJSHookReadyEvent ssr=SsrMode::Async/>
|
||||
<Route path=path!("wasm-bindgen-effect") view=WasmBindgenEffect ssr=SsrMode::Async/>
|
||||
<Route path=path!("wasm-bindgen-direct") view=WasmBindgenDirect ssr=SsrMode::Async/>
|
||||
<Route path=path!("wasm-bindgen-direct-fixed") view=WasmBindgenDirectFixed ssr=SsrMode::Async/>
|
||||
</FlatRoutes>
|
||||
</article>
|
||||
</main>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "hydration_context"
|
||||
version = "0.2.0-beta4"
|
||||
version = "0.2.0-beta5"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
|
|
@ -13,7 +13,7 @@ use reactive_graph::{
|
|||
effect::RenderEffect,
|
||||
owner::{provide_context, use_context, Owner},
|
||||
signal::ArcRwSignal,
|
||||
traits::{Get, Read, Track, With},
|
||||
traits::{Dispose, Get, Read, Track, With},
|
||||
};
|
||||
use slotmap::{DefaultKey, SlotMap};
|
||||
use tachys::{
|
||||
|
@ -286,7 +286,7 @@ where
|
|||
self.children.dry_resolve();
|
||||
|
||||
// check the set of tasks to see if it is empty, now or later
|
||||
let eff = reactive_graph::effect::RenderEffect::new_isomorphic({
|
||||
let eff = reactive_graph::effect::Effect::new_isomorphic({
|
||||
move |_| {
|
||||
tasks.track();
|
||||
if tasks.read().is_empty() {
|
||||
|
@ -338,7 +338,7 @@ where
|
|||
}
|
||||
children = children => {
|
||||
// clean up the (now useless) effect
|
||||
drop(eff);
|
||||
eff.dispose();
|
||||
|
||||
Some(OwnedView::new_with_owner(children, owner))
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "leptos_macro"
|
||||
version = "0.7.0-beta4"
|
||||
version = "0.7.0-beta5"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
|
|
|
@ -204,11 +204,11 @@ impl ToTokens for Model {
|
|||
)]
|
||||
},
|
||||
quote! {
|
||||
let span = ::leptos::tracing::Span::current();
|
||||
let __span = ::leptos::tracing::Span::current();
|
||||
},
|
||||
quote! {
|
||||
#[cfg(debug_assertions)]
|
||||
let _guard = span.entered();
|
||||
let _guard = __span.entered();
|
||||
},
|
||||
if no_props || !cfg!(feature = "trace-component-props") {
|
||||
quote!()
|
||||
|
|
|
@ -9,7 +9,7 @@ use reactive_graph::{
|
|||
},
|
||||
owner::use_context,
|
||||
signal::guards::{AsyncPlain, ReadGuard},
|
||||
traits::{DefinedAt, ReadUntracked},
|
||||
traits::{DefinedAt, IsDisposed, ReadUntracked},
|
||||
};
|
||||
use send_wrapper::SendWrapper;
|
||||
use std::{
|
||||
|
@ -121,6 +121,13 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> IsDisposed for ArcLocalResource<T> {
|
||||
#[inline(always)]
|
||||
fn is_disposed(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> ToAnySource for ArcLocalResource<T> {
|
||||
fn to_any_source(&self) -> AnySource {
|
||||
self.data.to_any_source()
|
||||
|
@ -292,6 +299,12 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> IsDisposed for LocalResource<T> {
|
||||
fn is_disposed(&self) -> bool {
|
||||
self.data.is_disposed()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> ToAnySource for LocalResource<T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
|
|
|
@ -81,13 +81,15 @@ where
|
|||
let is_ready = initial.is_some();
|
||||
|
||||
let refetch = ArcRwSignal::new(0);
|
||||
let source = ArcMemo::new(move |_| source());
|
||||
let source = ArcMemo::new({
|
||||
let refetch = refetch.clone();
|
||||
move |_| (refetch.get(), source())
|
||||
});
|
||||
let fun = {
|
||||
let source = source.clone();
|
||||
let refetch = refetch.clone();
|
||||
move || {
|
||||
refetch.track();
|
||||
fetcher(source.get())
|
||||
let (_, source) = source.get();
|
||||
fetcher(source)
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "leptos_meta"
|
||||
version = "0.7.0-beta4"
|
||||
version = "0.7.0-beta5"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "next_tuple"
|
||||
version = "0.1.0-beta4"
|
||||
version = "0.1.0-beta5"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "reactive_graph"
|
||||
version = "0.1.0-beta4"
|
||||
version = "0.1.0-beta5"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
|
|
@ -163,10 +163,10 @@ where
|
|||
#[deprecated = "This function is being removed to conform to Rust idioms. \
|
||||
Please use `Selector::new()` instead."]
|
||||
pub fn create_selector<T>(
|
||||
source: impl Fn() -> T + Clone + 'static,
|
||||
source: impl Fn() -> T + Clone + Send + Sync + 'static,
|
||||
) -> Selector<T>
|
||||
where
|
||||
T: PartialEq + Eq + Clone + std::hash::Hash + 'static,
|
||||
T: PartialEq + Eq + Send + Sync + Clone + std::hash::Hash + 'static,
|
||||
{
|
||||
Selector::new(source)
|
||||
}
|
||||
|
@ -178,11 +178,11 @@ where
|
|||
#[deprecated = "This function is being removed to conform to Rust idioms. \
|
||||
Please use `Selector::new_with_fn()` instead."]
|
||||
pub fn create_selector_with_fn<T>(
|
||||
source: impl Fn() -> T + Clone + 'static,
|
||||
source: impl Fn() -> T + Clone + Send + Sync + 'static,
|
||||
f: impl Fn(&T, &T) -> bool + Send + Sync + Clone + 'static,
|
||||
) -> Selector<T>
|
||||
where
|
||||
T: PartialEq + Eq + Clone + std::hash::Hash + 'static,
|
||||
T: PartialEq + Eq + Send + Sync + Clone + std::hash::Hash + 'static,
|
||||
{
|
||||
Selector::new_with_fn(source, f)
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use crate::{
|
|||
guards::{Mapped, Plain, ReadGuard},
|
||||
ArcReadSignal, ArcRwSignal,
|
||||
},
|
||||
traits::{DefinedAt, Get, ReadUntracked},
|
||||
traits::{DefinedAt, Get, IsDisposed, ReadUntracked},
|
||||
};
|
||||
use core::fmt::Debug;
|
||||
use or_poisoned::OrPoisoned;
|
||||
|
@ -260,6 +260,16 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: 'static, S> IsDisposed for ArcMemo<T, S>
|
||||
where
|
||||
S: Storage<T>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn is_disposed(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static, S> ToAnySource for ArcMemo<T, S>
|
||||
where
|
||||
S: Storage<T>,
|
||||
|
|
|
@ -18,7 +18,8 @@ use crate::{
|
|||
ArcTrigger,
|
||||
},
|
||||
traits::{
|
||||
DefinedAt, ReadUntracked, Track, Trigger, UntrackableGuard, Writeable,
|
||||
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
|
||||
Writeable,
|
||||
},
|
||||
transition::AsyncTransition,
|
||||
};
|
||||
|
@ -580,8 +581,8 @@ impl<T: 'static> ReadUntracked for ArcAsyncDerived<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> Trigger for ArcAsyncDerived<T> {
|
||||
fn trigger(&self) {
|
||||
impl<T: 'static> Notify for ArcAsyncDerived<T> {
|
||||
fn notify(&self) {
|
||||
Self::notify_subs(&self.wakers, &self.inner, &self.loading, None);
|
||||
}
|
||||
}
|
||||
|
@ -600,6 +601,13 @@ impl<T: 'static> Writeable for ArcAsyncDerived<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> IsDisposed for ArcAsyncDerived<T> {
|
||||
#[inline(always)]
|
||||
fn is_disposed(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> ToAnySource for ArcAsyncDerived<T> {
|
||||
fn to_any_source(&self) -> AnySource {
|
||||
AnySource(
|
||||
|
|
|
@ -7,7 +7,8 @@ use crate::{
|
|||
owner::{FromLocal, LocalStorage, Storage, StoredValue, SyncStorage},
|
||||
signal::guards::{AsyncPlain, ReadGuard, WriteGuard},
|
||||
traits::{
|
||||
DefinedAt, Dispose, ReadUntracked, Trigger, UntrackableGuard, Writeable,
|
||||
DefinedAt, Dispose, IsDisposed, Notify, ReadUntracked,
|
||||
UntrackableGuard, Writeable,
|
||||
},
|
||||
unwrap_signal,
|
||||
};
|
||||
|
@ -291,13 +292,13 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<T, S> Trigger for AsyncDerived<T, S>
|
||||
impl<T, S> Notify for AsyncDerived<T, S>
|
||||
where
|
||||
T: 'static,
|
||||
S: Storage<ArcAsyncDerived<T>>,
|
||||
{
|
||||
fn trigger(&self) {
|
||||
self.inner.try_with_value(|inner| inner.trigger());
|
||||
fn notify(&self) {
|
||||
self.inner.try_with_value(|inner| inner.notify());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -322,6 +323,16 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<T, S> IsDisposed for AsyncDerived<T, S>
|
||||
where
|
||||
T: 'static,
|
||||
S: Storage<ArcAsyncDerived<T>>,
|
||||
{
|
||||
fn is_disposed(&self) -> bool {
|
||||
self.inner.is_disposed()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> ToAnySource for AsyncDerived<T, S>
|
||||
where
|
||||
T: 'static,
|
||||
|
|
|
@ -30,7 +30,7 @@ use std::{
|
|||
/// let a = RwSignal::new(0);
|
||||
/// let is_selected = Selector::new(move || a.get());
|
||||
/// let total_notifications = StoredValue::new(0);
|
||||
/// Effect::new({
|
||||
/// Effect::new_isomorphic({
|
||||
/// let is_selected = is_selected.clone();
|
||||
/// move |_| {
|
||||
/// if is_selected.selected(5) {
|
||||
|
@ -55,7 +55,7 @@ use std::{
|
|||
///
|
||||
/// # any_spawner::Executor::tick().await;
|
||||
/// assert_eq!(is_selected.selected(5), false);
|
||||
/// # });
|
||||
/// # }).await;
|
||||
/// # });
|
||||
/// ```
|
||||
#[derive(Clone)]
|
||||
|
@ -74,17 +74,17 @@ where
|
|||
|
||||
impl<T> Selector<T>
|
||||
where
|
||||
T: PartialEq + Eq + Clone + Hash + 'static,
|
||||
T: PartialEq + Send + Sync + Eq + Clone + Hash + 'static,
|
||||
{
|
||||
/// Creates a new selector that compares values using [`PartialEq`].
|
||||
pub fn new(source: impl Fn() -> T + Clone + 'static) -> Self {
|
||||
pub fn new(source: impl Fn() -> T + Send + Sync + Clone + 'static) -> Self {
|
||||
Self::new_with_fn(source, PartialEq::eq)
|
||||
}
|
||||
|
||||
/// Creates a new selector that compares values by returning `true` from a comparator function
|
||||
/// if the values are the same.
|
||||
pub fn new_with_fn(
|
||||
source: impl Fn() -> T + Clone + 'static,
|
||||
source: impl Fn() -> T + Clone + Send + Sync + 'static,
|
||||
f: impl Fn(&T, &T) -> bool + Send + Sync + Clone + 'static,
|
||||
) -> Self {
|
||||
let subs: Arc<RwLock<FxHashMap<T, ArcRwSignal<bool>>>> =
|
||||
|
@ -92,7 +92,7 @@ where
|
|||
let v: Arc<RwLock<Option<T>>> = Default::default();
|
||||
let f = Arc::new(f) as Arc<dyn Fn(&T, &T) -> bool + Send + Sync>;
|
||||
|
||||
let effect = Arc::new(RenderEffect::new({
|
||||
let effect = Arc::new(RenderEffect::new_isomorphic({
|
||||
let subs = Arc::clone(&subs);
|
||||
let f = Arc::clone(&f);
|
||||
let v = Arc::clone(&v);
|
||||
|
|
|
@ -43,6 +43,7 @@ use std::{
|
|||
/// # use reactive_graph::owner::StoredValue;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # tokio::task::LocalSet::new().run_until(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// let a = RwSignal::new(0);
|
||||
/// let b = RwSignal::new(0);
|
||||
///
|
||||
|
@ -52,7 +53,9 @@ use std::{
|
|||
/// println!("Value: {}", a.get());
|
||||
/// });
|
||||
///
|
||||
/// # assert_eq!(a.get(), 0);
|
||||
/// a.set(1);
|
||||
/// # assert_eq!(a.get(), 1);
|
||||
/// // ✅ because it's subscribed to `a`, the effect reruns and prints "Value: 1"
|
||||
///
|
||||
/// // ❌ don't use effects to synchronize state within the reactive system
|
||||
|
@ -61,7 +64,7 @@ use std::{
|
|||
/// // and easily lead to problems like infinite loops
|
||||
/// b.set(a.get() + 1);
|
||||
/// });
|
||||
/// # });
|
||||
/// # }).await;
|
||||
/// # });
|
||||
/// ```
|
||||
/// ## Web-Specific Notes
|
||||
|
@ -182,6 +185,7 @@ impl Effect<LocalStorage> {
|
|||
/// # use reactive_graph::signal::signal;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # tokio::task::LocalSet::new().run_until(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// #
|
||||
/// let (num, set_num) = signal(0);
|
||||
///
|
||||
|
@ -192,13 +196,16 @@ impl Effect<LocalStorage> {
|
|||
/// },
|
||||
/// false,
|
||||
/// );
|
||||
/// # assert_eq!(num.get(), 0);
|
||||
///
|
||||
/// set_num.set(1); // > "Number: 1; Prev: Some(0)"
|
||||
/// # assert_eq!(num.get(), 1);
|
||||
///
|
||||
/// effect.stop(); // stop watching
|
||||
///
|
||||
/// set_num.set(2); // (nothing happens)
|
||||
/// # });
|
||||
/// # assert_eq!(num.get(), 2);
|
||||
/// # }).await;
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
|
@ -210,6 +217,7 @@ impl Effect<LocalStorage> {
|
|||
/// # use reactive_graph::signal::signal;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # tokio::task::LocalSet::new().run_until(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// #
|
||||
/// let (num, set_num) = signal(0);
|
||||
/// let (cb_num, set_cb_num) = signal(0);
|
||||
|
@ -222,12 +230,17 @@ impl Effect<LocalStorage> {
|
|||
/// false,
|
||||
/// );
|
||||
///
|
||||
/// # assert_eq!(num.get(), 0);
|
||||
/// set_num.set(1); // > "Number: 1; Cb: 0"
|
||||
/// # assert_eq!(num.get(), 1);
|
||||
///
|
||||
/// # assert_eq!(cb_num.get(), 0);
|
||||
/// set_cb_num.set(1); // (nothing happens)
|
||||
/// # assert_eq!(cb_num.get(), 1);
|
||||
///
|
||||
/// set_num.set(2); // > "Number: 2; Cb: 1"
|
||||
/// # });
|
||||
/// # assert_eq!(num.get(), 2);
|
||||
/// # }).await;
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
|
@ -243,6 +256,7 @@ impl Effect<LocalStorage> {
|
|||
/// # use reactive_graph::signal::signal;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # tokio::task::LocalSet::new().run_until(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// #
|
||||
/// let (num, set_num) = signal(0);
|
||||
///
|
||||
|
@ -254,8 +268,10 @@ impl Effect<LocalStorage> {
|
|||
/// true,
|
||||
/// ); // > "Number: 0; Prev: None"
|
||||
///
|
||||
/// # assert_eq!(num.get(), 0);
|
||||
/// set_num.set(1); // > "Number: 1; Prev: Some(0)"
|
||||
/// # });
|
||||
/// # assert_eq!(num.get(), 1);
|
||||
/// # }).await;
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn watch<D, T>(
|
||||
|
|
|
@ -135,44 +135,50 @@ where
|
|||
{
|
||||
/// Creates a render effect that will run whether the `effects` feature is enabled or not.
|
||||
pub fn new_isomorphic(
|
||||
mut fun: impl FnMut(Option<T>) -> T + Send + 'static,
|
||||
fun: impl FnMut(Option<T>) -> T + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
let (mut observer, mut rx) = channel();
|
||||
observer.notify();
|
||||
fn erased<T: Send + Sync + 'static>(
|
||||
mut fun: Box<dyn FnMut(Option<T>) -> T + Send + Sync + 'static>,
|
||||
) -> RenderEffect<T> {
|
||||
let (observer, mut rx) = channel();
|
||||
let value = Arc::new(RwLock::new(None::<T>));
|
||||
let owner = Owner::new();
|
||||
let inner = Arc::new(RwLock::new(EffectInner {
|
||||
dirty: false,
|
||||
observer,
|
||||
sources: SourceSet::new(),
|
||||
}));
|
||||
|
||||
let value = Arc::new(RwLock::new(None::<T>));
|
||||
let owner = Owner::new();
|
||||
let inner = Arc::new(RwLock::new(EffectInner {
|
||||
dirty: false,
|
||||
observer,
|
||||
sources: SourceSet::new(),
|
||||
}));
|
||||
let mut first_run = true;
|
||||
let initial_value = owner
|
||||
.with(|| inner.to_any_subscriber().with_observer(|| fun(None)));
|
||||
*value.write().or_poisoned() = Some(initial_value);
|
||||
|
||||
Executor::spawn({
|
||||
let value = Arc::clone(&value);
|
||||
let subscriber = inner.to_any_subscriber();
|
||||
Executor::spawn({
|
||||
let value = Arc::clone(&value);
|
||||
let subscriber = inner.to_any_subscriber();
|
||||
|
||||
async move {
|
||||
while rx.next().await.is_some() {
|
||||
if first_run
|
||||
|| subscriber
|
||||
async move {
|
||||
while rx.next().await.is_some() {
|
||||
if subscriber
|
||||
.with_observer(|| subscriber.update_if_necessary())
|
||||
{
|
||||
first_run = false;
|
||||
subscriber.clear_sources(&subscriber);
|
||||
{
|
||||
subscriber.clear_sources(&subscriber);
|
||||
|
||||
let old_value =
|
||||
mem::take(&mut *value.write().or_poisoned());
|
||||
let new_value = owner.with_cleanup(|| {
|
||||
subscriber.with_observer(|| fun(old_value))
|
||||
});
|
||||
*value.write().or_poisoned() = Some(new_value);
|
||||
let old_value =
|
||||
mem::take(&mut *value.write().or_poisoned());
|
||||
let new_value = owner.with_cleanup(|| {
|
||||
subscriber.with_observer(|| fun(old_value))
|
||||
});
|
||||
*value.write().or_poisoned() = Some(new_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
RenderEffect { value, inner }
|
||||
});
|
||||
|
||||
RenderEffect { value, inner }
|
||||
}
|
||||
|
||||
erased(Box::new(fun))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use super::{node::ReactiveNode, AnySubscriber};
|
||||
use crate::traits::DefinedAt;
|
||||
use crate::traits::{DefinedAt, IsDisposed};
|
||||
use core::{fmt::Debug, hash::Hash};
|
||||
use std::{panic::Location, sync::Weak};
|
||||
|
||||
/// Abstracts over the type of any reactive source.
|
||||
pub trait ToAnySource {
|
||||
pub trait ToAnySource: IsDisposed {
|
||||
/// Converts this type to its type-erased equivalent.
|
||||
fn to_any_source(&self) -> AnySource;
|
||||
}
|
||||
|
@ -62,6 +62,13 @@ impl PartialEq for AnySource {
|
|||
|
||||
impl Eq for AnySource {}
|
||||
|
||||
impl IsDisposed for AnySource {
|
||||
#[inline(always)]
|
||||
fn is_disposed(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl ToAnySource for AnySource {
|
||||
fn to_any_source(&self) -> AnySource {
|
||||
self.clone()
|
||||
|
|
|
@ -582,7 +582,7 @@ impl<T, S> Dispose for StoredValue<T, S> {
|
|||
#[inline(always)]
|
||||
#[track_caller]
|
||||
#[deprecated(
|
||||
since = "0.7.0-beta4",
|
||||
since = "0.7.0-beta5",
|
||||
note = "This function is being removed to conform to Rust idioms. Please \
|
||||
use `StoredValue::new()` or `StoredValue::new_local()` instead."
|
||||
)]
|
||||
|
|
|
@ -9,6 +9,7 @@ pub mod guards;
|
|||
mod read;
|
||||
mod rw;
|
||||
mod subscriber_traits;
|
||||
mod trigger;
|
||||
mod write;
|
||||
|
||||
use crate::owner::LocalStorage;
|
||||
|
@ -18,6 +19,7 @@ pub use arc_trigger::*;
|
|||
pub use arc_write::*;
|
||||
pub use read::*;
|
||||
pub use rw::*;
|
||||
pub use trigger::*;
|
||||
pub use write::*;
|
||||
|
||||
/// Creates a reference-counted signal.
|
||||
|
|
|
@ -5,7 +5,7 @@ use super::{
|
|||
};
|
||||
use crate::{
|
||||
graph::{ReactiveNode, SubscriberSet},
|
||||
prelude::{IsDisposed, Trigger},
|
||||
prelude::{IsDisposed, Notify},
|
||||
traits::{DefinedAt, ReadUntracked, UntrackableGuard, Writeable},
|
||||
};
|
||||
use core::fmt::{Debug, Formatter, Result};
|
||||
|
@ -247,8 +247,8 @@ impl<T: 'static> ReadUntracked for ArcRwSignal<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> Trigger for ArcRwSignal<T> {
|
||||
fn trigger(&self) {
|
||||
impl<T> Notify for ArcRwSignal<T> {
|
||||
fn notify(&self) {
|
||||
self.mark_dirty();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use super::subscriber_traits::AsSubscriberSet;
|
||||
use crate::{
|
||||
graph::{ReactiveNode, SubscriberSet},
|
||||
traits::{DefinedAt, IsDisposed, Trigger},
|
||||
traits::{DefinedAt, IsDisposed, Notify},
|
||||
};
|
||||
use std::{
|
||||
fmt::{Debug, Formatter, Result},
|
||||
|
@ -83,8 +83,8 @@ impl DefinedAt for ArcTrigger {
|
|||
}
|
||||
}
|
||||
|
||||
impl Trigger for ArcTrigger {
|
||||
fn trigger(&self) {
|
||||
impl Notify for ArcTrigger {
|
||||
fn notify(&self) {
|
||||
self.inner.mark_dirty();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use super::guards::{UntrackedWriteGuard, WriteGuard};
|
||||
use crate::{
|
||||
graph::{ReactiveNode, SubscriberSet},
|
||||
prelude::{IsDisposed, Trigger},
|
||||
prelude::{IsDisposed, Notify},
|
||||
traits::{DefinedAt, UntrackableGuard, Writeable},
|
||||
};
|
||||
use core::fmt::{Debug, Formatter, Result};
|
||||
|
@ -116,8 +116,8 @@ impl<T> IsDisposed for ArcWriteSignal<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> Trigger for ArcWriteSignal<T> {
|
||||
fn trigger(&self) {
|
||||
impl<T> Notify for ArcWriteSignal<T> {
|
||||
fn notify(&self) {
|
||||
self.inner.mark_dirty();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use crate::{
|
||||
computed::BlockingLock,
|
||||
traits::{Trigger, UntrackableGuard},
|
||||
traits::{Notify, UntrackableGuard},
|
||||
};
|
||||
use core::fmt::Debug;
|
||||
use guardian::{ArcRwLockReadGuardian, ArcRwLockWriteGuardian};
|
||||
|
@ -259,7 +259,7 @@ where
|
|||
#[derive(Debug)]
|
||||
pub struct WriteGuard<S, G>
|
||||
where
|
||||
S: Trigger,
|
||||
S: Notify,
|
||||
{
|
||||
pub(crate) triggerable: Option<S>,
|
||||
pub(crate) guard: Option<G>,
|
||||
|
@ -267,7 +267,7 @@ where
|
|||
|
||||
impl<S, G> WriteGuard<S, G>
|
||||
where
|
||||
S: Trigger,
|
||||
S: Notify,
|
||||
{
|
||||
/// Creates a new guard from the inner mutable guard type, and the signal that should be
|
||||
/// triggered on drop.
|
||||
|
@ -281,7 +281,7 @@ where
|
|||
|
||||
impl<S, G> UntrackableGuard for WriteGuard<S, G>
|
||||
where
|
||||
S: Trigger,
|
||||
S: Notify,
|
||||
G: DerefMut,
|
||||
{
|
||||
/// Removes the triggerable type, so that it is no longer notifies when dropped.
|
||||
|
@ -292,7 +292,7 @@ where
|
|||
|
||||
impl<S, G> Deref for WriteGuard<S, G>
|
||||
where
|
||||
S: Trigger,
|
||||
S: Notify,
|
||||
G: Deref,
|
||||
{
|
||||
type Target = G::Target;
|
||||
|
@ -310,7 +310,7 @@ where
|
|||
|
||||
impl<S, G> DerefMut for WriteGuard<S, G>
|
||||
where
|
||||
S: Trigger,
|
||||
S: Notify,
|
||||
G: DerefMut,
|
||||
{
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
|
@ -354,7 +354,7 @@ impl<T> DerefMut for UntrackedWriteGuard<T> {
|
|||
// Dropping the write guard will notify dependencies.
|
||||
impl<S, T> Drop for WriteGuard<S, T>
|
||||
where
|
||||
S: Trigger,
|
||||
S: Notify,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
// first, drop the inner guard
|
||||
|
@ -362,7 +362,7 @@ where
|
|||
|
||||
// then, notify about a change
|
||||
if let Some(triggerable) = self.triggerable.as_ref() {
|
||||
triggerable.trigger();
|
||||
triggerable.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::{
|
|||
owner::{FromLocal, LocalStorage, Storage, StoredValue, SyncStorage},
|
||||
signal::guards::{UntrackedWriteGuard, WriteGuard},
|
||||
traits::{
|
||||
DefinedAt, Dispose, IsDisposed, ReadUntracked, Trigger,
|
||||
DefinedAt, Dispose, IsDisposed, Notify, ReadUntracked,
|
||||
UntrackableGuard, Writeable,
|
||||
},
|
||||
unwrap_signal,
|
||||
|
@ -340,11 +340,11 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<T, S> Trigger for RwSignal<T, S>
|
||||
impl<T, S> Notify for RwSignal<T, S>
|
||||
where
|
||||
S: Storage<ArcRwSignal<T>>,
|
||||
{
|
||||
fn trigger(&self) {
|
||||
fn notify(&self) {
|
||||
self.mark_dirty();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ use crate::{
|
|||
AnySource, AnySubscriber, ReactiveNode, Source, SubscriberSet,
|
||||
ToAnySource,
|
||||
},
|
||||
traits::DefinedAt,
|
||||
traits::{DefinedAt, IsDisposed},
|
||||
unwrap_signal,
|
||||
};
|
||||
use or_poisoned::OrPoisoned;
|
||||
|
@ -93,10 +93,11 @@ impl<T: AsSubscriberSet + DefinedAt> Source for T {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: AsSubscriberSet + DefinedAt> ToAnySource for T
|
||||
impl<T: AsSubscriberSet + DefinedAt + IsDisposed> ToAnySource for T
|
||||
where
|
||||
T::Output: Borrow<Arc<RwLock<SubscriberSet>>>,
|
||||
{
|
||||
#[track_caller]
|
||||
fn to_any_source(&self) -> AnySource {
|
||||
self.as_subscriber_set()
|
||||
.map(|subs| {
|
||||
|
|
103
reactive_graph/src/signal/trigger.rs
Normal file
103
reactive_graph/src/signal/trigger.rs
Normal file
|
@ -0,0 +1,103 @@
|
|||
use super::{subscriber_traits::AsSubscriberSet, ArcTrigger};
|
||||
use crate::{
|
||||
graph::{ReactiveNode, SubscriberSet},
|
||||
owner::StoredValue,
|
||||
traits::{DefinedAt, Dispose, IsDisposed, Notify},
|
||||
};
|
||||
use std::{
|
||||
fmt::{Debug, Formatter, Result},
|
||||
panic::Location,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
/// A trigger is a data-less signal with the sole purpose of notifying other reactive code of a change.
|
||||
///
|
||||
/// This can be useful for when using external data not stored in signals, for example.
|
||||
///
|
||||
/// This is an arena-allocated Trigger, which is `Copy` and is disposed when its reactive
|
||||
/// [`Owner`](crate::owner::Owner) cleans up. For a reference-counted trigger that lives
|
||||
/// as long as a reference to it is alive, see [`ArcTrigger`].
|
||||
pub struct Trigger {
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
pub(crate) inner: StoredValue<ArcTrigger>,
|
||||
}
|
||||
|
||||
impl Trigger {
|
||||
/// Creates a new trigger.
|
||||
#[track_caller]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new(ArcTrigger::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Trigger {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Trigger {
|
||||
#[track_caller]
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl Copy for Trigger {}
|
||||
|
||||
impl Debug for Trigger {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
f.debug_struct("Trigger").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispose for Trigger {
|
||||
fn dispose(self) {
|
||||
self.inner.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
impl IsDisposed for Trigger {
|
||||
#[inline(always)]
|
||||
fn is_disposed(&self) -> bool {
|
||||
self.inner.is_disposed()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsSubscriberSet for Trigger {
|
||||
type Output = Arc<RwLock<SubscriberSet>>;
|
||||
|
||||
#[inline(always)]
|
||||
fn as_subscriber_set(&self) -> Option<Self::Output> {
|
||||
self.inner
|
||||
.try_get_value()
|
||||
.and_then(|arc_trigger| arc_trigger.as_subscriber_set())
|
||||
}
|
||||
}
|
||||
|
||||
impl DefinedAt for Trigger {
|
||||
#[inline(always)]
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Notify for Trigger {
|
||||
fn notify(&self) {
|
||||
if let Some(inner) = self.inner.try_get_value() {
|
||||
inner.mark_dirty();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ use super::{guards::WriteGuard, ArcWriteSignal};
|
|||
use crate::{
|
||||
owner::{Storage, StoredValue, SyncStorage},
|
||||
traits::{
|
||||
DefinedAt, Dispose, IsDisposed, Trigger, UntrackableGuard, Writeable,
|
||||
DefinedAt, Dispose, IsDisposed, Notify, UntrackableGuard, Writeable,
|
||||
},
|
||||
};
|
||||
use core::fmt::Debug;
|
||||
|
@ -116,14 +116,14 @@ impl<T, S> IsDisposed for WriteSignal<T, S> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T, S> Trigger for WriteSignal<T, S>
|
||||
impl<T, S> Notify for WriteSignal<T, S>
|
||||
where
|
||||
T: 'static,
|
||||
S: Storage<ArcWriteSignal<T>>,
|
||||
{
|
||||
fn trigger(&self) {
|
||||
fn notify(&self) {
|
||||
if let Some(inner) = self.inner.try_get_value() {
|
||||
inner.trigger();
|
||||
inner.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,6 +107,10 @@ pub trait Track {
|
|||
impl<T: Source + ToAnySource + DefinedAt> Track for T {
|
||||
#[track_caller]
|
||||
fn track(&self) {
|
||||
if self.is_disposed() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(subscriber) = Observer::get() {
|
||||
subscriber.add_source(self.to_any_source());
|
||||
self.add_subscriber(subscriber);
|
||||
|
@ -209,7 +213,7 @@ pub trait UntrackableGuard: DerefMut {
|
|||
|
||||
/// Gives mutable access to a signal's value through a guard type. When the guard is dropped, the
|
||||
/// signal's subscribers will be notified.
|
||||
pub trait Writeable: Sized + DefinedAt + Trigger {
|
||||
pub trait Writeable: Sized + DefinedAt + Notify {
|
||||
/// The type of the signal's value.
|
||||
type Value: Sized + 'static;
|
||||
|
||||
|
@ -381,9 +385,9 @@ where
|
|||
}
|
||||
|
||||
/// Notifies subscribers of a change in this signal.
|
||||
pub trait Trigger {
|
||||
pub trait Notify {
|
||||
/// Notifies subscribers of a change in this signal.
|
||||
fn trigger(&self);
|
||||
fn notify(&self);
|
||||
}
|
||||
|
||||
/// Updates the value of a signal by applying a function that updates it in place,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "reactive_stores"
|
||||
version = "0.1.0-beta4"
|
||||
version = "0.1.0-beta5"
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::{
|
|||
use reactive_graph::{
|
||||
signal::ArcTrigger,
|
||||
traits::{
|
||||
DefinedAt, IsDisposed, ReadUntracked, Track, Trigger, UntrackableGuard,
|
||||
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
|
||||
},
|
||||
};
|
||||
use std::{
|
||||
|
@ -241,9 +241,9 @@ impl<T> DefinedAt for ArcField<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> Trigger for ArcField<T> {
|
||||
fn trigger(&self) {
|
||||
self.trigger.trigger();
|
||||
impl<T> Notify for ArcField<T> {
|
||||
fn notify(&self) {
|
||||
self.trigger.notify();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
|||
use reactive_graph::{
|
||||
owner::{Storage, StoredValue, SyncStorage},
|
||||
signal::ArcTrigger,
|
||||
traits::{DefinedAt, IsDisposed, ReadUntracked, Track, Trigger},
|
||||
traits::{DefinedAt, IsDisposed, Notify, ReadUntracked, Track},
|
||||
unwrap_signal,
|
||||
};
|
||||
use std::{fmt::Debug, hash::Hash, ops::IndexMut, panic::Location};
|
||||
|
@ -142,13 +142,13 @@ impl<T, S> DefinedAt for Field<T, S> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T, S> Trigger for Field<T, S>
|
||||
impl<T, S> Notify for Field<T, S>
|
||||
where
|
||||
S: Storage<ArcField<T>>,
|
||||
{
|
||||
fn trigger(&self) {
|
||||
fn notify(&self) {
|
||||
if let Some(inner) = self.inner.try_get_value() {
|
||||
inner.trigger();
|
||||
inner.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use reactive_graph::{
|
|||
ArcTrigger,
|
||||
},
|
||||
traits::{
|
||||
DefinedAt, IsDisposed, ReadUntracked, Track, Trigger, UntrackableGuard,
|
||||
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
|
||||
Writeable,
|
||||
},
|
||||
};
|
||||
|
@ -141,15 +141,15 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<Inner, Prev> Trigger for AtIndex<Inner, Prev>
|
||||
impl<Inner, Prev> Notify for AtIndex<Inner, Prev>
|
||||
where
|
||||
Inner: StoreField<Value = Prev>,
|
||||
Prev: IndexMut<usize> + 'static,
|
||||
Prev::Output: Sized,
|
||||
{
|
||||
fn trigger(&self) {
|
||||
fn notify(&self) {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.trigger();
|
||||
trigger.notify();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ use reactive_graph::{
|
|||
guards::{Plain, ReadGuard},
|
||||
ArcTrigger,
|
||||
},
|
||||
traits::{DefinedAt, IsDisposed, ReadUntracked, Track, Trigger},
|
||||
traits::{DefinedAt, IsDisposed, Notify, ReadUntracked, Track},
|
||||
};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::{
|
||||
|
@ -218,10 +218,9 @@ impl<T: 'static> Track for ArcStore<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> Trigger for ArcStore<T> {
|
||||
fn trigger(&self) {
|
||||
self.get_trigger(self.path().into_iter().collect())
|
||||
.trigger();
|
||||
impl<T: 'static> Notify for ArcStore<T> {
|
||||
fn notify(&self) {
|
||||
self.get_trigger(self.path().into_iter().collect()).notify();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -326,14 +325,14 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<T, S> Trigger for Store<T, S>
|
||||
impl<T, S> Notify for Store<T, S>
|
||||
where
|
||||
T: 'static,
|
||||
S: Storage<ArcStore<T>>,
|
||||
{
|
||||
fn trigger(&self) {
|
||||
fn notify(&self) {
|
||||
if let Some(inner) = self.inner.try_get_value() {
|
||||
inner.trigger();
|
||||
inner.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{path::StorePath, StoreField};
|
||||
use itertools::{EitherOrBoth, Itertools};
|
||||
use reactive_graph::traits::{Trigger, UntrackableGuard};
|
||||
use reactive_graph::traits::{Notify, UntrackableGuard};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
|
||||
|
@ -33,7 +33,7 @@ where
|
|||
writer.untrack();
|
||||
let mut notify = |path: &StorePath| {
|
||||
println!("notifying on {path:?}");
|
||||
self.get_trigger(path.to_owned()).trigger();
|
||||
self.get_trigger(path.to_owned()).notify();
|
||||
};
|
||||
writer.patch_field(new, &path, &mut notify);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use reactive_graph::{
|
|||
ArcTrigger,
|
||||
},
|
||||
traits::{
|
||||
DefinedAt, IsDisposed, ReadUntracked, Track, Trigger, UntrackableGuard,
|
||||
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
|
||||
Writeable,
|
||||
},
|
||||
};
|
||||
|
@ -135,14 +135,14 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<Inner, Prev, T> Trigger for Subfield<Inner, Prev, T>
|
||||
impl<Inner, Prev, T> Notify for Subfield<Inner, Prev, T>
|
||||
where
|
||||
Inner: StoreField<Value = Prev>,
|
||||
Prev: 'static,
|
||||
{
|
||||
fn trigger(&self) {
|
||||
fn notify(&self) {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.trigger();
|
||||
trigger.notify();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "reactive_stores_macro"
|
||||
version = "0.1.0-beta4"
|
||||
version = "0.1.0-beta5"
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "leptos_router"
|
||||
version = "0.7.0-beta4"
|
||||
version = "0.7.0-beta5"
|
||||
authors = ["Greg Johnston", "Ben Wishovich"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
|
|
@ -2,6 +2,33 @@ use super::{PartialPathMatch, PathSegment, PossibleRouteMatch};
|
|||
use core::iter;
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// A segment that captures a value from the url and maps it to a key.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # (|| -> Option<()> { // Option does not impl Terminate, so no main
|
||||
/// use leptos::prelude::*;
|
||||
/// use leptos_router::{path, ParamSegment, PossibleRouteMatch};
|
||||
///
|
||||
/// let path = &"/hello";
|
||||
///
|
||||
/// // Manual definition
|
||||
/// let manual = (ParamSegment("message"),);
|
||||
/// let (key, value) = manual.test(path)?.params().last()?;
|
||||
///
|
||||
/// assert_eq!(key, "message");
|
||||
/// assert_eq!(value, "hello");
|
||||
///
|
||||
/// // Macro definition
|
||||
/// let using_macro = path!("/:message");
|
||||
/// let (key, value) = using_macro.test(path)?.params().last()?;
|
||||
///
|
||||
/// assert_eq!(key, "message");
|
||||
/// assert_eq!(value, "hello");
|
||||
///
|
||||
/// # Some(())
|
||||
/// # })().unwrap();
|
||||
/// ```
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ParamSegment(pub &'static str);
|
||||
|
||||
|
@ -51,6 +78,46 @@ impl PossibleRouteMatch for ParamSegment {
|
|||
}
|
||||
}
|
||||
|
||||
/// A segment that captures all remaining values from the url and maps it to a key.
|
||||
///
|
||||
/// A [`WildcardSegment`] __must__ be the last segment of your path definition.
|
||||
///
|
||||
/// ```rust
|
||||
/// # (|| -> Option<()> { // Option does not impl Terminate, so no main
|
||||
/// use leptos::prelude::*;
|
||||
/// use leptos_router::{
|
||||
/// path, ParamSegment, PossibleRouteMatch, StaticSegment, WildcardSegment,
|
||||
/// };
|
||||
///
|
||||
/// let path = &"/echo/send/sync/and/static";
|
||||
///
|
||||
/// // Manual definition
|
||||
/// let manual = (StaticSegment("echo"), WildcardSegment("kitchen_sink"));
|
||||
/// let (key, value) = manual.test(path)?.params().last()?;
|
||||
///
|
||||
/// assert_eq!(key, "kitchen_sink");
|
||||
/// assert_eq!(value, "send/sync/and/static");
|
||||
///
|
||||
/// // Macro definition
|
||||
/// let using_macro = path!("/echo/*else");
|
||||
/// let (key, value) = using_macro.test(path)?.params().last()?;
|
||||
///
|
||||
/// assert_eq!(key, "else");
|
||||
/// assert_eq!(value, "send/sync/and/static");
|
||||
///
|
||||
/// // This fails to compile because the macro will catch the bad ordering
|
||||
/// // let bad = path!("/echo/*foo/bar/:baz");
|
||||
///
|
||||
/// // This compiles but may not work as you expect at runtime.
|
||||
/// (
|
||||
/// StaticSegment("echo"),
|
||||
/// WildcardSegment("foo"),
|
||||
/// ParamSegment("baz"),
|
||||
/// );
|
||||
///
|
||||
/// # Some(())
|
||||
/// # })().unwrap();
|
||||
/// ```
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct WildcardSegment(pub &'static str);
|
||||
|
||||
|
|
|
@ -25,6 +25,37 @@ impl AsPath for &'static str {
|
|||
}
|
||||
}
|
||||
|
||||
/// A segment that is expected to be static. Not requiring mapping into params.
|
||||
///
|
||||
/// Should work exactly as you would expect.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # (|| -> Option<()> { // Option does not impl Terminate, so no main
|
||||
/// use leptos::prelude::*;
|
||||
/// use leptos_router::{path, PossibleRouteMatch, StaticSegment};
|
||||
///
|
||||
/// let path = &"/users";
|
||||
///
|
||||
/// // Manual definition
|
||||
/// let manual = (StaticSegment("users"),);
|
||||
/// let matched = manual.test(path)?;
|
||||
/// assert_eq!(matched.matched(), "/users");
|
||||
///
|
||||
/// // Params are empty as we had no `ParamSegement`s or `WildcardSegment`s
|
||||
/// // If you did have additional dynamic segments, this would not be empty.
|
||||
/// assert_eq!(matched.params().count(), 0);
|
||||
///
|
||||
/// // Macro definition
|
||||
/// let using_macro = path!("/users");
|
||||
/// let matched = manual.test(path)?;
|
||||
/// assert_eq!(matched.matched(), "/users");
|
||||
///
|
||||
/// assert_eq!(matched.params().count(), 0);
|
||||
///
|
||||
/// # Some(())
|
||||
/// # })().unwrap();
|
||||
/// ```
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct StaticSegment<T: AsPath>(pub T);
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ use reactive_graph::{
|
|||
computed::{ArcMemo, ScopedFuture},
|
||||
owner::{provide_context, use_context, Owner},
|
||||
signal::{ArcRwSignal, ArcTrigger},
|
||||
traits::{Get, GetUntracked, ReadUntracked, Set, Track, Trigger},
|
||||
traits::{Get, GetUntracked, Notify, ReadUntracked, Set, Track},
|
||||
wrappers::write::SignalSetter,
|
||||
};
|
||||
use send_wrapper::SendWrapper;
|
||||
|
@ -124,7 +124,7 @@ where
|
|||
ScopedFuture::new(async move {
|
||||
let triggers = join_all(loaders).await;
|
||||
for trigger in triggers {
|
||||
trigger.trigger();
|
||||
trigger.notify();
|
||||
}
|
||||
matched_view.rebuild(&mut *view.borrow_mut());
|
||||
})
|
||||
|
@ -179,7 +179,7 @@ where
|
|||
let triggers = join_all(loaders).await;
|
||||
// tell each one of the outlet triggers that it's ready
|
||||
for trigger in triggers {
|
||||
trigger.trigger();
|
||||
trigger.notify();
|
||||
}
|
||||
if let Some(loc) = location {
|
||||
loc.ready_to_complete();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "leptos_router_macro"
|
||||
version = "0.7.0-beta4"
|
||||
version = "0.7.0-beta5"
|
||||
authors = ["Greg Johnston", "Ben Wishovich"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "tachys"
|
||||
version = "0.1.0-beta4"
|
||||
version = "0.1.0-beta5"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
|
|
@ -719,7 +719,7 @@ where
|
|||
}
|
||||
|
||||
fn to_html(self, style: &mut String) {
|
||||
if let Some(inner) = self.now_or_never() {
|
||||
if let Some(inner) = self.inner.now_or_never() {
|
||||
inner.to_html(style);
|
||||
} else {
|
||||
panic!("You cannot use Suspend on an attribute outside Suspense");
|
||||
|
@ -736,7 +736,8 @@ where
|
|||
let state = Rc::clone(&state);
|
||||
async move {
|
||||
*state.borrow_mut() =
|
||||
Some(self.await.hydrate::<FROM_SERVER>(&el));
|
||||
Some(self.inner.await.hydrate::<FROM_SERVER>(&el));
|
||||
self.subscriber.forward();
|
||||
}
|
||||
});
|
||||
state
|
||||
|
@ -748,7 +749,8 @@ where
|
|||
Executor::spawn_local({
|
||||
let state = Rc::clone(&state);
|
||||
async move {
|
||||
*state.borrow_mut() = Some(self.await.build(&el));
|
||||
*state.borrow_mut() = Some(self.inner.await.build(&el));
|
||||
self.subscriber.forward();
|
||||
}
|
||||
});
|
||||
state
|
||||
|
@ -758,11 +760,12 @@ where
|
|||
Executor::spawn_local({
|
||||
let state = Rc::clone(state);
|
||||
async move {
|
||||
let value = self.await;
|
||||
let value = self.inner.await;
|
||||
let mut state = state.borrow_mut();
|
||||
if let Some(state) = state.as_mut() {
|
||||
value.rebuild(state);
|
||||
}
|
||||
self.subscriber.forward();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -778,6 +781,6 @@ where
|
|||
fn dry_resolve(&mut self) {}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
self.await
|
||||
self.inner.await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -401,7 +401,8 @@ where
|
|||
let state = Rc::clone(&state);
|
||||
async move {
|
||||
*state.borrow_mut() =
|
||||
Some(self.await.hydrate::<FROM_SERVER>(&key, &el));
|
||||
Some(self.inner.await.hydrate::<FROM_SERVER>(&key, &el));
|
||||
self.subscriber.forward();
|
||||
}
|
||||
});
|
||||
state
|
||||
|
@ -414,7 +415,8 @@ where
|
|||
Executor::spawn_local({
|
||||
let state = Rc::clone(&state);
|
||||
async move {
|
||||
*state.borrow_mut() = Some(self.await.build(&el, &key));
|
||||
*state.borrow_mut() = Some(self.inner.await.build(&el, &key));
|
||||
self.subscriber.forward();
|
||||
}
|
||||
});
|
||||
state
|
||||
|
@ -425,11 +427,12 @@ where
|
|||
Executor::spawn_local({
|
||||
let state = Rc::clone(state);
|
||||
async move {
|
||||
let value = self.await;
|
||||
let value = self.inner.await;
|
||||
let mut state = state.borrow_mut();
|
||||
if let Some(state) = state.as_mut() {
|
||||
value.rebuild(&key, state);
|
||||
}
|
||||
self.subscriber.forward();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -447,7 +450,7 @@ where
|
|||
fn dry_resolve(&mut self) {}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
self.await
|
||||
self.inner.await
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -447,7 +447,7 @@ where
|
|||
type CloneableOwned = Self;
|
||||
|
||||
fn to_html(self, style: &mut String) {
|
||||
if let Some(inner) = self.now_or_never() {
|
||||
if let Some(inner) = self.inner.now_or_never() {
|
||||
inner.to_html(style);
|
||||
} else {
|
||||
panic!("You cannot use Suspend on an attribute outside Suspense");
|
||||
|
@ -464,7 +464,8 @@ where
|
|||
let state = Rc::clone(&state);
|
||||
async move {
|
||||
*state.borrow_mut() =
|
||||
Some(self.await.hydrate::<FROM_SERVER>(&el));
|
||||
Some(self.inner.await.hydrate::<FROM_SERVER>(&el));
|
||||
self.subscriber.forward();
|
||||
}
|
||||
});
|
||||
state
|
||||
|
@ -476,7 +477,8 @@ where
|
|||
Executor::spawn_local({
|
||||
let state = Rc::clone(&state);
|
||||
async move {
|
||||
*state.borrow_mut() = Some(self.await.build(&el));
|
||||
*state.borrow_mut() = Some(self.inner.await.build(&el));
|
||||
self.subscriber.forward();
|
||||
}
|
||||
});
|
||||
state
|
||||
|
@ -486,11 +488,12 @@ where
|
|||
Executor::spawn_local({
|
||||
let state = Rc::clone(state);
|
||||
async move {
|
||||
let value = self.await;
|
||||
let value = self.inner.await;
|
||||
let mut state = state.borrow_mut();
|
||||
if let Some(state) = state.as_mut() {
|
||||
value.rebuild(state);
|
||||
}
|
||||
self.subscriber.forward();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -506,6 +509,6 @@ where
|
|||
fn dry_resolve(&mut self) {}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
self.await
|
||||
self.inner.await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,56 +10,115 @@ use crate::{
|
|||
};
|
||||
use any_spawner::Executor;
|
||||
use futures::{select, FutureExt};
|
||||
use or_poisoned::OrPoisoned;
|
||||
use reactive_graph::{
|
||||
computed::{
|
||||
suspense::{LocalResourceNotifier, SuspenseContext},
|
||||
ScopedFuture,
|
||||
},
|
||||
graph::{
|
||||
AnySource, AnySubscriber, Observer, ReactiveNode, Source, Subscriber,
|
||||
ToAnySubscriber, WithObserver,
|
||||
},
|
||||
owner::{provide_context, use_context},
|
||||
};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
fmt::Debug,
|
||||
future::Future,
|
||||
mem,
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
task::{Context, Poll},
|
||||
sync::{Arc, Mutex, Weak},
|
||||
};
|
||||
|
||||
/// A suspended `Future`, which can be used in the view.
|
||||
#[derive(Clone)]
|
||||
pub struct Suspend<Fut> {
|
||||
inner: Pin<Box<ScopedFuture<Fut>>>,
|
||||
pub(crate) subscriber: SuspendSubscriber,
|
||||
pub(crate) inner: Pin<Box<ScopedFuture<Fut>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct SuspendSubscriber {
|
||||
inner: Arc<SuspendSubscriberInner>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SuspendSubscriberInner {
|
||||
outer_subscriber: Option<AnySubscriber>,
|
||||
sources: Mutex<Vec<AnySource>>,
|
||||
}
|
||||
|
||||
impl SuspendSubscriber {
|
||||
pub fn new() -> Self {
|
||||
let outer_subscriber = Observer::get();
|
||||
Self {
|
||||
inner: Arc::new(SuspendSubscriberInner {
|
||||
outer_subscriber,
|
||||
sources: Default::default(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Re-links all reactive sources from this to another subscriber.
|
||||
///
|
||||
/// This is used to collect reactive dependencies during the rendering phase, and only later
|
||||
/// connect them to any outer effect, to prevent the completion of async resources from
|
||||
/// triggering the render effect to run a second time.
|
||||
pub fn forward(&self) {
|
||||
if let Some(to) = &self.inner.outer_subscriber {
|
||||
let sources =
|
||||
mem::take(&mut *self.inner.sources.lock().or_poisoned());
|
||||
for source in sources {
|
||||
source.add_subscriber(to.clone());
|
||||
to.add_source(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ReactiveNode for SuspendSubscriberInner {
|
||||
fn mark_dirty(&self) {}
|
||||
|
||||
fn mark_check(&self) {}
|
||||
|
||||
fn mark_subscribers_check(&self) {}
|
||||
|
||||
fn update_if_necessary(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl Subscriber for SuspendSubscriberInner {
|
||||
fn add_source(&self, source: AnySource) {
|
||||
self.sources.lock().or_poisoned().push(source);
|
||||
}
|
||||
|
||||
fn clear_sources(&self, subscriber: &AnySubscriber) {
|
||||
for source in mem::take(&mut *self.sources.lock().or_poisoned()) {
|
||||
source.remove_subscriber(subscriber);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToAnySubscriber for SuspendSubscriber {
|
||||
fn to_any_subscriber(&self) -> AnySubscriber {
|
||||
AnySubscriber(
|
||||
Arc::as_ptr(&self.inner) as usize,
|
||||
Arc::downgrade(&self.inner) as Weak<dyn Subscriber + Send + Sync>,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Fut> Suspend<Fut> {
|
||||
/// Creates a new suspended view.
|
||||
pub fn new(fut: Fut) -> Self {
|
||||
Self {
|
||||
inner: Box::pin(ScopedFuture::new(fut)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Fut> Future for Suspend<Fut>
|
||||
where
|
||||
Fut: Future,
|
||||
{
|
||||
type Output = Fut::Output;
|
||||
|
||||
fn poll(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Self::Output> {
|
||||
self.inner.as_mut().poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Fut> From<ScopedFuture<Fut>> for Suspend<Fut> {
|
||||
fn from(inner: ScopedFuture<Fut>) -> Self {
|
||||
Self {
|
||||
inner: Box::pin(inner),
|
||||
}
|
||||
let subscriber = SuspendSubscriber::new();
|
||||
let any_subscriber = subscriber.to_any_subscriber();
|
||||
let inner =
|
||||
any_subscriber.with_observer(|| Box::pin(ScopedFuture::new(fut)));
|
||||
Self { subscriber, inner }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,10 +165,12 @@ where
|
|||
|
||||
// TODO cancelation if it fires multiple times
|
||||
fn build(self) -> Self::State {
|
||||
let Self { subscriber, inner } = self;
|
||||
|
||||
// poll the future once immediately
|
||||
// if it's already available, start in the ready state
|
||||
// otherwise, start with the fallback
|
||||
let mut fut = Box::pin(self);
|
||||
let mut fut = Box::pin(inner);
|
||||
let initial = fut.as_mut().now_or_never();
|
||||
let initially_pending = initial.is_none();
|
||||
let inner = Rc::new(RefCell::new(initial.build()));
|
||||
|
@ -127,6 +188,8 @@ where
|
|||
let value = fut.as_mut().await;
|
||||
drop(id);
|
||||
Some(value).rebuild(&mut *state.borrow_mut());
|
||||
|
||||
subscriber.forward();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -135,8 +198,10 @@ where
|
|||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
let Self { subscriber, inner } = self;
|
||||
|
||||
// get a unique ID if there's a SuspenseContext
|
||||
let fut = self;
|
||||
let fut = inner;
|
||||
let id = use_context::<SuspenseContext>().map(|sc| sc.task_id());
|
||||
|
||||
// spawn the future, and rebuild the state when it resolves
|
||||
|
@ -150,6 +215,8 @@ where
|
|||
// has no parent
|
||||
any_spawner::Executor::tick().await;
|
||||
Some(value).rebuild(&mut *state.borrow_mut());
|
||||
|
||||
subscriber.forward();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -208,7 +275,7 @@ where
|
|||
// TODO wrap this with a Suspense as needed
|
||||
// currently this is just used for Routes, which creates a Suspend but never actually needs
|
||||
// it (because we don't lazy-load routes on the server)
|
||||
if let Some(inner) = self.now_or_never() {
|
||||
if let Some(inner) = self.inner.now_or_never() {
|
||||
inner.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
}
|
||||
}
|
||||
|
@ -222,7 +289,7 @@ where
|
|||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut fut = Box::pin(self);
|
||||
let mut fut = Box::pin(self.inner);
|
||||
match fut.as_mut().now_or_never() {
|
||||
Some(inner) => inner.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf,
|
||||
|
@ -287,10 +354,12 @@ where
|
|||
cursor: &Cursor<Rndr>,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
let Self { subscriber, inner } = self;
|
||||
|
||||
// poll the future once immediately
|
||||
// if it's already available, start in the ready state
|
||||
// otherwise, start with the fallback
|
||||
let mut fut = Box::pin(self);
|
||||
let mut fut = Box::pin(inner);
|
||||
let initial = fut.as_mut().now_or_never();
|
||||
let initially_pending = initial.is_none();
|
||||
let inner = Rc::new(RefCell::new(
|
||||
|
@ -310,15 +379,19 @@ where
|
|||
let value = fut.as_mut().await;
|
||||
drop(id);
|
||||
Some(value).rebuild(&mut *state.borrow_mut());
|
||||
|
||||
subscriber.forward();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
subscriber.forward();
|
||||
}
|
||||
|
||||
SuspendState { inner }
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
Some(self.await)
|
||||
Some(self.inner.await)
|
||||
}
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
|
|
Loading…
Reference in a new issue