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