gpui: Reduce window.refresh to improve cache hit of the cached views (#25009)

Release Notes:

- Improved performance when using the scroll wheel and some other mouse
interactions.

Based on some cache details about GPUI `AnyView::cached` that I found in
the discussion of
https://github.com/zed-industries/zed/discussions/24260#discussioncomment-12135749,
and combined with the optimization points found in actual applications.

This change may have some scenarios that I have not considered, so I
just make a draft to put forward my ideas first for discussion.

From my analysis, `AnyView::cached` will always invalid by Div's mouse
events, because of it called `window.refresh`. I understand that (mouse
move event) this is because the interface changes related to hover and
mouse_move will be affected style, so `window.refresh` is required.
Since Div does not have the `entity_id` of View, it is impossible to
know which View should be refreshed, so the entire window can only be
refreshed.

With this change, we can reduce a lot of `render` method calls on
ScrollWheel or Mouse Event.
This commit is contained in:
Jason Lee 2025-03-19 05:52:20 +08:00 committed by GitHub
parent 89ae4ca9a3
commit 985ac4e5f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 38 additions and 22 deletions

View file

@ -1708,13 +1708,14 @@ impl Interactivity {
}); });
let was_hovered = hitbox.is_hovered(window); let was_hovered = hitbox.is_hovered(window);
let current_view = window.current_view();
window.on_mouse_event({ window.on_mouse_event({
let hitbox = hitbox.clone(); let hitbox = hitbox.clone();
move |_: &MouseMoveEvent, phase, window, _| { move |_: &MouseMoveEvent, phase, window, cx| {
if phase == DispatchPhase::Capture { if phase == DispatchPhase::Capture {
let hovered = hitbox.is_hovered(window); let hovered = hitbox.is_hovered(window);
if hovered != was_hovered { if hovered != was_hovered {
window.refresh(); cx.notify(current_view)
} }
} }
} }
@ -1828,10 +1829,11 @@ impl Interactivity {
{ {
let hitbox = hitbox.clone(); let hitbox = hitbox.clone();
let was_hovered = hitbox.is_hovered(window); let was_hovered = hitbox.is_hovered(window);
window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, _cx| { let current_view = window.current_view();
window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
let hovered = hitbox.is_hovered(window); let hovered = hitbox.is_hovered(window);
if phase == DispatchPhase::Capture && hovered != was_hovered { if phase == DispatchPhase::Capture && hovered != was_hovered {
window.refresh(); cx.notify(current_view);
} }
}); });
} }
@ -2117,10 +2119,11 @@ impl Interactivity {
if let Some(group_hitbox) = group_hitbox { if let Some(group_hitbox) = group_hitbox {
let was_hovered = group_hitbox.is_hovered(window); let was_hovered = group_hitbox.is_hovered(window);
window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, _cx| { let current_view = window.current_view();
window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
let hovered = group_hitbox.is_hovered(window); let hovered = group_hitbox.is_hovered(window);
if phase == DispatchPhase::Capture && hovered != was_hovered { if phase == DispatchPhase::Capture && hovered != was_hovered {
window.refresh(); cx.notify(current_view);
} }
}); });
} }
@ -2139,6 +2142,7 @@ impl Interactivity {
let restrict_scroll_to_axis = style.restrict_scroll_to_axis; let restrict_scroll_to_axis = style.restrict_scroll_to_axis;
let line_height = window.line_height(); let line_height = window.line_height();
let hitbox = hitbox.clone(); let hitbox = hitbox.clone();
let current_view = window.current_view();
window.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| { window.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| {
if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) { if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
let mut scroll_offset = scroll_offset.borrow_mut(); let mut scroll_offset = scroll_offset.borrow_mut();
@ -2172,7 +2176,7 @@ impl Interactivity {
scroll_offset.x += delta_x; scroll_offset.x += delta_x;
cx.stop_propagation(); cx.stop_propagation();
if *scroll_offset != old_scroll_offset { if *scroll_offset != old_scroll_offset {
window.refresh(); cx.notify(current_view);
} }
} }
}); });

View file

