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 {
cx: Scope,
#[cfg(all(target_arch = "wasm32", feature = "web"))]
document_fragment: web_sys::DocumentFragment,
document_fragment: Option<web_sys::DocumentFragment>,
#[cfg(debug_assertions)]
opening: Comment,
pub(crate) child: View,
closing: Comment,
closing: Option<Comment>,
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
pub(crate) id: HydrationKey,
}
@ -192,16 +192,22 @@ impl fmt::Debug for EachItem {
impl EachItem {
fn new(cx: Scope, child: View) -> Self {
let id = HydrationCtx::id();
let needs_closing = !matches!(child, View::Element(_));
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)]
Comment::new(Cow::Borrowed("<EachItem>"), &id, false),
);
#[cfg(all(target_arch = "wasm32", feature = "web"))]
let document_fragment = {
let document_fragment = if needs_closing {
let fragment = crate::document().create_document_fragment();
let closing = markers.0.as_ref().unwrap();
// Insert the comments into the document fragment
// so they can serve as our references when inserting
@ -209,14 +215,16 @@ impl EachItem {
if !HydrationCtx::is_hydrating() {
#[cfg(debug_assertions)]
fragment
.append_with_node_2(&markers.1.node, &markers.0.node)
.append_with_node_2(&markers.1.node, &closing.node)
.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 {
@ -243,7 +251,11 @@ impl Drop for EachItem {
#[cfg(all(target_arch = "wasm32", feature = "web"))]
impl Mountable for EachItem {
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 {
@ -255,7 +267,11 @@ impl Mountable for EachItem {
}
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.
#[cfg(all(target_arch = "wasm32", feature = "web"))]
fn prepare_for_move(&self) {
if let Some(fragment) = &self.document_fragment {
let start = self.get_opening_node();
let end = &self.closing.node;
let end = &self.get_closing_node();
let mut sibling = start;
while sibling != *end {
let next_sibling = sibling.next_sibling().unwrap();
self.document_fragment.append_child(&sibling).unwrap();
fragment.append_child(&sibling).unwrap();
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! {
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();
#[cfg(all(target_arch = "wasm32", feature = "web"))]
@ -359,14 +380,22 @@ where
closing.clone()
};
let items = items_fn();
let items_iter = items_fn().into_iter();
let items = items.into_iter().collect::<SmallVec<[_; 128]>>();
let hashed_items =
items.iter().map(&key_fn).collect::<FxIndexSet<_>>();
let (capacity, _) = items_iter.size_hint();
let mut hashed_items = FxIndexSet::with_capacity_and_hasher(
capacity,
BuildHasherDefault::<FxHasher>::default()
);
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);
apply_cmds(
@ -377,21 +406,34 @@ where
&closing,
cmds,
&mut children_borrow,
items.into_iter().map(|t| Some(t)).collect(),
items,
&each_fn
);
} else {
*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)));
return HashRun(hashed_items);
}
}
// if previous run is empty
*children_borrow = Vec::with_capacity(capacity);
#[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));
}
}
#[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)
});
@ -421,6 +463,18 @@ fn diff<K: Eq + Hash>(from: &FxIndexSet<K>, to: &FxIndexSet<K>) -> Diff {
clear: true,
..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
@ -445,7 +499,7 @@ fn diff<K: Eq + Hash>(from: &FxIndexSet<K>, to: &FxIndexSet<K>) -> Diff {
// Get moved items
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 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)]
#[allow(unused)]
struct Diff {
removed: SmallVec<[DiffOpRemove; 8]>,
moved: SmallVec<[DiffOpMove; 8]>,
added: SmallVec<[DiffOpAdd; 8]>,
removed: Vec<DiffOpRemove>,
moved: Vec<DiffOpMove>,
added: Vec<DiffOpAdd>,
clear: bool,
}
@ -588,7 +642,7 @@ fn apply_cmds<T, EF, N>(
closing: &web_sys::Node,
mut cmds: Diff,
children: &mut Vec<Option<EachItem>>,
mut items: SmallVec<[Option<T>; 128]>,
mut items: Vec<Option<T>>,
each_fn: &EF,
) where
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();
if event.bubbles() {
add_event_listener(target, event_name, event_handler);
add_event_listener(
target,
event.event_delegation_key(),
event_name,
event_handler,
);
} else {
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"))]
pub fn add_event_listener<E>(
target: &web_sys::Element,
key: Cow<'static, str>,
event_name: Cow<'static, str>,
#[cfg(debug_assertions)] mut 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 key = event_delegation_key(&event_name);
let key = intern(&key);
_ = 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)]
@ -61,7 +67,8 @@ pub fn add_event_listener<E>(
pub(crate) fn add_event_listener_undelegated<E>(
target: &web_sys::Element,
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
E: FromWasmAbi + 'static,
{
@ -82,12 +89,15 @@ pub(crate) fn add_event_listener_undelegated<E>(
// cf eventHandler in ryansolid/dom-expressions
#[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| {
let mut events = global_events.borrow_mut();
if !events.contains(&event_name) {
// 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 target = ev.target();
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`.
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,
/// but `focus` does not.
///
@ -34,6 +37,10 @@ impl<Ev: EventDescriptor> EventDescriptor for undelegated<Ev> {
self.0.name()
}
fn event_delegation_key(&self) -> Cow<'static, str> {
self.0.event_delegation_key()
}
fn bubbles(&self) -> bool {
false
}
@ -61,6 +68,10 @@ impl<E: FromWasmAbi> EventDescriptor for Custom<E> {
self.name.clone()
}
fn event_delegation_key(&self) -> Cow<'static, str> {
format!("$$${}", self.name).into()
}
fn bubbles(&self) -> bool {
false
}
@ -97,6 +108,10 @@ macro_rules! generate_event_types {
stringify!($event).into()
}
fn event_delegation_key(&self) -> Cow<'static, str> {
concat!("$$$", stringify!($event)).into()
}
$(
generate_event_types!($does_not_bubble);
)?

View File

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

View File

@ -584,7 +584,7 @@ impl View {
match &self {
Self::Element(el) => {
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 {
add_event_listener_undelegated(
&el.element,