Skip to content

Commit

Permalink
Erased routing, codegen opts (#3623)
Browse files Browse the repository at this point in the history
  • Loading branch information
zakstucke authored Feb 18, 2025
1 parent 7157958 commit 49e44a2
Show file tree
Hide file tree
Showing 21 changed files with 1,110 additions and 598 deletions.
14 changes: 7 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions leptos_macro/src/view/component_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,14 @@ pub(crate) fn component_to_tokens(
.collect::<Vec<_>>();

let spreads = (!(spreads.is_empty())).then(|| {
quote! {
.add_any_attr((#(#spreads,)*).into_attr())
if cfg!(erase_components) {
quote! {
.add_any_attr(vec![#(#spreads.into_attr().into_any_attr(),)*])
}
} else {
quote! {
.add_any_attr((#(#spreads,)*).into_attr())
}
}
});

Expand Down
26 changes: 22 additions & 4 deletions leptos_macro/src/view/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,12 @@ fn element_children_to_tokens(
{ #child }
)
})
} else if cfg!(erase_components) {
Some(quote! {
.child(
leptos::tachys::view::iterators::StaticVec::from(vec![#(#children.into_maybe_erased()),*])
)
})
} else if children.len() > 16 {
// implementations of various traits used in routing and rendering are implemented for
// tuples of sizes 0, 1, 2, 3, ... N. N varies but is > 16. The traits are also implemented
Expand Down Expand Up @@ -468,6 +474,10 @@ fn fragment_to_tokens(
None
} else if children.len() == 1 {
children.into_iter().next()
} else if cfg!(erase_components) {
Some(quote! {
leptos::tachys::view::iterators::StaticVec::from(vec![#(#children.into_maybe_erased()),*])
})
} else if children.len() > 16 {
// implementations of various traits used in routing and rendering are implemented for
// tuples of sizes 0, 1, 2, 3, ... N. N varies but is > 16. The traits are also implemented
Expand Down Expand Up @@ -752,10 +762,18 @@ pub(crate) fn element_to_tokens(
}
}
}
Some(quote! {
(#(#attributes,)*)
#(.add_any_attr(#additions))*
})

if cfg!(erase_components) {
Some(quote! {
vec![#(#attributes.into_attr().into_any_attr(),)*]
#(.add_any_attr(#additions))*
})
} else {
Some(quote! {
(#(#attributes,)*)
#(.add_any_attr(#additions))*
})
}
} else {
let tag = name.to_string();
// collect close_tag name to emit semantic information for IDE.
Expand Down
125 changes: 76 additions & 49 deletions reactive_graph/src/computed/inner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
use or_poisoned::OrPoisoned;
use std::{
fmt::Debug,
sync::{Arc, RwLock},
sync::{Arc, RwLock, RwLockWriteGuard},
};

pub struct MemoInner<T, S>
Expand Down Expand Up @@ -72,17 +72,21 @@ where
}

fn mark_check(&self) {
{
let mut lock = self.reactivity.write().or_poisoned();
if lock.state != ReactiveNodeState::Dirty {
lock.state = ReactiveNodeState::Check;
/// codegen optimisation:
fn inner(reactivity: &RwLock<MemoInnerReactivity>) {
{
let mut lock = reactivity.write().or_poisoned();
if lock.state != ReactiveNodeState::Dirty {
lock.state = ReactiveNodeState::Check;
}
}
for sub in
(&reactivity.read().or_poisoned().subscribers).into_iter()
{
sub.mark_check();
}
}
for sub in
(&self.reactivity.read().or_poisoned().subscribers).into_iter()
{
sub.mark_check();
}
inner(&self.reactivity);
}

fn mark_subscribers_check(&self) {
Expand All @@ -93,64 +97,87 @@ where
}

fn update_if_necessary(&self) -> bool {
let (state, sources) = {
let inner = self.reactivity.read().or_poisoned();
(inner.state, inner.sources.clone())
};

let needs_update = match state {
ReactiveNodeState::Clean => false,
ReactiveNodeState::Dirty => true,
ReactiveNodeState::Check => (&sources).into_iter().any(|source| {
source.update_if_necessary()
|| self.reactivity.read().or_poisoned().state
== ReactiveNodeState::Dirty
}),
};
/// codegen optimisation:
fn needs_update(reactivity: &RwLock<MemoInnerReactivity>) -> bool {
let (state, sources) = {
let inner = reactivity.read().or_poisoned();
(inner.state, inner.sources.clone())
};
match state {
ReactiveNodeState::Clean => false,
ReactiveNodeState::Dirty => true,
ReactiveNodeState::Check => {
(&sources).into_iter().any(|source| {
source.update_if_necessary()
|| reactivity.read().or_poisoned().state
== ReactiveNodeState::Dirty
})
}
}
}

if needs_update {
let fun = self.fun.clone();
let owner = self.owner.clone();
if needs_update(&self.reactivity) {
// No deadlock risk, because we only hold the value lock.
let value = self.value.write().or_poisoned().take();

let any_subscriber =
{ self.reactivity.read().or_poisoned().any_subscriber.clone() };
any_subscriber.clear_sources(&any_subscriber);
let (new_value, changed) = owner.with_cleanup(|| {
/// codegen optimisation:
fn inner_1(
reactivity: &RwLock<MemoInnerReactivity>,
) -> AnySubscriber {
let any_subscriber =
reactivity.read().or_poisoned().any_subscriber.clone();
any_subscriber.clear_sources(&any_subscriber);
any_subscriber
.with_observer(|| fun(value.map(StorageAccess::into_taken)))
}
let any_subscriber = inner_1(&self.reactivity);

let (new_value, changed) = self.owner.with_cleanup(|| {
any_subscriber.with_observer(|| {
(self.fun)(value.map(StorageAccess::into_taken))
})
});

// Two locks are aquired, so order matters.
let mut reactivity_lock = self.reactivity.write().or_poisoned();
let reactivity_lock = self.reactivity.write().or_poisoned();
{
// Safety: Can block endlessly if the user is has a ReadGuard on the value
let mut value_lock = self.value.write().or_poisoned();
*value_lock = Some(S::wrap(new_value));
}
reactivity_lock.state = ReactiveNodeState::Clean;

if changed {
let subs = reactivity_lock.subscribers.clone();
drop(reactivity_lock);
for sub in subs {
// don't trigger reruns of effects/memos
// basically: if one of the observers has triggered this memo to
// run, it doesn't need to be re-triggered because of this change
if !Observer::is(&sub) {
sub.mark_dirty();

/// codegen optimisation:
fn inner_2(
changed: bool,
mut reactivity_lock: RwLockWriteGuard<'_, MemoInnerReactivity>,
) {
reactivity_lock.state = ReactiveNodeState::Clean;

if changed {
let subs = reactivity_lock.subscribers.clone();
drop(reactivity_lock);
for sub in subs {
// don't trigger reruns of effects/memos
// basically: if one of the observers has triggered this memo to
// run, it doesn't need to be re-triggered because of this change
if !Observer::is(&sub) {
sub.mark_dirty();
}
}
} else {
drop(reactivity_lock);
}
} else {
drop(reactivity_lock);
}
inner_2(changed, reactivity_lock);

changed
} else {
let mut lock = self.reactivity.write().or_poisoned();
lock.state = ReactiveNodeState::Clean;
false
/// codegen optimisation:
fn inner(reactivity: &RwLock<MemoInnerReactivity>) -> bool {
let mut lock = reactivity.write().or_poisoned();
lock.state = ReactiveNodeState::Clean;
false
}
inner(&self.reactivity)
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions router/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,9 @@ rustdoc-args = ["--generate-link-to-definition"]

[package.metadata.cargo-all-features]
denylist = ["tracing"]

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(leptos_debuginfo)',
'cfg(erase_components)',
] }
Loading

0 comments on commit 49e44a2

Please sign in to comment.