mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-27 16:54:41 -05:00
Compare commits
24 Commits
ci
...
remove-any
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0375df5431 | ||
|
|
6206073c5e | ||
|
|
3d2cdc21a1 | ||
|
|
93d939aef8 | ||
|
|
fb04750607 | ||
|
|
a080496e7e | ||
|
|
9fc1002167 | ||
|
|
bc5c766530 | ||
|
|
17821f863a | ||
|
|
1ca4f34ef3 | ||
|
|
8f0a1554b1 | ||
|
|
38d4f26d03 | ||
|
|
2b04c2710d | ||
|
|
a4937a1236 | ||
|
|
f6f2c39686 | ||
|
|
d7eacf1ab5 | ||
|
|
d1a4bbe28e | ||
|
|
412ecd6b1b | ||
|
|
9bc0152121 | ||
|
|
4b05cada8f | ||
|
|
a818862704 | ||
|
|
173487debc | ||
|
|
449d96cc9a | ||
|
|
f9bf6a95ed |
42
Cargo.toml
42
Cargo.toml
@@ -40,36 +40,36 @@ members = [
|
||||
exclude = ["benchmarks", "examples", "projects"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.7.0-beta5"
|
||||
version = "0.7.0-beta6"
|
||||
edition = "2021"
|
||||
rust-version = "1.76"
|
||||
|
||||
[workspace.dependencies]
|
||||
throw_error = { path = "./any_error/", version = "0.2.0-beta5" }
|
||||
throw_error = { path = "./any_error/", version = "0.2.0-beta6" }
|
||||
any_spawner = { path = "./any_spawner/", version = "0.1.0" }
|
||||
const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1.0" }
|
||||
either_of = { path = "./either_of/", version = "0.1.0" }
|
||||
hydration_context = { path = "./hydration_context", version = "0.2.0-beta5" }
|
||||
leptos = { path = "./leptos", version = "0.7.0-beta5" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.7.0-beta5" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.7.0-beta5" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.0-beta5" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.0-beta5" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.7.0-beta5" }
|
||||
leptos_router = { path = "./router", version = "0.7.0-beta5" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.7.0-beta5" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.7.0-beta5" }
|
||||
leptos_meta = { path = "./meta", version = "0.7.0-beta5" }
|
||||
next_tuple = { path = "./next_tuple", version = "0.1.0-beta5" }
|
||||
hydration_context = { path = "./hydration_context", version = "0.2.0-beta6" }
|
||||
leptos = { path = "./leptos", version = "0.7.0-beta6" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.7.0-beta6" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.7.0-beta6" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.0-beta6" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.0-beta6" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.7.0-beta6" }
|
||||
leptos_router = { path = "./router", version = "0.7.0-beta6" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.7.0-beta6" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.7.0-beta6" }
|
||||
leptos_meta = { path = "./meta", version = "0.7.0-beta6" }
|
||||
next_tuple = { path = "./next_tuple", version = "0.1.0-beta6" }
|
||||
oco_ref = { path = "./oco", version = "0.2.0" }
|
||||
or_poisoned = { path = "./or_poisoned", version = "0.1.0" }
|
||||
reactive_graph = { path = "./reactive_graph", version = "0.1.0-beta5" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.1.0-beta5" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.0-beta5" }
|
||||
server_fn = { path = "./server_fn", version = "0.7.0-beta5" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.7.0-beta5" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.0-beta5" }
|
||||
tachys = { path = "./tachys", version = "0.1.0-beta5" }
|
||||
reactive_graph = { path = "./reactive_graph", version = "0.1.0-beta6" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.1.0-beta6" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.0-beta6" }
|
||||
server_fn = { path = "./server_fn", version = "0.7.0-beta6" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.7.0-beta6" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.0-beta6" }
|
||||
tachys = { path = "./tachys", version = "0.1.0-beta6" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "throw_error"
|
||||
version = "0.2.0-beta5"
|
||||
version = "0.2.0-beta6"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -2,8 +2,8 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use chrono::{Local, NaiveDate};
|
||||
use leptos::prelude::*;
|
||||
use reactive_stores::{Field, Store};
|
||||
use reactive_stores_macro::Store;
|
||||
use reactive_stores::{Field, Patch, Store};
|
||||
use reactive_stores_macro::{Patch, Store};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// ID starts higher than 0 because we have a few starting todos by default
|
||||
@@ -11,11 +11,17 @@ static NEXT_ID: AtomicUsize = AtomicUsize::new(3);
|
||||
|
||||
#[derive(Debug, Store, Serialize, Deserialize)]
|
||||
struct Todos {
|
||||
user: String,
|
||||
user: User,
|
||||
#[store(key: usize = |todo| todo.id)]
|
||||
todos: Vec<Todo>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Store, Patch, Serialize, Deserialize)]
|
||||
struct User {
|
||||
name: String,
|
||||
email: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Store, Serialize, Deserialize)]
|
||||
struct Todo {
|
||||
id: usize,
|
||||
@@ -58,7 +64,10 @@ impl Todo {
|
||||
|
||||
fn data() -> Todos {
|
||||
Todos {
|
||||
user: "Bob".to_string(),
|
||||
user: User {
|
||||
name: "Bob".to_string(),
|
||||
email: "lawblog@bobloblaw.com".into(),
|
||||
},
|
||||
todos: vec![
|
||||
Todo {
|
||||
id: 0,
|
||||
@@ -86,7 +95,9 @@ pub fn App() -> impl IntoView {
|
||||
let input_ref = NodeRef::new();
|
||||
|
||||
view! {
|
||||
<p>"Hello, " {move || store.user().get()}</p>
|
||||
<p>"Hello, " {move || store.user().name().get()}</p>
|
||||
<UserForm user=store.user()/>
|
||||
<hr/>
|
||||
<form on:submit=move |ev| {
|
||||
ev.prevent_default();
|
||||
store.todos().write().push(Todo::new(input_ref.get().unwrap().value()));
|
||||
@@ -98,7 +109,15 @@ pub fn App() -> impl IntoView {
|
||||
// because `todos` is a keyed field, `store.todos()` returns a struct that
|
||||
// directly implements IntoIterator, so we can use it in <For/> and
|
||||
// it will manage reactivity for the store fields correctly
|
||||
<For each=move || store.todos() key=|row| row.id().get() let:todo>
|
||||
<For
|
||||
each=move || {
|
||||
leptos::logging::log!("RERUNNING FOR CALCULATION");
|
||||
store.todos()
|
||||
}
|
||||
|
||||
key=|row| row.id().get()
|
||||
let:todo
|
||||
>
|
||||
<TodoRow store todo/>
|
||||
</For>
|
||||
|
||||
@@ -107,6 +126,33 @@ pub fn App() -> impl IntoView {
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn UserForm(#[prop(into)] user: Field<User>) -> impl IntoView {
|
||||
let error = RwSignal::new(None);
|
||||
|
||||
view! {
|
||||
{move || error.get().map(|n| view! { <p>{n}</p> })}
|
||||
<form on:submit:target=move |ev| {
|
||||
ev.prevent_default();
|
||||
match User::from_event(&ev) {
|
||||
Ok(new_user) => {
|
||||
error.set(None);
|
||||
user.patch(new_user);
|
||||
}
|
||||
Err(e) => error.set(Some(e.to_string())),
|
||||
}
|
||||
}>
|
||||
<label>
|
||||
"Name" <input type="text" name="name" prop:value=move || user.name().get()/>
|
||||
</label>
|
||||
<label>
|
||||
"Email" <input type="email" name="email" prop:value=move || user.email().get()/>
|
||||
</label>
|
||||
<input type="submit"/>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn TodoRow(
|
||||
store: Store<Todos>,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "hydration_context"
|
||||
version = "0.2.0-beta5"
|
||||
version = "0.2.0-beta6"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_macro"
|
||||
version = "0.7.0-beta5"
|
||||
version = "0.7.0-beta6"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
|
||||
@@ -347,41 +347,60 @@ impl ToTokens for Model {
|
||||
let island_props = if is_island_with_children
|
||||
|| is_island_with_other_props
|
||||
{
|
||||
let (destructure, prop_builders) = if is_island_with_other_props
|
||||
{
|
||||
let prop_names = props
|
||||
.iter()
|
||||
.filter_map(|prop| {
|
||||
if prop.name.ident == "children" {
|
||||
None
|
||||
} else {
|
||||
let name = &prop.name.ident;
|
||||
Some(quote! { #name, })
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
let destructure = quote! {
|
||||
let #props_serialized_name {
|
||||
#prop_names
|
||||
} = props;
|
||||
let (destructure, prop_builders, optional_props) =
|
||||
if is_island_with_other_props {
|
||||
let prop_names = props
|
||||
.iter()
|
||||
.filter_map(|prop| {
|
||||
if prop.name.ident == "children" {
|
||||
None
|
||||
} else {
|
||||
let name = &prop.name.ident;
|
||||
Some(quote! { #name, })
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
let destructure = quote! {
|
||||
let #props_serialized_name {
|
||||
#prop_names
|
||||
} = props;
|
||||
};
|
||||
let prop_builders = props
|
||||
.iter()
|
||||
.filter_map(|prop| {
|
||||
if prop.name.ident == "children"
|
||||
|| prop.prop_opts.optional
|
||||
{
|
||||
None
|
||||
} else {
|
||||
let name = &prop.name.ident;
|
||||
Some(quote! {
|
||||
.#name(#name)
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
let optional_props = props
|
||||
.iter()
|
||||
.filter_map(|prop| {
|
||||
if prop.name.ident == "children"
|
||||
|| !prop.prop_opts.optional
|
||||
{
|
||||
None
|
||||
} else {
|
||||
let name = &prop.name.ident;
|
||||
Some(quote! {
|
||||
if let Some(#name) = #name {
|
||||
props.#name = Some(#name)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
(destructure, prop_builders, optional_props)
|
||||
} else {
|
||||
(quote! {}, quote! {}, quote! {})
|
||||
};
|
||||
let prop_builders = props
|
||||
.iter()
|
||||
.filter_map(|prop| {
|
||||
if prop.name.ident == "children" {
|
||||
None
|
||||
} else {
|
||||
let name = &prop.name.ident;
|
||||
Some(quote! {
|
||||
.#name(#name)
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
(destructure, prop_builders)
|
||||
} else {
|
||||
(quote! {}, quote! {})
|
||||
};
|
||||
let children = if is_island_with_children {
|
||||
quote! {
|
||||
.children({Box::new(|| {
|
||||
@@ -405,10 +424,14 @@ impl ToTokens for Model {
|
||||
|
||||
quote! {{
|
||||
#destructure
|
||||
#props_name::builder()
|
||||
let mut props = #props_name::builder()
|
||||
#prop_builders
|
||||
#children
|
||||
.build()
|
||||
.build();
|
||||
|
||||
#optional_props
|
||||
|
||||
props
|
||||
}}
|
||||
} else {
|
||||
quote! {}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_meta"
|
||||
version = "0.7.0-beta5"
|
||||
version = "0.7.0-beta6"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "next_tuple"
|
||||
version = "0.1.0-beta5"
|
||||
version = "0.1.0-beta6"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_graph"
|
||||
version = "0.1.0-beta5"
|
||||
version = "0.1.0-beta6"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
computed::{ArcMemo, Memo},
|
||||
diagnostics::is_suppressing_resource_load,
|
||||
owner::{FromLocal, LocalStorage, Storage, StoredValue, SyncStorage},
|
||||
owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage},
|
||||
signal::{ArcRwSignal, RwSignal},
|
||||
traits::{DefinedAt, Dispose, Get, GetUntracked, Update},
|
||||
unwrap_signal,
|
||||
@@ -575,7 +575,7 @@ where
|
||||
/// let action3 = Action::new(|input: &(usize, String)| async { todo!() });
|
||||
/// ```
|
||||
pub struct Action<I, O, S = SyncStorage> {
|
||||
inner: StoredValue<ArcAction<I, O>, S>,
|
||||
inner: ArenaItem<ArcAction<I, O>, S>,
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
@@ -639,7 +639,7 @@ where
|
||||
Fu: Future<Output = O> + Send + 'static,
|
||||
{
|
||||
Self {
|
||||
inner: StoredValue::new(ArcAction::new(action_fn)),
|
||||
inner: ArenaItem::new(ArcAction::new(action_fn)),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
@@ -664,9 +664,7 @@ where
|
||||
Fu: Future<Output = O> + Send + 'static,
|
||||
{
|
||||
Self {
|
||||
inner: StoredValue::new(ArcAction::new_with_value(
|
||||
value, action_fn,
|
||||
)),
|
||||
inner: ArenaItem::new(ArcAction::new_with_value(value, action_fn)),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
@@ -688,7 +686,7 @@ where
|
||||
Fu: Future<Output = O> + Send + 'static,
|
||||
{
|
||||
Self {
|
||||
inner: StoredValue::new_local(ArcAction::new_unsync(action_fn)),
|
||||
inner: ArenaItem::new_local(ArcAction::new_unsync(action_fn)),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
@@ -704,7 +702,7 @@ where
|
||||
Fu: Future<Output = O> + Send + 'static,
|
||||
{
|
||||
Self {
|
||||
inner: StoredValue::new_local(ArcAction::new_unsync_with_value(
|
||||
inner: ArenaItem::new_local(ArcAction::new_unsync_with_value(
|
||||
value, action_fn,
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -908,7 +906,9 @@ where
|
||||
/// Calls the `async` function with a reference to the input type as its argument.
|
||||
#[track_caller]
|
||||
pub fn dispatch(&self, input: I) -> ActionAbortHandle {
|
||||
self.inner.with_value(|inner| inner.dispatch(input))
|
||||
self.inner
|
||||
.try_with_value(|inner| inner.dispatch(input))
|
||||
.unwrap_or_else(unwrap_signal!(self))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -921,7 +921,9 @@ where
|
||||
/// Calls the `async` function with a reference to the input type as its argument.
|
||||
#[track_caller]
|
||||
pub fn dispatch_local(&self, input: I) -> ActionAbortHandle {
|
||||
self.inner.with_value(|inner| inner.dispatch_local(input))
|
||||
self.inner
|
||||
.try_with_value(|inner| inner.dispatch_local(input))
|
||||
.unwrap_or_else(unwrap_signal!(self))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -942,7 +944,7 @@ where
|
||||
Fu: Future<Output = O> + 'static,
|
||||
{
|
||||
Self {
|
||||
inner: StoredValue::new_with_storage(ArcAction::new_unsync(
|
||||
inner: ArenaItem::new_with_storage(ArcAction::new_unsync(
|
||||
action_fn,
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -961,7 +963,7 @@ where
|
||||
Fu: Future<Output = O> + 'static,
|
||||
{
|
||||
Self {
|
||||
inner: StoredValue::new_with_storage(
|
||||
inner: ArenaItem::new_with_storage(
|
||||
ArcAction::new_unsync_with_value(value, action_fn),
|
||||
),
|
||||
#[cfg(debug_assertions)]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
diagnostics::is_suppressing_resource_load,
|
||||
owner::{FromLocal, LocalStorage, Storage, StoredValue, SyncStorage},
|
||||
owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage},
|
||||
signal::{ArcReadSignal, ArcRwSignal, ReadSignal, RwSignal},
|
||||
traits::{DefinedAt, Dispose, GetUntracked, Set, Update},
|
||||
unwrap_signal,
|
||||
@@ -45,7 +45,7 @@ use std::{fmt::Debug, future::Future, panic::Location, pin::Pin, sync::Arc};
|
||||
/// # });
|
||||
/// ```
|
||||
pub struct MultiAction<I, O, S = SyncStorage> {
|
||||
inner: StoredValue<ArcMultiAction<I, O>, S>,
|
||||
inner: ArenaItem<ArcMultiAction<I, O>, S>,
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
@@ -129,9 +129,7 @@ where
|
||||
Fut: Future<Output = O> + Send + 'static,
|
||||
{
|
||||
Self {
|
||||
inner: StoredValue::new_with_storage(ArcMultiAction::new(
|
||||
action_fn,
|
||||
)),
|
||||
inner: ArenaItem::new_with_storage(ArcMultiAction::new(action_fn)),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
@@ -189,7 +187,7 @@ where
|
||||
/// ```
|
||||
pub fn dispatch(&self, input: I) {
|
||||
if !is_suppressing_resource_load() {
|
||||
self.inner.with_value(|inner| inner.dispatch(input));
|
||||
self.inner.try_with_value(|inner| inner.dispatch(input));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,7 +230,8 @@ where
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn dispatch_sync(&self, value: O) {
|
||||
self.inner.with_value(|inner| inner.dispatch_sync(value));
|
||||
self.inner
|
||||
.try_with_value(|inner| inner.dispatch_sync(value));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
AnySource, AnySubscriber, ReactiveNode, Source, Subscriber,
|
||||
ToAnySource, ToAnySubscriber,
|
||||
},
|
||||
owner::{FromLocal, LocalStorage, Storage, StoredValue, SyncStorage},
|
||||
owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage},
|
||||
signal::guards::{AsyncPlain, ReadGuard, WriteGuard},
|
||||
traits::{
|
||||
DefinedAt, Dispose, IsDisposed, Notify, ReadUntracked,
|
||||
@@ -85,7 +85,7 @@ use std::{future::Future, ops::DerefMut, panic::Location};
|
||||
pub struct AsyncDerived<T, S = SyncStorage> {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
pub(crate) inner: StoredValue<ArcAsyncDerived<T>, S>,
|
||||
pub(crate) inner: ArenaItem<ArcAsyncDerived<T>, S>,
|
||||
}
|
||||
|
||||
impl<T, S> Dispose for AsyncDerived<T, S> {
|
||||
@@ -104,7 +104,7 @@ where
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at,
|
||||
inner: StoredValue::new_with_storage(value),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,7 +119,7 @@ where
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at,
|
||||
inner: StoredValue::new_with_storage(value),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,7 +141,7 @@ where
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(ArcAsyncDerived::new(fun)),
|
||||
inner: ArenaItem::new_with_storage(ArcAsyncDerived::new(fun)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ where
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(
|
||||
inner: ArenaItem::new_with_storage(
|
||||
ArcAsyncDerived::new_with_initial(initial_value, fun),
|
||||
),
|
||||
}
|
||||
@@ -176,9 +176,7 @@ impl<T> AsyncDerived<SendWrapper<T>> {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(ArcAsyncDerived::new_mock(
|
||||
fun,
|
||||
)),
|
||||
inner: ArenaItem::new_with_storage(ArcAsyncDerived::new_mock(fun)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,7 +198,7 @@ where
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(ArcAsyncDerived::new_unsync(
|
||||
inner: ArenaItem::new_with_storage(ArcAsyncDerived::new_unsync(
|
||||
fun,
|
||||
)),
|
||||
}
|
||||
@@ -221,7 +219,7 @@ where
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(
|
||||
inner: ArenaItem::new_with_storage(
|
||||
ArcAsyncDerived::new_unsync_with_initial(initial_value, fun),
|
||||
),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::{inner::MemoInner, ArcMemo};
|
||||
use crate::{
|
||||
owner::{FromLocal, LocalStorage, Storage, StoredValue, SyncStorage},
|
||||
owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage},
|
||||
signal::{
|
||||
guards::{Mapped, Plain, ReadGuard},
|
||||
ArcReadSignal,
|
||||
@@ -102,7 +102,7 @@ where
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
inner: StoredValue<ArcMemo<T, S>, S>,
|
||||
inner: ArenaItem<ArcMemo<T, S>, S>,
|
||||
}
|
||||
|
||||
impl<T, S> Dispose for Memo<T, S>
|
||||
@@ -123,7 +123,7 @@ where
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(value),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,7 +137,7 @@ where
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(value),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -177,7 +177,7 @@ where
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(ArcMemo::new(fun)),
|
||||
inner: ArenaItem::new_with_storage(ArcMemo::new(fun)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ where
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(ArcMemo::new_with_compare(
|
||||
inner: ArenaItem::new_with_storage(ArcMemo::new_with_compare(
|
||||
fun, changed,
|
||||
)),
|
||||
}
|
||||
@@ -229,7 +229,7 @@ where
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(ArcMemo::new_owning(fun)),
|
||||
inner: ArenaItem::new_with_storage(ArcMemo::new_owning(fun)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::{
|
||||
AnySubscriber, ReactiveNode, SourceSet, Subscriber, ToAnySubscriber,
|
||||
WithObserver,
|
||||
},
|
||||
owner::{LocalStorage, Owner, Storage, StoredValue, SyncStorage},
|
||||
owner::{ArenaItem, LocalStorage, Owner, Storage, SyncStorage},
|
||||
traits::Dispose,
|
||||
};
|
||||
use any_spawner::Executor;
|
||||
@@ -40,7 +40,7 @@ use std::{
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::effect::Effect;
|
||||
/// # use reactive_graph::owner::StoredValue;
|
||||
/// # use reactive_graph::owner::ArenaItem;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # tokio::task::LocalSet::new().run_until(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
@@ -78,7 +78,7 @@ use std::{
|
||||
/// If you need an effect to run on the server, use [`Effect::new_isomorphic`].
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Effect<S> {
|
||||
inner: Option<StoredValue<StoredEffect, S>>,
|
||||
inner: Option<ArenaItem<StoredEffect, S>>,
|
||||
}
|
||||
|
||||
type StoredEffect = Option<Arc<RwLock<EffectInner>>>;
|
||||
@@ -165,7 +165,7 @@ impl Effect<LocalStorage> {
|
||||
}
|
||||
});
|
||||
|
||||
StoredValue::new_with_storage(Some(inner))
|
||||
ArenaItem::new_with_storage(Some(inner))
|
||||
});
|
||||
|
||||
Self { inner }
|
||||
@@ -334,7 +334,7 @@ impl Effect<LocalStorage> {
|
||||
}
|
||||
});
|
||||
|
||||
StoredValue::new_with_storage(Some(inner))
|
||||
ArenaItem::new_with_storage(Some(inner))
|
||||
});
|
||||
|
||||
Self { inner }
|
||||
@@ -383,7 +383,7 @@ impl Effect<SyncStorage> {
|
||||
}
|
||||
});
|
||||
|
||||
StoredValue::new_with_storage(Some(inner))
|
||||
ArenaItem::new_with_storage(Some(inner))
|
||||
});
|
||||
|
||||
Self { inner }
|
||||
@@ -430,7 +430,7 @@ impl Effect<SyncStorage> {
|
||||
crate::spawn(task);
|
||||
|
||||
Self {
|
||||
inner: Some(StoredValue::new_with_storage(Some(inner))),
|
||||
inner: Some(ArenaItem::new_with_storage(Some(inner))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,7 +498,7 @@ impl Effect<SyncStorage> {
|
||||
}
|
||||
});
|
||||
|
||||
StoredValue::new_with_storage(Some(inner))
|
||||
ArenaItem::new_with_storage(Some(inner))
|
||||
});
|
||||
|
||||
Self { inner }
|
||||
|
||||
@@ -13,24 +13,25 @@ use std::{
|
||||
};
|
||||
|
||||
mod arena;
|
||||
mod arena_item;
|
||||
mod context;
|
||||
mod storage;
|
||||
mod stored_value;
|
||||
use self::arena::Arena;
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
pub use arena::sandboxed::Sandboxed;
|
||||
use arena::NodeId;
|
||||
pub use arena_item::*;
|
||||
pub use context::*;
|
||||
pub use storage::*;
|
||||
#[allow(deprecated)] // allow exporting deprecated fn
|
||||
pub use stored_value::{
|
||||
store_value, FromLocal, LocalStorage, Storage, StorageAccess, StoredValue,
|
||||
SyncStorage,
|
||||
};
|
||||
pub use stored_value::{store_value, FromLocal, StoredValue};
|
||||
|
||||
/// A reactive owner, which manages
|
||||
/// 1) the cancelation of [`Effect`](crate::effect::Effect)s,
|
||||
/// 2) providing and accessing environment data via [`provide_context`] and [`use_context`],
|
||||
/// 3) running cleanup functions defined via [`Owner::on_cleanup`], and
|
||||
/// 4) an arena storage system to provide `Copy` handles via [`StoredValue`], which is what allows
|
||||
/// 4) an arena storage system to provide `Copy` handles via [`ArenaItem`], which is what allows
|
||||
/// types like [`RwSignal`](crate::signal::RwSignal), [`Memo`](crate::computed::Memo), and so on to be `Copy`.
|
||||
///
|
||||
/// Every effect and computed reactive value has an associated `Owner`. While it is running, this
|
||||
@@ -209,7 +210,7 @@ impl Owner {
|
||||
/// Cleans up this owner in the following order:
|
||||
/// 1) Runs `cleanup` on all children,
|
||||
/// 2) Runs all cleanup functions registered with [`Owner::on_cleanup`],
|
||||
/// 3) Drops the values of any arena-allocated [`StoredValue`]s.
|
||||
/// 3) Drops the values of any arena-allocated [`ArenaItem`]s.
|
||||
pub fn cleanup(&self) {
|
||||
self.inner.cleanup();
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ pub mod sandboxed {
|
||||
}
|
||||
|
||||
impl<T> Sandboxed<T> {
|
||||
/// Wraps the given [`Future`], ensuring that any [`StoredValue`] created while it is being
|
||||
/// Wraps the given [`Future`], ensuring that any [`ArenaItem`] created while it is being
|
||||
/// polled will be associated with the same arena that was active when this was called.
|
||||
pub fn new(inner: T) -> Self {
|
||||
let arena = MAP.with_borrow(|current| {
|
||||
|
||||
136
reactive_graph/src/owner/arena_item.rs
Normal file
136
reactive_graph/src/owner/arena_item.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
use super::{
|
||||
arena::{Arena, NodeId},
|
||||
LocalStorage, Storage, SyncStorage, OWNER,
|
||||
};
|
||||
use crate::traits::{Dispose, IsDisposed};
|
||||
use send_wrapper::SendWrapper;
|
||||
use std::{any::Any, hash::Hash, marker::PhantomData};
|
||||
|
||||
/// A copyable, stable reference for any value, stored on the arena whose ownership is managed by the
|
||||
/// reactive ownership tree.
|
||||
#[derive(Debug)]
|
||||
pub struct ArenaItem<T, S = SyncStorage> {
|
||||
node: NodeId,
|
||||
#[allow(clippy::type_complexity)]
|
||||
ty: PhantomData<fn() -> (SendWrapper<T>, S)>,
|
||||
}
|
||||
|
||||
impl<T, S> Copy for ArenaItem<T, S> {}
|
||||
|
||||
impl<T, S> Clone for ArenaItem<T, S> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> PartialEq for ArenaItem<T, S> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.node == other.node
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> Eq for ArenaItem<T, S> {}
|
||||
|
||||
impl<T, S> Hash for ArenaItem<T, S> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.node.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> ArenaItem<T, S>
|
||||
where
|
||||
T: 'static,
|
||||
S: Storage<T>,
|
||||
{
|
||||
/// Stores the given value in the arena allocator.
|
||||
#[track_caller]
|
||||
pub fn new_with_storage(value: T) -> Self {
|
||||
let node = {
|
||||
Arena::with_mut(|arena| {
|
||||
arena.insert(
|
||||
Box::new(S::wrap(value)) as Box<dyn Any + Send + Sync>
|
||||
)
|
||||
})
|
||||
};
|
||||
OWNER.with(|o| {
|
||||
if let Some(owner) = &*o.borrow() {
|
||||
owner.register(node);
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
node,
|
||||
ty: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> Default for ArenaItem<T, S>
|
||||
where
|
||||
T: Default + 'static,
|
||||
S: Storage<T>,
|
||||
{
|
||||
#[track_caller] // Default trait is not annotated with #[track_caller]
|
||||
fn default() -> Self {
|
||||
Self::new_with_storage(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ArenaItem<T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
/// Stores the given value in the arena allocator.
|
||||
#[track_caller]
|
||||
pub fn new(value: T) -> Self {
|
||||
ArenaItem::new_with_storage(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ArenaItem<T, LocalStorage>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
/// Stores the given value in the arena allocator.
|
||||
#[track_caller]
|
||||
pub fn new_local(value: T) -> Self {
|
||||
ArenaItem::new_with_storage(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S: Storage<T>> ArenaItem<T, S> {
|
||||
/// Applies a function to a reference to the stored value and returns the result, or `None` if it has already been disposed.
|
||||
#[track_caller]
|
||||
pub fn try_with_value<U>(&self, fun: impl FnOnce(&T) -> U) -> Option<U> {
|
||||
S::try_with(self.node, fun)
|
||||
}
|
||||
|
||||
/// Applies a function to a mutable reference to the stored value and returns the result, or `None` if it has already been disposed.
|
||||
#[track_caller]
|
||||
pub fn try_update_value<U>(
|
||||
&self,
|
||||
fun: impl FnOnce(&mut T) -> U,
|
||||
) -> Option<U> {
|
||||
S::try_with_mut(self.node, fun)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone, S: Storage<T>> ArenaItem<T, S> {
|
||||
/// Returns a clone of the stored value, or `None` if it has already been disposed.
|
||||
#[track_caller]
|
||||
pub fn try_get_value(&self) -> Option<T> {
|
||||
S::try_with(self.node, Clone::clone)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> IsDisposed for ArenaItem<T, S> {
|
||||
fn is_disposed(&self) -> bool {
|
||||
Arena::with(|arena| !arena.contains_key(self.node))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> Dispose for ArenaItem<T, S> {
|
||||
fn dispose(self) {
|
||||
Arena::with_mut(|arena| arena.remove(self.node));
|
||||
}
|
||||
}
|
||||
151
reactive_graph/src/owner/storage.rs
Normal file
151
reactive_graph/src/owner/storage.rs
Normal file
@@ -0,0 +1,151 @@
|
||||
use super::arena::{Arena, NodeId};
|
||||
use send_wrapper::SendWrapper;
|
||||
|
||||
/// A trait for borrowing and taking data.
|
||||
pub trait StorageAccess<T> {
|
||||
/// Borrows the value.
|
||||
fn as_borrowed(&self) -> &T;
|
||||
|
||||
/// Takes the value.
|
||||
fn into_taken(self) -> T;
|
||||
}
|
||||
|
||||
impl<T> StorageAccess<T> for T {
|
||||
fn as_borrowed(&self) -> &T {
|
||||
self
|
||||
}
|
||||
|
||||
fn into_taken(self) -> T {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> StorageAccess<T> for SendWrapper<T> {
|
||||
fn as_borrowed(&self) -> &T {
|
||||
self
|
||||
}
|
||||
|
||||
fn into_taken(self) -> T {
|
||||
self.take()
|
||||
}
|
||||
}
|
||||
|
||||
/// A way of storing a [`ArenaItem`], either as itself or with a wrapper to make it threadsafe.
|
||||
///
|
||||
/// This exists because all items stored in the arena must be `Send + Sync`, but in single-threaded
|
||||
/// environments you might want or need to use thread-unsafe types.
|
||||
pub trait Storage<T>: Send + Sync + 'static {
|
||||
/// The type being stored, once it has been wrapped.
|
||||
type Wrapped: StorageAccess<T> + Send + Sync + 'static;
|
||||
|
||||
/// Adds any needed wrapper to the type.
|
||||
fn wrap(value: T) -> Self::Wrapped;
|
||||
|
||||
/// Applies the given function to the stored value, if it exists and can be accessed from this
|
||||
/// thread.
|
||||
fn try_with<U>(node: NodeId, fun: impl FnOnce(&T) -> U) -> Option<U>;
|
||||
|
||||
/// Applies the given function to a mutable reference to the stored value, if it exists and can be accessed from this
|
||||
/// thread.
|
||||
fn try_with_mut<U>(
|
||||
node: NodeId,
|
||||
fun: impl FnOnce(&mut T) -> U,
|
||||
) -> Option<U>;
|
||||
|
||||
/// Sets a new value for the stored value. If it has been disposed, returns `Some(T)`.
|
||||
fn try_set(node: NodeId, value: T) -> Option<T>;
|
||||
}
|
||||
|
||||
/// A form of [`Storage`] that stores the type as itself, with no wrapper.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct SyncStorage;
|
||||
|
||||
impl<T> Storage<T> for SyncStorage
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
type Wrapped = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn wrap(value: T) -> Self::Wrapped {
|
||||
value
|
||||
}
|
||||
|
||||
fn try_with<U>(node: NodeId, fun: impl FnOnce(&T) -> U) -> Option<U> {
|
||||
Arena::with(|arena| {
|
||||
let m = arena.get(node);
|
||||
m.and_then(|n| n.downcast_ref::<T>()).map(fun)
|
||||
})
|
||||
}
|
||||
|
||||
fn try_with_mut<U>(
|
||||
node: NodeId,
|
||||
fun: impl FnOnce(&mut T) -> U,
|
||||
) -> Option<U> {
|
||||
Arena::with_mut(|arena| {
|
||||
let m = arena.get_mut(node);
|
||||
m.and_then(|n| n.downcast_mut::<T>()).map(fun)
|
||||
})
|
||||
}
|
||||
|
||||
fn try_set(node: NodeId, value: T) -> Option<T> {
|
||||
Arena::with_mut(|arena| {
|
||||
let m = arena.get_mut(node);
|
||||
match m.and_then(|n| n.downcast_mut::<T>()) {
|
||||
Some(inner) => {
|
||||
*inner = value;
|
||||
None
|
||||
}
|
||||
None => Some(value),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A form of [`Storage`] that stores the type with a wrapper that makes it `Send + Sync`, but only
|
||||
/// allows it to be accessed from the thread on which it was created.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct LocalStorage;
|
||||
|
||||
impl<T> Storage<T> for LocalStorage
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
type Wrapped = SendWrapper<T>;
|
||||
|
||||
fn wrap(value: T) -> Self::Wrapped {
|
||||
SendWrapper::new(value)
|
||||
}
|
||||
|
||||
fn try_with<U>(node: NodeId, fun: impl FnOnce(&T) -> U) -> Option<U> {
|
||||
Arena::with(|arena| {
|
||||
let m = arena.get(node);
|
||||
m.and_then(|n| n.downcast_ref::<SendWrapper<T>>())
|
||||
.map(|inner| fun(inner))
|
||||
})
|
||||
}
|
||||
|
||||
fn try_with_mut<U>(
|
||||
node: NodeId,
|
||||
fun: impl FnOnce(&mut T) -> U,
|
||||
) -> Option<U> {
|
||||
Arena::with_mut(|arena| {
|
||||
let m = arena.get_mut(node);
|
||||
m.and_then(|n| n.downcast_mut::<SendWrapper<T>>())
|
||||
.map(|inner| fun(&mut *inner))
|
||||
})
|
||||
}
|
||||
|
||||
fn try_set(node: NodeId, value: T) -> Option<T> {
|
||||
Arena::with_mut(|arena| {
|
||||
let m = arena.get_mut(node);
|
||||
match m.and_then(|n| n.downcast_mut::<SendWrapper<T>>()) {
|
||||
Some(inner) => {
|
||||
*inner = SendWrapper::new(value);
|
||||
None
|
||||
}
|
||||
None => Some(value),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,162 +1,14 @@
|
||||
use super::{
|
||||
arena::{Arena, NodeId},
|
||||
OWNER,
|
||||
};
|
||||
use super::{ArenaItem, LocalStorage, Storage, SyncStorage};
|
||||
use crate::{
|
||||
traits::{DefinedAt, Dispose, IsDisposed},
|
||||
unwrap_signal,
|
||||
};
|
||||
use send_wrapper::SendWrapper;
|
||||
use std::{any::Any, hash::Hash, marker::PhantomData, panic::Location};
|
||||
|
||||
/// A trait for borrowing and taking data.
|
||||
pub trait StorageAccess<T> {
|
||||
/// Borrows the value.
|
||||
fn as_borrowed(&self) -> &T;
|
||||
|
||||
/// Takes the value.
|
||||
fn into_taken(self) -> T;
|
||||
}
|
||||
|
||||
impl<T> StorageAccess<T> for T {
|
||||
fn as_borrowed(&self) -> &T {
|
||||
self
|
||||
}
|
||||
|
||||
fn into_taken(self) -> T {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> StorageAccess<T> for SendWrapper<T> {
|
||||
fn as_borrowed(&self) -> &T {
|
||||
self
|
||||
}
|
||||
|
||||
fn into_taken(self) -> T {
|
||||
self.take()
|
||||
}
|
||||
}
|
||||
|
||||
/// A way of storing a [`StoredValue`], either as itself or with a wrapper to make it threadsafe.
|
||||
///
|
||||
/// This exists because all items stored in the arena must be `Send + Sync`, but in single-threaded
|
||||
/// environments you might want or need to use thread-unsafe types.
|
||||
pub trait Storage<T>: Send + Sync + 'static {
|
||||
/// The type being stored, once it has been wrapped.
|
||||
type Wrapped: StorageAccess<T> + Send + Sync + 'static;
|
||||
|
||||
/// Adds any needed wrapper to the type.
|
||||
fn wrap(value: T) -> Self::Wrapped;
|
||||
|
||||
/// Applies the given function to the stored value, if it exists and can be accessed from this
|
||||
/// thread.
|
||||
fn try_with<U>(node: NodeId, fun: impl FnOnce(&T) -> U) -> Option<U>;
|
||||
|
||||
/// Applies the given function to a mutable reference to the stored value, if it exists and can be accessed from this
|
||||
/// thread.
|
||||
fn try_with_mut<U>(
|
||||
node: NodeId,
|
||||
fun: impl FnOnce(&mut T) -> U,
|
||||
) -> Option<U>;
|
||||
|
||||
/// Sets a new value for the stored value. If it has been disposed, returns `Some(T)`.
|
||||
fn try_set(node: NodeId, value: T) -> Option<T>;
|
||||
}
|
||||
|
||||
/// A form of [`Storage`] that stores the type as itself, with no wrapper.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct SyncStorage;
|
||||
|
||||
impl<T> Storage<T> for SyncStorage
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
type Wrapped = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn wrap(value: T) -> Self::Wrapped {
|
||||
value
|
||||
}
|
||||
|
||||
fn try_with<U>(node: NodeId, fun: impl FnOnce(&T) -> U) -> Option<U> {
|
||||
Arena::with(|arena| {
|
||||
let m = arena.get(node);
|
||||
m.and_then(|n| n.downcast_ref::<T>()).map(fun)
|
||||
})
|
||||
}
|
||||
|
||||
fn try_with_mut<U>(
|
||||
node: NodeId,
|
||||
fun: impl FnOnce(&mut T) -> U,
|
||||
) -> Option<U> {
|
||||
Arena::with_mut(|arena| {
|
||||
let m = arena.get_mut(node);
|
||||
m.and_then(|n| n.downcast_mut::<T>()).map(fun)
|
||||
})
|
||||
}
|
||||
|
||||
fn try_set(node: NodeId, value: T) -> Option<T> {
|
||||
Arena::with_mut(|arena| {
|
||||
let m = arena.get_mut(node);
|
||||
match m.and_then(|n| n.downcast_mut::<T>()) {
|
||||
Some(inner) => {
|
||||
*inner = value;
|
||||
None
|
||||
}
|
||||
None => Some(value),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A form of [`Storage`] that stores the type with a wrapper that makes it `Send + Sync`, but only
|
||||
/// allows it to be accessed from the thread on which it was created.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct LocalStorage;
|
||||
|
||||
impl<T> Storage<T> for LocalStorage
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
type Wrapped = SendWrapper<T>;
|
||||
|
||||
fn wrap(value: T) -> Self::Wrapped {
|
||||
SendWrapper::new(value)
|
||||
}
|
||||
|
||||
fn try_with<U>(node: NodeId, fun: impl FnOnce(&T) -> U) -> Option<U> {
|
||||
Arena::with(|arena| {
|
||||
let m = arena.get(node);
|
||||
m.and_then(|n| n.downcast_ref::<SendWrapper<T>>())
|
||||
.map(|inner| fun(inner))
|
||||
})
|
||||
}
|
||||
|
||||
fn try_with_mut<U>(
|
||||
node: NodeId,
|
||||
fun: impl FnOnce(&mut T) -> U,
|
||||
) -> Option<U> {
|
||||
Arena::with_mut(|arena| {
|
||||
let m = arena.get_mut(node);
|
||||
m.and_then(|n| n.downcast_mut::<SendWrapper<T>>())
|
||||
.map(|inner| fun(&mut *inner))
|
||||
})
|
||||
}
|
||||
|
||||
fn try_set(node: NodeId, value: T) -> Option<T> {
|
||||
Arena::with_mut(|arena| {
|
||||
let m = arena.get_mut(node);
|
||||
match m.and_then(|n| n.downcast_mut::<SendWrapper<T>>()) {
|
||||
Some(inner) => {
|
||||
*inner = SendWrapper::new(value);
|
||||
None
|
||||
}
|
||||
None => Some(value),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
use or_poisoned::OrPoisoned;
|
||||
use std::{
|
||||
hash::Hash,
|
||||
panic::Location,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
/// A **non-reactive**, `Copy` handle for any value.
|
||||
///
|
||||
@@ -167,8 +19,7 @@ where
|
||||
/// updating it does not notify anything else.
|
||||
#[derive(Debug)]
|
||||
pub struct StoredValue<T, S = SyncStorage> {
|
||||
node: NodeId,
|
||||
ty: PhantomData<(SendWrapper<T>, S)>,
|
||||
value: ArenaItem<Arc<RwLock<T>>, S>,
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
@@ -183,7 +34,7 @@ impl<T, S> Clone for StoredValue<T, S> {
|
||||
|
||||
impl<T, S> PartialEq for StoredValue<T, S> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.node == other.node
|
||||
self.value == other.value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +42,7 @@ impl<T, S> Eq for StoredValue<T, S> {}
|
||||
|
||||
impl<T, S> Hash for StoredValue<T, S> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.node.hash(state);
|
||||
self.value.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,27 +62,13 @@ impl<T, S> DefinedAt for StoredValue<T, S> {
|
||||
impl<T, S> StoredValue<T, S>
|
||||
where
|
||||
T: 'static,
|
||||
S: Storage<T>,
|
||||
S: Storage<Arc<RwLock<T>>>,
|
||||
{
|
||||
/// Stores the given value in the arena allocator.
|
||||
#[track_caller]
|
||||
pub fn new_with_storage(value: T) -> Self {
|
||||
let node = {
|
||||
Arena::with_mut(|arena| {
|
||||
arena.insert(
|
||||
Box::new(S::wrap(value)) as Box<dyn Any + Send + Sync>
|
||||
)
|
||||
})
|
||||
};
|
||||
OWNER.with(|o| {
|
||||
if let Some(owner) = &*o.borrow() {
|
||||
owner.register(node);
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
node,
|
||||
ty: PhantomData,
|
||||
value: ArenaItem::new_with_storage(Arc::new(RwLock::new(value))),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
@@ -241,7 +78,7 @@ where
|
||||
impl<T, S> Default for StoredValue<T, S>
|
||||
where
|
||||
T: Default + 'static,
|
||||
S: Storage<T>,
|
||||
S: Storage<Arc<RwLock<T>>>,
|
||||
{
|
||||
#[track_caller] // Default trait is not annotated with #[track_caller]
|
||||
fn default() -> Self {
|
||||
@@ -271,7 +108,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S: Storage<T>> StoredValue<T, S> {
|
||||
impl<T, S: Storage<Arc<RwLock<T>>>> StoredValue<T, S> {
|
||||
/// Returns an [`Option`] of applying a function to the value within the [`StoredValue`].
|
||||
///
|
||||
/// If the owner of the reactive node has not been disposed [`Some`] is returned. Calling this
|
||||
@@ -315,7 +152,9 @@ impl<T, S: Storage<T>> StoredValue<T, S> {
|
||||
/// ```
|
||||
#[track_caller]
|
||||
pub fn try_with_value<U>(&self, fun: impl FnOnce(&T) -> U) -> Option<U> {
|
||||
S::try_with(self.node, fun)
|
||||
self.value
|
||||
.try_get_value()
|
||||
.map(|inner| fun(&*inner.read().or_poisoned()))
|
||||
}
|
||||
|
||||
/// Returns the output of applying a function to the value within the [`StoredValue`].
|
||||
@@ -360,7 +199,9 @@ impl<T, S: Storage<T>> StoredValue<T, S> {
|
||||
&self,
|
||||
fun: impl FnOnce(&mut T) -> U,
|
||||
) -> Option<U> {
|
||||
S::try_with_mut(self.node, fun)
|
||||
self.value
|
||||
.try_get_value()
|
||||
.map(|inner| fun(&mut *inner.write().or_poisoned()))
|
||||
}
|
||||
|
||||
/// Updates the value within [`StoredValue`] by applying a function to it.
|
||||
@@ -453,7 +294,13 @@ impl<T, S: Storage<T>> StoredValue<T, S> {
|
||||
/// assert_eq!(reset().as_deref(), Some(""));
|
||||
/// ```
|
||||
pub fn try_set_value(&self, value: T) -> Option<T> {
|
||||
S::try_set(self.node, value)
|
||||
match self.value.try_get_value() {
|
||||
Some(inner) => {
|
||||
*inner.write().or_poisoned() = value;
|
||||
None
|
||||
}
|
||||
None => Some(value),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the value within [`StoredValue`].
|
||||
@@ -490,11 +337,11 @@ impl<T, S: Storage<T>> StoredValue<T, S> {
|
||||
|
||||
impl<T, S> IsDisposed for StoredValue<T, S> {
|
||||
fn is_disposed(&self) -> bool {
|
||||
Arena::with(|arena| !arena.contains_key(self.node))
|
||||
self.value.is_disposed()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S: Storage<T>> StoredValue<T, S>
|
||||
impl<T, S: Storage<Arc<RwLock<T>>>> StoredValue<T, S>
|
||||
where
|
||||
T: Clone + 'static,
|
||||
{
|
||||
@@ -574,7 +421,7 @@ where
|
||||
|
||||
impl<T, S> Dispose for StoredValue<T, S> {
|
||||
fn dispose(self) {
|
||||
Arena::with_mut(|arena| arena.remove(self.node));
|
||||
self.value.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ use std::{
|
||||
/// > Each of these has a related `_untracked()` method, which updates the signal
|
||||
/// > without notifying subscribers. Untracked updates are not desirable in most
|
||||
/// > cases, as they cause “tearing” between the signal’s value and its observed
|
||||
/// > value. If you want a non-reactive container, used [`StoredValue`](crate::owner::StoredValue)
|
||||
/// > value. If you want a non-reactive container, used [`ArenaItem`](crate::owner::ArenaItem)
|
||||
/// > instead.
|
||||
///
|
||||
/// ## Examples
|
||||
|
||||
@@ -29,7 +29,7 @@ use std::{
|
||||
/// > Each of these has a related `_untracked()` method, which updates the signal
|
||||
/// > without notifying subscribers. Untracked updates are not desirable in most
|
||||
/// > cases, as they cause “tearing” between the signal’s value and its observed
|
||||
/// > value. If you want a non-reactive container, used [`StoredValue`](crate::owner::StoredValue)
|
||||
/// > value. If you want a non-reactive container, used [`ArenaItem`](crate::owner::ArenaItem)
|
||||
/// > instead.
|
||||
///
|
||||
/// ## Examples
|
||||
|
||||
@@ -5,7 +5,7 @@ use super::{
|
||||
};
|
||||
use crate::{
|
||||
graph::SubscriberSet,
|
||||
owner::{FromLocal, LocalStorage, Storage, StoredValue, SyncStorage},
|
||||
owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage},
|
||||
traits::{DefinedAt, Dispose, IsDisposed, ReadUntracked},
|
||||
unwrap_signal,
|
||||
};
|
||||
@@ -60,7 +60,7 @@ use std::{
|
||||
pub struct ReadSignal<T, S = SyncStorage> {
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
pub(crate) inner: StoredValue<ArcReadSignal<T>, S>,
|
||||
pub(crate) inner: ArenaItem<ArcReadSignal<T>, S>,
|
||||
}
|
||||
|
||||
impl<T, S> Dispose for ReadSignal<T, S> {
|
||||
@@ -158,7 +158,7 @@ where
|
||||
ReadSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(value),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,7 +172,7 @@ where
|
||||
ReadSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(value),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use super::{
|
||||
};
|
||||
use crate::{
|
||||
graph::{ReactiveNode, SubscriberSet},
|
||||
owner::{FromLocal, LocalStorage, Storage, StoredValue, SyncStorage},
|
||||
owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage},
|
||||
signal::guards::{UntrackedWriteGuard, WriteGuard},
|
||||
traits::{
|
||||
DefinedAt, Dispose, IsDisposed, Notify, ReadUntracked,
|
||||
@@ -63,7 +63,7 @@ use std::{
|
||||
/// > Each of these has a related `_untracked()` method, which updates the signal
|
||||
/// > without notifying subscribers. Untracked updates are not desirable in most
|
||||
/// > cases, as they cause “tearing” between the signal’s value and its observed
|
||||
/// > value. If you want a non-reactive container, used [`StoredValue`] instead.
|
||||
/// > value. If you want a non-reactive container, used [`ArenaItem`] instead.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
@@ -102,7 +102,7 @@ use std::{
|
||||
pub struct RwSignal<T, S = SyncStorage> {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
inner: StoredValue<ArcRwSignal<T>, S>,
|
||||
inner: ArenaItem<ArcRwSignal<T>, S>,
|
||||
}
|
||||
|
||||
impl<T, S> Dispose for RwSignal<T, S> {
|
||||
@@ -141,7 +141,7 @@ where
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(ArcRwSignal::new(value)),
|
||||
inner: ArenaItem::new_with_storage(ArcRwSignal::new(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -174,7 +174,7 @@ where
|
||||
ReadSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(
|
||||
inner: ArenaItem::new_with_storage(
|
||||
self.inner
|
||||
.try_get_value()
|
||||
.map(|inner| inner.read_only())
|
||||
@@ -196,7 +196,7 @@ where
|
||||
WriteSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(
|
||||
inner: ArenaItem::new_with_storage(
|
||||
self.inner
|
||||
.try_get_value()
|
||||
.map(|inner| inner.write_only())
|
||||
@@ -233,7 +233,7 @@ where
|
||||
Some(Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(ArcRwSignal {
|
||||
inner: ArenaItem::new_with_storage(ArcRwSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
value: Arc::clone(&read.value),
|
||||
@@ -365,7 +365,9 @@ where
|
||||
|
||||
#[allow(refining_impl_trait)]
|
||||
fn try_write_untracked(&self) -> Option<UntrackedWriteGuard<Self::Value>> {
|
||||
self.inner.with_value(|n| n.try_write_untracked())
|
||||
self.inner
|
||||
.try_with_value(|n| n.try_write_untracked())
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,7 +380,7 @@ where
|
||||
RwSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(value),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -402,7 +404,7 @@ where
|
||||
RwSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(value),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::{subscriber_traits::AsSubscriberSet, ArcTrigger};
|
||||
use crate::{
|
||||
graph::{ReactiveNode, SubscriberSet},
|
||||
owner::StoredValue,
|
||||
owner::ArenaItem,
|
||||
traits::{DefinedAt, Dispose, IsDisposed, Notify},
|
||||
};
|
||||
use std::{
|
||||
@@ -20,7 +20,7 @@ use std::{
|
||||
pub struct Trigger {
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
pub(crate) inner: StoredValue<ArcTrigger>,
|
||||
pub(crate) inner: ArenaItem<ArcTrigger>,
|
||||
}
|
||||
|
||||
impl Trigger {
|
||||
@@ -30,7 +30,7 @@ impl Trigger {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new(ArcTrigger::new()),
|
||||
inner: ArenaItem::new(ArcTrigger::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::{guards::WriteGuard, ArcWriteSignal};
|
||||
use crate::{
|
||||
owner::{Storage, StoredValue, SyncStorage},
|
||||
owner::{ArenaItem, Storage, SyncStorage},
|
||||
traits::{
|
||||
DefinedAt, Dispose, IsDisposed, Notify, UntrackableGuard, Writeable,
|
||||
},
|
||||
@@ -28,7 +28,7 @@ use std::{hash::Hash, ops::DerefMut, panic::Location, sync::Arc};
|
||||
/// > Each of these has a related `_untracked()` method, which updates the signal
|
||||
/// > without notifying subscribers. Untracked updates are not desirable in most
|
||||
/// > cases, as they cause “tearing” between the signal’s value and its observed
|
||||
/// > value. If you want a non-reactive container, used [`StoredValue`] instead.
|
||||
/// > value. If you want a non-reactive container, used [`ArenaItem`] instead.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
@@ -54,7 +54,7 @@ use std::{hash::Hash, ops::DerefMut, panic::Location, sync::Arc};
|
||||
pub struct WriteSignal<T, S = SyncStorage> {
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
pub(crate) inner: StoredValue<ArcWriteSignal<T>, S>,
|
||||
pub(crate) inner: ArenaItem<ArcWriteSignal<T>, S>,
|
||||
}
|
||||
|
||||
impl<T, S> Dispose for WriteSignal<T, S> {
|
||||
@@ -145,6 +145,8 @@ where
|
||||
fn try_write_untracked(
|
||||
&self,
|
||||
) -> Option<impl DerefMut<Target = Self::Value>> {
|
||||
self.inner.with_value(|n| n.try_write_untracked())
|
||||
self.inner
|
||||
.try_with_value(|n| n.try_write_untracked())
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
pub mod read {
|
||||
use crate::{
|
||||
computed::{ArcMemo, Memo},
|
||||
owner::{FromLocal, LocalStorage, Storage, StoredValue, SyncStorage},
|
||||
owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage},
|
||||
signal::{ArcReadSignal, ArcRwSignal, ReadSignal, RwSignal},
|
||||
traits::{DefinedAt, Dispose, Get, With, WithUntracked},
|
||||
untrack, unwrap_signal,
|
||||
@@ -279,7 +279,7 @@ pub mod read {
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
inner: StoredValue<SignalTypes<T, S>, S>,
|
||||
inner: ArenaItem<SignalTypes<T, S>, S>,
|
||||
}
|
||||
|
||||
impl<T, S> Dispose for Signal<T, S>
|
||||
@@ -425,9 +425,9 @@ pub mod read {
|
||||
};
|
||||
|
||||
Self {
|
||||
inner: StoredValue::new_with_storage(
|
||||
SignalTypes::DerivedSignal(Arc::new(derived_signal)),
|
||||
),
|
||||
inner: ArenaItem::new_with_storage(SignalTypes::DerivedSignal(
|
||||
Arc::new(derived_signal),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
@@ -452,7 +452,7 @@ pub mod read {
|
||||
};
|
||||
|
||||
Self {
|
||||
inner: StoredValue::new_local(SignalTypes::DerivedSignal(
|
||||
inner: ArenaItem::new_local(SignalTypes::DerivedSignal(
|
||||
Arc::new(derived_signal),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -515,7 +515,7 @@ pub mod read {
|
||||
Signal {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new(value.inner),
|
||||
inner: ArenaItem::new(value.inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -529,7 +529,7 @@ pub mod read {
|
||||
Signal {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_local(value.inner),
|
||||
inner: ArenaItem::new_local(value.inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -558,7 +558,7 @@ pub mod read {
|
||||
#[track_caller]
|
||||
fn from(value: ReadSignal<T>) -> Self {
|
||||
Self {
|
||||
inner: StoredValue::new(SignalTypes::ReadSignal(value.into())),
|
||||
inner: ArenaItem::new(SignalTypes::ReadSignal(value.into())),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
@@ -572,7 +572,7 @@ pub mod read {
|
||||
#[track_caller]
|
||||
fn from(value: ReadSignal<T, LocalStorage>) -> Self {
|
||||
Self {
|
||||
inner: StoredValue::new_local(SignalTypes::ReadSignal(
|
||||
inner: ArenaItem::new_local(SignalTypes::ReadSignal(
|
||||
value.into(),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -588,7 +588,7 @@ pub mod read {
|
||||
#[track_caller]
|
||||
fn from(value: RwSignal<T>) -> Self {
|
||||
Self {
|
||||
inner: StoredValue::new(SignalTypes::ReadSignal(
|
||||
inner: ArenaItem::new(SignalTypes::ReadSignal(
|
||||
value.read_only().into(),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -604,7 +604,7 @@ pub mod read {
|
||||
#[track_caller]
|
||||
fn from(value: RwSignal<T, LocalStorage>) -> Self {
|
||||
Self {
|
||||
inner: StoredValue::new_local(SignalTypes::ReadSignal(
|
||||
inner: ArenaItem::new_local(SignalTypes::ReadSignal(
|
||||
value.read_only().into(),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -620,7 +620,7 @@ pub mod read {
|
||||
#[track_caller]
|
||||
fn from(value: Memo<T>) -> Self {
|
||||
Self {
|
||||
inner: StoredValue::new(SignalTypes::Memo(value.into())),
|
||||
inner: ArenaItem::new(SignalTypes::Memo(value.into())),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
@@ -634,7 +634,7 @@ pub mod read {
|
||||
#[track_caller]
|
||||
fn from(value: Memo<T, LocalStorage>) -> Self {
|
||||
Self {
|
||||
inner: StoredValue::new_local(SignalTypes::Memo(value.into())),
|
||||
inner: ArenaItem::new_local(SignalTypes::Memo(value.into())),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
@@ -1246,7 +1246,7 @@ pub mod read {
|
||||
/// Types that abstract over the ability to update a signal.
|
||||
pub mod write {
|
||||
use crate::{
|
||||
owner::{Storage, StoredValue, SyncStorage},
|
||||
owner::{ArenaItem, Storage, SyncStorage},
|
||||
signal::{ArcRwSignal, ArcWriteSignal, RwSignal, WriteSignal},
|
||||
traits::Set,
|
||||
};
|
||||
@@ -1341,7 +1341,7 @@ pub mod write {
|
||||
SignalSetterTypes::Default => {}
|
||||
SignalSetterTypes::Write(w) => w.set(new_value),
|
||||
SignalSetterTypes::Mapped(s) => {
|
||||
s.with_value(|setter| setter(new_value))
|
||||
s.try_with_value(|setter| setter(new_value));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1371,9 +1371,9 @@ pub mod write {
|
||||
#[track_caller]
|
||||
pub fn map(mapped_setter: impl Fn(T) + Send + Sync + 'static) -> Self {
|
||||
Self {
|
||||
inner: SignalSetterTypes::Mapped(
|
||||
StoredValue::new_with_storage(Box::new(mapped_setter)),
|
||||
),
|
||||
inner: SignalSetterTypes::Mapped(ArenaItem::new_with_storage(
|
||||
Box::new(mapped_setter),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
@@ -1411,7 +1411,7 @@ pub mod write {
|
||||
T: 'static,
|
||||
{
|
||||
Write(WriteSignal<T, S>),
|
||||
Mapped(StoredValue<Box<dyn Fn(T) + Send + Sync>, S>),
|
||||
Mapped(ArenaItem<Box<dyn Fn(T) + Send + Sync>, S>),
|
||||
Default,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_stores"
|
||||
version = "0.1.0-beta5"
|
||||
version = "0.1.0-beta6"
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
use crate::{
|
||||
path::{StorePath, StorePathSegment},
|
||||
AtIndex, AtKeyed, KeyMap, KeyedSubfield, StoreField, Subfield,
|
||||
AtIndex, AtKeyed, KeyMap, KeyedSubfield, StoreField, StoreFieldTrigger,
|
||||
Subfield,
|
||||
};
|
||||
use reactive_graph::{
|
||||
signal::ArcTrigger,
|
||||
traits::{
|
||||
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
|
||||
},
|
||||
use reactive_graph::traits::{
|
||||
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
|
||||
};
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
@@ -23,8 +21,8 @@ where
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
path: StorePath,
|
||||
trigger: ArcTrigger,
|
||||
get_trigger: Arc<dyn Fn(StorePath) -> ArcTrigger + Send + Sync>,
|
||||
trigger: StoreFieldTrigger,
|
||||
get_trigger: Arc<dyn Fn(StorePath) -> StoreFieldTrigger + Send + Sync>,
|
||||
read: Arc<dyn Fn() -> Option<StoreFieldReader<T>> + Send + Sync>,
|
||||
write: Arc<dyn Fn() -> Option<StoreFieldWriter<T>> + Send + Sync>,
|
||||
keys: Arc<dyn Fn() -> Option<KeyMap> + Send + Sync>,
|
||||
@@ -78,9 +76,8 @@ impl<T> StoreField for ArcField<T> {
|
||||
type Value = T;
|
||||
type Reader = StoreFieldReader<T>;
|
||||
type Writer = StoreFieldWriter<T>;
|
||||
type UntrackedWriter = StoreFieldWriter<T>;
|
||||
|
||||
fn get_trigger(&self, path: StorePath) -> ArcTrigger {
|
||||
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
|
||||
(self.get_trigger)(path)
|
||||
}
|
||||
|
||||
@@ -96,12 +93,6 @@ impl<T> StoreField for ArcField<T> {
|
||||
(self.write)().map(StoreFieldWriter::new)
|
||||
}
|
||||
|
||||
fn untracked_writer(&self) -> Option<Self::UntrackedWriter> {
|
||||
let mut writer = (self.write)().map(StoreFieldWriter::new)?;
|
||||
writer.untrack();
|
||||
Some(writer)
|
||||
}
|
||||
|
||||
fn keys(&self) -> Option<KeyMap> {
|
||||
(self.keys)()
|
||||
}
|
||||
@@ -243,13 +234,14 @@ impl<T> DefinedAt for ArcField<T> {
|
||||
|
||||
impl<T> Notify for ArcField<T> {
|
||||
fn notify(&self) {
|
||||
self.trigger.notify();
|
||||
self.trigger.this.notify();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Track for ArcField<T> {
|
||||
fn track(&self) {
|
||||
self.trigger.track();
|
||||
self.trigger.this.track();
|
||||
self.trigger.children.track();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use crate::{
|
||||
arc_field::{StoreFieldReader, StoreFieldWriter},
|
||||
path::{StorePath, StorePathSegment},
|
||||
ArcField, AtIndex, AtKeyed, KeyMap, KeyedSubfield, StoreField, Subfield,
|
||||
ArcField, AtIndex, AtKeyed, KeyMap, KeyedSubfield, StoreField,
|
||||
StoreFieldTrigger, Subfield,
|
||||
};
|
||||
use reactive_graph::{
|
||||
owner::{Storage, StoredValue, SyncStorage},
|
||||
signal::ArcTrigger,
|
||||
owner::{ArenaItem, Storage, SyncStorage},
|
||||
traits::{DefinedAt, IsDisposed, Notify, ReadUntracked, Track},
|
||||
unwrap_signal,
|
||||
};
|
||||
@@ -17,7 +17,7 @@ where
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
inner: StoredValue<ArcField<T>, S>,
|
||||
inner: ArenaItem<ArcField<T>, S>,
|
||||
}
|
||||
|
||||
impl<T, S> StoreField for Field<T, S>
|
||||
@@ -27,9 +27,8 @@ where
|
||||
type Value = T;
|
||||
type Reader = StoreFieldReader<T>;
|
||||
type Writer = StoreFieldWriter<T>;
|
||||
type UntrackedWriter = StoreFieldWriter<T>;
|
||||
|
||||
fn get_trigger(&self, path: StorePath) -> ArcTrigger {
|
||||
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
|
||||
self.inner
|
||||
.try_get_value()
|
||||
.map(|inner| inner.get_trigger(path))
|
||||
@@ -51,12 +50,6 @@ where
|
||||
self.inner.try_get_value().and_then(|inner| inner.writer())
|
||||
}
|
||||
|
||||
fn untracked_writer(&self) -> Option<Self::UntrackedWriter> {
|
||||
self.inner
|
||||
.try_get_value()
|
||||
.and_then(|inner| inner.untracked_writer())
|
||||
}
|
||||
|
||||
fn keys(&self) -> Option<KeyMap> {
|
||||
self.inner.try_get_value().and_then(|n| n.keys())
|
||||
}
|
||||
@@ -75,7 +68,7 @@ where
|
||||
Field {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(value.into()),
|
||||
inner: ArenaItem::new_with_storage(value.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,7 +86,7 @@ where
|
||||
Field {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(value.into()),
|
||||
inner: ArenaItem::new_with_storage(value.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,7 +109,7 @@ where
|
||||
Field {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(value.into()),
|
||||
inner: ArenaItem::new_with_storage(value.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
path::{StorePath, StorePathSegment},
|
||||
store_field::StoreField,
|
||||
KeyMap,
|
||||
KeyMap, StoreFieldTrigger,
|
||||
};
|
||||
use reactive_graph::{
|
||||
signal::{
|
||||
@@ -69,8 +69,6 @@ where
|
||||
type Reader = MappedMutArc<Inner::Reader, Prev::Output>;
|
||||
type Writer =
|
||||
MappedMutArc<WriteGuard<ArcTrigger, Inner::Writer>, Prev::Output>;
|
||||
type UntrackedWriter =
|
||||
MappedMutArc<WriteGuard<ArcTrigger, Inner::Writer>, Prev::Output>;
|
||||
|
||||
fn path(&self) -> impl IntoIterator<Item = StorePathSegment> {
|
||||
self.inner
|
||||
@@ -79,7 +77,7 @@ where
|
||||
.chain(iter::once(self.index.into()))
|
||||
}
|
||||
|
||||
fn get_trigger(&self, path: StorePath) -> ArcTrigger {
|
||||
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
|
||||
self.inner.get_trigger(path)
|
||||
}
|
||||
|
||||
@@ -95,7 +93,7 @@ where
|
||||
|
||||
fn writer(&self) -> Option<Self::Writer> {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
let inner = WriteGuard::new(trigger, self.inner.writer()?);
|
||||
let inner = WriteGuard::new(trigger.children, self.inner.writer()?);
|
||||
let index = self.index;
|
||||
Some(MappedMutArc::new(
|
||||
inner,
|
||||
@@ -104,12 +102,6 @@ where
|
||||
))
|
||||
}
|
||||
|
||||
fn untracked_writer(&self) -> Option<Self::UntrackedWriter> {
|
||||
let mut guard = self.writer()?;
|
||||
guard.untrack();
|
||||
Some(guard)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn keys(&self) -> Option<KeyMap> {
|
||||
self.inner.keys()
|
||||
@@ -149,7 +141,7 @@ where
|
||||
{
|
||||
fn notify(&self) {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.notify();
|
||||
trigger.this.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +153,8 @@ where
|
||||
{
|
||||
fn track(&self) {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.track();
|
||||
trigger.this.track();
|
||||
trigger.children.track();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +217,8 @@ where
|
||||
fn iter(self) -> StoreFieldIter<Inner, Prev> {
|
||||
// reactively track changes to this field
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.track();
|
||||
trigger.this.track();
|
||||
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);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
path::{StorePath, StorePathSegment},
|
||||
store_field::StoreField,
|
||||
KeyMap,
|
||||
KeyMap, StoreFieldTrigger,
|
||||
};
|
||||
use reactive_graph::{
|
||||
signal::{
|
||||
@@ -96,8 +96,6 @@ where
|
||||
type Value = T;
|
||||
type Reader = Mapped<Inner::Reader, T>;
|
||||
type Writer = MappedMut<WriteGuard<ArcTrigger, Inner::Writer>, T>;
|
||||
type UntrackedWriter =
|
||||
MappedMut<WriteGuard<ArcTrigger, Inner::UntrackedWriter>, T>;
|
||||
|
||||
fn path(&self) -> impl IntoIterator<Item = StorePathSegment> {
|
||||
self.inner
|
||||
@@ -106,7 +104,7 @@ where
|
||||
.chain(iter::once(self.path_segment))
|
||||
}
|
||||
|
||||
fn get_trigger(&self, path: StorePath) -> ArcTrigger {
|
||||
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
|
||||
self.inner.get_trigger(path)
|
||||
}
|
||||
|
||||
@@ -118,16 +116,10 @@ where
|
||||
fn writer(&self) -> Option<Self::Writer> {
|
||||
let path = self.path().into_iter().collect::<StorePath>();
|
||||
let trigger = self.get_trigger(path.clone());
|
||||
let guard = WriteGuard::new(trigger, self.inner.writer()?);
|
||||
let guard = WriteGuard::new(trigger.children, self.inner.writer()?);
|
||||
Some(MappedMut::new(guard, self.read, self.write))
|
||||
}
|
||||
|
||||
fn untracked_writer(&self) -> Option<Self::UntrackedWriter> {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
let inner = WriteGuard::new(trigger, self.inner.untracked_writer()?);
|
||||
Some(MappedMut::new(inner, self.read, self.write))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn keys(&self) -> Option<KeyMap> {
|
||||
self.inner.keys()
|
||||
@@ -237,6 +229,7 @@ where
|
||||
// now that the write lock is release, we can get a read lock to refresh this keyed field
|
||||
// based on the new value
|
||||
self.inner.update_keys();
|
||||
self.inner.notify();
|
||||
|
||||
// reactive updates happen on the next tick
|
||||
}
|
||||
@@ -279,7 +272,8 @@ where
|
||||
{
|
||||
fn notify(&self) {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.notify();
|
||||
trigger.this.notify();
|
||||
trigger.children.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,9 +287,13 @@ where
|
||||
K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static,
|
||||
{
|
||||
fn track(&self) {
|
||||
self.inner.track();
|
||||
let inner = self
|
||||
.inner
|
||||
.get_trigger(self.inner.path().into_iter().collect());
|
||||
inner.this.track();
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.track();
|
||||
trigger.this.track();
|
||||
trigger.children.track();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,13 +415,6 @@ where
|
||||
T::Output,
|
||||
>,
|
||||
>;
|
||||
type UntrackedWriter = WriteGuard<
|
||||
ArcTrigger,
|
||||
MappedMutArc<
|
||||
<KeyedSubfield<Inner, Prev, K, T> as StoreField>::Writer,
|
||||
T::Output,
|
||||
>,
|
||||
>;
|
||||
|
||||
fn path(&self) -> impl IntoIterator<Item = StorePathSegment> {
|
||||
let inner = self.inner.path().into_iter().collect::<StorePath>();
|
||||
@@ -442,7 +433,7 @@ where
|
||||
inner.into_iter().chain(this)
|
||||
}
|
||||
|
||||
fn get_trigger(&self, path: StorePath) -> ArcTrigger {
|
||||
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
|
||||
self.inner.get_trigger(path)
|
||||
}
|
||||
|
||||
@@ -491,7 +482,7 @@ where
|
||||
.expect("reading from a keyed field that has not yet been created");
|
||||
|
||||
Some(WriteGuard::new(
|
||||
trigger,
|
||||
trigger.children,
|
||||
MappedMutArc::new(
|
||||
inner,
|
||||
move |n| &n[index],
|
||||
@@ -500,12 +491,6 @@ where
|
||||
))
|
||||
}
|
||||
|
||||
fn untracked_writer(&self) -> Option<Self::UntrackedWriter> {
|
||||
let mut guard = self.writer()?;
|
||||
guard.untrack();
|
||||
Some(guard)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn keys(&self) -> Option<KeyMap> {
|
||||
self.inner.keys()
|
||||
@@ -550,7 +535,8 @@ where
|
||||
{
|
||||
fn notify(&self) {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.notify();
|
||||
trigger.this.notify();
|
||||
trigger.children.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,7 +552,8 @@ where
|
||||
{
|
||||
fn track(&self) {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.track();
|
||||
trigger.this.track();
|
||||
trigger.children.track();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -655,7 +642,7 @@ where
|
||||
fn into_iter(self) -> StoreFieldKeyedIter<Inner, Prev, K, T> {
|
||||
// reactively track changes to this field
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.track();
|
||||
trigger.this.track();
|
||||
|
||||
// get the current length of the field by accessing slice
|
||||
let reader = self
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
use or_poisoned::OrPoisoned;
|
||||
use reactive_graph::{
|
||||
owner::{LocalStorage, Storage, StoredValue, SyncStorage},
|
||||
owner::{ArenaItem, LocalStorage, Storage, SyncStorage},
|
||||
signal::{
|
||||
guards::{Plain, ReadGuard},
|
||||
guards::{Plain, ReadGuard, WriteGuard},
|
||||
ArcTrigger,
|
||||
},
|
||||
traits::{DefinedAt, IsDisposed, Notify, ReadUntracked, Track},
|
||||
traits::{
|
||||
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
|
||||
Writeable,
|
||||
},
|
||||
};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::{
|
||||
@@ -13,6 +16,7 @@ use std::{
|
||||
collections::HashMap,
|
||||
fmt::Debug,
|
||||
hash::Hash,
|
||||
ops::DerefMut,
|
||||
panic::Location,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
@@ -33,26 +37,38 @@ pub use iter::*;
|
||||
pub use keyed::*;
|
||||
pub use option::*;
|
||||
pub use patch::*;
|
||||
use path::{StorePath, StorePathSegment};
|
||||
pub use path::{StorePath, StorePathSegment};
|
||||
pub use store_field::{StoreField, Then};
|
||||
pub use subfield::Subfield;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct TriggerMap(FxHashMap<StorePath, ArcTrigger>);
|
||||
struct TriggerMap(FxHashMap<StorePath, StoreFieldTrigger>);
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct StoreFieldTrigger {
|
||||
pub this: ArcTrigger,
|
||||
pub children: ArcTrigger,
|
||||
}
|
||||
|
||||
impl StoreFieldTrigger {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl TriggerMap {
|
||||
fn get_or_insert(&mut self, key: StorePath) -> ArcTrigger {
|
||||
fn get_or_insert(&mut self, key: StorePath) -> StoreFieldTrigger {
|
||||
if let Some(trigger) = self.0.get(&key) {
|
||||
trigger.clone()
|
||||
} else {
|
||||
let new = ArcTrigger::new();
|
||||
let new = StoreFieldTrigger::new();
|
||||
self.0.insert(key, new.clone());
|
||||
new
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn remove(&mut self, key: &StorePath) -> Option<ArcTrigger> {
|
||||
fn remove(&mut self, key: &StorePath) -> Option<StoreFieldTrigger> {
|
||||
self.0.remove(key)
|
||||
}
|
||||
}
|
||||
@@ -238,22 +254,46 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Writeable for ArcStore<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
type Value = T;
|
||||
|
||||
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
|
||||
self.writer()
|
||||
.map(|writer| WriteGuard::new(self.clone(), writer))
|
||||
}
|
||||
|
||||
fn try_write_untracked(
|
||||
&self,
|
||||
) -> Option<impl DerefMut<Target = Self::Value>> {
|
||||
let mut writer = self.writer()?;
|
||||
writer.untrack();
|
||||
Some(writer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> Track for ArcStore<T> {
|
||||
fn track(&self) {
|
||||
self.get_trigger(Default::default()).track();
|
||||
let trigger = self.get_trigger(Default::default());
|
||||
trigger.this.track();
|
||||
trigger.children.track();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> Notify for ArcStore<T> {
|
||||
fn notify(&self) {
|
||||
self.get_trigger(self.path().into_iter().collect()).notify();
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.this.notify();
|
||||
trigger.children.notify();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Store<T, S = SyncStorage> {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
inner: StoredValue<ArcStore<T>, S>,
|
||||
inner: ArenaItem<ArcStore<T>, S>,
|
||||
}
|
||||
|
||||
impl<T> Store<T>
|
||||
@@ -264,7 +304,7 @@ where
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(ArcStore::new(value)),
|
||||
inner: ArenaItem::new_with_storage(ArcStore::new(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,7 +317,7 @@ where
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(ArcStore::new(value)),
|
||||
inner: ArenaItem::new_with_storage(ArcStore::new(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -335,7 +375,27 @@ where
|
||||
fn try_read_untracked(&self) -> Option<Self::Value> {
|
||||
self.inner
|
||||
.try_get_value()
|
||||
.map(|inner| inner.read_untracked())
|
||||
.and_then(|inner| inner.try_read_untracked())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> Writeable for Store<T, S>
|
||||
where
|
||||
T: 'static,
|
||||
S: Storage<ArcStore<T>>,
|
||||
{
|
||||
type Value = T;
|
||||
|
||||
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
|
||||
self.writer().map(|writer| WriteGuard::new(*self, writer))
|
||||
}
|
||||
|
||||
fn try_write_untracked(
|
||||
&self,
|
||||
) -> Option<impl DerefMut<Target = Self::Value>> {
|
||||
let mut writer = self.writer()?;
|
||||
writer.untrack();
|
||||
Some(writer)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,13 +440,13 @@ mod tests {
|
||||
tokio::time::sleep(std::time::Duration::from_micros(1)).await;
|
||||
}
|
||||
|
||||
#[derive(Debug, Store, Patch)]
|
||||
#[derive(Debug, Store, Patch, Default)]
|
||||
struct Todos {
|
||||
user: String,
|
||||
todos: Vec<Todo>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Store, Patch)]
|
||||
#[derive(Debug, Store, Patch, Default)]
|
||||
struct Todo {
|
||||
label: String,
|
||||
completed: bool,
|
||||
@@ -482,9 +542,37 @@ mod tests {
|
||||
tick().await;
|
||||
store.user().update(|name| name.push_str("!!!"));
|
||||
tick().await;
|
||||
// TODO known to be broken, I just need to fix CI for now
|
||||
// the effect reads from `user`, so it should trigger every time
|
||||
//assert_eq!(combined_count.load(Ordering::Relaxed), 1);
|
||||
// the effect reads from `todos`, so it shouldn't trigger every time
|
||||
assert_eq!(combined_count.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn parent_does_notify() {
|
||||
_ = any_spawner::Executor::init_tokio();
|
||||
|
||||
let combined_count = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let store = Store::new(data());
|
||||
|
||||
Effect::new_sync({
|
||||
let combined_count = Arc::clone(&combined_count);
|
||||
move |prev: Option<()>| {
|
||||
if prev.is_none() {
|
||||
println!("first run");
|
||||
} else {
|
||||
println!("next run");
|
||||
}
|
||||
println!("{:?}", *store.todos().read());
|
||||
combined_count.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
});
|
||||
tick().await;
|
||||
tick().await;
|
||||
store.set(Todos::default());
|
||||
tick().await;
|
||||
store.set(data());
|
||||
tick().await;
|
||||
assert_eq!(combined_count.load(Ordering::Relaxed), 3);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -27,12 +27,13 @@ where
|
||||
type Value = T::Value;
|
||||
|
||||
fn patch(&self, new: Self::Value) {
|
||||
let path = StorePath::default();
|
||||
let path = self.path().into_iter().collect::<StorePath>();
|
||||
if let Some(mut writer) = self.writer() {
|
||||
// don't track the writer for the whole store
|
||||
writer.untrack();
|
||||
let mut notify = |path: &StorePath| {
|
||||
self.get_trigger(path.to_owned()).notify();
|
||||
self.get_trigger(path.to_owned()).this.notify();
|
||||
self.get_trigger(path.to_owned()).children.notify();
|
||||
};
|
||||
writer.patch_field(new, &path, &mut notify);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
path::{StorePath, StorePathSegment},
|
||||
ArcStore, KeyMap, Store,
|
||||
ArcStore, KeyMap, Store, StoreFieldTrigger,
|
||||
};
|
||||
use or_poisoned::OrPoisoned;
|
||||
use reactive_graph::{
|
||||
@@ -26,24 +26,22 @@ pub trait StoreField: Sized {
|
||||
type Value;
|
||||
type Reader: Deref<Target = Self::Value>;
|
||||
type Writer: UntrackableGuard<Target = Self::Value>;
|
||||
type UntrackedWriter: DerefMut<Target = Self::Value>;
|
||||
|
||||
fn get_trigger(&self, path: StorePath) -> ArcTrigger;
|
||||
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger;
|
||||
|
||||
fn path(&self) -> impl IntoIterator<Item = StorePathSegment>;
|
||||
|
||||
fn track_field(&self) {
|
||||
let path = self.path().into_iter().collect();
|
||||
let trigger = self.get_trigger(path);
|
||||
trigger.track();
|
||||
trigger.this.track();
|
||||
trigger.children.track();
|
||||
}
|
||||
|
||||
fn reader(&self) -> Option<Self::Reader>;
|
||||
|
||||
fn writer(&self) -> Option<Self::Writer>;
|
||||
|
||||
fn untracked_writer(&self) -> Option<Self::UntrackedWriter>;
|
||||
|
||||
fn keys(&self) -> Option<KeyMap>;
|
||||
|
||||
#[track_caller]
|
||||
@@ -69,9 +67,8 @@ where
|
||||
type Value = T;
|
||||
type Reader = Plain<T>;
|
||||
type Writer = WriteGuard<ArcTrigger, UntrackedWriteGuard<T>>;
|
||||
type UntrackedWriter = UntrackedWriteGuard<T>;
|
||||
|
||||
fn get_trigger(&self, path: StorePath) -> ArcTrigger {
|
||||
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
|
||||
let triggers = &self.signals;
|
||||
let trigger = triggers.write().or_poisoned().get_or_insert(path);
|
||||
trigger
|
||||
@@ -87,12 +84,8 @@ where
|
||||
|
||||
fn writer(&self) -> Option<Self::Writer> {
|
||||
let trigger = self.get_trigger(Default::default());
|
||||
let guard = self.untracked_writer()?;
|
||||
Some(WriteGuard::new(trigger, guard))
|
||||
}
|
||||
|
||||
fn untracked_writer(&self) -> Option<Self::UntrackedWriter> {
|
||||
UntrackedWriteGuard::try_new(Arc::clone(&self.value))
|
||||
let guard = UntrackedWriteGuard::try_new(Arc::clone(&self.value))?;
|
||||
Some(WriteGuard::new(trigger.children, guard))
|
||||
}
|
||||
|
||||
fn keys(&self) -> Option<KeyMap> {
|
||||
@@ -108,9 +101,8 @@ where
|
||||
type Value = T;
|
||||
type Reader = Plain<T>;
|
||||
type Writer = WriteGuard<ArcTrigger, UntrackedWriteGuard<T>>;
|
||||
type UntrackedWriter = UntrackedWriteGuard<T>;
|
||||
|
||||
fn get_trigger(&self, path: StorePath) -> ArcTrigger {
|
||||
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
|
||||
self.inner
|
||||
.try_get_value()
|
||||
.map(|n| n.get_trigger(path))
|
||||
@@ -132,12 +124,6 @@ where
|
||||
self.inner.try_get_value().and_then(|n| n.writer())
|
||||
}
|
||||
|
||||
fn untracked_writer(&self) -> Option<Self::UntrackedWriter> {
|
||||
self.inner
|
||||
.try_get_value()
|
||||
.and_then(|n| n.untracked_writer())
|
||||
}
|
||||
|
||||
fn keys(&self) -> Option<KeyMap> {
|
||||
self.inner.try_get_value().and_then(|inner| inner.keys())
|
||||
}
|
||||
@@ -182,9 +168,8 @@ where
|
||||
type Value = T;
|
||||
type Reader = Mapped<S::Reader, T>;
|
||||
type Writer = MappedMut<S::Writer, T>;
|
||||
type UntrackedWriter = MappedMut<S::UntrackedWriter, T>;
|
||||
|
||||
fn get_trigger(&self, path: StorePath) -> ArcTrigger {
|
||||
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
|
||||
self.inner.get_trigger(path)
|
||||
}
|
||||
|
||||
@@ -202,11 +187,6 @@ where
|
||||
Some(MappedMut::new(inner, self.map_fn, self.map_fn_mut))
|
||||
}
|
||||
|
||||
fn untracked_writer(&self) -> Option<Self::UntrackedWriter> {
|
||||
let inner = self.inner.untracked_writer()?;
|
||||
Some(MappedMut::new(inner, self.map_fn, self.map_fn_mut))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn keys(&self) -> Option<KeyMap> {
|
||||
self.inner.keys()
|
||||
@@ -244,7 +224,8 @@ where
|
||||
{
|
||||
fn notify(&self) {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.notify();
|
||||
trigger.this.notify();
|
||||
trigger.children.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,7 +235,8 @@ where
|
||||
{
|
||||
fn track(&self) {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.track();
|
||||
trigger.this.track();
|
||||
trigger.children.track();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
path::{StorePath, StorePathSegment},
|
||||
store_field::StoreField,
|
||||
KeyMap,
|
||||
KeyMap, StoreFieldTrigger,
|
||||
};
|
||||
use reactive_graph::{
|
||||
signal::{
|
||||
@@ -73,8 +73,6 @@ where
|
||||
type Value = T;
|
||||
type Reader = Mapped<Inner::Reader, T>;
|
||||
type Writer = MappedMut<WriteGuard<ArcTrigger, Inner::Writer>, T>;
|
||||
type UntrackedWriter =
|
||||
MappedMut<WriteGuard<ArcTrigger, Inner::UntrackedWriter>, T>;
|
||||
|
||||
fn path(&self) -> impl IntoIterator<Item = StorePathSegment> {
|
||||
self.inner
|
||||
@@ -83,7 +81,7 @@ where
|
||||
.chain(iter::once(self.path_segment))
|
||||
}
|
||||
|
||||
fn get_trigger(&self, path: StorePath) -> ArcTrigger {
|
||||
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
|
||||
self.inner.get_trigger(path)
|
||||
}
|
||||
|
||||
@@ -94,13 +92,7 @@ where
|
||||
|
||||
fn writer(&self) -> Option<Self::Writer> {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
let inner = WriteGuard::new(trigger, self.inner.writer()?);
|
||||
Some(MappedMut::new(inner, self.read, self.write))
|
||||
}
|
||||
|
||||
fn untracked_writer(&self) -> Option<Self::UntrackedWriter> {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
let inner = WriteGuard::new(trigger, self.inner.untracked_writer()?);
|
||||
let inner = WriteGuard::new(trigger.children, self.inner.writer()?);
|
||||
Some(MappedMut::new(inner, self.read, self.write))
|
||||
}
|
||||
|
||||
@@ -142,7 +134,8 @@ where
|
||||
{
|
||||
fn notify(&self) {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.notify();
|
||||
trigger.this.notify();
|
||||
trigger.children.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,9 +146,13 @@ where
|
||||
T: 'static,
|
||||
{
|
||||
fn track(&self) {
|
||||
self.inner.track();
|
||||
let inner = self
|
||||
.inner
|
||||
.get_trigger(self.inner.path().into_iter().collect());
|
||||
inner.this.track();
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.track();
|
||||
trigger.this.track();
|
||||
trigger.children.track();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_stores_macro"
|
||||
version = "0.1.0-beta5"
|
||||
version = "0.1.0-beta6"
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_router"
|
||||
version = "0.7.0-beta5"
|
||||
version = "0.7.0-beta6"
|
||||
authors = ["Greg Johnston", "Ben Wishovich"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_router_macro"
|
||||
version = "0.7.0-beta5"
|
||||
version = "0.7.0-beta6"
|
||||
authors = ["Greg Johnston", "Ben Wishovich"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tachys"
|
||||
version = "0.1.0-beta5"
|
||||
version = "0.1.0-beta6"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -1,325 +0,0 @@
|
||||
/* The implementations here are no longer valid because the read guards are not Send.
|
||||
* If we switch to using some kind of Send async lock, it would be possible to restore them.
|
||||
*
|
||||
*
|
||||
* //! Implements the [`Render`] and [`RenderHtml`] traits for signal guard types.
|
||||
|
||||
use crate::{
|
||||
html::attribute::Attribute,
|
||||
hydration::Cursor,
|
||||
prelude::RenderHtml,
|
||||
renderer::{CastFrom, Renderer},
|
||||
view::{
|
||||
add_attr::AddAnyAttr, strings::StrState, Mountable, Position,
|
||||
PositionState, Render, ToTemplate,
|
||||
},
|
||||
};
|
||||
use reactive_graph::signal::guards::ReadGuard;
|
||||
use std::{
|
||||
fmt::Write,
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
|
||||
num::{
|
||||
NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8,
|
||||
NonZeroIsize, NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64,
|
||||
NonZeroU8, NonZeroUsize,
|
||||
},
|
||||
ops::Deref,
|
||||
};
|
||||
// any changes here should also be made in src/view/primitives.rs
|
||||
// TODO should also apply to mapped signal read guards
|
||||
macro_rules! render_primitive {
|
||||
($($child_type:ty),* $(,)?) => {
|
||||
$(
|
||||
paste::paste! {
|
||||
pub struct [<ReadGuard $child_type:camel State>]<R>(R::Text, $child_type) where R: Renderer;
|
||||
|
||||
impl<'a, R: Renderer> Mountable<R> for [<ReadGuard $child_type:camel State>]<R> {
|
||||
fn unmount(&mut self) {
|
||||
self.0.unmount()
|
||||
}
|
||||
|
||||
fn mount(
|
||||
&mut self,
|
||||
parent: &<R as Renderer>::Element,
|
||||
marker: Option<&<R as Renderer>::Node>,
|
||||
) {
|
||||
R::insert_node(parent, self.0.as_ref(), marker);
|
||||
}
|
||||
|
||||
fn insert_before_this(&self,
|
||||
child: &mut dyn Mountable<R>,
|
||||
) -> bool {
|
||||
child.mount(parent, Some(self.0.as_ref()));
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<G, R: Renderer> Render<R> for ReadGuard<$child_type, G>
|
||||
where G: Deref<Target = $child_type>
|
||||
{
|
||||
type State = [<ReadGuard $child_type:camel State>]<R>;
|
||||
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
let node = R::create_text_node(&self.to_string());
|
||||
[<ReadGuard $child_type:camel State>](node, *self)
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
let [<ReadGuard $child_type:camel State>](node, this) = state;
|
||||
if &self != this {
|
||||
R::set_text(node, &self.to_string());
|
||||
*this = *self;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<G, R> AddAnyAttr<R> for ReadGuard<$child_type, G>
|
||||
where
|
||||
R: Renderer,
|
||||
G: Deref<Target = $child_type> + Send
|
||||
{
|
||||
type Output<SomeNewAttr: Attribute<R>> = ReadGuard<$child_type, G>;
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<R>>(
|
||||
self,
|
||||
_attr: NewAttr,
|
||||
) -> Self::Output<NewAttr>
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml<R>,
|
||||
{
|
||||
// TODO: there is a strange compiler thing that seems to prevent us returning Self here,
|
||||
// even though we've already said that Output is always the same as Self
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<G, R> RenderHtml<R> for ReadGuard<$child_type, G>
|
||||
where
|
||||
R: Renderer,
|
||||
G: Deref<Target = $child_type> + Send
|
||||
{
|
||||
type AsyncOutput = Self;
|
||||
|
||||
const MIN_LENGTH: usize = 0;
|
||||
|
||||
fn to_html_with_buf(self, buf: &mut String, position: &mut Position, escape: bool, mark_branches: bool) {
|
||||
// add a comment node to separate from previous sibling, if any
|
||||
if matches!(position, Position::NextChildAfterText) {
|
||||
buf.push_str("<!>")
|
||||
}
|
||||
_ = write!(buf, "{}", self);
|
||||
*position = Position::NextChildAfterText;
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
cursor: &Cursor<R>,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
if position.get() == Position::FirstChild {
|
||||
cursor.child();
|
||||
} else {
|
||||
cursor.sibling();
|
||||
}
|
||||
|
||||
// separating placeholder marker comes before text node
|
||||
if matches!(position.get(), Position::NextChildAfterText) {
|
||||
cursor.sibling();
|
||||
}
|
||||
|
||||
let node = cursor.current();
|
||||
let node = R::Text::cast_from(node)
|
||||
.expect("couldn't cast text node from node");
|
||||
|
||||
if !FROM_SERVER {
|
||||
R::set_text(&node, &self.to_string());
|
||||
}
|
||||
position.set(Position::NextChildAfterText);
|
||||
|
||||
[<ReadGuard $child_type:camel State>](node, *self)
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, G> ToTemplate for ReadGuard<$child_type, G>
|
||||
{
|
||||
const TEMPLATE: &'static str = " <!>";
|
||||
|
||||
fn to_template(
|
||||
buf: &mut String,
|
||||
_class: &mut String,
|
||||
_style: &mut String,
|
||||
_inner_html: &mut String,
|
||||
position: &mut Position,
|
||||
) {
|
||||
if matches!(*position, Position::NextChildAfterText) {
|
||||
buf.push_str("<!>")
|
||||
}
|
||||
buf.push(' ');
|
||||
*position = Position::NextChildAfterText;
|
||||
}
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
render_primitive![
|
||||
usize,
|
||||
u8,
|
||||
u16,
|
||||
u32,
|
||||
u64,
|
||||
u128,
|
||||
isize,
|
||||
i8,
|
||||
i16,
|
||||
i32,
|
||||
i64,
|
||||
i128,
|
||||
f32,
|
||||
f64,
|
||||
char,
|
||||
bool,
|
||||
IpAddr,
|
||||
SocketAddr,
|
||||
SocketAddrV4,
|
||||
SocketAddrV6,
|
||||
Ipv4Addr,
|
||||
Ipv6Addr,
|
||||
NonZeroI8,
|
||||
NonZeroU8,
|
||||
NonZeroI16,
|
||||
NonZeroU16,
|
||||
NonZeroI32,
|
||||
NonZeroU32,
|
||||
NonZeroI64,
|
||||
NonZeroU64,
|
||||
NonZeroI128,
|
||||
NonZeroU128,
|
||||
NonZeroIsize,
|
||||
NonZeroUsize,
|
||||
];
|
||||
|
||||
// strings
|
||||
pub struct ReadGuardStringState<R: Renderer> {
|
||||
node: R::Text,
|
||||
str: String,
|
||||
}
|
||||
|
||||
impl<G, R: Renderer> Render<R> for ReadGuard<String, G>
|
||||
where
|
||||
G: Deref<Target = String>,
|
||||
{
|
||||
type State = ReadGuardStringState<R>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
let node = R::create_text_node(&self);
|
||||
ReadGuardStringState {
|
||||
node,
|
||||
str: self.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
let ReadGuardStringState { node, str } = state;
|
||||
if *self != *str {
|
||||
R::set_text(node, &self);
|
||||
str.clear();
|
||||
str.push_str(&self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<G, R> AddAnyAttr<R> for ReadGuard<String, G>
|
||||
where
|
||||
G: Deref<Target = String> + Send,
|
||||
R: Renderer,
|
||||
{
|
||||
type Output<SomeNewAttr: Attribute<R>> = ReadGuard<String, G>;
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<R>>(
|
||||
self,
|
||||
attr: NewAttr,
|
||||
) -> Self::Output<NewAttr>
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml<R>,
|
||||
{
|
||||
// TODO: there is a strange compiler thing that seems to prevent us returning Self here,
|
||||
// even though we've already said that Output is always the same as Self
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<G, R> RenderHtml<R> for ReadGuard<String, G>
|
||||
where
|
||||
R: Renderer,
|
||||
G: Deref<Target = String> + Send,
|
||||
{
|
||||
type AsyncOutput = Self;
|
||||
|
||||
const MIN_LENGTH: usize = 0;
|
||||
|
||||
fn to_html_with_buf(self, buf: &mut String, position: &mut Position, escape: bool, mark_branches: bool) {
|
||||
<&str as RenderHtml<R>>::to_html_with_buf(&self, buf, position)
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
cursor: &Cursor<R>,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
let this: &str = self.as_ref();
|
||||
let StrState { node, .. } =
|
||||
this.hydrate::<FROM_SERVER>(cursor, position);
|
||||
ReadGuardStringState {
|
||||
node,
|
||||
str: self.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<G> ToTemplate for ReadGuard<String, G> {
|
||||
const TEMPLATE: &'static str = <&str as ToTemplate>::TEMPLATE;
|
||||
|
||||
fn to_template(
|
||||
buf: &mut String,
|
||||
class: &mut String,
|
||||
style: &mut String,
|
||||
inner_html: &mut String,
|
||||
position: &mut Position,
|
||||
) {
|
||||
<&str as ToTemplate>::to_template(
|
||||
buf, class, style, inner_html, position,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Renderer> Mountable<R> for ReadGuardStringState<R> {
|
||||
fn unmount(&mut self) {
|
||||
self.node.unmount()
|
||||
}
|
||||
|
||||
fn mount(
|
||||
&mut self,
|
||||
parent: &<R as Renderer>::Element,
|
||||
marker: Option<&<R as Renderer>::Node>,
|
||||
) {
|
||||
R::insert_node(parent, self.node.as_ref(), marker);
|
||||
}
|
||||
|
||||
fn insert_before_this(&self,
|
||||
child: &mut dyn Mountable<R>,
|
||||
) -> bool {
|
||||
child.mount(parent, Some(self.node.as_ref()));
|
||||
true
|
||||
}
|
||||
}*/
|
||||
@@ -18,7 +18,6 @@ use std::{
|
||||
};
|
||||
|
||||
mod class;
|
||||
mod guards;
|
||||
mod inner_html;
|
||||
/// Provides a reactive [`NodeRef`](node_ref::NodeRef) type.
|
||||
pub mod node_ref;
|
||||
|
||||
@@ -10,8 +10,8 @@ use rustc_hash::FxHashSet;
|
||||
use std::{any::TypeId, borrow::Cow, cell::RefCell};
|
||||
use wasm_bindgen::{intern, prelude::Closure, JsCast, JsValue};
|
||||
use web_sys::{
|
||||
Comment, CssStyleDeclaration, DocumentFragment, DomTokenList, Element,
|
||||
Event, HtmlElement, HtmlTemplateElement, Node, Text,
|
||||
Comment, CssStyleDeclaration, DomTokenList, Element, Event, HtmlElement,
|
||||
HtmlTemplateElement, Node, Text,
|
||||
};
|
||||
|
||||
/// A [`Renderer`] that uses `web-sys` to manipulate DOM elements in the browser.
|
||||
@@ -478,26 +478,6 @@ impl Mountable<Dom> for Element {
|
||||
}
|
||||
}
|
||||
|
||||
impl Mountable<Dom> for DocumentFragment {
|
||||
fn unmount(&mut self) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn mount(&mut self, parent: &Element, marker: Option<&Node>) {
|
||||
Dom::insert_node(parent, self, marker);
|
||||
}
|
||||
|
||||
fn insert_before_this(&self, child: &mut dyn Mountable<Dom>) -> bool {
|
||||
let parent =
|
||||
Dom::get_parent(self.as_ref()).and_then(Element::cast_from);
|
||||
if let Some(parent) = parent {
|
||||
child.mount(&parent, Some(self));
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl CastFrom<Node> for Text {
|
||||
fn cast_from(node: Node) -> Option<Text> {
|
||||
node.clone().dyn_into().ok()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{BoxedView, RenderHtml};
|
||||
use super::{BoxedView, RenderHtml, WrappedView};
|
||||
use crate::{html::attribute::Attribute, renderer::Renderer};
|
||||
|
||||
/// Allows adding a new attribute to some type, before it is rendered.
|
||||
@@ -47,11 +47,10 @@ macro_rules! no_attrs {
|
||||
|
||||
impl<T, Rndr> AddAnyAttr<Rndr> for BoxedView<T>
|
||||
where
|
||||
T: AddAnyAttr<Rndr>,
|
||||
T: AddAnyAttr<Rndr> + Send,
|
||||
Rndr: Renderer,
|
||||
{
|
||||
type Output<SomeNewAttr: Attribute<Rndr>> =
|
||||
BoxedView<T::Output<SomeNewAttr>>;
|
||||
type Output<SomeNewAttr: Attribute<Rndr>> = T::Output<SomeNewAttr>;
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<Rndr>>(
|
||||
self,
|
||||
@@ -60,6 +59,24 @@ where
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml<Rndr>,
|
||||
{
|
||||
BoxedView::new(self.into_inner().add_any_attr(attr))
|
||||
self.into_inner().add_any_attr(attr)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Rndr> AddAnyAttr<Rndr> for WrappedView<T>
|
||||
where
|
||||
T: AddAnyAttr<Rndr> + Send,
|
||||
Rndr: Renderer,
|
||||
{
|
||||
type Output<SomeNewAttr: Attribute<Rndr>> = T::Output<SomeNewAttr>;
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<Rndr>>(
|
||||
self,
|
||||
attr: NewAttr,
|
||||
) -> Self::Output<NewAttr>
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml<Rndr>,
|
||||
{
|
||||
self.into_inner().add_any_attr(attr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ use super::{
|
||||
RenderHtml,
|
||||
};
|
||||
use crate::{
|
||||
html::attribute::Attribute, hydration::Cursor, renderer::Renderer,
|
||||
html::attribute::{any_attribute::AnyAttribute, Attribute},
|
||||
hydration::Cursor,
|
||||
renderer::Renderer,
|
||||
ssr::StreamBuilder,
|
||||
};
|
||||
use std::{
|
||||
@@ -49,6 +51,8 @@ where
|
||||
fn(Box<dyn Any>, &mut StreamBuilder, &mut Position, bool, bool),
|
||||
build: fn(Box<dyn Any>) -> AnyViewState<R>,
|
||||
rebuild: fn(TypeId, Box<dyn Any>, &mut AnyViewState<R>),
|
||||
#[allow(unused)]
|
||||
add_any_attr: fn(Box<dyn Any>, AnyAttribute<R>) -> AnyView<R>,
|
||||
#[cfg(feature = "ssr")]
|
||||
#[allow(clippy::type_complexity)]
|
||||
resolve:
|
||||
@@ -290,11 +294,20 @@ where
|
||||
*state = new;
|
||||
}
|
||||
};
|
||||
|
||||
let add_any_attr = |value: Box<dyn Any>, attr: AnyAttribute<R>| {
|
||||
let value = value
|
||||
.downcast::<T>()
|
||||
.expect("AnyView::add_any_attr() couldn't downcast value");
|
||||
value.add_any_attr(attr).into_any()
|
||||
};
|
||||
|
||||
AnyView {
|
||||
type_id: TypeId::of::<T>(),
|
||||
value,
|
||||
build,
|
||||
rebuild,
|
||||
add_any_attr,
|
||||
#[cfg(feature = "ssr")]
|
||||
resolve,
|
||||
#[cfg(feature = "ssr")]
|
||||
@@ -332,7 +345,7 @@ impl<R> AddAnyAttr<R> for AnyView<R>
|
||||
where
|
||||
R: Renderer + 'static,
|
||||
{
|
||||
type Output<SomeNewAttr: Attribute<R>> = Self;
|
||||
type Output<SomeNewAttr: Attribute<R>> = AnyView<R>;
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<R>>(
|
||||
self,
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::{
|
||||
ssr::StreamBuilder,
|
||||
};
|
||||
use either_of::*;
|
||||
use futures::future::join;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
impl<A, B, Rndr> Render<Rndr> for Either<A, B>
|
||||
@@ -340,26 +341,85 @@ where
|
||||
B: RenderHtml<Rndr>,
|
||||
Rndr: Renderer,
|
||||
{
|
||||
type AsyncOutput = Either<A::AsyncOutput, B::AsyncOutput>;
|
||||
type AsyncOutput = EitherKeepAlive<A::AsyncOutput, B::AsyncOutput>;
|
||||
|
||||
const MIN_LENGTH: usize = 0;
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
todo!()
|
||||
if let Some(inner) = &mut self.a {
|
||||
inner.dry_resolve();
|
||||
}
|
||||
if let Some(inner) = &mut self.b {
|
||||
inner.dry_resolve();
|
||||
}
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
todo!()
|
||||
let EitherKeepAlive { a, b, show_b } = self;
|
||||
let (a, b) = join(
|
||||
async move {
|
||||
match a {
|
||||
Some(a) => Some(a.resolve().await),
|
||||
None => None,
|
||||
}
|
||||
},
|
||||
async move {
|
||||
match b {
|
||||
Some(b) => Some(b.resolve().await),
|
||||
None => None,
|
||||
}
|
||||
},
|
||||
)
|
||||
.await;
|
||||
EitherKeepAlive { a, b, show_b }
|
||||
}
|
||||
|
||||
fn to_html_with_buf(
|
||||
self,
|
||||
_buf: &mut String,
|
||||
_position: &mut Position,
|
||||
_escape: bool,
|
||||
_mark_branches: bool,
|
||||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
todo!()
|
||||
if self.show_b {
|
||||
self.b
|
||||
.expect("rendering B to HTML without filling it")
|
||||
.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
} else {
|
||||
self.a
|
||||
.expect("rendering A to HTML without filling it")
|
||||
.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
}
|
||||
}
|
||||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
self,
|
||||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
if self.show_b {
|
||||
self.b
|
||||
.expect("rendering B to HTML without filling it")
|
||||
.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
} else {
|
||||
self.a
|
||||
.expect("rendering A to HTML without filling it")
|
||||
.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
buf,
|
||||
position,
|
||||
escape,
|
||||
mark_branches,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
|
||||
@@ -345,97 +345,3 @@ where
|
||||
VecState { states, marker }
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
pub trait IterView<R: Renderer> {
|
||||
type Iterator: Iterator<Item = Self::View>;
|
||||
type View: Render<R>;
|
||||
|
||||
fn iter_view(self) -> RenderIter<Self::Iterator, Self::View, R>;
|
||||
}
|
||||
|
||||
impl<I, V, R> IterView<R> for I
|
||||
where
|
||||
I: Iterator<Item = V>,
|
||||
V: Render<R>,
|
||||
R: Renderer,
|
||||
{
|
||||
type Iterator = I;
|
||||
type View = V;
|
||||
|
||||
fn iter_view(self) -> RenderIter<Self::Iterator, Self::View, R> {
|
||||
RenderIter {
|
||||
inner: self,
|
||||
rndr: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RenderIter<I, V, R>
|
||||
where
|
||||
I: Iterator<Item = V>,
|
||||
V: Render<R>,
|
||||
R: Renderer,
|
||||
{
|
||||
inner: I,
|
||||
rndr: PhantomData<R>,
|
||||
}
|
||||
|
||||
impl<I, V, R> Render<R> for RenderIter<I, V, R>
|
||||
where
|
||||
I: Iterator<Item = V>,
|
||||
V: Render<R>,
|
||||
R: Renderer,
|
||||
{
|
||||
type State = ();
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, V, R> RenderHtml<R> for RenderIter<I, V, R>
|
||||
where
|
||||
I: Iterator<Item = V>,
|
||||
V: RenderHtml<R>,
|
||||
R: Renderer,
|
||||
|
||||
{
|
||||
fn to_html(self, buf: &mut String, position: &PositionState) {
|
||||
for mut next in self.0.by_ref() {
|
||||
next.to_html(buf, position);
|
||||
}
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
cursor: &Cursor<R>,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::IterView;
|
||||
use crate::view::{Render, RenderHtml};
|
||||
|
||||
#[test]
|
||||
fn iter_view_takes_iterator() {
|
||||
let strings = vec!["a", "b", "c"];
|
||||
let mut iter_view = strings
|
||||
.into_iter()
|
||||
.map(|n| n.to_ascii_uppercase())
|
||||
.iter_view();
|
||||
let mut buf = String::new();
|
||||
iter_view.to_html(&mut buf, &Default::default());
|
||||
assert_eq!(buf, "ABC");
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
use self::add_attr::AddAnyAttr;
|
||||
use crate::{hydration::Cursor, renderer::Renderer, ssr::StreamBuilder};
|
||||
use parking_lot::RwLock;
|
||||
use std::{cell::RefCell, future::Future, rc::Rc, sync::Arc};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
future::Future,
|
||||
ops::{Deref, DerefMut},
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
/// Add attributes to typed views.
|
||||
pub mod add_attr;
|
||||
@@ -430,9 +436,9 @@ pub enum Position {
|
||||
///
|
||||
/// This is a newtype around `Box<_>` that allows us to implement rendering traits on it.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BoxedView<T>(Box<T>);
|
||||
pub struct BoxedView<T: Send>(Box<T>);
|
||||
|
||||
impl<T> BoxedView<T> {
|
||||
impl<T: Send> BoxedView<T> {
|
||||
/// Stores view on the heap.
|
||||
pub fn new(value: T) -> Self {
|
||||
Self(Box::new(value))
|
||||
@@ -444,21 +450,35 @@ impl<T> BoxedView<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<T> for BoxedView<T> {
|
||||
impl<T: Send> AsRef<T> for BoxedView<T> {
|
||||
fn as_ref(&self) -> &T {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsMut<T> for BoxedView<T> {
|
||||
impl<T: Send> AsMut<T> for BoxedView<T> {
|
||||
fn as_mut(&mut self) -> &mut T {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send> Deref for BoxedView<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send> DerefMut for BoxedView<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Rndr> Render<Rndr> for BoxedView<T>
|
||||
where
|
||||
T: Render<Rndr>,
|
||||
T: Render<Rndr> + Send,
|
||||
Rndr: Renderer,
|
||||
{
|
||||
type State = T::State;
|
||||
@@ -474,7 +494,7 @@ where
|
||||
|
||||
impl<T, Rndr> RenderHtml<Rndr> for BoxedView<T>
|
||||
where
|
||||
T: RenderHtml<Rndr>,
|
||||
T: RenderHtml<Rndr> + Send,
|
||||
Rndr: Renderer,
|
||||
{
|
||||
type AsyncOutput = BoxedView<T::AsyncOutput>;
|
||||
@@ -509,3 +529,131 @@ where
|
||||
self.into_inner().hydrate::<FROM_SERVER>(cursor, position)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ToTemplate for BoxedView<T>
|
||||
where
|
||||
T: ToTemplate + Send,
|
||||
{
|
||||
fn to_template(
|
||||
buf: &mut String,
|
||||
class: &mut String,
|
||||
style: &mut String,
|
||||
inner_html: &mut String,
|
||||
position: &mut Position,
|
||||
) {
|
||||
T::to_template(buf, class, style, inner_html, position);
|
||||
}
|
||||
}
|
||||
|
||||
/// A newtype around a view that allows us to get out of certain compile errors.
|
||||
///
|
||||
/// It is unlikely that you need this in your own work.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct WrappedView<T: Send>(T);
|
||||
|
||||
impl<T: Send> WrappedView<T> {
|
||||
/// Wraps the view.
|
||||
pub fn new(value: T) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
|
||||
/// Unwraps the view to its inner value.
|
||||
pub fn into_inner(self) -> T {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send> Deref for WrappedView<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send> DerefMut for WrappedView<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send> AsRef<T> for WrappedView<T> {
|
||||
fn as_ref(&self) -> &T {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send> AsMut<T> for WrappedView<T> {
|
||||
fn as_mut(&mut self) -> &mut T {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Rndr> Render<Rndr> for WrappedView<T>
|
||||
where
|
||||
T: Render<Rndr> + Send,
|
||||
Rndr: Renderer,
|
||||
{
|
||||
type State = T::State;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
self.into_inner().build()
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
self.into_inner().rebuild(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Rndr> RenderHtml<Rndr> for WrappedView<T>
|
||||
where
|
||||
T: RenderHtml<Rndr> + Send,
|
||||
Rndr: Renderer,
|
||||
{
|
||||
type AsyncOutput = BoxedView<T::AsyncOutput>;
|
||||
|
||||
const MIN_LENGTH: usize = T::MIN_LENGTH;
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
self.as_mut().dry_resolve();
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
let inner = self.into_inner().resolve().await;
|
||||
BoxedView::new(inner)
|
||||
}
|
||||
|
||||
fn to_html_with_buf(
|
||||
self,
|
||||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
self.into_inner()
|
||||
.to_html_with_buf(buf, position, escape, mark_branches)
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
cursor: &Cursor<Rndr>,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
self.into_inner().hydrate::<FROM_SERVER>(cursor, position)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ToTemplate for WrappedView<T>
|
||||
where
|
||||
T: ToTemplate + Send,
|
||||
{
|
||||
fn to_template(
|
||||
buf: &mut String,
|
||||
class: &mut String,
|
||||
style: &mut String,
|
||||
inner_html: &mut String,
|
||||
position: &mut Position,
|
||||
) {
|
||||
T::to_template(buf, class, style, inner_html, position);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,8 +225,6 @@ where
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml<R>,
|
||||
{
|
||||
// TODO: there is a strange compiler thing that seems to prevent us returning Self here,
|
||||
// even though we've already said that Output is always the same as Self
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,11 +106,11 @@ where
|
||||
}
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
todo!()
|
||||
self.view.dry_resolve();
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
todo!()
|
||||
self.view.resolve().await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user