diff --git a/Cargo.lock b/Cargo.lock index 61fdaceef1f..ee5ce6392b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5229,6 +5229,7 @@ dependencies = [ name = "yew-router-macro" version = "0.20.0" dependencies = [ + "matchit 0.9.2", "proc-macro2", "quote", "rustversion", diff --git a/examples/async_clock/Trunk.toml b/examples/async_clock/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/async_clock/Trunk.toml +++ b/examples/async_clock/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/boids/Trunk.toml b/examples/boids/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/boids/Trunk.toml +++ b/examples/boids/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/communication_child_to_parent/Trunk.toml b/examples/communication_child_to_parent/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/communication_child_to_parent/Trunk.toml +++ b/examples/communication_child_to_parent/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/communication_grandchild_with_grandparent/Trunk.toml b/examples/communication_grandchild_with_grandparent/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/communication_grandchild_with_grandparent/Trunk.toml +++ b/examples/communication_grandchild_with_grandparent/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/communication_grandparent_to_grandchild/Trunk.toml b/examples/communication_grandparent_to_grandchild/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/communication_grandparent_to_grandchild/Trunk.toml +++ b/examples/communication_grandparent_to_grandchild/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/communication_parent_to_child/Trunk.toml b/examples/communication_parent_to_child/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/communication_parent_to_child/Trunk.toml +++ b/examples/communication_parent_to_child/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/contexts/Trunk.toml b/examples/contexts/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/contexts/Trunk.toml +++ b/examples/contexts/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/counter/Trunk.toml b/examples/counter/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/counter/Trunk.toml +++ b/examples/counter/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/counter_functional/Trunk.toml b/examples/counter_functional/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/counter_functional/Trunk.toml +++ b/examples/counter_functional/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/dyn_create_destroy_apps/Trunk.toml b/examples/dyn_create_destroy_apps/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/dyn_create_destroy_apps/Trunk.toml +++ b/examples/dyn_create_destroy_apps/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/file_upload/Trunk.toml b/examples/file_upload/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/file_upload/Trunk.toml +++ b/examples/file_upload/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/function_delayed_input/Trunk.toml b/examples/function_delayed_input/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/function_delayed_input/Trunk.toml +++ b/examples/function_delayed_input/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/function_memory_game/Trunk.toml b/examples/function_memory_game/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/function_memory_game/Trunk.toml +++ b/examples/function_memory_game/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/function_router/Trunk.toml b/examples/function_router/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/function_router/Trunk.toml +++ b/examples/function_router/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/function_todomvc/Trunk.toml b/examples/function_todomvc/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/function_todomvc/Trunk.toml +++ b/examples/function_todomvc/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/futures/Trunk.toml b/examples/futures/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/futures/Trunk.toml +++ b/examples/futures/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/game_of_life/Trunk.toml b/examples/game_of_life/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/game_of_life/Trunk.toml +++ b/examples/game_of_life/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/immutable/Trunk.toml b/examples/immutable/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/immutable/Trunk.toml +++ b/examples/immutable/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/inner_html/Trunk.toml b/examples/inner_html/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/inner_html/Trunk.toml +++ b/examples/inner_html/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/js_callback/Trunk.toml b/examples/js_callback/Trunk.toml index c18aef7c57c..806f328106e 100644 --- a/examples/js_callback/Trunk.toml +++ b/examples/js_callback/Trunk.toml @@ -1,5 +1,5 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" [[hooks]] stage = "pre_build" diff --git a/examples/keyed_list/Trunk.toml b/examples/keyed_list/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/keyed_list/Trunk.toml +++ b/examples/keyed_list/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/mount_point/Trunk.toml b/examples/mount_point/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/mount_point/Trunk.toml +++ b/examples/mount_point/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/nested_list/Trunk.toml b/examples/nested_list/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/nested_list/Trunk.toml +++ b/examples/nested_list/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/node_refs/Trunk.toml b/examples/node_refs/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/node_refs/Trunk.toml +++ b/examples/node_refs/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/password_strength/Trunk.toml b/examples/password_strength/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/password_strength/Trunk.toml +++ b/examples/password_strength/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/portals/Trunk.toml b/examples/portals/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/portals/Trunk.toml +++ b/examples/portals/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/router/Trunk.toml b/examples/router/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/router/Trunk.toml +++ b/examples/router/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/suspense/Trunk.toml b/examples/suspense/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/suspense/Trunk.toml +++ b/examples/suspense/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/timer/Trunk.toml b/examples/timer/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/timer/Trunk.toml +++ b/examples/timer/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/timer_functional/Trunk.toml b/examples/timer_functional/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/timer_functional/Trunk.toml +++ b/examples/timer_functional/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/todomvc/Trunk.toml b/examples/todomvc/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/todomvc/Trunk.toml +++ b/examples/todomvc/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/two_apps/Trunk.toml b/examples/two_apps/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/two_apps/Trunk.toml +++ b/examples/two_apps/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/web_worker_fib/Trunk.toml b/examples/web_worker_fib/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/web_worker_fib/Trunk.toml +++ b/examples/web_worker_fib/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/web_worker_prime/Trunk.toml b/examples/web_worker_prime/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/web_worker_prime/Trunk.toml +++ b/examples/web_worker_prime/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/examples/webgl/Trunk.toml b/examples/webgl/Trunk.toml index f6ad36c4520..783e0ba4fd5 100644 --- a/examples/webgl/Trunk.toml +++ b/examples/webgl/Trunk.toml @@ -1,2 +1,2 @@ [tools] -wasm_opt = "version_129" +wasm_opt = "version_131" diff --git a/packages/yew/src/app_handle.rs b/packages/yew/src/app_handle.rs index 94356a2c1df..5f805020e83 100644 --- a/packages/yew/src/app_handle.rs +++ b/packages/yew/src/app_handle.rs @@ -87,7 +87,7 @@ fn clear_element(host: &Element) { #[cfg(feature = "hydration")] mod feat_hydration { use super::*; - use crate::dom_bundle::Fragment; + use crate::dom_bundle::{Fragment, SlotBulletin}; impl AppHandle where @@ -106,17 +106,13 @@ mod feat_hydration { let mut fragment = Fragment::collect_children(&host); let hosting_root = BSubtree::create_root(&host); - let mut previous_next_sibling = None; app.scope.hydrate_in_place( hosting_root, host.clone(), &mut fragment, Rc::clone(&props), - &mut previous_next_sibling, + &mut SlotBulletin::new(), ); - if let Some(previous_next_sibling) = previous_next_sibling { - previous_next_sibling.reassign(DomSlot::at_end()); - } // We remove all remaining nodes, this mimics the clear_element behaviour in // mount_with_props. diff --git a/packages/yew/src/dom_bundle/bcomp.rs b/packages/yew/src/dom_bundle/bcomp.rs index a28dbb15c44..f8cf509add4 100644 --- a/packages/yew/src/dom_bundle/bcomp.rs +++ b/packages/yew/src/dom_bundle/bcomp.rs @@ -6,7 +6,7 @@ use std::fmt; use web_sys::Element; -use super::{BNode, BSubtree, DomSlot, DynamicDomSlot, Reconcilable, ReconcileTarget}; +use super::{BNode, BSubtree, DomSlot, Reconcilable, ReconcileTarget}; use crate::html::{AnyScope, Scoped}; use crate::virtual_dom::{Key, VComp}; @@ -16,7 +16,7 @@ pub(super) struct BComp { scope: Box, /// An internal [`DomSlot`] passed around to track this components position. This /// will dynamically adjust when a lifecycle changes the render state of this component. - own_position: DynamicDomSlot, + own_position: DomSlot, key: Option, } @@ -43,7 +43,7 @@ impl ReconcileTarget for BComp { fn shift(&self, next_parent: &Element, slot: DomSlot) -> DomSlot { self.scope.shift_node(next_parent.clone(), slot); - self.own_position.to_position() + self.own_position.clone() } } @@ -64,14 +64,14 @@ impl Reconcilable for VComp { .. } = self; - let (scope, internal_ref) = mountable.mount(root, parent_scope, parent.to_owned(), slot); + let (scope, own_position) = mountable.mount(root, parent_scope, parent.to_owned(), slot); ( - internal_ref.to_position(), + own_position.clone(), BComp { type_id, scope, - own_position: internal_ref, + own_position, key, }, ) @@ -106,14 +106,14 @@ impl Reconcilable for VComp { bcomp.key = key; mountable.reuse(bcomp.scope.borrow(), slot); - bcomp.own_position.to_position() + bcomp.own_position.clone() } } #[cfg(feature = "hydration")] mod feat_hydration { use super::*; - use crate::dom_bundle::{Fragment, Hydratable}; + use crate::dom_bundle::{Fragment, Hydratable, SlotBulletin}; impl Hydratable for VComp { fn hydrate( @@ -122,7 +122,7 @@ mod feat_hydration { parent_scope: &AnyScope, parent: &Element, fragment: &mut Fragment, - prev_next_sibling: &mut Option, + prev_next_sibling: &mut SlotBulletin<'_>, ) -> Self::Bundle { let VComp { type_id, @@ -207,19 +207,19 @@ mod tests { #[test] fn set_properties_to_component() { - html! { + let _ = html! { }; - html! { + let _ = html! { }; - html! { + let _ = html! { }; - html! { + let _ = html! { }; @@ -228,7 +228,7 @@ mod tests { field_2: 1, }; - html! { + let _ = html! { }; } diff --git a/packages/yew/src/dom_bundle/blist.rs b/packages/yew/src/dom_bundle/blist.rs index 05d99dde83b..92b71e2aa5a 100644 --- a/packages/yew/src/dom_bundle/blist.rs +++ b/packages/yew/src/dom_bundle/blist.rs @@ -516,7 +516,7 @@ impl Reconcilable for VList { #[cfg(feature = "hydration")] mod feat_hydration { use super::*; - use crate::dom_bundle::{DynamicDomSlot, Fragment, Hydratable}; + use crate::dom_bundle::{Fragment, Hydratable, SlotBulletin}; impl Hydratable for VList { fn hydrate( @@ -525,7 +525,7 @@ mod feat_hydration { parent_scope: &AnyScope, parent: &Element, fragment: &mut Fragment, - prev_next_sibling: &mut Option, + prev_next_sibling: &mut SlotBulletin<'_>, ) -> Self::Bundle { let (key, fully_keyed, vchildren) = self.split_for_blist(); diff --git a/packages/yew/src/dom_bundle/bnode.rs b/packages/yew/src/dom_bundle/bnode.rs index 39bc39add3d..fdfe33d6f43 100644 --- a/packages/yew/src/dom_bundle/bnode.rs +++ b/packages/yew/src/dom_bundle/bnode.rs @@ -267,7 +267,7 @@ impl fmt::Debug for BNode { #[cfg(feature = "hydration")] mod feat_hydration { use super::*; - use crate::dom_bundle::{DynamicDomSlot, Fragment, Hydratable}; + use crate::dom_bundle::{Fragment, Hydratable, SlotBulletin}; impl Hydratable for VNode { fn hydrate( @@ -276,7 +276,7 @@ mod feat_hydration { parent_scope: &AnyScope, parent: &Element, fragment: &mut Fragment, - prev_next_sibling: &mut Option, + prev_next_sibling: &mut SlotBulletin<'_>, ) -> Self::Bundle { match self { VNode::VTag(vtag) => RcExt::unwrap_or_clone(vtag) diff --git a/packages/yew/src/dom_bundle/braw.rs b/packages/yew/src/dom_bundle/braw.rs index abf520060bc..0b82489e4ae 100644 --- a/packages/yew/src/dom_bundle/braw.rs +++ b/packages/yew/src/dom_bundle/braw.rs @@ -146,7 +146,7 @@ impl Reconcilable for VRaw { #[cfg(feature = "hydration")] mod feat_hydration { use super::*; - use crate::dom_bundle::{DynamicDomSlot, Fragment, Hydratable}; + use crate::dom_bundle::{Fragment, Hydratable, SlotBulletin}; use crate::virtual_dom::Collectable; impl Hydratable for VRaw { @@ -156,17 +156,14 @@ mod feat_hydration { _parent_scope: &AnyScope, parent: &Element, fragment: &mut Fragment, - prev_next_sibling: &mut Option, + prev_next_sibling: &mut SlotBulletin<'_>, ) -> Self::Bundle { let collectable = Collectable::Raw; let fallback_fragment = Fragment::collect_between(fragment, &collectable, parent); let first_child = fallback_fragment.iter().next().cloned(); - if let (Some(first_child), prev_next_sibling) = (&first_child, prev_next_sibling) { - if let Some(prev_next_sibling) = prev_next_sibling { - prev_next_sibling.reassign(DomSlot::at(first_child.clone())); - } - *prev_next_sibling = None; + if let Some(first_child) = &first_child { + prev_next_sibling.write_at_node(first_child.clone()); } let Self { html } = self; diff --git a/packages/yew/src/dom_bundle/bsuspense.rs b/packages/yew/src/dom_bundle/bsuspense.rs index f5e639303fa..be79a222954 100644 --- a/packages/yew/src/dom_bundle/bsuspense.rs +++ b/packages/yew/src/dom_bundle/bsuspense.rs @@ -224,7 +224,7 @@ impl Reconcilable for VSuspense { #[cfg(feature = "hydration")] mod feat_hydration { use super::*; - use crate::dom_bundle::{DynamicDomSlot, Fragment, Hydratable}; + use crate::dom_bundle::{Fragment, Hydratable, SlotBulletin}; use crate::virtual_dom::Collectable; impl Hydratable for VSuspense { @@ -234,7 +234,7 @@ mod feat_hydration { parent_scope: &AnyScope, parent: &Element, fragment: &mut Fragment, - previous_next_sibling: &mut Option, + previous_next_sibling: &mut SlotBulletin<'_>, ) -> Self::Bundle { let detached_parent = document() .create_element("div") diff --git a/packages/yew/src/dom_bundle/btag/mod.rs b/packages/yew/src/dom_bundle/btag/mod.rs index 3b822409e7b..f9c269db031 100644 --- a/packages/yew/src/dom_bundle/btag/mod.rs +++ b/packages/yew/src/dom_bundle/btag/mod.rs @@ -352,7 +352,7 @@ mod feat_hydration { use web_sys::Node; use super::*; - use crate::dom_bundle::{DynamicDomSlot, Fragment, Hydratable, node_type_str}; + use crate::dom_bundle::{Fragment, Hydratable, SlotBulletin, node_type_str}; impl Hydratable for VTag { fn hydrate( @@ -361,7 +361,7 @@ mod feat_hydration { parent_scope: &AnyScope, _parent: &Element, fragment: &mut Fragment, - prev_next_sibling: &mut Option, + prev_next_sibling: &mut SlotBulletin<'_>, ) -> Self::Bundle { let tag_name = self.tag().to_owned(); @@ -429,12 +429,13 @@ mod feat_hydration { } VTagInner::Other { children, tag } => { let mut nodes = Fragment::collect_children(&el); - let mut prev_next_child = None; - let child_bundle = - children.hydrate(root, parent_scope, &el, &mut nodes, &mut prev_next_child); - if let Some(prev_next_child) = prev_next_child { - prev_next_child.reassign(DomSlot::at_end()); - } + let child_bundle = children.hydrate( + root, + parent_scope, + &el, + &mut nodes, + &mut SlotBulletin::new(), + ); nodes.trim_start_text_nodes(); @@ -445,10 +446,7 @@ mod feat_hydration { }; node_ref.set(Some((*el).clone())); - if let Some(prev_next_sibling) = prev_next_sibling { - prev_next_sibling.reassign(DomSlot::at((*el).clone())); - } - *prev_next_sibling = None; + prev_next_sibling.write_at_node((*el).clone()); BTag { inner, diff --git a/packages/yew/src/dom_bundle/btext.rs b/packages/yew/src/dom_bundle/btext.rs index fa5fd882016..a1eba090d0e 100644 --- a/packages/yew/src/dom_bundle/btext.rs +++ b/packages/yew/src/dom_bundle/btext.rs @@ -94,7 +94,7 @@ mod feat_hydration { use web_sys::Node; use super::*; - use crate::dom_bundle::{DynamicDomSlot, Fragment, Hydratable}; + use crate::dom_bundle::{Fragment, Hydratable, SlotBulletin}; impl Hydratable for VText { fn hydrate( @@ -103,7 +103,7 @@ mod feat_hydration { _parent_scope: &AnyScope, parent: &Element, fragment: &mut Fragment, - previous_next_sibling: &mut Option, + previous_next_sibling: &mut SlotBulletin<'_>, ) -> Self::Bundle { let create_at = |next_sibling: Option, text: AttrValue| { // If there are multiple text nodes placed back-to-back in SSR, it may be parsed as @@ -140,10 +140,7 @@ mod feat_hydration { } _ => create_at(fragment.sibling_at_end().cloned(), self.text), }; - if let Some(previous_next_sibling) = previous_next_sibling { - previous_next_sibling.reassign(DomSlot::at(btext.text_node.clone().into())); - } - *previous_next_sibling = None; + previous_next_sibling.write_at_node(btext.text_node.clone().into()); btext } } diff --git a/packages/yew/src/dom_bundle/mod.rs b/packages/yew/src/dom_bundle/mod.rs index aa82d211838..b64690fb833 100644 --- a/packages/yew/src/dom_bundle/mod.rs +++ b/packages/yew/src/dom_bundle/mod.rs @@ -32,6 +32,8 @@ use braw::BRaw; use bsuspense::BSuspense; use btag::{BTag, Registry}; use btext::BText; +#[cfg(feature = "hydration")] +pub(crate) use position::SlotBulletin; pub(crate) use position::{DomSlot, DynamicDomSlot}; use subtree_root::EventDescriptor; pub use subtree_root::{BSubtree, set_event_bubbling}; @@ -94,7 +96,7 @@ mod feat_hydration { parent: &Element, fragment: &mut Fragment, node: VNode, - previous_next_sibling: &mut Option, + previous_next_sibling: &mut SlotBulletin<'_>, ) -> Self { let bundle = node.hydrate(root, parent_scope, parent, fragment, previous_next_sibling); Self(bundle) diff --git a/packages/yew/src/dom_bundle/position.rs b/packages/yew/src/dom_bundle/position.rs index 8c64b91f048..ad180b6e0fb 100644 --- a/packages/yew/src/dom_bundle/position.rs +++ b/packages/yew/src/dom_bundle/position.rs @@ -1,10 +1,12 @@ //! Structs for keeping track where in the DOM a node belongs use std::cell::RefCell; -use std::rc::Rc; +use std::marker::PhantomData; use web_sys::{Element, Node}; +type PhantomNotSendNorSync = PhantomData<*const u8>; + /// A position in the list of children of an implicit parent [`Element`]. /// /// This can either be in front of a `DomSlot::at(next_sibling)`, at the end of the list with @@ -14,19 +16,6 @@ pub(crate) struct DomSlot { variant: DomSlotVariant, } -#[derive(Clone)] -enum DomSlotVariant { - Node(Option), - Chained(DynamicDomSlot), -} - -/// A dynamic dom slot can be reassigned. This change is also seen by the [`DomSlot`] from -/// [`Self::to_position`] before the reassignment took place. -#[derive(Clone)] -pub(crate) struct DynamicDomSlot { - target: Rc>, -} - impl std::fmt::Debug for DomSlot { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.with_next_sibling(|n| { @@ -40,43 +29,76 @@ impl std::fmt::Debug for DomSlot { } } +#[derive(Clone)] +enum DomSlotVariant { + Node(Option), + Chained(DynamicDomSlotHandle), +} + +mod forest; +use forest::{LinkForest, LinkId}; + +// This handle is only valid when trap nodes are active +const RESERVED_TRAP_HANDLE: DynamicDomSlotHandle = DynamicDomSlotHandle { + link: 0, + _phantom: PhantomData, +}; +thread_local! { + static LINK_FOREST: RefCell = { + let mut slots = LinkForest::default(); + trap_impl::with_trap_ref(|trap| { + if let Some(trap) = trap { + let trap_link = slots.insert(DomSlot::at(trap.clone())); + assert_eq!(trap_link, RESERVED_TRAP_HANDLE.link); + slots.leak(trap_link); + } + }); + RefCell::new(slots) + }; +} + +/// A dynamic dom slot can be reassigned. This change is also seen by the [`DomSlot`] from +/// [`Self::to_position`] before the reassignment took place. +pub(crate) struct DynamicDomSlot { + link: LinkId, + // The link is tied to this specific thread and can't be accessed elsewhere + _phantom: PhantomNotSendNorSync, +} + impl std::fmt::Debug for DynamicDomSlot { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:#?}", *self.target.borrow()) + write!(f, "#{} -> {:?}", self.link, self.to_position()) } } +#[derive(Clone)] +struct DynamicDomSlotHandle { + link: LinkId, + // The link is tied to this specific thread and can't be accessed elsewhere + _phantom: PhantomNotSendNorSync, +} + mod trap_impl { use super::Node; - #[cfg(debug_assertions)] + #[cfg(all(debug_assertions, feature = "hydration"))] thread_local! { // A special marker element that should not be referenced static TRAP: Node = gloo::utils::document().create_element("div").unwrap().into(); } - /// Get a "trap" node, or None if compiled without debug_assertions - #[cfg(feature = "hydration")] - pub fn get_trap_node() -> Option { - #[cfg(debug_assertions)] + #[inline] + pub fn with_trap_ref(f: impl FnOnce(Option<&Node>) -> R) -> R { + #[cfg(all(debug_assertions, feature = "hydration"))] { - TRAP.with(|trap| Some(trap.clone())) + TRAP.with(|trap| f(Some(trap))) } - #[cfg(not(debug_assertions))] + #[cfg(not(all(debug_assertions, feature = "hydration")))] { - None + f(None) } } #[inline] pub fn is_trap(node: &Node) -> bool { - #[cfg(debug_assertions)] - { - TRAP.with(|trap| node == trap) - } - #[cfg(not(debug_assertions))] - { - // When not running with debug_assertions, there is no trap node - let _ = node; - false - } + with_trap_ref(|trap| trap == Some(node)) } } @@ -97,13 +119,6 @@ impl DomSlot { } } - /// A new "placeholder" [DomSlot] that should not be used to insert nodes - #[inline] - #[cfg(feature = "hydration")] - pub fn new_debug_trapped() -> Self { - Self::create(trap_impl::get_trap_node()) - } - /// Get the [Node] that comes just after the position, or `None` if this denotes the position at /// the end fn with_next_sibling_check_trap(&self, f: impl FnOnce(Option<&Node>) -> R) -> R { @@ -157,77 +172,143 @@ impl DynamicDomSlot { /// Create a dynamic dom slot that initially represents ("targets") the same slot as the /// argument. pub fn new(initial_position: DomSlot) -> Self { + let link = LINK_FOREST.with_borrow_mut(|slots| slots.insert(initial_position)); Self { - target: Rc::new(RefCell::new(initial_position)), + link, + _phantom: PhantomData, } } - #[cfg(feature = "hydration")] - pub fn new_debug_trapped() -> Self { - Self::new(DomSlot::new_debug_trapped()) - } - - /// Move out of self, leaving behind a trapped slot. `self` should not be used afterwards. - /// Used during the transition from a hydrating to a rendered component to move state between - /// enum variants. - #[cfg(feature = "hydration")] - pub fn take(&mut self) -> Self { - std::mem::replace(self, Self::new(DomSlot::new_debug_trapped())) - } - /// Change the [`DomSlot`] that is targeted. Subsequently, this will behave as if `self` was /// created from the passed DomSlot in the first place. pub fn reassign(&self, next_position: DomSlot) { - // TODO: is not defensive against accidental reference loops - *self.target.borrow_mut() = next_position; + self.clone_to_handle().reassign_unchecked(next_position); } /// Get a [`DomSlot`] that gets automatically updated when `self` gets reassigned. All such /// slots are equivalent to each other and point to the same position. pub fn to_position(&self) -> DomSlot { + self.clone_to_handle().into_position() + } + + /// There can only be one owner of a dynamic dom slot. Reassigning a dom slot is only allowed + /// while that owner is still alive. All other accesses (e.g. through DomSlot) are followers + /// and should only read the value, but never write to it. + /// This does not imply that access is always serialized! Followers are allowed to write at any + /// point without prior synchronization, as long as they ensure that the owner is still alive. + fn clone_to_handle(&self) -> DynamicDomSlotHandle { + DynamicDomSlotHandle { + link: self.link, + _phantom: self._phantom, + } + } +} + +impl Drop for DynamicDomSlot { + fn drop(&mut self) { + LINK_FOREST.with_borrow_mut(|links| links.remove(self.link)); + } +} + +impl DynamicDomSlotHandle { + fn into_position(self) -> DomSlot { DomSlot { - variant: DomSlotVariant::Chained(self.clone()), + variant: DomSlotVariant::Chained(self), } } + /// Reassign through a handle. This is only valid if the owning [DynamicDomSlot] is still alive. + fn reassign_unchecked(&self, next_position: DomSlot) { + // TODO: is not defensive against accidental reference loops + LINK_FOREST.with_borrow_mut(|forest| { + forest.reassign(self.link, next_position); + }); + } + fn with_next_sibling(&self, f: impl FnOnce(Option<&Node>) -> R) -> R { // We use an iterative approach to traverse a possible long chain of references. // See issue #3043 for why a recursive call is impossible for large lists in vdom. - // - // TODO: there could be some data structure that performs better here. E.g. a balanced tree - // with parent pointers come to mind, but they are a bit fiddly to implement in rust - // - // We traverse via raw pointers to avoid Rc refcount overhead (clone + drop) per hop, then - // clone the terminal next-sibling out of the chain before invoking `f`. Invoking `f` with - // no borrow held and no reliance on chain structure keeps the traversal sound: `f` runs - // arbitrary code (panic drop glue, `gloo::console::error`, tracing subscribers) that - // could, in principle, reassign a link in the chain and drop the last strong reference - // to the RefCell we would otherwise still borrow from. - // - // SAFETY: All RefCells visited by the loop remain live while we dereference them: - // - `self.target` (Rc) is alive because `self` is borrowed - // - Each DomSlot::Chained(DynamicDomSlot { target }) in the chain holds a strong Rc to the - // next RefCell, so all links are transitively kept alive - // - Yew is single-threaded and the loop body does not run user code, so no mutable borrow - // (e.g. from reassign()) can occur on any RefCell in the chain during traversal - // - Each RefCell::borrow() is dropped before advancing to the next hop - let node: Option = { - let mut ptr: *const RefCell = Rc::as_ptr(&self.target); - loop { - let cell = unsafe { &*ptr }; - let slot_ref = cell.borrow(); - match &slot_ref.variant { - DomSlotVariant::Node(n) => break n.clone(), - DomSlotVariant::Chained(chain) => { - ptr = Rc::as_ptr(&chain.target); - } - } - } - }; + let node = LINK_FOREST.with_borrow_mut(|forest| forest.find_root(self.link).clone()); f(node.as_ref()) } } +#[cfg(feature = "hydration")] +mod feat_hydration { + use std::marker::PhantomData; + + use web_sys::Node; + + use super::{DomSlot, DynamicDomSlot, DynamicDomSlotHandle, RESERVED_TRAP_HANDLE}; + + fn trapped_position() -> DomSlot { + super::trap_impl::with_trap_ref(|trap| match trap { + Some(_) => RESERVED_TRAP_HANDLE.into_position(), + None => DomSlot::at_end(), + }) + } + + impl DynamicDomSlot { + pub fn new_debug_trapped() -> Self { + Self::new(trapped_position()) + } + + /// Move out of self, leaving behind a trapped slot. `self` should not be used afterwards. + /// Used during the transition from a hydrating to a rendered component to move state + /// between enum variants. + pub fn take(&mut self) -> Self { + std::mem::replace(self, Self::new_debug_trapped()) + } + } + + pub struct SlotBulletin<'tree> { + prev_next_sibling: Option, + _owner: PhantomData<&'tree mut DynamicDomSlot>, + } + impl<'tree> SlotBulletin<'tree> { + pub fn start(slot: &'tree mut DynamicDomSlot) -> Self { + // We take a follower, but we are sure the owner is alive + Self { + prev_next_sibling: Some(slot.clone_to_handle()), + _owner: PhantomData, + } + } + + pub fn new() -> Self { + Self { + prev_next_sibling: None, + _owner: PhantomData, + } + } + + fn write(&mut self, pos: DomSlot) { + if let Some(slot) = &mut self.prev_next_sibling { + slot.reassign_unchecked(pos); + } + } + + pub fn write_at_node(&mut self, node: Node) { + self.write(DomSlot::at(node)); + self.prev_next_sibling = None; + } + + // This method does not track that `inner_next_sibling` (which is the owner) lives for + // lifetime of this call. This must be done by the caller, which puts it somewhere in + // its component state + pub fn write_at_comp(&mut self, slot: DomSlot, inner_next_sibling: &DynamicDomSlot) { + self.write(slot); + self.prev_next_sibling = Some(inner_next_sibling.clone_to_handle()); + } + } + impl Drop for SlotBulletin<'_> { + fn drop(&mut self) { + self.write(DomSlot::at_end()) + } + } +} +#[cfg(feature = "hydration")] +pub(crate) use feat_hydration::SlotBulletin; + #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] #[cfg(test)] mod layout_tests { @@ -306,7 +387,7 @@ mod layout_tests { fn debug_printing() { // basic tests that these don't panic. We don't enforce any specific format. println!("At end: {:?}", DomSlot::at_end()); - println!("Trapped: {:?}", DomSlot::new_debug_trapped()); + println!("Trapped: {:?}", DynamicDomSlot::new_debug_trapped()); println!( "At element: {:?}", DomSlot::at(document().create_element("p").unwrap().into()) diff --git a/packages/yew/src/dom_bundle/position/forest.rs b/packages/yew/src/dom_bundle/position/forest.rs new file mode 100644 index 00000000000..91300ac384b --- /dev/null +++ b/packages/yew/src/dom_bundle/position/forest.rs @@ -0,0 +1,417 @@ +use std::ops::{Deref, DerefMut}; + +use slab::Slab; + +use super::{DomSlot, DomSlotVariant, Node}; + +#[derive(Default)] +pub struct LinkForest { + nodes: Slab, +} + +#[allow(unused)] +macro_rules! trace { + ($msg:literal $(,)?) => { + ::gloo::console::log!( + ::std::format!("%c[{}:{}] ", ::std::file!(), ::std::line!()), + "font-weight: bold", + ::std::format!($msg) + ) + }; + ($msg:literal , $( $args:tt ),*) => { + ::gloo::console::log!( + ::std::format!("%c[{}:{}] ", ::std::file!(), ::std::line!()), + "font-weight: bold", + ::std::format!($msg, $( $args ),* ), + ) + } +} + +impl LinkForest { + #[allow(unused)] + fn print_all(&self) { + for (n, node) in &self.nodes { + let node = LinkRef::new(n, node); + gloo::console::console_dbg!(node.debug()); + } + } + + pub fn insert(&mut self, link: DomSlot) -> LinkId { + let entry = self.nodes.vacant_entry(); + let link_id = entry.key(); + let (link, parent) = Link::new(link, link_id); + entry.insert(link); + if let Some(parent) = parent { + self.node_mut(parent).add_ref(); + } + link_id + } + + fn node(&self, link: LinkId) -> LinkRef<&Link> { + LinkRef::new(link, &self.nodes[link]) + } + + fn node_mut(&mut self, link: LinkId) -> LinkRef<&mut Link> { + LinkRef::new(link, &mut self.nodes[link]) + } + + fn remove_node(&mut self, link: LinkId) -> LinkRef { + LinkRef::new(link, self.nodes.remove(link)) + } + + pub fn remove(&mut self, link: LinkId) { + self.remove_link(link, true); + } + + pub fn leak(&mut self, link: LinkId) { + self.node_mut(link).leak(); + } + + fn remove_link(&mut self, link: LinkId, owner: bool) { + if !self.node_mut(link).dec_ref(owner) { + return; + } + let mut n = link; + loop { + let node = self.remove_node(n); + debug_assert!( + node.right().is_none(), + "can't have children in the represented tree" + ); + let l = node.left(); + let rep_p = node.rep_parent(); + let p = node.into_inner().parent; + if let &LinkParent::AuxParent(p) = &p { + debug_assert!(self.node(p).right() == Some(n)); + self.node_mut(p).set_right(l); + } + if let Some(l) = l { + self.node_mut(l).parent = p; + } + let Some(rep_p) = rep_p else { + break; + }; + if !self.node_mut(rep_p).dec_ref(false) { + break; + } + n = rep_p; + } + // from time to time, clean up memory in the slab + // TODO: this needs more analysis under amortized runtime costs and a clever potential + // definition. shrink_to_fit will first check if there are any vacant slots at "the end". If + // there are, it will then do a full pass over empty and filled slots. The problem is that + // "the end" is not easily available from the public API. We know it's somewhere between + // len() and capacity(), and also past the link(s) we just removed. But we can't check the + // internal entries.len(). + const ALLOWED_SLACK: usize = 64 * 1024 * 1024 / size_of::(); + let slots = &mut self.nodes; + if slots.capacity() / 4 > slots.len() && slots.capacity() - slots.len() > ALLOWED_SLACK { + slots.shrink_to_fit(); + } + } + + pub fn reassign(&mut self, link: LinkId, new_parent: DomSlot) { + // removes `link` from its represented tree and moves it to `new_parent`. + // we also have to keep track of ref counts. + debug_assert!( + self.node(link).has_owner(), + "owner must be alive to reassign" + ); + let old_parent_id = self.node(link).rep_parent(); + let (new_parent, new_parent_id) = match new_parent.variant { + DomSlotVariant::Chained(link) => (LinkParent::PathParent(link.link), Some(link.link)), + DomSlotVariant::Node(data) => (LinkParent::Root(data), None), + }; + if old_parent_id == new_parent_id && old_parent_id.is_some() { + // reassigned to its existing parent, no need to modify. + return; + } + if let Some(new_parent_id) = new_parent_id { + self.node_mut(new_parent_id).add_ref(); + } + self.splay(link); + let l = self.node(link).left(); + let parent = self.node_mut(link).parent.replace(new_parent); + self.node_mut(link).set_left(None); + self.node_mut(link).set_rep_parent(new_parent_id); + if let Some(l) = l { + self.node_mut(l).parent = parent; + } + if let Some(old_parent_id) = old_parent_id { + self.remove_link(old_parent_id, false); + } + } + + pub fn find_root(&mut self, link: LinkId) -> &Option { + self.access(link); + match &self.node(link).into_inner().parent { + LinkParent::Root(node) => node, + _ => unreachable!("access method buggy"), + } + } + + // Splay operations on the auxiliary tree + // In fact, none of the splay operations change the refcount, since they do not modify the + // represented tree. + fn splay_parent(&self, link: LinkId) -> Result { + // Due to borrow issues (fixed with polonius?) we can't borrow data here, and do that + // in the caller with a double match :/ + match &self.node(link).parent { + &LinkParent::AuxParent(parent) => Ok(parent), + &LinkParent::PathParent(link) => Err(SplayResult::Link(link)), + LinkParent::Root(_) => Err(SplayResult::Root()), + } + } + + fn rotate(&mut self, x: LinkId, p: LinkId) { + // shift the middle node `m` from `x` to `p`. + let m; + debug_assert!(self.node(p).right() == Some(x) || self.node(p).left() == Some(x)); + if self.node(p).left() == Some(x) { + m = self.node(x).right(); + self.node_mut(x).set_right(Some(p)); + self.node_mut(p).set_left(m); + } else { + m = self.node(x).left(); + self.node_mut(x).set_left(Some(p)); + self.node_mut(p).set_right(m); + }; + if let Some(m) = m { + debug_assert_eq!(self.node_mut(m).parent, LinkParent::AuxParent(x)); + self.node_mut(m).parent = LinkParent::AuxParent(p); + } + // attach `x` to the parent of `p` + let g = self.node_mut(p).parent.replace(LinkParent::AuxParent(x)); + if let LinkParent::AuxParent(g) = g { + let mut g = self.node_mut(g); + debug_assert!(g.right() == Some(p) || g.left() == Some(p)); + if g.left() == Some(p) { + g.set_left(Some(x)); + } else { + g.set_right(Some(x)); + } + } + self.node_mut(x).parent = g; + } + + fn splay(&mut self, link: LinkId) -> SplayResult { + let x = link; + loop { + let mut p = match self.splay_parent(x) { + Ok(p) => p, + Err(done) => return done, + }; + if let Ok(g) = self.splay_parent(p) { + // check for zig-zig or zig-zag + // zig-zig can be implemented by first rotating p and g, followed by x and p + // zig-zag can be implemented by first rotating x and p, followed by x and g + let x_is_left = self.node(p).left() == Some(x); + let p_is_left = self.node(g).left() == Some(p); + if x_is_left == p_is_left { + self.rotate(p, g); + } else { + self.rotate(x, p); + p = g; + } + } + self.rotate(x, p); + } + } + + // Link/cut operations + fn access(&mut self, link: LinkId) { + // Also does not change any refcounts + let (mut curr, mut prev) = (link, None); + loop { + let link = self.splay(curr); + // found a path-parent pointer. now we cut this one + let d = self.node_mut(curr).right(); + if let Some(prev) = prev { + debug_assert_eq!(self.node(prev).parent, LinkParent::PathParent(curr)); + self.node_mut(prev).parent = LinkParent::AuxParent(curr); + // small deviation from the original paper: we do not remove the tail + // of the preferred path the first node is already on. + // this would originally run unconditionally of prev.is_some() + self.node_mut(curr).set_right(Some(prev)); + if let Some(d) = d { + debug_assert_eq!(self.node(d).parent, LinkParent::AuxParent(curr)); + self.node_mut(d).parent = LinkParent::PathParent(curr); + } + } + let SplayResult::Link(link) = link else { break }; + (prev, curr) = (Some(curr), link); + } + // now link is on the preferred path, so splay it one last time to put it on top. + let res = self.splay(link); + debug_assert!(matches!(res, SplayResult::Root())); + } +} + +pub type LinkId = usize; + +enum SplayResult { + Link(LinkId), + Root( + // &'a Option + ), +} + +#[derive(PartialEq, Debug)] +enum LinkParent { + Root(Option), + // parent is on the same preferred path + AuxParent(LinkId), + // "path-parent pointer" to some other preferred path + PathParent(LinkId), +} + +impl LinkParent { + fn replace(&mut self, next: LinkParent) -> LinkParent { + std::mem::replace(self, next) + } +} + +struct Link { + parent: LinkParent, + // We use a link's own id to signal that it has no right/left child or represented parent + left_aux: LinkId, + right_aux: LinkId, + rep_parent: LinkId, + /// counts the owner + the number of links in LINK_FOREST that refer to this link. + /// to save a bit, the owner is counted in the lowest bit, handles are counted in the upper + /// bits + ref_count: usize, +} + +impl AsRef for Link { + fn as_ref(&self) -> &Link { + self + } +} + +impl AsMut for Link { + fn as_mut(&mut self) -> &mut Link { + self + } +} + +struct LinkRef { + id: LinkId, + link: L, +} + +impl> Deref for LinkRef { + type Target = Link; + + fn deref(&self) -> &Self::Target { + self.link.as_ref() + } +} + +impl + AsMut> DerefMut for LinkRef { + fn deref_mut(&mut self) -> &mut Self::Target { + self.link.as_mut() + } +} + +impl LinkRef { + fn new(id: LinkId, link: L) -> Self { + Self { id, link } + } + + fn into_inner(self) -> L { + self.link + } +} + +impl> LinkRef { + fn debug(&self) -> impl '_ + std::fmt::Debug { + #[expect(unused)] + #[derive(Debug)] + struct Link<'a> { + id: LinkId, + parent: &'a LinkParent, + left_aux: Option, + right_aux: Option, + rep_parent: Option, + ref_count: usize, + has_owner: bool, + } + let has_owner = self.has_owner(); + Link { + id: self.id, + parent: &self.parent, + left_aux: self.left(), + right_aux: self.right(), + rep_parent: self.rep_parent(), + ref_count: self.ref_count / 2 + has_owner as usize, + has_owner, + } + } + + fn left(&self) -> Option { + (self.left_aux != self.id).then_some(self.left_aux) + } + + fn right(&self) -> Option { + (self.right_aux != self.id).then_some(self.right_aux) + } + + fn rep_parent(&self) -> Option { + (self.rep_parent != self.id).then_some(self.rep_parent) + } +} + +impl> LinkRef { + fn set_left(&mut self, left: Option) { + self.link.as_mut().left_aux = left.unwrap_or(self.id); + } + + fn set_right(&mut self, right: Option) { + self.link.as_mut().right_aux = right.unwrap_or(self.id); + } + + fn set_rep_parent(&mut self, rep_parent: Option) { + self.link.as_mut().rep_parent = rep_parent.unwrap_or(self.id); + } +} + +impl Link { + pub fn new(parent: DomSlot, this: LinkId) -> (Self, Option) { + let (parent, link) = match parent.variant { + DomSlotVariant::Node(node) => (LinkParent::Root(node), None), + DomSlotVariant::Chained(handle) => { + (LinkParent::PathParent(handle.link), Some(handle.link)) + } + }; + let this = Self { + parent, + left_aux: this, + right_aux: this, + rep_parent: link.unwrap_or(this), + ref_count: 1, + }; + (this, link) + } + + fn leak(&mut self) { + self.add_ref(); + self.dec_ref(true); + } + + fn has_owner(&self) -> bool { + (self.ref_count & 0b1) != 0 + } + + fn dec_ref(&mut self, owner: bool) -> bool { + let weight = if owner { 1 } else { 2 }; + debug_assert!(self.ref_count >= weight, "must have refs"); + self.ref_count -= weight; + self.ref_count == 0 + } + + fn add_ref(&mut self) { + debug_assert!(self.ref_count > 0, "no revives"); + self.ref_count += 2; + } +} diff --git a/packages/yew/src/dom_bundle/traits.rs b/packages/yew/src/dom_bundle/traits.rs index 629b8604de2..a07e6c3c79d 100644 --- a/packages/yew/src/dom_bundle/traits.rs +++ b/packages/yew/src/dom_bundle/traits.rs @@ -100,7 +100,7 @@ pub(super) trait Reconcilable { #[cfg(feature = "hydration")] mod feat_hydration { use super::*; - use crate::dom_bundle::{DynamicDomSlot, Fragment}; + use crate::dom_bundle::{Fragment, SlotBulletin}; pub(in crate::dom_bundle) trait Hydratable: Reconcilable { /// hydrates current tree. @@ -121,7 +121,7 @@ mod feat_hydration { // `Node.insertAfter`) Hence, we pass an optional argument to inform of the // new hydrated node's position. This should end up assigning the same // position that would have been returned from `Self::attach` on creation. - prev_next_sibling: &mut Option, + prev_next_sibling: &mut SlotBulletin<'_>, ) -> Self::Bundle; } } diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index 4452cbbc57f..f2ed846a255 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -8,11 +8,11 @@ use web_sys::Element; use super::BaseComponent; use super::scope::{AnyScope, Scope}; -#[cfg(feature = "hydration")] -use crate::dom_bundle::Fragment; #[cfg(feature = "csr")] use crate::dom_bundle::{BSubtree, Bundle, DomSlot, DynamicDomSlot}; #[cfg(feature = "hydration")] +use crate::dom_bundle::{Fragment, SlotBulletin}; +#[cfg(feature = "hydration")] use crate::html::RenderMode; use crate::html::{Html, RenderError}; use crate::scheduler::{self, Runnable, Shared}; @@ -545,6 +545,7 @@ impl ComponentState { // We schedule a "first" render to run immediately after hydration. // Most notably, only this render will trigger the "rendered" callback, hence we // want to prioritize this. + scheduler::push_component_priority_render( self.comp_id, Box::new(RenderRunner { @@ -559,7 +560,8 @@ impl ComponentState { parent, fragment, new_vdom, - &mut Some(own_slot.clone()), + // own_slot is alive for the call + &mut SlotBulletin::start(own_slot), ); // We trim all text nodes before checking as it's likely these are whitespaces. diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 4a4681f385e..4490718f716 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -546,11 +546,11 @@ mod feat_csr { parent: Element, slot: DomSlot, props: Rc, - ) -> DynamicDomSlot { + ) -> DomSlot { let bundle = Bundle::new(); let sibling_slot = DynamicDomSlot::new(slot); let own_slot = DynamicDomSlot::new(sibling_slot.to_position()); - let shared_slot = own_slot.clone(); + let shared_slot = own_slot.to_position(); let state = ComponentRenderState::Render { bundle, @@ -654,7 +654,7 @@ mod feat_hydration { use web_sys::{Element, HtmlScriptElement}; use super::*; - use crate::dom_bundle::{BSubtree, DomSlot, DynamicDomSlot, Fragment}; + use crate::dom_bundle::{BSubtree, DomSlot, DynamicDomSlot, Fragment, SlotBulletin}; use crate::html::component::lifecycle::{ComponentRenderState, CreateRunner, RenderRunner}; use crate::scheduler; use crate::virtual_dom::Collectable; @@ -677,8 +677,8 @@ mod feat_hydration { parent: Element, fragment: &mut Fragment, props: Rc, - prev_next_sibling: &mut Option, - ) -> DynamicDomSlot { + prev_next_sibling: &mut SlotBulletin<'_>, + ) -> DomSlot { // This is very helpful to see which component is failing during hydration // which means this component may not having a stable layout / differs between // client-side and server-side. @@ -705,16 +705,15 @@ mod feat_hydration { _ => None, }; + let sibling_slot = DynamicDomSlot::new_debug_trapped(); let own_slot = match fragment.front().cloned() { Some(first_node) => DynamicDomSlot::new(DomSlot::at(first_node)), - None => DynamicDomSlot::new(DomSlot::at_end()), + None => DynamicDomSlot::new(sibling_slot.to_position()), }; - let shared_slot = own_slot.clone(); - let sibling_slot = DynamicDomSlot::new_debug_trapped(); - if let Some(prev_next_sibling) = prev_next_sibling { - prev_next_sibling.reassign(shared_slot.to_position()); - } - *prev_next_sibling = Some(sibling_slot.clone()); + // sibling must be reassignable, but only for this call hierarchy. Hence the owner is + // alive when that write in the sibling component happens. + prev_next_sibling.write_at_comp(own_slot.to_position(), &sibling_slot); + let shared_slot = own_slot.to_position(); let state = ComponentRenderState::Hydration { parent, root, diff --git a/packages/yew/src/virtual_dom/vcomp.rs b/packages/yew/src/virtual_dom/vcomp.rs index 5087cc36c7a..ca082625e06 100644 --- a/packages/yew/src/virtual_dom/vcomp.rs +++ b/packages/yew/src/virtual_dom/vcomp.rs @@ -10,10 +10,10 @@ use futures::future::{FutureExt, LocalBoxFuture}; use web_sys::Element; use super::Key; -#[cfg(feature = "hydration")] -use crate::dom_bundle::Fragment; #[cfg(feature = "csr")] -use crate::dom_bundle::{BSubtree, DomSlot, DynamicDomSlot}; +use crate::dom_bundle::{BSubtree, DomSlot}; +#[cfg(feature = "hydration")] +use crate::dom_bundle::{Fragment, SlotBulletin}; use crate::html::BaseComponent; #[cfg(feature = "csr")] use crate::html::Scoped; @@ -65,7 +65,7 @@ pub(crate) trait Mountable { parent_scope: &AnyScope, parent: Element, slot: DomSlot, - ) -> (Box, DynamicDomSlot); + ) -> (Box, DomSlot); #[cfg(feature = "csr")] fn reuse(self: Box, scope: &dyn Scoped, slot: DomSlot); @@ -86,8 +86,8 @@ pub(crate) trait Mountable { parent_scope: &AnyScope, parent: Element, fragment: &mut Fragment, - prev_next_sibling: &mut Option, - ) -> (Box, DynamicDomSlot); + prev_next_sibling: &mut SlotBulletin<'_>, + ) -> (Box, DomSlot); } pub(crate) struct PropsWrapper { @@ -126,7 +126,7 @@ impl Mountable for PropsWrapper { parent_scope: &AnyScope, parent: Element, slot: DomSlot, - ) -> (Box, DynamicDomSlot) { + ) -> (Box, DomSlot) { let scope: Scope = Scope::new(Some(parent_scope.clone())); let own_slot = scope.mount_in_place(root.clone(), parent, slot, self.props); @@ -164,8 +164,8 @@ impl Mountable for PropsWrapper { parent_scope: &AnyScope, parent: Element, fragment: &mut Fragment, - prev_next_sibling: &mut Option, - ) -> (Box, DynamicDomSlot) { + prev_next_sibling: &mut SlotBulletin<'_>, + ) -> (Box, DomSlot) { let scope: Scope = Scope::new(Some(parent_scope.clone())); let own_slot = scope.hydrate_in_place(root, parent, fragment, self.props, prev_next_sibling); diff --git a/packages/yew/tests/use_state.rs b/packages/yew/tests/use_state.rs index 1dbe17ce423..7593bc7fad0 100644 --- a/packages/yew/tests/use_state.rs +++ b/packages/yew/tests/use_state.rs @@ -2,8 +2,6 @@ mod common; -use std::rc::Rc; - use common::obtain_result; use wasm_bindgen_test::*; use yew::prelude::*; diff --git a/tools/build-examples/src/main.rs b/tools/build-examples/src/main.rs index d9e55ddc12a..abb3aa2f513 100644 --- a/tools/build-examples/src/main.rs +++ b/tools/build-examples/src/main.rs @@ -60,7 +60,7 @@ fn main() -> ExitCode { println!("::endgroup::"); if !sample_success { - eprintln!("::error ::{example} failed to build"); + println!("::error ::{example} failed to build"); failure = true; } }