Compare commits

...

24 Commits

Author SHA1 Message Date
Greg Johnston
0375df5431 remove WrappedView add_any_attr impls 2024-09-23 11:26:04 -04:00
Greg Johnston
6206073c5e does this fix the linker error? 2024-09-23 10:29:30 -04:00
Greg Johnston
3d2cdc21a1 beta6 2024-09-23 08:57:44 -04:00
Greg Johnston
93d939aef8 Merge pull request #3009 from leptos-rs/stores-patch
Stores patching
2024-09-22 21:45:10 -04:00
Greg Johnston
fb04750607 example: add example of patching fields 2024-09-22 19:52:48 -04:00
Greg Johnston
a080496e7e fix: notify correctly when patching stores 2024-09-22 19:51:50 -04:00
Greg Johnston
9fc1002167 perf: do not notify keyed iterator when only an inner field has changed 2024-09-22 19:50:30 -04:00
Greg Johnston
bc5c766530 fix: notify stores when modifying parent, without notifying siblings 2024-09-22 19:33:14 -04:00
Greg Johnston
17821f863a fix: restructure StoredValue so that nested calls do not deadlock (closes #2968) (#3004) 2024-09-22 19:21:49 -04:00
Baptiste
1ca4f34ef3 fix: optional props with islands (#3005) 2024-09-21 18:13:58 -04:00
Greg Johnston
8f0a1554b1 Merge pull request #3000 from leptos-rs/add-attr-any-view
Doing chores
2024-09-21 08:51:32 -04:00
Greg Johnston
38d4f26d03 fix: snip infinite type recursion 2024-09-21 07:51:27 -04:00
Greg Johnston
2b04c2710d Merge pull request #3002 from leptos-rs/fix-stores
Fix stores
2024-09-21 07:37:54 -04:00
Greg Johnston
a4937a1236 fix: correctly triggering parents vs siblings 2024-09-20 21:25:42 -04:00
Greg Johnston
f6f2c39686 chore: remove UntrackedWriter 2024-09-20 20:47:46 -04:00
Greg Johnston
d7eacf1ab5 chore: remove unused DocumentFragment mounting code 2024-09-20 20:41:51 -04:00
Greg Johnston
d1a4bbe28e chore: fix adding attr todo for static types 2024-09-20 20:41:51 -04:00
Greg Johnston
412ecd6b1b chore: remove dead iterator wrapper code 2024-09-20 20:41:51 -04:00
Greg Johnston
9bc0152121 chore: clean up todos in template SSR code 2024-09-20 20:41:51 -04:00
Greg Johnston
4b05cada8f chore: remove dead code for rendering guards 2024-09-20 20:41:51 -04:00
Greg Johnston
a818862704 chore: tidy up todos on EitherKeepAlive 2024-09-20 20:41:51 -04:00
Greg Johnston
173487debc fix: add add_any_attr() for AnyView<R> 2024-09-20 20:41:51 -04:00
Greg Johnston
449d96cc9a feat: add wrapper to support add_any_attr() fixes 2024-09-20 20:41:51 -04:00
Greg Johnston
f9bf6a95ed Merge pull request #3001 from leptos-rs/ci
Fix CI
2024-09-20 20:40:53 -04:00
49 changed files with 1002 additions and 965 deletions

View File

@@ -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

View File

@@ -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"

View File

@@ -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>,

View File

@@ -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"

View File

@@ -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"

View File

@@ -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! {}

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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)]

View File

@@ -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));
}
}

View File

@@ -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),
),
}

View File

@@ -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)),
}
}
}

View File

@@ -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 }

View File

@@ -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();
}

View File

@@ -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| {

View 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));
}
}

View 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),
}
})
}
}

View File

@@ -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();
}
}

View File

@@ -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 signals 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

View File

@@ -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 signals 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

View File

@@ -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),
}
}
}

View File

@@ -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 signals 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),
}
}
}

View File

@@ -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()),
}
}
}

View File

@@ -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 signals 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()
}
}

View File

@@ -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,
}

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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()),
}
}
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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]

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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
}
}*/

View File

@@ -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;

View File

@@ -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()

View File

@@ -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)
}
}

View File

@@ -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,

View File

@@ -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>(

View File

@@ -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");
}
}
*/

View File

@@ -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);
}
}

View File

@@ -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!()
}
}

View File

@@ -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
}
}