@ -9,8 +9,8 @@
use crate::{ use crate::{
point, px, size, AnyElement, App, AvailableSpace, Bounds, ContentMask, DispatchPhase, Edges, point, px, size, AnyElement, App, AvailableSpace, Bounds, ContentMask, DispatchPhase, Edges,
Element, FocusHandle, GlobalElementId, Hitbox, IntoElement, Pixels, Point, ScrollWheelEvent, Element, EntityId, FocusHandle, GlobalElementId, Hitbox, IntoElement, Pixels, Point,
Size, Style, StyleRefinement, Styled, Window, ScrollWheelEvent, Size, Style, StyleRefinement, Styled, Window,
}; };
use collections::VecDeque; use collections::VecDeque;
use refineable::Refineable as _; use refineable::Refineable as _;
@ -371,6 +371,7 @@ impl StateInner {
scroll_top: &ListOffset, scroll_top: &ListOffset,
height: Pixels, height: Pixels,
delta: Point<Pixels>, delta: Point<Pixels>,
current_view: EntityId,
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
) { ) {
@ -413,7 +414,7 @@ impl StateInner {
); );
} }
window.refresh(); cx.notify(current_view);
} }
fn logical_scroll_top(&self) -> ListOffset { fn logical_scroll_top(&self) -> ListOffset {
@ -847,6 +848,7 @@ impl Element for List {
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
) { ) {
let current_view = window.current_view();
window.with_content_mask(Some(ContentMask { bounds }), |window| { window.with_content_mask(Some(ContentMask { bounds }), |window| {
for item in &mut prepaint.layout.item_layouts { for item in &mut prepaint.layout.item_layouts {
item.element.paint(window, cx); item.element.paint(window, cx);
@ -863,6 +865,7 @@ impl Element for List {
&scroll_top, &scroll_top,
height, height,
event.delta.pixel_delta(px(20.)), event.delta.pixel_delta(px(20.)),
current_view,
window, window,
cx, cx,
) )
@ -967,7 +970,10 @@ mod test {
#[gpui::test] #[gpui::test]
fn test_reset_after_paint_before_scroll(cx: &mut TestAppContext) { fn test_reset_after_paint_before_scroll(cx: &mut TestAppContext) {
use crate::{div, list, point, px, size, Element, ListState, Styled}; use crate::{
div, list, point, px, size, AppContext, Context, Element, IntoElement, ListState,
Render, Styled, Window,
};
let cx = cx.add_empty_window(); let cx = cx.add_empty_window();
@ -981,9 +987,16 @@ mod test {
offset_in_item: px(0.0), offset_in_item: px(0.0),
}); });
struct TestView(ListState);
impl Render for TestView {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
list(self.0.clone()).w_full().h_full()
}
}
// Paint // Paint
cx.draw(point(px(0.), px(0.)), size(px(100.), px(20.)), |_, _| { cx.draw(point(px(0.), px(0.)), size(px(100.), px(20.)), |_, cx| {
list(state.clone()).w_full().h_full() cx.new(|_| TestView(state.clone()))
}); });
// Reset // Reset

View file

@ -691,6 +691,7 @@ impl Element for InteractiveText {
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
) { ) {
let current_view = window.current_view();
let text_layout = self.text.layout().clone(); let text_layout = self.text.layout().clone();
window.with_element_state::<InteractiveTextState, _>( window.with_element_state::<InteractiveTextState, _>(
global_id.unwrap(), global_id.unwrap(),
@ -764,7 +765,7 @@ impl Element for InteractiveText {
if let Some(hover_listener) = hover_listener.as_ref() { if let Some(hover_listener) = hover_listener.as_ref() {
hover_listener(updated, event.clone(), window, cx); hover_listener(updated, event.clone(), window, cx);
} }
window.refresh(); cx.notify(current_view);
} }
} }
} }

View file

@ -265,6 +265,8 @@ mod uniform_list {
window: &mut Window, window: &mut Window,
_cx: &mut App, _cx: &mut App,
) { ) {
let current_view = window.current_view();
match prepaint { match prepaint {
IndentGuidesElementPrepaintState::Static => { IndentGuidesElementPrepaintState::Static => {
for indent_guide in self.indent_guides.as_ref() { for indent_guide in self.indent_guides.as_ref() {
@ -326,7 +328,7 @@ mod uniform_list {
window.on_mouse_event({ window.on_mouse_event({
let prev_hovered_hitbox_id = hovered_hitbox_id; let prev_hovered_hitbox_id = hovered_hitbox_id;
let hitboxes = hitboxes.clone(); let hitboxes = hitboxes.clone();
move |_: &MouseMoveEvent, phase, window, _cx| { move |_: &MouseMoveEvent, phase, window, cx| {
let mut hovered_hitbox_id = None; let mut hovered_hitbox_id = None;
for hitbox in hitboxes.as_ref() { for hitbox in hitboxes.as_ref() {
if hitbox.is_hovered(window) { if hitbox.is_hovered(window) {
@ -339,15 +341,11 @@ mod uniform_list {
match (prev_hovered_hitbox_id, hovered_hitbox_id) { match (prev_hovered_hitbox_id, hovered_hitbox_id) {
(Some(prev_id), Some(id)) => { (Some(prev_id), Some(id)) => {
if prev_id != id { if prev_id != id {
window.refresh(); cx.notify(current_view)
} }
} }
(None, Some(_)) => { (None, Some(_)) => cx.notify(current_view),
window.refresh(); (Some(_), None) => cx.notify(current_view),
}
(Some(_), None) => {
window.refresh();
}
(None, None) => {} (None, None) => {}
} }
} }