Compare commits

...

4 Commits
main ... perf

Author SHA1 Message Date
Greg Johnston 9ae3f6f7f0 fix 2023-02-26 07:11:27 -05:00
Greg Johnston 73f67d9a55 chore: warnings 2023-02-25 23:00:07 -05:00
Greg Johnston 8643126d09 perf: optimizations to event delegation 2023-02-25 22:57:25 -05:00
Greg Johnston 99d28ed045 perf: optimizations to `<EachItem/>` creation 2023-02-25 22:57:19 -05:00
5 changed files with 143 additions and 64 deletions

View File

@ -166,11 +166,11 @@ impl Mountable for EachRepr {
pub(crate) struct EachItem { pub(crate) struct EachItem {
cx: Scope, cx: Scope,
#[cfg(all(target_arch = "wasm32", feature = "web"))] #[cfg(all(target_arch = "wasm32", feature = "web"))]
document_fragment: web_sys::DocumentFragment, document_fragment: Option<web_sys::DocumentFragment>,
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
opening: Comment, opening: Comment,
pub(crate) child: View, pub(crate) child: View,
closing: Comment, closing: Option<Comment>,
#[cfg(not(all(target_arch = "wasm32", feature = "web")))] #[cfg(not(all(target_arch = "wasm32", feature = "web")))]
pub(crate) id: HydrationKey, pub(crate) id: HydrationKey,
} }
@ -192,16 +192,22 @@ impl fmt::Debug for EachItem {
impl EachItem { impl EachItem {
fn new(cx: Scope, child: View) -> Self { fn new(cx: Scope, child: View) -> Self {
let id = HydrationCtx::id(); let id = HydrationCtx::id();
let needs_closing = !matches!(child, View::Element(_));
let markers = ( let markers = (
Comment::new(Cow::Borrowed("</EachItem>"), &id, true), if needs_closing {
Some(Comment::new(Cow::Borrowed("</EachItem>"), &id, true))
} else {
None
},
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
Comment::new(Cow::Borrowed("<EachItem>"), &id, false), Comment::new(Cow::Borrowed("<EachItem>"), &id, false),
); );
#[cfg(all(target_arch = "wasm32", feature = "web"))] #[cfg(all(target_arch = "wasm32", feature = "web"))]
let document_fragment = { let document_fragment = if needs_closing {
let fragment = crate::document().create_document_fragment(); let fragment = crate::document().create_document_fragment();
let closing = markers.0.as_ref().unwrap();
// Insert the comments into the document fragment // Insert the comments into the document fragment
// so they can serve as our references when inserting // so they can serve as our references when inserting
@ -209,14 +215,16 @@ impl EachItem {
if !HydrationCtx::is_hydrating() { if !HydrationCtx::is_hydrating() {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
fragment fragment
.append_with_node_2(&markers.1.node, &markers.0.node) .append_with_node_2(&markers.1.node, &closing.node)
.unwrap(); .unwrap();
fragment.append_with_node_1(&markers.0.node).unwrap(); fragment.append_with_node_1(&closing.node).unwrap();
} }
mount_child(MountKind::Before(&markers.0.node), &child); mount_child(MountKind::Before(&closing.node), &child);
fragment Some(fragment)
} else {
None
}; };
Self { Self {
@ -243,7 +251,11 @@ impl Drop for EachItem {
#[cfg(all(target_arch = "wasm32", feature = "web"))] #[cfg(all(target_arch = "wasm32", feature = "web"))]
impl Mountable for EachItem { impl Mountable for EachItem {
fn get_mountable_node(&self) -> web_sys::Node { fn get_mountable_node(&self) -> web_sys::Node {
self.document_fragment.clone().unchecked_into() if let Some(fragment) = &self.document_fragment {
fragment.clone().unchecked_into()
} else {
self.child.get_mountable_node()
}
} }
fn get_opening_node(&self) -> web_sys::Node { fn get_opening_node(&self) -> web_sys::Node {
@ -255,7 +267,11 @@ impl Mountable for EachItem {
} }
fn get_closing_node(&self) -> web_sys::Node { fn get_closing_node(&self) -> web_sys::Node {
self.closing.node.clone() if let Some(closing) = &self.closing {
closing.node.clone().unchecked_into()
} else {
self.child.get_mountable_node().clone()
}
} }
} }
@ -264,20 +280,25 @@ impl EachItem {
/// order to be reinserted somewhere else. /// order to be reinserted somewhere else.
#[cfg(all(target_arch = "wasm32", feature = "web"))] #[cfg(all(target_arch = "wasm32", feature = "web"))]
fn prepare_for_move(&self) { fn prepare_for_move(&self) {
if let Some(fragment) = &self.document_fragment {
let start = self.get_opening_node(); let start = self.get_opening_node();
let end = &self.closing.node; let end = &self.get_closing_node();
let mut sibling = start; let mut sibling = start;
while sibling != *end { while sibling != *end {
let next_sibling = sibling.next_sibling().unwrap(); let next_sibling = sibling.next_sibling().unwrap();
self.document_fragment.append_child(&sibling).unwrap(); fragment.append_child(&sibling).unwrap();
sibling = next_sibling; sibling = next_sibling;
} }
self.document_fragment.append_with_node_1(end).unwrap(); fragment.append_with_node_1(end).unwrap();
} else {
let node = self.child.get_mountable_node();
node.unchecked_into::<web_sys::Element>().remove();
}
} }
} }
@ -349,7 +370,7 @@ where
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(all(target_arch = "wasm32", feature = "web"))] { if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
create_effect(cx, move |prev_hash_run| { create_effect(cx, move |prev_hash_run: Option<HashRun<FxIndexSet<K>>>| {
let mut children_borrow = children.borrow_mut(); let mut children_borrow = children.borrow_mut();
#[cfg(all(target_arch = "wasm32", feature = "web"))] #[cfg(all(target_arch = "wasm32", feature = "web"))]
@ -359,14 +380,22 @@ where
closing.clone() closing.clone()
}; };
let items = items_fn(); let items_iter = items_fn().into_iter();
let items = items.into_iter().collect::<SmallVec<[_; 128]>>(); let (capacity, _) = items_iter.size_hint();
let mut hashed_items = FxIndexSet::with_capacity_and_hasher(
let hashed_items = capacity,
items.iter().map(&key_fn).collect::<FxIndexSet<_>>(); BuildHasherDefault::<FxHasher>::default()
);
if let Some(HashRun(prev_hash_run)) = prev_hash_run { if let Some(HashRun(prev_hash_run)) = prev_hash_run {
if !prev_hash_run.is_empty() {
let mut items = Vec::with_capacity(capacity);
for item in items_iter {
hashed_items.insert(key_fn(&item));
items.push(Some(item));
}
let cmds = diff(&prev_hash_run, &hashed_items); let cmds = diff(&prev_hash_run, &hashed_items);
apply_cmds( apply_cmds(
@ -377,21 +406,34 @@ where
&closing, &closing,
cmds, cmds,
&mut children_borrow, &mut children_borrow,
items.into_iter().map(|t| Some(t)).collect(), items,
&each_fn &each_fn
); );
} else { return HashRun(hashed_items);
*children_borrow = Vec::with_capacity(items.len()); }
}
for item in items {
let (each_item, _) = cx.run_child_scope(|cx| EachItem::new(cx, each_fn(cx, item).into_view(cx)));
// if previous run is empty
*children_borrow = Vec::with_capacity(capacity);
#[cfg(all(target_arch = "wasm32", feature = "web"))] #[cfg(all(target_arch = "wasm32", feature = "web"))]
mount_child(MountKind::Before(&closing), &each_item); let fragment = crate::document().create_document_fragment();
for item in items_iter {
hashed_items.insert(key_fn(&item));
let (each_item, _) = cx.run_child_scope(|cx| EachItem::new(cx, each_fn(cx, item).into_view(cx)));
#[cfg(all(target_arch = "wasm32", feature = "web"))]
{
_ = fragment.append_child(&each_item.get_mountable_node());
}
children_borrow.push(Some(each_item)); children_borrow.push(Some(each_item));
} }
}
#[cfg(all(target_arch = "wasm32", feature = "web"))]
closing
.unchecked_ref::<web_sys::Element>()
.before_with_node_1(&fragment)
.expect("before to not err");
HashRun(hashed_items) HashRun(hashed_items)
}); });
@ -421,6 +463,18 @@ fn diff<K: Eq + Hash>(from: &FxIndexSet<K>, to: &FxIndexSet<K>) -> Diff {
clear: true, clear: true,
..Default::default() ..Default::default()
}; };
} else if from.is_empty() {
return Diff {
added: to
.iter()
.enumerate()
.map(|(at, _)| DiffOpAdd {
at,
mode: DiffOpAddMode::Append,
})
.collect(),
..Default::default()
};
} }
// Get removed items // Get removed items
@ -445,7 +499,7 @@ fn diff<K: Eq + Hash>(from: &FxIndexSet<K>, to: &FxIndexSet<K>) -> Diff {
// Get moved items // Get moved items
let mut normalized_idx = 0; let mut normalized_idx = 0;
let mut move_cmds = SmallVec::<[_; 8]>::with_capacity(to.len()); let mut move_cmds = Vec::new();
let mut added_idx = added.next().map(|k| to.get_full(k).unwrap().0); let mut added_idx = added.next().map(|k| to.get_full(k).unwrap().0);
let mut removed_idx = removed.next().map(|k| from.get_full(k).unwrap().0); let mut removed_idx = removed.next().map(|k| from.get_full(k).unwrap().0);
@ -539,9 +593,9 @@ fn apply_opts<K: Eq + Hash>(
#[derive(Debug, Default)] #[derive(Debug, Default)]
#[allow(unused)] #[allow(unused)]
struct Diff { struct Diff {
removed: SmallVec<[DiffOpRemove; 8]>, removed: Vec<DiffOpRemove>,
moved: SmallVec<[DiffOpMove; 8]>, moved: Vec<DiffOpMove>,
added: SmallVec<[DiffOpAdd; 8]>, added: Vec<DiffOpAdd>,
clear: bool, clear: bool,
} }
@ -588,7 +642,7 @@ fn apply_cmds<T, EF, N>(
closing: &web_sys::Node, closing: &web_sys::Node,
mut cmds: Diff, mut cmds: Diff,
children: &mut Vec<Option<EachItem>>, children: &mut Vec<Option<EachItem>>,
mut items: SmallVec<[Option<T>; 128]>, mut items: Vec<Option<T>>,
each_fn: &EF, each_fn: &EF,
) where ) where
EF: Fn(Scope, T) -> N, EF: Fn(Scope, T) -> N,

View File

@ -23,7 +23,12 @@ pub fn add_event_helper<E: crate::ev::EventDescriptor + 'static>(
let event_name = event.name(); let event_name = event.name();
if event.bubbles() { if event.bubbles() {
add_event_listener(target, event_name, event_handler); add_event_listener(
target,
event.event_delegation_key(),
event_name,
event_handler,
);
} else { } else {
add_event_listener_undelegated(target, &event_name, event_handler); add_event_listener_undelegated(target, &event_name, event_handler);
} }
@ -34,6 +39,7 @@ pub fn add_event_helper<E: crate::ev::EventDescriptor + 'static>(
#[cfg(all(target_arch = "wasm32", feature = "web"))] #[cfg(all(target_arch = "wasm32", feature = "web"))]
pub fn add_event_listener<E>( pub fn add_event_listener<E>(
target: &web_sys::Element, target: &web_sys::Element,
key: Cow<'static, str>,
event_name: Cow<'static, str>, event_name: Cow<'static, str>,
#[cfg(debug_assertions)] mut cb: impl FnMut(E) + 'static, #[cfg(debug_assertions)] mut cb: impl FnMut(E) + 'static,
#[cfg(not(debug_assertions))] cb: impl FnMut(E) + 'static, #[cfg(not(debug_assertions))] cb: impl FnMut(E) + 'static,
@ -51,9 +57,9 @@ pub fn add_event_listener<E>(
} }
let cb = Closure::wrap(Box::new(cb) as Box<dyn FnMut(E)>).into_js_value(); let cb = Closure::wrap(Box::new(cb) as Box<dyn FnMut(E)>).into_js_value();
let key = event_delegation_key(&event_name); let key = intern(&key);
_ = js_sys::Reflect::set(target, &JsValue::from_str(&key), &cb); _ = js_sys::Reflect::set(target, &JsValue::from_str(&key), &cb);
add_delegated_event_listener(event_name); add_delegated_event_listener(&key, event_name);
} }
#[doc(hidden)] #[doc(hidden)]
@ -61,7 +67,8 @@ pub fn add_event_listener<E>(
pub(crate) fn add_event_listener_undelegated<E>( pub(crate) fn add_event_listener_undelegated<E>(
target: &web_sys::Element, target: &web_sys::Element,
event_name: &str, event_name: &str,
mut cb: impl FnMut(E) + 'static, #[cfg(debug_assertions)] mut cb: impl FnMut(E) + 'static,
#[cfg(not(debug_assertions))] cb: impl FnMut(E) + 'static,
) where ) where
E: FromWasmAbi + 'static, E: FromWasmAbi + 'static,
{ {
@ -82,12 +89,15 @@ pub(crate) fn add_event_listener_undelegated<E>(
// cf eventHandler in ryansolid/dom-expressions // cf eventHandler in ryansolid/dom-expressions
#[cfg(all(target_arch = "wasm32", feature = "web"))] #[cfg(all(target_arch = "wasm32", feature = "web"))]
pub(crate) fn add_delegated_event_listener(event_name: Cow<'static, str>) { pub(crate) fn add_delegated_event_listener(
key: &str,
event_name: Cow<'static, str>,
) {
GLOBAL_EVENTS.with(|global_events| { GLOBAL_EVENTS.with(|global_events| {
let mut events = global_events.borrow_mut(); let mut events = global_events.borrow_mut();
if !events.contains(&event_name) { if !events.contains(&event_name) {
// create global handler // create global handler
let key = JsValue::from_str(&event_delegation_key(&event_name)); let key = JsValue::from_str(&key);
let handler = move |ev: web_sys::Event| { let handler = move |ev: web_sys::Event| {
let target = ev.target(); let target = ev.target();
let node = ev.composed_path().get(0); let node = ev.composed_path().get(0);
@ -163,11 +173,3 @@ pub(crate) fn add_delegated_event_listener(event_name: Cow<'static, str>) {
} }
}) })
} }
#[cfg(all(target_arch = "wasm32", feature = "web"))]
pub(crate) fn event_delegation_key(event_name: &str) -> String {
let event_name = intern(event_name);
let mut n = String::from("$$$");
n.push_str(event_name);
n
}

View File

@ -11,6 +11,9 @@ pub trait EventDescriptor: Clone {
/// The name of the event, such as `click` or `mouseover`. /// The name of the event, such as `click` or `mouseover`.
fn name(&self) -> Cow<'static, str>; fn name(&self) -> Cow<'static, str>;
/// The key used for event delegation.
fn event_delegation_key(&self) -> Cow<'static, str>;
/// Indicates if this event bubbles. For example, `click` bubbles, /// Indicates if this event bubbles. For example, `click` bubbles,
/// but `focus` does not. /// but `focus` does not.
/// ///
@ -34,6 +37,10 @@ impl<Ev: EventDescriptor> EventDescriptor for undelegated<Ev> {
self.0.name() self.0.name()
} }
fn event_delegation_key(&self) -> Cow<'static, str> {
self.0.event_delegation_key()
}
fn bubbles(&self) -> bool { fn bubbles(&self) -> bool {
false false
} }
@ -61,6 +68,10 @@ impl<E: FromWasmAbi> EventDescriptor for Custom<E> {
self.name.clone() self.name.clone()
} }
fn event_delegation_key(&self) -> Cow<'static, str> {
format!("$$${}", self.name).into()
}
fn bubbles(&self) -> bool { fn bubbles(&self) -> bool {
false false
} }
@ -97,6 +108,10 @@ macro_rules! generate_event_types {
stringify!($event).into() stringify!($event).into()
} }
fn event_delegation_key(&self) -> Cow<'static, str> {
concat!("$$$", stringify!($event)).into()
}
$( $(
generate_event_types!($does_not_bubble); generate_event_types!($does_not_bubble);
)? )?

View File

@ -623,9 +623,12 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
} }
let event_name = event.name(); let event_name = event.name();
let key = event.event_delegation_key();
if event.bubbles() { if event.bubbles() {
add_event_listener( add_event_listener(
self.element.as_ref(), self.element.as_ref(),
key,
event_name, event_name,
event_handler, event_handler,
); );
@ -905,6 +908,11 @@ fn create_leptos_element(
id: crate::HydrationKey, id: crate::HydrationKey,
clone_element: fn() -> web_sys::HtmlElement, clone_element: fn() -> web_sys::HtmlElement,
) -> web_sys::HtmlElement { ) -> web_sys::HtmlElement {
#[cfg(not(debug_assertions))]
{
_ = tag;
}
if HydrationCtx::is_hydrating() { if HydrationCtx::is_hydrating() {
if let Some(el) = crate::document().get_element_by_id(&format!("_{id}")) if let Some(el) = crate::document().get_element_by_id(&format!("_{id}"))
{ {

View File

@ -584,7 +584,7 @@ impl View {
match &self { match &self {
Self::Element(el) => { Self::Element(el) => {
if event.bubbles() { if event.bubbles() {
add_event_listener(&el.element, event.name(), event_handler); add_event_listener(&el.element, event.event_delegation_key(), event.name(), event_handler);
} else { } else {
add_event_listener_undelegated( add_event_listener_undelegated(
&el.element, &el.element,