mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-28 05:32:42 -05:00
Compare commits
30 Commits
0.8.0-alph
...
0.8.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2faae43d5f | ||
|
|
6760c87e83 | ||
|
|
e19e42c650 | ||
|
|
fb4be49ebf | ||
|
|
0b4cbbc17d | ||
|
|
dbbeb7c6ef | ||
|
|
36aef2565d | ||
|
|
a2a7eb8a2a | ||
|
|
97e22e2506 | ||
|
|
8bedacb0c7 | ||
|
|
56b7b9a16a | ||
|
|
d04d4c77f9 | ||
|
|
5c75928b5b | ||
|
|
abc5631654 | ||
|
|
40e5288ac1 | ||
|
|
335934d40e | ||
|
|
6ee72f42e2 | ||
|
|
93af23a970 | ||
|
|
95e8ae84af | ||
|
|
5cfe7f6b5e | ||
|
|
0404efd5c3 | ||
|
|
93173c1400 | ||
|
|
cd2904f6a6 | ||
|
|
6b453845f9 | ||
|
|
111b84ce3b | ||
|
|
5633148047 | ||
|
|
4edb012de3 | ||
|
|
330920eae2 | ||
|
|
a94bc0a6da | ||
|
|
f85e01f4d6 |
42
Cargo.lock
generated
42
Cargo.lock
generated
@@ -1776,7 +1776,7 @@ checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
|
||||
|
||||
[[package]]
|
||||
name = "leptos"
|
||||
version = "0.8.0-alpha"
|
||||
version = "0.8.0-beta"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"base64",
|
||||
@@ -1826,7 +1826,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_actix"
|
||||
version = "0.8.0-alpha"
|
||||
version = "0.8.0-beta"
|
||||
dependencies = [
|
||||
"actix-files",
|
||||
"actix-http",
|
||||
@@ -1852,7 +1852,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_axum"
|
||||
version = "0.8.0-alpha"
|
||||
version = "0.8.0-beta"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"axum",
|
||||
@@ -1876,7 +1876,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_config"
|
||||
version = "0.8.0-alpha"
|
||||
version = "0.8.0-beta"
|
||||
dependencies = [
|
||||
"config",
|
||||
"regex",
|
||||
@@ -1890,7 +1890,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_dom"
|
||||
version = "0.8.0-alpha"
|
||||
version = "0.8.0-beta"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"leptos",
|
||||
@@ -1907,7 +1907,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_hot_reload"
|
||||
version = "0.8.0-alpha"
|
||||
version = "0.8.0-beta"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
@@ -1923,7 +1923,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_integration_utils"
|
||||
version = "0.8.0-alpha"
|
||||
version = "0.8.0-beta"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"hydration_context",
|
||||
@@ -1936,7 +1936,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_macro"
|
||||
version = "0.8.0-alpha"
|
||||
version = "0.8.0-beta"
|
||||
dependencies = [
|
||||
"attribute-derive",
|
||||
"cfg-if",
|
||||
@@ -1955,7 +1955,7 @@ dependencies = [
|
||||
"rstml",
|
||||
"serde",
|
||||
"server_fn",
|
||||
"server_fn_macro 0.8.0-alpha",
|
||||
"server_fn_macro 0.8.0-beta",
|
||||
"syn 2.0.100",
|
||||
"tracing",
|
||||
"trybuild",
|
||||
@@ -1965,7 +1965,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_meta"
|
||||
version = "0.8.0-alpha"
|
||||
version = "0.8.0-beta"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"indexmap",
|
||||
@@ -1980,7 +1980,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_router"
|
||||
version = "0.8.0-alpha"
|
||||
version = "0.8.0-beta"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"either_of",
|
||||
@@ -2004,7 +2004,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_router_macro"
|
||||
version = "0.8.0-alpha"
|
||||
version = "0.8.0-beta"
|
||||
dependencies = [
|
||||
"leptos_macro",
|
||||
"leptos_router",
|
||||
@@ -2016,7 +2016,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_server"
|
||||
version = "0.8.0-alpha2"
|
||||
version = "0.8.0-beta"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"base64",
|
||||
@@ -2732,7 +2732,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reactive_graph"
|
||||
version = "0.2.0-alpha2"
|
||||
version = "0.2.0-beta"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"async-lock",
|
||||
@@ -2754,7 +2754,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reactive_stores"
|
||||
version = "0.2.0-alpha"
|
||||
version = "0.2.0-beta"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"guardian",
|
||||
@@ -2771,7 +2771,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reactive_stores_macro"
|
||||
version = "0.2.0-alpha"
|
||||
version = "0.2.0-beta"
|
||||
dependencies = [
|
||||
"convert_case 0.7.1",
|
||||
"proc-macro-error2",
|
||||
@@ -3228,7 +3228,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "server_fn"
|
||||
version = "0.8.0-alpha"
|
||||
version = "0.8.0-beta"
|
||||
dependencies = [
|
||||
"actix-web",
|
||||
"actix-ws",
|
||||
@@ -3289,7 +3289,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "server_fn_macro"
|
||||
version = "0.8.0-alpha"
|
||||
version = "0.8.0-beta"
|
||||
dependencies = [
|
||||
"const_format",
|
||||
"convert_case 0.6.0",
|
||||
@@ -3301,9 +3301,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "server_fn_macro_default"
|
||||
version = "0.8.0-alpha"
|
||||
version = "0.8.0-beta"
|
||||
dependencies = [
|
||||
"server_fn_macro 0.8.0-alpha",
|
||||
"server_fn_macro 0.8.0-beta",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
@@ -3497,7 +3497,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tachys"
|
||||
version = "0.2.0-alpha"
|
||||
version = "0.2.0-beta"
|
||||
dependencies = [
|
||||
"any_spawner",
|
||||
"async-trait",
|
||||
|
||||
36
Cargo.toml
36
Cargo.toml
@@ -40,7 +40,7 @@ members = [
|
||||
exclude = ["benchmarks", "examples", "projects"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.8.0-alpha"
|
||||
version = "0.8.0-beta"
|
||||
edition = "2021"
|
||||
rust-version = "1.76"
|
||||
|
||||
@@ -51,27 +51,27 @@ const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1" }
|
||||
either_of = { path = "./either_of/", version = "0.1.5" }
|
||||
hydration_context = { path = "./hydration_context", version = "0.3.0" }
|
||||
itertools = "0.14.0"
|
||||
leptos = { path = "./leptos", version = "0.8.0-alpha" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.8.0-alpha" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.8.0-alpha" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.8.0-alpha" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.8.0-alpha" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.8.0-alpha" }
|
||||
leptos_router = { path = "./router", version = "0.8.0-alpha" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.8.0-alpha" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.8.0-alpha" }
|
||||
leptos_meta = { path = "./meta", version = "0.8.0-alpha" }
|
||||
leptos = { path = "./leptos", version = "0.8.0-beta" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.8.0-beta" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.8.0-beta" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.8.0-beta" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.8.0-beta" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.8.0-beta" }
|
||||
leptos_router = { path = "./router", version = "0.8.0-beta" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.8.0-beta" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.8.0-beta" }
|
||||
leptos_meta = { path = "./meta", version = "0.8.0-beta" }
|
||||
next_tuple = { path = "./next_tuple", version = "0.1.0" }
|
||||
oco_ref = { path = "./oco", version = "0.2.0" }
|
||||
or_poisoned = { path = "./or_poisoned", version = "0.1.0" }
|
||||
reactive_graph = { path = "./reactive_graph", version = "0.2.0-alpha" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.2.0-alpha" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.2.0-alpha" }
|
||||
reactive_graph = { path = "./reactive_graph", version = "0.2.0-beta" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.2.0-beta" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.2.0-beta" }
|
||||
serde_json = "1.0.0"
|
||||
server_fn = { path = "./server_fn", version = "0.8.0-alpha" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.8.0-alpha" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.8.0-alpha" }
|
||||
tachys = { path = "./tachys", version = "0.2.0-alpha" }
|
||||
server_fn = { path = "./server_fn", version = "0.8.0-beta" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.8.0-beta" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.8.0-beta" }
|
||||
tachys = { path = "./tachys", version = "0.2.0-beta" }
|
||||
wasm-bindgen = { version = "0.2.100" }
|
||||
|
||||
[profile.release]
|
||||
|
||||
@@ -24,7 +24,7 @@ pub fn RouterExample() -> impl IntoView {
|
||||
// contexts are passed down through the route tree
|
||||
provide_context(ExampleContext(0));
|
||||
|
||||
// this signal will be ued to set whether we are allowed to access a protected route
|
||||
// this signal will be used to set whether we are allowed to access a protected route
|
||||
let (logged_in, set_logged_in) = signal(true);
|
||||
let (is_routing, set_is_routing) = signal(false);
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
@check_instrumented_issue_3719
|
||||
Feature: Using instrumented counters to test regression from #3502.
|
||||
Check that the suspend/suspense and the underlying resources are
|
||||
called with the expected number of times. If this was already in
|
||||
place by #3502 (5c43c18) it should have caught this regression.
|
||||
For a better minimum demonstration see #3719.
|
||||
|
||||
Background:
|
||||
|
||||
Given I see the app
|
||||
And I select the mode Instrumented
|
||||
|
||||
Scenario: follow all paths via CSR avoids #3502
|
||||
Given I select the following links
|
||||
| Item Listing |
|
||||
| Item 1 |
|
||||
| Inspect path2 |
|
||||
| Inspect path2/field3 |
|
||||
And I click on Reset CSR Counters
|
||||
When I select the following links
|
||||
| Inspect path2/field1 |
|
||||
| Inspect path2/field2 |
|
||||
And I go check the Counters
|
||||
Then I see the following counters under section
|
||||
| Suspend Calls | |
|
||||
| item_listing | 0 |
|
||||
| item_overview | 0 |
|
||||
| item_inspect | 2 |
|
||||
And the following counters under section
|
||||
| Server Calls (CSR) | |
|
||||
| list_items | 0 |
|
||||
| get_item | 0 |
|
||||
| inspect_item_root | 0 |
|
||||
| inspect_item_field | 2 |
|
||||
|
||||
# To show that starting directly from within a param will simply
|
||||
# cause the problem.
|
||||
Scenario: Quicker way to demonstrate regression caused by #3502
|
||||
Given I select the link Target 123
|
||||
# And I click on Reset CSR Counters
|
||||
When I select the following links
|
||||
| Inspect path2/field1 |
|
||||
| Inspect path2/field2 |
|
||||
And I go check the Counters
|
||||
Then I see the following counters under section
|
||||
| Suspend Calls | |
|
||||
| item_listing | 0 |
|
||||
| item_overview | 0 |
|
||||
| item_inspect | 3 |
|
||||
And the following counters under section
|
||||
| Server Calls (CSR) | |
|
||||
| list_items | 1 |
|
||||
| get_item | 1 |
|
||||
| inspect_item_root | 0 |
|
||||
| inspect_item_field | 4 |
|
||||
|
||||
Scenario: Follow paths ordinarily down to a target
|
||||
Given I select the following links
|
||||
| Item Listing |
|
||||
| Item 1 |
|
||||
And I click on Reset CSR Counters
|
||||
When I select the following links
|
||||
| Target 4## |
|
||||
| Target 3## |
|
||||
And I go check the Counters
|
||||
Then I see the following counters under section
|
||||
| Suspend Calls | |
|
||||
| item_listing | 0 |
|
||||
| item_overview | 2 |
|
||||
| item_inspect | 0 |
|
||||
And the following counters under section
|
||||
| Server Calls (CSR) | |
|
||||
| list_items | 0 |
|
||||
| get_item | 2 |
|
||||
| inspect_item_root | 0 |
|
||||
| inspect_item_field | 0 |
|
||||
|
||||
Scenario: Same as above, but add a refresh to test hydration
|
||||
Given I select the following links
|
||||
| Item Listing |
|
||||
| Item 1 |
|
||||
And I refresh the page
|
||||
And I click on Reset CSR Counters
|
||||
When I select the following links
|
||||
| Target 4## |
|
||||
| Target 3## |
|
||||
And I go check the Counters
|
||||
Then I see the following counters under section
|
||||
| Suspend Calls | |
|
||||
| item_listing | 0 |
|
||||
| item_overview | 2 |
|
||||
| item_inspect | 0 |
|
||||
And the following counters under section
|
||||
| Server Calls (CSR) | |
|
||||
| list_items | 0 |
|
||||
| get_item | 2 |
|
||||
| inspect_item_root | 0 |
|
||||
| inspect_item_field | 0 |
|
||||
|
||||
@@ -3,12 +3,28 @@ mod fixtures;
|
||||
use anyhow::Result;
|
||||
use cucumber::World;
|
||||
use fixtures::world::AppWorld;
|
||||
use std::{ffi::OsStr, fs::read_dir};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
AppWorld::cucumber()
|
||||
.fail_on_skipped()
|
||||
.run_and_exit("./features")
|
||||
.await;
|
||||
// Normally the below is done, but it's now gotten to the point of
|
||||
// having a sufficient number of tests where the resource contention
|
||||
// of the concurrently running browsers will cause failures on CI.
|
||||
// AppWorld::cucumber()
|
||||
// .fail_on_skipped()
|
||||
// .run_and_exit("./features")
|
||||
// .await;
|
||||
|
||||
// Mitigate the issue by manually stepping through each feature,
|
||||
// rather than letting cucumber glob them and dispatch all at once.
|
||||
for entry in read_dir("./features")? {
|
||||
let path = entry?.path();
|
||||
if path.extension() == Some(OsStr::new("feature")) {
|
||||
AppWorld::cucumber()
|
||||
.fail_on_skipped()
|
||||
.run_and_exit(path)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -492,8 +492,9 @@ fn ItemInspect() -> impl IntoView {
|
||||
// result
|
||||
},
|
||||
);
|
||||
on_cleanup(|| {
|
||||
if let Some(c) = use_context::<WriteSignal<Option<FieldNavCtx>>>() {
|
||||
let ws = use_context::<WriteSignal<Option<FieldNavCtx>>>();
|
||||
on_cleanup(move || {
|
||||
if let Some(c) = ws {
|
||||
c.set(None);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Axum integrations for the Leptos web framework."
|
||||
version = "0.8.0-alpha2"
|
||||
version = "0.8.0-beta"
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
|
||||
@@ -2013,7 +2013,7 @@ where
|
||||
#[cfg(feature = "default")]
|
||||
pub fn file_and_error_handler_with_context<S, IV>(
|
||||
additional_context: impl Fn() + 'static + Clone + Send,
|
||||
shell: fn(LeptosOptions) -> IV,
|
||||
shell: impl Fn(LeptosOptions) -> IV + 'static + Clone + Send,
|
||||
) -> impl Fn(
|
||||
Uri,
|
||||
State<S>,
|
||||
@@ -2030,6 +2030,7 @@ where
|
||||
move |uri: Uri, State(state): State<S>, req: Request<Body>| {
|
||||
Box::pin({
|
||||
let additional_context = additional_context.clone();
|
||||
let shell = shell.clone();
|
||||
async move {
|
||||
let options = LeptosOptions::from_ref(&state);
|
||||
let res =
|
||||
@@ -2073,7 +2074,7 @@ where
|
||||
/// simply reuse the source code of this function in your own application.
|
||||
#[cfg(feature = "default")]
|
||||
pub fn file_and_error_handler<S, IV>(
|
||||
shell: fn(LeptosOptions) -> IV,
|
||||
shell: impl Fn(LeptosOptions) -> IV + 'static + Clone + Send,
|
||||
) -> impl Fn(
|
||||
Uri,
|
||||
State<S>,
|
||||
|
||||
@@ -91,6 +91,9 @@ pub trait ToChildren<F> {
|
||||
fn to_children(f: F) -> Self;
|
||||
}
|
||||
|
||||
/// Compiler optimisation, can be used with certain type to avoid unique closures in the view!{} macro.
|
||||
pub struct ChildrenOptContainer<T>(pub T);
|
||||
|
||||
impl<F, C> ToChildren<F> for Children
|
||||
where
|
||||
F: FnOnce() -> C + Send + 'static,
|
||||
@@ -102,6 +105,16 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ToChildren<ChildrenOptContainer<T>> for Children
|
||||
where
|
||||
T: IntoAny + Send + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn to_children(t: ChildrenOptContainer<T>) -> Self {
|
||||
Box::new(move || t.0.into_any())
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, C> ToChildren<F> for ChildrenFn
|
||||
where
|
||||
F: Fn() -> C + Send + Sync + 'static,
|
||||
@@ -113,6 +126,16 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ToChildren<ChildrenOptContainer<T>> for ChildrenFn
|
||||
where
|
||||
T: IntoAny + Clone + Send + Sync + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn to_children(t: ChildrenOptContainer<T>) -> Self {
|
||||
Arc::new(move || t.0.clone().into_any())
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, C> ToChildren<F> for ChildrenFnMut
|
||||
where
|
||||
F: Fn() -> C + Send + 'static,
|
||||
@@ -124,6 +147,16 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ToChildren<ChildrenOptContainer<T>> for ChildrenFnMut
|
||||
where
|
||||
T: IntoAny + Clone + Send + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn to_children(t: ChildrenOptContainer<T>) -> Self {
|
||||
Box::new(move || t.0.clone().into_any())
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, C> ToChildren<F> for BoxedChildrenFn
|
||||
where
|
||||
F: Fn() -> C + Send + 'static,
|
||||
@@ -135,6 +168,16 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ToChildren<ChildrenOptContainer<T>> for BoxedChildrenFn
|
||||
where
|
||||
T: IntoAny + Clone + Send + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn to_children(t: ChildrenOptContainer<T>) -> Self {
|
||||
Box::new(move || t.0.clone().into_any())
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, C> ToChildren<F> for ChildrenFragment
|
||||
where
|
||||
F: FnOnce() -> C + Send + 'static,
|
||||
@@ -146,6 +189,16 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ToChildren<ChildrenOptContainer<T>> for ChildrenFragment
|
||||
where
|
||||
T: IntoAny + Send + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn to_children(t: ChildrenOptContainer<T>) -> Self {
|
||||
Box::new(move || Fragment::new(vec![t.0.into_any()]))
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, C> ToChildren<F> for ChildrenFragmentFn
|
||||
where
|
||||
F: Fn() -> C + Send + 'static,
|
||||
@@ -157,6 +210,16 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ToChildren<ChildrenOptContainer<T>> for ChildrenFragmentFn
|
||||
where
|
||||
T: IntoAny + Clone + Send + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn to_children(t: ChildrenOptContainer<T>) -> Self {
|
||||
Arc::new(move || Fragment::new(vec![t.0.clone().into_any()]))
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, C> ToChildren<F> for ChildrenFragmentMut
|
||||
where
|
||||
F: FnMut() -> C + Send + 'static,
|
||||
@@ -168,6 +231,16 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ToChildren<ChildrenOptContainer<T>> for ChildrenFragmentMut
|
||||
where
|
||||
T: IntoAny + Clone + Send + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn to_children(t: ChildrenOptContainer<T>) -> Self {
|
||||
Box::new(move || Fragment::new(vec![t.0.clone().into_any()]))
|
||||
}
|
||||
}
|
||||
|
||||
/// New-type wrapper for a function that returns a view with `From` and `Default` traits implemented
|
||||
/// to enable optional props in for example `<Show>` and `<Suspense>`.
|
||||
#[derive(Clone)]
|
||||
@@ -246,6 +319,16 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ToChildren<ChildrenOptContainer<T>> for TypedChildren<T>
|
||||
where
|
||||
T: IntoView + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn to_children(t: ChildrenOptContainer<T>) -> Self {
|
||||
TypedChildren(Box::new(move || t.0.into_view()))
|
||||
}
|
||||
}
|
||||
|
||||
/// A typed equivalent to [`ChildrenFnMut`], which takes a generic but preserves type information to
|
||||
/// allow the compiler to optimize the view more effectively.
|
||||
pub struct TypedChildrenMut<T>(Box<dyn FnMut() -> View<T> + Send>);
|
||||
@@ -275,6 +358,16 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ToChildren<ChildrenOptContainer<T>> for TypedChildrenMut<T>
|
||||
where
|
||||
T: IntoView + Clone + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn to_children(t: ChildrenOptContainer<T>) -> Self {
|
||||
TypedChildrenMut(Box::new(move || t.0.clone().into_view()))
|
||||
}
|
||||
}
|
||||
|
||||
/// A typed equivalent to [`ChildrenFn`], which takes a generic but preserves type information to
|
||||
/// allow the compiler to optimize the view more effectively.
|
||||
pub struct TypedChildrenFn<T>(Arc<dyn Fn() -> View<T> + Send + Sync>);
|
||||
@@ -310,3 +403,13 @@ where
|
||||
TypedChildrenFn(Arc::new(move || f().into_view()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ToChildren<ChildrenOptContainer<T>> for TypedChildrenFn<T>
|
||||
where
|
||||
T: IntoView + Clone + Sync + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn to_children(t: ChildrenOptContainer<T>) -> Self {
|
||||
TypedChildrenFn(Arc::new(move || t.0.clone().into_view()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
use super::{
|
||||
fragment_to_tokens, utils::is_nostrip_optional_and_update_key, TagType,
|
||||
};
|
||||
use crate::view::{attribute_absolute, utils::filter_prefixed_attrs};
|
||||
use crate::view::{
|
||||
attribute_absolute, text_to_tokens, utils::filter_prefixed_attrs,
|
||||
};
|
||||
use proc_macro2::{Ident, TokenStream, TokenTree};
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
use rstml::node::{
|
||||
CustomNode, KeyedAttributeValue, NodeAttribute, NodeBlock, NodeElement,
|
||||
NodeName,
|
||||
CustomNode, KeyedAttributeValue, Node, NodeAttribute, NodeBlock,
|
||||
NodeElement, NodeName,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use syn::{spanned::Spanned, Expr, ExprPath, ExprRange, RangeLimits, Stmt};
|
||||
use syn::{
|
||||
spanned::Spanned, Expr, ExprPath, ExprRange, Item, RangeLimits, Stmt,
|
||||
};
|
||||
|
||||
pub(crate) fn component_to_tokens(
|
||||
node: &mut NodeElement<impl CustomNode>,
|
||||
@@ -197,6 +201,12 @@ pub(crate) fn component_to_tokens(
|
||||
let mut slots = HashMap::new();
|
||||
let children = if node.children.is_empty() {
|
||||
quote! {}
|
||||
} else if let Some(children) = maybe_optimised_component_children(
|
||||
&node.children,
|
||||
&items_to_bind,
|
||||
&items_to_clone,
|
||||
) {
|
||||
children
|
||||
} else {
|
||||
let children = fragment_to_tokens(
|
||||
&mut node.children,
|
||||
@@ -225,10 +235,7 @@ pub(crate) fn component_to_tokens(
|
||||
let bindables =
|
||||
items_to_bind.iter().map(|ident| quote! { #ident, });
|
||||
|
||||
let clonables = items_to_clone.iter().map(|ident| {
|
||||
let ident_ref = quote_spanned!(ident.span()=> &#ident);
|
||||
quote! { let #ident = ::core::clone::Clone::clone(#ident_ref); }
|
||||
});
|
||||
let clonables = items_to_clone_to_tokens(&items_to_clone);
|
||||
|
||||
if bindables.len() > 0 {
|
||||
quote_spanned! {children.span()=>
|
||||
@@ -319,3 +326,111 @@ fn is_attr_let(key: &NodeName) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn items_to_clone_to_tokens<'a>(
|
||||
items_to_clone: &'a [Ident],
|
||||
) -> impl Iterator<Item = TokenStream> + 'a {
|
||||
items_to_clone.iter().map(|ident| {
|
||||
let ident_ref = quote_spanned!(ident.span()=> &#ident);
|
||||
quote! { let #ident = ::core::clone::Clone::clone(#ident_ref); }
|
||||
})
|
||||
}
|
||||
|
||||
/// By default all children are placed in an outer closure || #children.
|
||||
/// This is to work with all the variants of the leptos::children::ToChildren::to_children trait.
|
||||
/// Strings are optimised to be passed without the wrapping closure, providing significant compile time and binary size improvements.
|
||||
pub fn maybe_optimised_component_children(
|
||||
children: &[Node<impl CustomNode>],
|
||||
items_to_bind: &[TokenStream],
|
||||
items_to_clone: &[Ident],
|
||||
) -> Option<TokenStream> {
|
||||
// If there are bindables will have to be in a closure:
|
||||
if !items_to_bind.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Filter out comments:
|
||||
let mut children_iter = children
|
||||
.iter()
|
||||
.filter(|child| !matches!(child, Node::Comment(_)));
|
||||
|
||||
let children = if let Some(child) = children_iter.next() {
|
||||
// If more than one child after filtering out comments, don't think we can optimise:
|
||||
if children_iter.next().is_some() {
|
||||
return None;
|
||||
}
|
||||
match child {
|
||||
Node::Text(text) => text_to_tokens(&text.value),
|
||||
Node::RawText(raw) => {
|
||||
let text = raw.to_string_best();
|
||||
let text = syn::LitStr::new(&text, raw.span());
|
||||
text_to_tokens(&text)
|
||||
}
|
||||
// Specifically allow std macros that produce strings:
|
||||
Node::Block(NodeBlock::ValidBlock(block)) => {
|
||||
fn is_supported(mac: &syn::Macro) -> bool {
|
||||
for string_macro in ["format", "include_str"] {
|
||||
if mac.path.is_ident(string_macro) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
if block.stmts.len() > 1 {
|
||||
return None;
|
||||
} else if let Some(stmt) = block.stmts.first() {
|
||||
match stmt {
|
||||
Stmt::Macro(mac) => {
|
||||
// eprintln!("Macro: {:?}", mac.mac.path);
|
||||
if is_supported(&mac.mac) {
|
||||
quote! { #block }
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Stmt::Item(Item::Macro(mac)) => {
|
||||
// eprintln!("Item Macro: {:?}", mac.mac.path);
|
||||
if is_supported(&mac.mac) {
|
||||
quote! { #block }
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Stmt::Expr(Expr::Macro(mac), _) => {
|
||||
// eprintln!("Expr Macro: {:?}", mac.mac.path);
|
||||
if is_supported(&mac.mac) {
|
||||
quote! { #block }
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
} else {
|
||||
return Some(quote! {});
|
||||
}
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
// // Debug check to see how many use this optimisation:
|
||||
// static COUNT: std::sync::atomic::AtomicUsize =
|
||||
// std::sync::atomic::AtomicUsize::new(0);
|
||||
// COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
// eprintln!(
|
||||
// "Optimised children: {}",
|
||||
// COUNT.load(std::sync::atomic::Ordering::Relaxed)
|
||||
// );
|
||||
|
||||
let clonables = items_to_clone_to_tokens(items_to_clone);
|
||||
Some(quote_spanned! {children.span()=>
|
||||
.children({
|
||||
#(#clonables)*
|
||||
|
||||
::leptos::children::ToChildren::to_children(::leptos::children::ChildrenOptContainer(#children))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use super::{convert_to_snake_case, ident_from_tag_name};
|
||||
use super::{
|
||||
component_builder::maybe_optimised_component_children,
|
||||
convert_to_snake_case, ident_from_tag_name,
|
||||
};
|
||||
use crate::view::{fragment_to_tokens, utils::filter_prefixed_attrs, TagType};
|
||||
use proc_macro2::{Ident, TokenStream, TokenTree};
|
||||
use quote::{quote, quote_spanned};
|
||||
@@ -70,7 +73,10 @@ pub(crate) fn slot_to_tokens(
|
||||
}
|
||||
});
|
||||
|
||||
let items_to_bind = filter_prefixed_attrs(attrs.iter(), "let:");
|
||||
let items_to_bind = filter_prefixed_attrs(attrs.iter(), "let:")
|
||||
.into_iter()
|
||||
.map(|ident| quote! { #ident })
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let items_to_clone = filter_prefixed_attrs(attrs.iter(), "clone:");
|
||||
|
||||
@@ -96,6 +102,12 @@ pub(crate) fn slot_to_tokens(
|
||||
let mut slots = HashMap::new();
|
||||
let children = if node.children.is_empty() {
|
||||
quote! {}
|
||||
} else if let Some(children) = maybe_optimised_component_children(
|
||||
&node.children,
|
||||
&items_to_bind,
|
||||
&items_to_clone,
|
||||
) {
|
||||
children
|
||||
} else {
|
||||
let children = fragment_to_tokens(
|
||||
&mut node.children,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "leptos_server"
|
||||
# TODO revert to { workspace = true } before 0.8.0 release
|
||||
# this is a hack because I missing bumping the hydration_context version number before publishing
|
||||
version = "0.8.0-alpha2"
|
||||
version = "0.8.0-beta"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
|
||||
@@ -8,15 +8,15 @@ use reactive_graph::{
|
||||
ToAnySource, ToAnySubscriber,
|
||||
},
|
||||
owner::use_context,
|
||||
send_wrapper_ext::MaybeSendWrapperOption,
|
||||
signal::{
|
||||
guards::{AsyncPlain, ReadGuard},
|
||||
guards::{AsyncPlain, Mapped, ReadGuard},
|
||||
ArcRwSignal, RwSignal,
|
||||
},
|
||||
traits::{
|
||||
DefinedAt, IsDisposed, ReadUntracked, Track, Update, With, Write,
|
||||
},
|
||||
};
|
||||
use send_wrapper::SendWrapper;
|
||||
use std::{
|
||||
future::{pending, Future, IntoFuture},
|
||||
panic::Location,
|
||||
@@ -24,7 +24,7 @@ use std::{
|
||||
|
||||
/// A reference-counted resource that only loads its data locally on the client.
|
||||
pub struct ArcLocalResource<T> {
|
||||
data: ArcAsyncDerived<SendWrapper<T>>,
|
||||
data: ArcAsyncDerived<T>,
|
||||
refetch: ArcRwSignal<usize>,
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
@@ -71,14 +71,12 @@ impl<T> ArcLocalResource<T> {
|
||||
}
|
||||
}
|
||||
};
|
||||
let fetcher = SendWrapper::new(fetcher);
|
||||
let refetch = ArcRwSignal::new(0);
|
||||
let data = {
|
||||
let refetch = refetch.clone();
|
||||
ArcAsyncDerived::new(move || {
|
||||
ArcAsyncDerived::new_unsync(move || {
|
||||
refetch.track();
|
||||
let fut = fetcher();
|
||||
SendWrapper::new(async move { SendWrapper::new(fut.await) })
|
||||
fetcher()
|
||||
})
|
||||
};
|
||||
Self {
|
||||
@@ -97,7 +95,7 @@ impl<T> ArcLocalResource<T> {
|
||||
/// Synchronously, reactively reads the current value of the resource and applies the function
|
||||
/// `f` to its value if it is `Some(_)`.
|
||||
#[track_caller]
|
||||
pub fn map<U>(&self, f: impl FnOnce(&SendWrapper<T>) -> U) -> Option<U>
|
||||
pub fn map<U>(&self, f: impl FnOnce(&T) -> U) -> Option<U>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
@@ -128,14 +126,9 @@ where
|
||||
T: Clone + 'static,
|
||||
{
|
||||
type Output = T;
|
||||
type IntoFuture = futures::future::Map<
|
||||
AsyncDerivedFuture<SendWrapper<T>>,
|
||||
fn(SendWrapper<T>) -> T,
|
||||
>;
|
||||
type IntoFuture = AsyncDerivedFuture<T>;
|
||||
|
||||
fn into_future(self) -> Self::IntoFuture {
|
||||
use futures::FutureExt;
|
||||
|
||||
if let Some(mut notifier) = use_context::<LocalResourceNotifier>() {
|
||||
notifier.notify();
|
||||
} else if cfg!(feature = "ssr") {
|
||||
@@ -145,7 +138,7 @@ where
|
||||
always pending on the server."
|
||||
);
|
||||
}
|
||||
self.data.into_future().map(|value| (*value).clone())
|
||||
self.data.into_future()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,18 +159,14 @@ impl<T> ReadUntracked for ArcLocalResource<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
type Value =
|
||||
ReadGuard<Option<SendWrapper<T>>, AsyncPlain<Option<SendWrapper<T>>>>;
|
||||
type Value = ReadGuard<
|
||||
Option<T>,
|
||||
Mapped<AsyncPlain<MaybeSendWrapperOption<T>>, Option<T>>,
|
||||
>;
|
||||
|
||||
fn try_read_untracked(&self) -> Option<Self::Value> {
|
||||
if let Some(mut notifier) = use_context::<LocalResourceNotifier>() {
|
||||
notifier.notify();
|
||||
} else if cfg!(feature = "ssr") {
|
||||
panic!(
|
||||
"Reading from a LocalResource outside Suspense in `ssr` mode \
|
||||
will cause the response to hang, because LocalResources are \
|
||||
always pending on the server."
|
||||
);
|
||||
}
|
||||
self.data.try_read_untracked()
|
||||
}
|
||||
@@ -246,7 +235,7 @@ impl<T> Subscriber for ArcLocalResource<T> {
|
||||
|
||||
/// A resource that only loads its data locally on the client.
|
||||
pub struct LocalResource<T> {
|
||||
data: AsyncDerived<SendWrapper<T>>,
|
||||
data: AsyncDerived<T>,
|
||||
refetch: RwSignal<usize>,
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
@@ -296,11 +285,9 @@ impl<T> LocalResource<T> {
|
||||
data: if cfg!(feature = "ssr") {
|
||||
AsyncDerived::new_mock(fetcher)
|
||||
} else {
|
||||
let fetcher = SendWrapper::new(fetcher);
|
||||
AsyncDerived::new(move || {
|
||||
AsyncDerived::new_unsync_threadsafe_storage(move || {
|
||||
refetch.track();
|
||||
let fut = fetcher();
|
||||
SendWrapper::new(async move { SendWrapper::new(fut.await) })
|
||||
fetcher()
|
||||
})
|
||||
},
|
||||
refetch,
|
||||
@@ -320,14 +307,9 @@ where
|
||||
T: Clone + 'static,
|
||||
{
|
||||
type Output = T;
|
||||
type IntoFuture = futures::future::Map<
|
||||
AsyncDerivedFuture<SendWrapper<T>>,
|
||||
fn(SendWrapper<T>) -> T,
|
||||
>;
|
||||
type IntoFuture = AsyncDerivedFuture<T>;
|
||||
|
||||
fn into_future(self) -> Self::IntoFuture {
|
||||
use futures::FutureExt;
|
||||
|
||||
if let Some(mut notifier) = use_context::<LocalResourceNotifier>() {
|
||||
notifier.notify();
|
||||
} else if cfg!(feature = "ssr") {
|
||||
@@ -337,7 +319,7 @@ where
|
||||
always pending on the server."
|
||||
);
|
||||
}
|
||||
self.data.into_future().map(|value| (*value).clone())
|
||||
self.data.into_future()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,18 +340,14 @@ impl<T> ReadUntracked for LocalResource<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
type Value =
|
||||
ReadGuard<Option<SendWrapper<T>>, AsyncPlain<Option<SendWrapper<T>>>>;
|
||||
type Value = ReadGuard<
|
||||
Option<T>,
|
||||
Mapped<AsyncPlain<MaybeSendWrapperOption<T>>, Option<T>>,
|
||||
>;
|
||||
|
||||
fn try_read_untracked(&self) -> Option<Self::Value> {
|
||||
if let Some(mut notifier) = use_context::<LocalResourceNotifier>() {
|
||||
notifier.notify();
|
||||
} else if cfg!(feature = "ssr") {
|
||||
panic!(
|
||||
"Reading from a LocalResource outside Suspense in `ssr` mode \
|
||||
will cause the response to hang, because LocalResources are \
|
||||
always pending on the server."
|
||||
);
|
||||
}
|
||||
self.data.try_read_untracked()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_meta"
|
||||
version = "0.8.0-alpha"
|
||||
version = "0.8.0-beta"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_graph"
|
||||
version = "0.2.0-alpha2"
|
||||
version = "0.2.0-beta"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -13,8 +13,9 @@ use crate::{
|
||||
SubscriberSet, ToAnySource, ToAnySubscriber, WithObserver,
|
||||
},
|
||||
owner::{use_context, Owner},
|
||||
send_wrapper_ext::MaybeSendWrapperOption,
|
||||
signal::{
|
||||
guards::{AsyncPlain, ReadGuard, WriteGuard},
|
||||
guards::{AsyncPlain, Mapped, MappedMut, ReadGuard, WriteGuard},
|
||||
ArcTrigger,
|
||||
},
|
||||
traits::{
|
||||
@@ -28,11 +29,10 @@ use async_lock::RwLock as AsyncRwLock;
|
||||
use core::fmt::Debug;
|
||||
use futures::{channel::oneshot, FutureExt, StreamExt};
|
||||
use or_poisoned::OrPoisoned;
|
||||
use send_wrapper::SendWrapper;
|
||||
use std::{
|
||||
future::Future,
|
||||
mem,
|
||||
ops::DerefMut,
|
||||
ops::{Deref, DerefMut},
|
||||
panic::Location,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
@@ -110,7 +110,7 @@ pub struct ArcAsyncDerived<T> {
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
// the current state of this signal
|
||||
pub(crate) value: Arc<AsyncRwLock<Option<T>>>,
|
||||
pub(crate) value: Arc<AsyncRwLock<MaybeSendWrapperOption<T>>>,
|
||||
// holds wakers generated when you .await this
|
||||
pub(crate) wakers: Arc<RwLock<Vec<Waker>>>,
|
||||
pub(crate) inner: Arc<RwLock<ArcAsyncDerivedInner>>,
|
||||
@@ -280,7 +280,7 @@ macro_rules! spawn_derived {
|
||||
let mut guard = this.inner.write().or_poisoned();
|
||||
|
||||
guard.state = AsyncDerivedState::Clean;
|
||||
*value.blocking_write() = Some(orig_value);
|
||||
*value.blocking_write() = orig_value;
|
||||
this.loading.store(false, Ordering::Relaxed);
|
||||
(true, None)
|
||||
}
|
||||
@@ -405,14 +405,14 @@ macro_rules! spawn_derived {
|
||||
|
||||
impl<T: 'static> ArcAsyncDerived<T> {
|
||||
async fn set_inner_value(
|
||||
new_value: T,
|
||||
value: Arc<AsyncRwLock<Option<T>>>,
|
||||
new_value: MaybeSendWrapperOption<T>,
|
||||
value: Arc<AsyncRwLock<MaybeSendWrapperOption<T>>>,
|
||||
wakers: Arc<RwLock<Vec<Waker>>>,
|
||||
inner: Arc<RwLock<ArcAsyncDerivedInner>>,
|
||||
loading: Arc<AtomicBool>,
|
||||
ready_tx: Option<oneshot::Sender<()>>,
|
||||
) {
|
||||
*value.write().await = Some(new_value);
|
||||
*value.write().await.deref_mut() = new_value;
|
||||
Self::notify_subs(&wakers, &inner, &loading, ready_tx);
|
||||
}
|
||||
|
||||
@@ -479,6 +479,11 @@ impl<T: 'static> ArcAsyncDerived<T> {
|
||||
T: Send + Sync + 'static,
|
||||
Fut: Future<Output = T> + Send + 'static,
|
||||
{
|
||||
let fun = move || {
|
||||
let fut = fun();
|
||||
async move { MaybeSendWrapperOption::new(Some(fut.await)) }
|
||||
};
|
||||
let initial_value = MaybeSendWrapperOption::new(initial_value);
|
||||
let (this, _) = spawn_derived!(
|
||||
Executor::spawn,
|
||||
initial_value,
|
||||
@@ -508,6 +513,11 @@ impl<T: 'static> ArcAsyncDerived<T> {
|
||||
Fut: Future<Output = T> + Send + 'static,
|
||||
S: Track,
|
||||
{
|
||||
let fun = move || {
|
||||
let fut = fun();
|
||||
async move { MaybeSendWrapperOption::new(Some(fut.await)) }
|
||||
};
|
||||
let initial_value = MaybeSendWrapperOption::new(initial_value);
|
||||
let (this, _) = spawn_derived!(
|
||||
Executor::spawn,
|
||||
initial_value,
|
||||
@@ -545,6 +555,11 @@ impl<T: 'static> ArcAsyncDerived<T> {
|
||||
T: 'static,
|
||||
Fut: Future<Output = T> + 'static,
|
||||
{
|
||||
let fun = move || {
|
||||
let fut = fun();
|
||||
async move { MaybeSendWrapperOption::new_local(Some(fut.await)) }
|
||||
};
|
||||
let initial_value = MaybeSendWrapperOption::new_local(initial_value);
|
||||
let (this, _) = spawn_derived!(
|
||||
Executor::spawn_local,
|
||||
initial_value,
|
||||
@@ -567,7 +582,7 @@ impl<T: 'static> ArcAsyncDerived<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> ArcAsyncDerived<SendWrapper<T>> {
|
||||
impl<T: 'static> ArcAsyncDerived<T> {
|
||||
#[doc(hidden)]
|
||||
#[track_caller]
|
||||
pub fn new_mock<Fut>(fun: impl Fn() -> Fut + 'static) -> Self
|
||||
@@ -575,13 +590,10 @@ impl<T: 'static> ArcAsyncDerived<SendWrapper<T>> {
|
||||
T: 'static,
|
||||
Fut: Future<Output = T> + 'static,
|
||||
{
|
||||
let initial = None::<SendWrapper<T>>;
|
||||
let initial = MaybeSendWrapperOption::new_local(None::<T>);
|
||||
let fun = move || {
|
||||
let fut = fun();
|
||||
async move {
|
||||
let value = fut.await;
|
||||
SendWrapper::new(value)
|
||||
}
|
||||
async move { MaybeSendWrapperOption::new_local(Some(fut.await)) }
|
||||
};
|
||||
let (this, _) = spawn_derived!(
|
||||
Executor::spawn_local,
|
||||
@@ -597,7 +609,10 @@ impl<T: 'static> ArcAsyncDerived<SendWrapper<T>> {
|
||||
}
|
||||
|
||||
impl<T: 'static> ReadUntracked for ArcAsyncDerived<T> {
|
||||
type Value = ReadGuard<Option<T>, AsyncPlain<Option<T>>>;
|
||||
type Value = ReadGuard<
|
||||
Option<T>,
|
||||
Mapped<AsyncPlain<MaybeSendWrapperOption<T>>, Option<T>>,
|
||||
>;
|
||||
|
||||
fn try_read_untracked(&self) -> Option<Self::Value> {
|
||||
if let Some(suspense_context) = use_context::<SuspenseContext>() {
|
||||
@@ -613,7 +628,9 @@ impl<T: 'static> ReadUntracked for ArcAsyncDerived<T> {
|
||||
.suspenses
|
||||
.push(suspense_context);
|
||||
}
|
||||
AsyncPlain::try_new(&self.value).map(ReadGuard::new)
|
||||
AsyncPlain::try_new(&self.value).map(|plain| {
|
||||
ReadGuard::new(Mapped::new_with_guard(plain, |v| v.deref()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -627,13 +644,21 @@ impl<T: 'static> Write for ArcAsyncDerived<T> {
|
||||
type Value = Option<T>;
|
||||
|
||||
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
|
||||
Some(WriteGuard::new(self.clone(), self.value.blocking_write()))
|
||||
Some(MappedMut::new(
|
||||
WriteGuard::new(self.clone(), self.value.blocking_write()),
|
||||
|v| v.deref(),
|
||||
|v| v.deref_mut(),
|
||||
))
|
||||
}
|
||||
|
||||
fn try_write_untracked(
|
||||
&self,
|
||||
) -> Option<impl DerefMut<Target = Self::Value>> {
|
||||
Some(self.value.blocking_write())
|
||||
Some(MappedMut::new(
|
||||
self.value.blocking_write(),
|
||||
|v| v.deref(),
|
||||
|v| v.deref_mut(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@ use crate::{
|
||||
ToAnySource, ToAnySubscriber,
|
||||
},
|
||||
owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage},
|
||||
signal::guards::{AsyncPlain, ReadGuard, WriteGuard},
|
||||
send_wrapper_ext::MaybeSendWrapperOption,
|
||||
signal::guards::{AsyncPlain, Mapped, MappedMut, ReadGuard, WriteGuard},
|
||||
traits::{
|
||||
DefinedAt, Dispose, IsDisposed, Notify, ReadUntracked,
|
||||
UntrackableGuard, Write,
|
||||
@@ -13,8 +14,11 @@ use crate::{
|
||||
unwrap_signal,
|
||||
};
|
||||
use core::fmt::Debug;
|
||||
use send_wrapper::SendWrapper;
|
||||
use std::{future::Future, ops::DerefMut, panic::Location};
|
||||
use std::{
|
||||
future::Future,
|
||||
ops::{Deref, DerefMut},
|
||||
panic::Location,
|
||||
};
|
||||
|
||||
/// A reactive value that is derived by running an asynchronous computation in response to changes
|
||||
/// in its sources.
|
||||
@@ -94,9 +98,10 @@ impl<T, S> Dispose for AsyncDerived<T, S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<ArcAsyncDerived<T>> for AsyncDerived<T>
|
||||
impl<T, S> From<ArcAsyncDerived<T>> for AsyncDerived<T, S>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
T: 'static,
|
||||
S: Storage<ArcAsyncDerived<T>>,
|
||||
{
|
||||
fn from(value: ArcAsyncDerived<T>) -> Self {
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
@@ -109,12 +114,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<AsyncDerived<T>> for ArcAsyncDerived<T>
|
||||
impl<T, S> From<AsyncDerived<T, S>> for ArcAsyncDerived<T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
T: 'static,
|
||||
S: Storage<ArcAsyncDerived<T>>,
|
||||
{
|
||||
#[track_caller]
|
||||
fn from(value: AsyncDerived<T>) -> Self {
|
||||
fn from(value: AsyncDerived<T, S>) -> Self {
|
||||
value
|
||||
.inner
|
||||
.try_get_value()
|
||||
@@ -179,7 +185,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsyncDerived<SendWrapper<T>> {
|
||||
impl<T> AsyncDerived<T> {
|
||||
#[doc(hidden)]
|
||||
pub fn new_mock<Fut>(fun: impl Fn() -> Fut + 'static) -> Self
|
||||
where
|
||||
@@ -192,6 +198,24 @@ impl<T> AsyncDerived<SendWrapper<T>> {
|
||||
inner: ArenaItem::new_with_storage(ArcAsyncDerived::new_mock(fun)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as [`AsyncDerived::new_unsync`] except it produces AsyncDerived<T> instead of AsyncDerived<T, LocalStorage>.
|
||||
/// The internal value will still be wrapped in a [`send_wrapper::SendWrapper`].
|
||||
pub fn new_unsync_threadsafe_storage<Fut>(
|
||||
fun: impl Fn() -> Fut + 'static,
|
||||
) -> Self
|
||||
where
|
||||
T: 'static,
|
||||
Fut: Future<Output = T> + 'static,
|
||||
{
|
||||
Self {
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(ArcAsyncDerived::new_unsync(
|
||||
fun,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsyncDerived<T, LocalStorage>
|
||||
@@ -294,7 +318,10 @@ where
|
||||
T: 'static,
|
||||
S: Storage<ArcAsyncDerived<T>>,
|
||||
{
|
||||
type Value = ReadGuard<Option<T>, AsyncPlain<Option<T>>>;
|
||||
type Value = ReadGuard<
|
||||
Option<T>,
|
||||
Mapped<AsyncPlain<MaybeSendWrapperOption<T>>, Option<T>>,
|
||||
>;
|
||||
|
||||
fn try_read_untracked(&self) -> Option<Self::Value> {
|
||||
self.inner
|
||||
@@ -324,13 +351,21 @@ where
|
||||
let guard = self
|
||||
.inner
|
||||
.try_with_value(|n| n.value.blocking_write_arc())?;
|
||||
Some(WriteGuard::new(*self, guard))
|
||||
Some(MappedMut::new(
|
||||
WriteGuard::new(*self, guard),
|
||||
|v| v.deref(),
|
||||
|v| v.deref_mut(),
|
||||
))
|
||||
}
|
||||
|
||||
fn try_write_untracked(
|
||||
&self,
|
||||
) -> Option<impl DerefMut<Target = Self::Value>> {
|
||||
self.inner.try_with_value(|n| n.value.blocking_write_arc())
|
||||
self.inner
|
||||
.try_with_value(|n| n.value.blocking_write_arc())
|
||||
.map(|inner| {
|
||||
MappedMut::new(inner, |v| v.deref(), |v| v.deref_mut())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::{
|
||||
diagnostics::SpecialNonReactiveZone,
|
||||
graph::{AnySource, ToAnySource},
|
||||
owner::{use_context, Storage},
|
||||
send_wrapper_ext::MaybeSendWrapperOption,
|
||||
signal::guards::{AsyncPlain, Mapped, ReadGuard},
|
||||
traits::{DefinedAt, Track},
|
||||
unwrap_signal,
|
||||
@@ -24,7 +25,8 @@ use std::{
|
||||
///
|
||||
/// Implements [`Deref`](std::ops::Deref) to access the inner value. This should not be held longer
|
||||
/// than it is needed, as it prevents updates to the inner value.
|
||||
pub type AsyncDerivedGuard<T> = ReadGuard<T, Mapped<AsyncPlain<Option<T>>, T>>;
|
||||
pub type AsyncDerivedGuard<T> =
|
||||
ReadGuard<T, Mapped<AsyncPlain<MaybeSendWrapperOption<T>>, T>>;
|
||||
|
||||
/// A [`Future`] that is ready when an [`ArcAsyncDerived`] is finished loading or reloading,
|
||||
/// but does not contain its value.
|
||||
@@ -106,7 +108,7 @@ where
|
||||
/// and contains its value. `.await`ing this clones the value `T`.
|
||||
pub struct AsyncDerivedFuture<T> {
|
||||
source: AnySource,
|
||||
value: Arc<async_lock::RwLock<Option<T>>>,
|
||||
value: Arc<async_lock::RwLock<MaybeSendWrapperOption<T>>>,
|
||||
loading: Arc<AtomicBool>,
|
||||
wakers: Arc<RwLock<Vec<Waker>>>,
|
||||
inner: Arc<RwLock<ArcAsyncDerivedInner>>,
|
||||
@@ -183,7 +185,7 @@ where
|
||||
/// and yields an [`AsyncDerivedGuard`] that dereferences to its value.
|
||||
pub struct AsyncDerivedRefFuture<T> {
|
||||
source: AnySource,
|
||||
value: Arc<async_lock::RwLock<Option<T>>>,
|
||||
value: Arc<async_lock::RwLock<MaybeSendWrapperOption<T>>>,
|
||||
loading: Arc<AtomicBool>,
|
||||
wakers: Arc<RwLock<Vec<Waker>>>,
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@ pub mod diagnostics;
|
||||
pub mod effect;
|
||||
pub mod graph;
|
||||
pub mod owner;
|
||||
pub mod send_wrapper_ext;
|
||||
#[cfg(feature = "serde")]
|
||||
mod serde;
|
||||
pub mod signal;
|
||||
|
||||
@@ -60,6 +60,38 @@ pub struct Owner {
|
||||
pub(crate) shared_context: Option<Arc<dyn SharedContext + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl Owner {
|
||||
fn downgrade(&self) -> WeakOwner {
|
||||
WeakOwner {
|
||||
inner: Arc::downgrade(&self.inner),
|
||||
#[cfg(feature = "hydration")]
|
||||
shared_context: self.shared_context.as_ref().map(Arc::downgrade),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct WeakOwner {
|
||||
inner: Weak<RwLock<OwnerInner>>,
|
||||
#[cfg(feature = "hydration")]
|
||||
shared_context: Option<Weak<dyn SharedContext + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl WeakOwner {
|
||||
fn upgrade(&self) -> Option<Owner> {
|
||||
self.inner.upgrade().map(|inner| {
|
||||
#[cfg(feature = "hydration")]
|
||||
let shared_context =
|
||||
self.shared_context.as_ref().and_then(|sc| sc.upgrade());
|
||||
Owner {
|
||||
inner,
|
||||
#[cfg(feature = "hydration")]
|
||||
shared_context,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Owner {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Arc::ptr_eq(&self.inner, &other.inner)
|
||||
@@ -67,7 +99,7 @@ impl PartialEq for Owner {
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static OWNER: RefCell<Option<Owner>> = Default::default();
|
||||
static OWNER: RefCell<Option<WeakOwner>> = Default::default();
|
||||
}
|
||||
|
||||
impl Owner {
|
||||
@@ -107,12 +139,16 @@ impl Owner {
|
||||
/// Creates a new `Owner` and registers it as a child of the current `Owner`, if there is one.
|
||||
pub fn new() -> Self {
|
||||
#[cfg(not(feature = "hydration"))]
|
||||
let parent = OWNER
|
||||
.with(|o| o.borrow().as_ref().map(|o| Arc::downgrade(&o.inner)));
|
||||
let parent = OWNER.with(|o| {
|
||||
o.borrow()
|
||||
.as_ref()
|
||||
.and_then(|o| o.upgrade())
|
||||
.map(|o| Arc::downgrade(&o.inner))
|
||||
});
|
||||
#[cfg(feature = "hydration")]
|
||||
let (parent, shared_context) = OWNER
|
||||
.with(|o| {
|
||||
o.borrow().as_ref().map(|o| {
|
||||
o.borrow().as_ref().and_then(|o| o.upgrade()).map(|o| {
|
||||
(Some(Arc::downgrade(&o.inner)), o.shared_context.clone())
|
||||
})
|
||||
})
|
||||
@@ -200,7 +236,7 @@ impl Owner {
|
||||
|
||||
/// Sets this as the current `Owner`.
|
||||
pub fn set(&self) {
|
||||
OWNER.with_borrow_mut(|owner| *owner = Some(self.clone()));
|
||||
OWNER.with_borrow_mut(|owner| *owner = Some(self.downgrade()));
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
Arena::set(&self.inner.read().or_poisoned().arena);
|
||||
}
|
||||
@@ -208,9 +244,10 @@ impl Owner {
|
||||
/// Runs the given function with this as the current `Owner`.
|
||||
pub fn with<T>(&self, fun: impl FnOnce() -> T) -> T {
|
||||
// codegen optimisation:
|
||||
fn inner_1(self_: &Owner) -> Option<Owner> {
|
||||
let prev =
|
||||
{ OWNER.with(|o| (*o.borrow_mut()).replace(self_.clone())) };
|
||||
fn inner_1(self_: &Owner) -> Option<WeakOwner> {
|
||||
let prev = {
|
||||
OWNER.with(|o| (*o.borrow_mut()).replace(self_.downgrade()))
|
||||
};
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
Arena::set(&self_.inner.read().or_poisoned().arena);
|
||||
prev
|
||||
@@ -220,7 +257,7 @@ impl Owner {
|
||||
let val = fun();
|
||||
|
||||
// monomorphisation optimisation:
|
||||
fn inner_2(prev: Option<Owner>) {
|
||||
fn inner_2(prev: Option<WeakOwner>) {
|
||||
OWNER.with(|o| {
|
||||
*o.borrow_mut() = prev;
|
||||
});
|
||||
@@ -266,7 +303,7 @@ impl Owner {
|
||||
|
||||
/// Returns the current `Owner`, if any.
|
||||
pub fn current() -> Option<Owner> {
|
||||
OWNER.with(|o| o.borrow().clone())
|
||||
OWNER.with(|o| o.borrow().as_ref().and_then(|n| n.upgrade()))
|
||||
}
|
||||
|
||||
/// Returns the [`SharedContext`] associated with this owner, if any.
|
||||
@@ -280,7 +317,7 @@ impl Owner {
|
||||
/// Removes this from its state as the thread-local owner and drops it.
|
||||
pub fn unset(self) {
|
||||
OWNER.with_borrow_mut(|owner| {
|
||||
if owner.as_ref() == Some(&self) {
|
||||
if owner.as_ref().and_then(|n| n.upgrade()) == Some(self) {
|
||||
mem::take(owner);
|
||||
}
|
||||
})
|
||||
@@ -293,6 +330,7 @@ impl Owner {
|
||||
OWNER.with(|o| {
|
||||
o.borrow()
|
||||
.as_ref()
|
||||
.and_then(|o| o.upgrade())
|
||||
.and_then(|current| current.shared_context.clone())
|
||||
})
|
||||
}
|
||||
@@ -306,6 +344,7 @@ impl Owner {
|
||||
|
||||
let sc = OWNER.with_borrow(|o| {
|
||||
o.as_ref()
|
||||
.and_then(|o| o.upgrade())
|
||||
.and_then(|current| current.shared_context.clone())
|
||||
});
|
||||
match sc {
|
||||
@@ -331,6 +370,7 @@ impl Owner {
|
||||
|
||||
let sc = OWNER.with_borrow(|o| {
|
||||
o.as_ref()
|
||||
.and_then(|o| o.upgrade())
|
||||
.and_then(|current| current.shared_context.clone())
|
||||
});
|
||||
match sc {
|
||||
|
||||
@@ -48,20 +48,30 @@ impl Arena {
|
||||
fun(&MAP.get_or_init(Default::default).read().or_poisoned())
|
||||
}
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
{
|
||||
Arena::try_with(fun).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"at {}, the `sandboxed-arenas` feature is active, but no \
|
||||
Arena is active",
|
||||
std::panic::Location::caller()
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn try_with<U>(fun: impl FnOnce(&ArenaMap) -> U) -> Option<U> {
|
||||
#[cfg(not(feature = "sandboxed-arenas"))]
|
||||
{
|
||||
Some(fun(&MAP.get_or_init(Default::default).read().or_poisoned()))
|
||||
}
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
{
|
||||
MAP.with_borrow(|arena| {
|
||||
fun(&arena
|
||||
arena
|
||||
.as_ref()
|
||||
.and_then(Weak::upgrade)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"at {}, the `sandboxed-arenas` feature is active, \
|
||||
but no Arena is active",
|
||||
std::panic::Location::caller()
|
||||
)
|
||||
})
|
||||
.read()
|
||||
.or_poisoned())
|
||||
.map(|n| fun(&n.read().or_poisoned()))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -74,20 +84,32 @@ impl Arena {
|
||||
}
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
{
|
||||
let caller = std::panic::Location::caller();
|
||||
Arena::try_with_mut(fun).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"at {}, the `sandboxed-arenas` feature is active, but no \
|
||||
Arena is active",
|
||||
std::panic::Location::caller()
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn try_with_mut<U>(fun: impl FnOnce(&mut ArenaMap) -> U) -> Option<U> {
|
||||
#[cfg(not(feature = "sandboxed-arenas"))]
|
||||
{
|
||||
Some(fun(&mut MAP
|
||||
.get_or_init(Default::default)
|
||||
.write()
|
||||
.or_poisoned()))
|
||||
}
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
{
|
||||
MAP.with_borrow(|arena| {
|
||||
fun(&mut arena
|
||||
arena
|
||||
.as_ref()
|
||||
.and_then(Weak::upgrade)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"at {}, the `sandboxed-arenas` feature is active, \
|
||||
but no Arena is active",
|
||||
caller
|
||||
)
|
||||
})
|
||||
.write()
|
||||
.or_poisoned())
|
||||
.map(|n| fun(&mut n.write().or_poisoned()))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -126,6 +148,7 @@ pub mod sandboxed {
|
||||
/// called.
|
||||
///
|
||||
/// [item]:[crate::owner::ArenaItem]
|
||||
#[track_caller]
|
||||
pub fn new(inner: T) -> Self {
|
||||
let arena = MAP.with_borrow(|n| n.as_ref().and_then(Weak::upgrade));
|
||||
Self { arena, inner }
|
||||
|
||||
@@ -53,7 +53,7 @@ where
|
||||
})
|
||||
};
|
||||
OWNER.with(|o| {
|
||||
if let Some(owner) = &*o.borrow() {
|
||||
if let Some(owner) = o.borrow().as_ref().and_then(|o| o.upgrade()) {
|
||||
owner.register(node);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -76,24 +76,26 @@ where
|
||||
}
|
||||
|
||||
fn try_with<U>(node: NodeId, fun: impl FnOnce(&T) -> U) -> Option<U> {
|
||||
Arena::with(|arena| {
|
||||
Arena::try_with(|arena| {
|
||||
let m = arena.get(node);
|
||||
m.and_then(|n| n.downcast_ref::<T>()).map(fun)
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn try_with_mut<U>(
|
||||
node: NodeId,
|
||||
fun: impl FnOnce(&mut T) -> U,
|
||||
) -> Option<U> {
|
||||
Arena::with_mut(|arena| {
|
||||
Arena::try_with_mut(|arena| {
|
||||
let m = arena.get_mut(node);
|
||||
m.and_then(|n| n.downcast_mut::<T>()).map(fun)
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn try_set(node: NodeId, value: T) -> Option<T> {
|
||||
Arena::with_mut(|arena| {
|
||||
Arena::try_with_mut(|arena| {
|
||||
let m = arena.get_mut(node);
|
||||
match m.and_then(|n| n.downcast_mut::<T>()) {
|
||||
Some(inner) => {
|
||||
@@ -103,6 +105,7 @@ where
|
||||
None => Some(value),
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn take(node: NodeId) -> Option<T> {
|
||||
|
||||
145
reactive_graph/src/send_wrapper_ext.rs
Normal file
145
reactive_graph/src/send_wrapper_ext.rs
Normal file
@@ -0,0 +1,145 @@
|
||||
//! Additional wrapper utilities for [`send_wrapper::SendWrapper`].
|
||||
|
||||
use send_wrapper::SendWrapper;
|
||||
use std::{
|
||||
fmt::{Debug, Formatter},
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
/// An optional value that might be wrapped in [`SendWrapper`].
|
||||
///
|
||||
/// This struct is useful because:
|
||||
/// - Can be dereffed to &Option<T>, even when T is wrapped in a SendWrapper.
|
||||
/// - Until [`DerefMut`] is called, the None case will not construct a SendWrapper, so no panics if initialised when None and dropped on a different thread. Any access other than [`DerefMut`] will not construct a SendWrapper.
|
||||
pub struct MaybeSendWrapperOption<T> {
|
||||
inner: Inner<T>,
|
||||
}
|
||||
// SAFETY: `MaybeSendWrapperOption` can *only* be given a T in four ways
|
||||
// 1) via new(), which requires T: Send + Sync
|
||||
// 2) via new_local(), which wraps T in a SendWrapper if given Some(T)
|
||||
// 3) via deref_mut(), which creates a SendWrapper<Option<T>> as needed
|
||||
// 4) via update(), which either dereferences an existing SendWrapper
|
||||
// or creates a new SendWrapper as needed
|
||||
unsafe impl<T> Send for MaybeSendWrapperOption<T> {}
|
||||
unsafe impl<T> Sync for MaybeSendWrapperOption<T> {}
|
||||
|
||||
enum Inner<T> {
|
||||
/// A threadsafe value.
|
||||
Threadsafe(Option<T>),
|
||||
/// A non-threadsafe value. If accessed/dropped from a different thread in the Some() variant, it will panic.
|
||||
Local(Option<SendWrapper<Option<T>>>),
|
||||
}
|
||||
|
||||
impl<T> MaybeSendWrapperOption<T>
|
||||
where
|
||||
T: Send + Sync,
|
||||
{
|
||||
/// Create a new threadsafe value.
|
||||
pub fn new(value: Option<T>) -> Self {
|
||||
Self {
|
||||
inner: Inner::Threadsafe(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> MaybeSendWrapperOption<T> {
|
||||
/// Create a new non-threadsafe value.
|
||||
pub fn new_local(value: Option<T>) -> Self {
|
||||
Self {
|
||||
inner: if let Some(value) = value {
|
||||
Inner::Local(Some(SendWrapper::new(Some(value))))
|
||||
} else {
|
||||
Inner::Local(None)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Update a value in place with a callback.
|
||||
///
|
||||
/// # Panics
|
||||
/// If the value is [`Inner::Local`] and it is called from a different thread than the one the instance has been created with, it will panic.
|
||||
pub fn update(&mut self, cb: impl FnOnce(&mut Option<T>)) {
|
||||
match &mut self.inner {
|
||||
Inner::Threadsafe(value) => cb(value),
|
||||
Inner::Local(value) => match value {
|
||||
Some(sw) => {
|
||||
cb(sw.deref_mut());
|
||||
if sw.is_none() {
|
||||
*value = None;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let mut inner = None;
|
||||
cb(&mut inner);
|
||||
if let Some(inner) = inner {
|
||||
*value = Some(SendWrapper::new(Some(inner)));
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume the value.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the [`Inner::Local`] variant and it is called from a different thread than the one the instance has been created with.
|
||||
pub fn take(self) -> Option<T> {
|
||||
match self.inner {
|
||||
Inner::Threadsafe(value) => value,
|
||||
Inner::Local(value) => value.and_then(|value| value.take()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for MaybeSendWrapperOption<T> {
|
||||
type Target = Option<T>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match &self.inner {
|
||||
Inner::Threadsafe(value) => value,
|
||||
Inner::Local(value) => match value {
|
||||
Some(value) => value.deref(),
|
||||
None => &None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for MaybeSendWrapperOption<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
match &mut self.inner {
|
||||
Inner::Threadsafe(value) => value,
|
||||
Inner::Local(value) => match value {
|
||||
Some(value) => value.deref_mut(),
|
||||
None => {
|
||||
*value = Some(SendWrapper::new(None));
|
||||
value.as_mut().unwrap().deref_mut()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for MaybeSendWrapperOption<T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match &self.inner {
|
||||
Inner::Threadsafe(value) => {
|
||||
write!(f, "MaybeSendWrapperOption::Threadsafe({:?})", value)
|
||||
}
|
||||
Inner::Local(value) => {
|
||||
write!(f, "MaybeSendWrapperOption::Local({:?})", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Clone for MaybeSendWrapperOption<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: match &self.inner {
|
||||
Inner::Threadsafe(value) => Inner::Threadsafe(value.clone()),
|
||||
Inner::Local(value) => Inner::Local(value.clone()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_stores"
|
||||
version = "0.2.0-alpha"
|
||||
version = "0.2.0-beta"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::{
|
||||
len::Len,
|
||||
path::{StorePath, StorePathSegment},
|
||||
store_field::StoreField,
|
||||
KeyMap, StoreFieldTrigger,
|
||||
@@ -209,7 +210,7 @@ impl<Inner, Prev> StoreFieldIterator<Prev> for Inner
|
||||
where
|
||||
Inner: StoreField<Value = Prev> + Clone,
|
||||
Prev::Output: Sized,
|
||||
Prev: IndexMut<usize> + AsRef<[Prev::Output]>,
|
||||
Prev: IndexMut<usize> + Len,
|
||||
{
|
||||
#[track_caller]
|
||||
fn at_unkeyed(self, index: usize) -> AtIndex<Inner, Prev> {
|
||||
@@ -224,7 +225,7 @@ where
|
||||
trigger.children.track();
|
||||
|
||||
// get the current length of the field by accessing slice
|
||||
let len = self.reader().map(|n| n.as_ref().len()).unwrap_or(0);
|
||||
let len = self.reader().map(|n| n.len()).unwrap_or(0);
|
||||
|
||||
// return the iterator
|
||||
StoreFieldIter {
|
||||
|
||||
217
reactive_stores/src/len.rs
Normal file
217
reactive_stores/src/len.rs
Normal file
@@ -0,0 +1,217 @@
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::{LinkedList, VecDeque},
|
||||
};
|
||||
|
||||
/// A trait for getting the length of a collection.
|
||||
pub trait Len {
|
||||
/// Returns the length of the collection.
|
||||
fn len(&self) -> usize;
|
||||
|
||||
/// Returns true if the collection is empty
|
||||
#[inline(always)]
|
||||
fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! delegate_impl_len {
|
||||
(<$($lt: lifetime,)*$($generics: ident,)*> $ty:ty) => {
|
||||
impl<$($lt,)*$($generics,)*> Len for $ty {
|
||||
#[inline(always)]
|
||||
fn len(&self) -> usize {
|
||||
<$ty>::len(self)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_empty(&self) -> bool {
|
||||
<$ty>::is_empty(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<$($lt,)*$($generics,)*> Len for &$ty {
|
||||
#[inline(always)]
|
||||
fn len(&self) -> usize {
|
||||
Len::len(*self)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_empty(&self) -> bool {
|
||||
Len::is_empty(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<$($lt,)*$($generics,)*> Len for &mut $ty {
|
||||
#[inline(always)]
|
||||
fn len(&self) -> usize {
|
||||
Len::len(*self)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_empty(&self) -> bool {
|
||||
Len::is_empty(*self)
|
||||
}
|
||||
}
|
||||
};
|
||||
($ty:ty) => {
|
||||
delegate_impl_len!(<> $ty);
|
||||
};
|
||||
}
|
||||
|
||||
delegate_impl_len!(<T,> [T]);
|
||||
delegate_impl_len!(<T,> Vec<T>);
|
||||
delegate_impl_len!(str);
|
||||
delegate_impl_len!(String);
|
||||
|
||||
impl<'a> Len for Cow<'a, str> {
|
||||
#[inline(always)]
|
||||
fn len(&self) -> usize {
|
||||
<str>::len(self)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_empty(&self) -> bool {
|
||||
<str>::is_empty(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Len for &Cow<'a, str> {
|
||||
#[inline(always)]
|
||||
fn len(&self) -> usize {
|
||||
Len::len(*self)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_empty(&self) -> bool {
|
||||
Len::is_empty(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Len for &mut Cow<'a, str> {
|
||||
#[inline(always)]
|
||||
fn len(&self) -> usize {
|
||||
Len::len(*self)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_empty(&self) -> bool {
|
||||
Len::is_empty(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Len for Cow<'a, [T]>
|
||||
where
|
||||
[T]: ToOwned,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn len(&self) -> usize {
|
||||
<[T]>::len(self)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_empty(&self) -> bool {
|
||||
<[T]>::is_empty(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Len for &Cow<'a, [T]>
|
||||
where
|
||||
[T]: ToOwned,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn len(&self) -> usize {
|
||||
Len::len(*self)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_empty(&self) -> bool {
|
||||
Len::is_empty(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Len for &mut Cow<'a, [T]>
|
||||
where
|
||||
[T]: ToOwned,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn len(&self) -> usize {
|
||||
Len::len(*self)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_empty(&self) -> bool {
|
||||
Len::is_empty(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Len for VecDeque<T> {
|
||||
#[inline(always)]
|
||||
fn len(&self) -> usize {
|
||||
<VecDeque<T>>::len(self)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_empty(&self) -> bool {
|
||||
<VecDeque<T>>::is_empty(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Len for &VecDeque<T> {
|
||||
#[inline(always)]
|
||||
fn len(&self) -> usize {
|
||||
Len::len(*self)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_empty(&self) -> bool {
|
||||
Len::is_empty(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Len for &mut VecDeque<T> {
|
||||
#[inline(always)]
|
||||
fn len(&self) -> usize {
|
||||
Len::len(&**self)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_empty(&self) -> bool {
|
||||
Len::is_empty(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Len for LinkedList<T> {
|
||||
#[inline(always)]
|
||||
fn len(&self) -> usize {
|
||||
<LinkedList<T>>::len(self)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_empty(&self) -> bool {
|
||||
<LinkedList<T>>::is_empty(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Len for &LinkedList<T> {
|
||||
#[inline(always)]
|
||||
fn len(&self) -> usize {
|
||||
Len::len(*self)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_empty(&self) -> bool {
|
||||
Len::is_empty(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Len for &mut LinkedList<T> {
|
||||
#[inline(always)]
|
||||
fn len(&self) -> usize {
|
||||
Len::len(&**self)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_empty(&self) -> bool {
|
||||
Len::is_empty(*self)
|
||||
}
|
||||
}
|
||||
@@ -269,6 +269,7 @@ mod deref;
|
||||
mod field;
|
||||
mod iter;
|
||||
mod keyed;
|
||||
mod len;
|
||||
mod option;
|
||||
mod patch;
|
||||
mod path;
|
||||
@@ -280,6 +281,7 @@ pub use deref::*;
|
||||
pub use field::Field;
|
||||
pub use iter::*;
|
||||
pub use keyed::*;
|
||||
pub use len::Len;
|
||||
pub use option::*;
|
||||
pub use patch::*;
|
||||
pub use path::{StorePath, StorePathSegment};
|
||||
|
||||
@@ -100,6 +100,9 @@ where
|
||||
|
||||
let mut full_path = self.path().into_iter().collect::<StorePath>();
|
||||
full_path.pop();
|
||||
|
||||
// build a list of triggers, starting with the full path to this node and ending with the root
|
||||
// this will mean that the root is the final item, and this path is first
|
||||
let mut triggers = Vec::with_capacity(full_path.len());
|
||||
triggers.push(trigger.this.clone());
|
||||
loop {
|
||||
@@ -110,6 +113,17 @@ where
|
||||
}
|
||||
full_path.pop();
|
||||
}
|
||||
|
||||
// when the WriteGuard is dropped, each trigger will be notified, in order
|
||||
// reversing the list will cause the triggers to be notified starting from the root,
|
||||
// then to each child down to this one
|
||||
//
|
||||
// notifying from the root down is important for things like OptionStoreExt::map()/unwrap(),
|
||||
// where it's really important that any effects that subscribe to .is_some() run before effects
|
||||
// that subscribe to the inner value, so that the inner effect can be canceled if the outer switches to `None`
|
||||
// (see https://github.com/leptos-rs/leptos/issues/3704)
|
||||
triggers.reverse();
|
||||
|
||||
let guard = WriteGuard::new(triggers, parent);
|
||||
|
||||
Some(MappedMut::new(guard, self.read, self.write))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_stores_macro"
|
||||
version = "0.2.0-alpha"
|
||||
version = "0.2.0-beta"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -604,9 +604,9 @@ impl ToTokens for PatchModel {
|
||||
let Field {
|
||||
attrs, ident, ..
|
||||
} = &field;
|
||||
let field_name = match &ident {
|
||||
Some(ident) => quote! { #ident },
|
||||
None => quote! { #idx },
|
||||
let locator = match &ident {
|
||||
Some(ident) => Either::Left(ident),
|
||||
None => Either::Right(Index::from(idx)),
|
||||
};
|
||||
let closure = attrs
|
||||
.iter()
|
||||
@@ -639,9 +639,9 @@ impl ToTokens for PatchModel {
|
||||
let params = closure.inputs;
|
||||
let body = closure.body;
|
||||
quote! {
|
||||
if new.#field_name != self.#field_name {
|
||||
if new.#locator != self.#locator {
|
||||
_ = {
|
||||
let (#params) = (&mut self.#field_name, new.#field_name);
|
||||
let (#params) = (&mut self.#locator, new.#locator);
|
||||
#body
|
||||
};
|
||||
notify(&new_path);
|
||||
@@ -651,8 +651,8 @@ impl ToTokens for PatchModel {
|
||||
} else {
|
||||
quote! {
|
||||
#library_path::PatchField::patch_field(
|
||||
&mut self.#field_name,
|
||||
new.#field_name,
|
||||
&mut self.#locator,
|
||||
new.#locator,
|
||||
&new_path,
|
||||
notify
|
||||
);
|
||||
@@ -684,3 +684,17 @@ impl ToTokens for PatchModel {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
enum Either<A, B> {
|
||||
Left(A),
|
||||
Right(B),
|
||||
}
|
||||
|
||||
impl<A: ToTokens, B: ToTokens> ToTokens for Either<A, B> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
match self {
|
||||
Either::Left(a) => a.to_tokens(tokens),
|
||||
Either::Right(b) => b.to_tokens(tokens),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_router"
|
||||
version = "0.8.0-alpha"
|
||||
version = "0.8.0-beta"
|
||||
authors = ["Greg Johnston", "Ben Wishovich"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -655,8 +655,10 @@ where
|
||||
ScopedFuture::new(view.choose()),
|
||||
);
|
||||
let view = view.await;
|
||||
let view =
|
||||
MatchedRoute(matched.0.get(), view);
|
||||
let view = MatchedRoute(
|
||||
matched.0.get_untracked(),
|
||||
view,
|
||||
);
|
||||
OwnedView::new(view).into_any()
|
||||
})
|
||||
as Pin<
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_router_macro"
|
||||
version = "0.8.0-alpha"
|
||||
version = "0.8.0-beta"
|
||||
authors = ["Greg Johnston", "Ben Wishovich"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tachys"
|
||||
version = "0.2.0-alpha"
|
||||
version = "0.2.0-beta"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -8,7 +8,6 @@ pub mod custom;
|
||||
pub mod global;
|
||||
mod key;
|
||||
pub(crate) mod maybe_next_attr_erasure_macros;
|
||||
pub(crate) mod panic_on_clone_attribute;
|
||||
mod value;
|
||||
|
||||
use crate::view::{Position, ToTemplate};
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
use super::{Attribute, NextAttribute};
|
||||
|
||||
/// When type erasing with `AnyAttribute`, the underling attribute must be cloneable.
|
||||
///
|
||||
/// For most this is possible, but for some like `NodeRef` it is not.
|
||||
///
|
||||
/// This allows for a panic to be thrown if a non-cloneable attribute is cloned, whilst still seeming like it can be cloned.
|
||||
pub struct PanicOnCloneAttr<T: Attribute + 'static> {
|
||||
msg: &'static str,
|
||||
attr: T,
|
||||
}
|
||||
|
||||
impl<T: Attribute + 'static> PanicOnCloneAttr<T> {
|
||||
pub(crate) fn new(attr: T, msg: &'static str) -> Self {
|
||||
Self { msg, attr }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Attribute + 'static> Clone for PanicOnCloneAttr<T> {
|
||||
fn clone(&self) -> Self {
|
||||
panic!("{}", self.msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Attribute + 'static> NextAttribute for PanicOnCloneAttr<T> {
|
||||
type Output<NewAttr: Attribute> = <T as NextAttribute>::Output<NewAttr>;
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute>(
|
||||
self,
|
||||
new_attr: NewAttr,
|
||||
) -> Self::Output<NewAttr> {
|
||||
self.attr.add_any_attr(new_attr)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Attribute + 'static> Attribute for PanicOnCloneAttr<T> {
|
||||
const MIN_LENGTH: usize = T::MIN_LENGTH;
|
||||
|
||||
type State = T::State;
|
||||
type AsyncOutput = T::AsyncOutput;
|
||||
type Cloneable = Self;
|
||||
type CloneableOwned = Self;
|
||||
|
||||
fn html_len(&self) -> usize {
|
||||
self.attr.html_len()
|
||||
}
|
||||
|
||||
fn to_html(
|
||||
self,
|
||||
buf: &mut String,
|
||||
class: &mut String,
|
||||
style: &mut String,
|
||||
inner_html: &mut String,
|
||||
) {
|
||||
self.attr.to_html(buf, class, style, inner_html)
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
el: &crate::renderer::types::Element,
|
||||
) -> Self::State {
|
||||
self.attr.hydrate::<FROM_SERVER>(el)
|
||||
}
|
||||
|
||||
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
|
||||
self.attr.build(el)
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
self.attr.rebuild(state)
|
||||
}
|
||||
|
||||
fn into_cloneable(self) -> Self::Cloneable {
|
||||
self
|
||||
}
|
||||
|
||||
fn into_cloneable_owned(self) -> Self::CloneableOwned {
|
||||
self
|
||||
}
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
self.attr.dry_resolve()
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
self.attr.resolve().await
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::{
|
||||
attribute::{
|
||||
maybe_next_attr_erasure_macros::next_attr_output_type,
|
||||
panic_on_clone_attribute::PanicOnCloneAttr, Attribute, NextAttribute,
|
||||
maybe_next_attr_erasure_macros::next_attr_output_type, Attribute,
|
||||
NextAttribute,
|
||||
},
|
||||
element::ElementType,
|
||||
};
|
||||
@@ -65,8 +65,8 @@ where
|
||||
const MIN_LENGTH: usize = 0;
|
||||
type AsyncOutput = Self;
|
||||
type State = crate::renderer::types::Element;
|
||||
type Cloneable = PanicOnCloneAttr<Self>;
|
||||
type CloneableOwned = PanicOnCloneAttr<Self>;
|
||||
type Cloneable = Self;
|
||||
type CloneableOwned = Self;
|
||||
|
||||
#[inline(always)]
|
||||
fn html_len(&self) -> usize {
|
||||
@@ -100,17 +100,11 @@ where
|
||||
}
|
||||
|
||||
fn into_cloneable(self) -> Self::Cloneable {
|
||||
PanicOnCloneAttr::new(
|
||||
self,
|
||||
"node_ref should not be spread across multiple elements.",
|
||||
)
|
||||
self
|
||||
}
|
||||
|
||||
fn into_cloneable_owned(self) -> Self::Cloneable {
|
||||
PanicOnCloneAttr::new(
|
||||
self,
|
||||
"node_ref should not be spread across multiple elements.",
|
||||
)
|
||||
self
|
||||
}
|
||||
|
||||
fn dry_resolve(&mut self) {}
|
||||
|
||||
Reference in New Issue
Block a user