virtual_list: Add element state to cache item size calculate. (#1048)

This commit is contained in:
Jason Lee 2025-07-07 11:47:35 +08:00 committed by GitHub
parent 4156c831c6
commit 40a2467374
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 82 additions and 68 deletions

View file

@ -143,6 +143,7 @@ pub fn measure_if(name: impl Into<SharedString>, if_: bool, f: impl FnOnce()) {
/// Measures the execution time. /// Measures the execution time.
#[inline] #[inline]
#[track_caller]
pub fn measure(name: impl Into<SharedString>, f: impl FnOnce()) { pub fn measure(name: impl Into<SharedString>, f: impl FnOnce()) {
measure_if(name, true, f); measure_if(name, true, f);
} }
@ -153,6 +154,7 @@ pub struct Measure {
} }
impl Measure { impl Measure {
#[track_caller]
pub fn new(name: impl Into<SharedString>) -> Self { pub fn new(name: impl Into<SharedString>) -> Self {
Self { Self {
name: name.into(), name: name.into(),
@ -160,6 +162,7 @@ impl Measure {
} }
} }
#[track_caller]
pub fn end(self) { pub fn end(self) {
let duration = self.start.elapsed(); let duration = self.start.elapsed();
tracing::trace!("{} in {:?}", self.name, duration); tracing::trace!("{} in {:?}", self.name, duration);

View file

@ -13,18 +13,20 @@
use std::{cmp, ops::Range, rc::Rc}; use std::{cmp, ops::Range, rc::Rc};
use gpui::{ use gpui::{
div, point, px, size, AnyElement, App, AvailableSpace, Axis, Bounds, ContentMask, Context, Div, div, point, px, size, Along, AnyElement, App, AvailableSpace, Axis, Bounds, ContentMask,
Element, ElementId, Entity, GlobalElementId, Hitbox, InteractiveElement, IntoElement, Context, Div, Element, ElementId, Entity, GlobalElementId, Hitbox, InteractiveElement,
IsZero as _, Pixels, Render, ScrollHandle, Size, Stateful, StatefulInteractiveElement, IntoElement, IsZero as _, Pixels, Render, ScrollHandle, Size, Stateful,
StyleRefinement, Styled, Window, StatefulInteractiveElement, StyleRefinement, Styled, Window,
}; };
use smallvec::SmallVec; use smallvec::SmallVec;
/// Create a virtual list in Vertical direction. /// Create a [`VirtualList`] in vertical direction.
/// ///
/// This is like `uniform_list` in GPUI, but support two axis. /// This is like `uniform_list` in GPUI, but support two axis.
/// ///
/// The `item_sizes` is the size of each column. /// The `item_sizes` is the size of each column.
///
/// See also [`h_virtual_list`]
#[inline] #[inline]
pub fn v_virtual_list<R, V>( pub fn v_virtual_list<R, V>(
view: Entity<V>, view: Entity<V>,
@ -39,7 +41,9 @@ where
virtual_list(view, id, Axis::Vertical, item_sizes, f) virtual_list(view, id, Axis::Vertical, item_sizes, f)
} }
/// Create a virtual list in Horizontal direction. /// Create a [`VirtualList`] in horizontal direction.
///
/// See also [`v_virtual_list`]
#[inline] #[inline]
pub fn h_virtual_list<R, V>( pub fn h_virtual_list<R, V>(
view: Entity<V>, view: Entity<V>,
@ -91,7 +95,7 @@ where
} }
} }
/// VirtualItem component for rendering a large number of differently sized columns. /// VirtualList component for rendering a large number of differently sized items.
pub struct VirtualList { pub struct VirtualList {
id: ElementId, id: ElementId,
axis: Axis, axis: Axis,
@ -153,8 +157,15 @@ impl VirtualList {
pub struct VirtualListFrameState { pub struct VirtualListFrameState {
/// Visible items to be painted. /// Visible items to be painted.
items: SmallVec<[AnyElement; 32]>, items: SmallVec<[AnyElement; 32]>,
item_sizes: Vec<Pixels>, size_layout: ItemSizeLayout,
item_origins: Vec<Pixels>, }
#[derive(Default, Clone)]
pub struct ItemSizeLayout {
items_sizes: Rc<Vec<Size<Pixels>>>,
container_size: Size<Pixels>,
sizes: Vec<Pixels>,
origins: Vec<Pixels>,
} }
impl IntoElement for VirtualList { impl IntoElement for VirtualList {
@ -197,68 +208,68 @@ impl Element for VirtualList {
} }
.to_pixels(font_size.into(), window.rem_size()); .to_pixels(font_size.into(), window.rem_size());
// TODO: To cache the item_sizes, item_origins let (layout_id, size_layout) = window.with_element_state(
// If there have 500,000 items, this method will speed about 500~600µs global_id.unwrap(),
// let start = std::time::Instant::now(); |state: Option<ItemSizeLayout>, window| {
// Prepare each item's size by axis let mut state = state.unwrap_or(ItemSizeLayout::default());
let item_sizes = match self.axis {
Axis::Horizontal => self
.item_sizes
.iter()
.enumerate()
.map(|(i, size)| {
if i == self.items_count - 1 {
size.width
} else {
size.width + gap
}
})
.collect::<Vec<_>>(),
Axis::Vertical => self
.item_sizes
.iter()
.enumerate()
.map(|(i, size)| {
if i == self.items_count - 1 {
size.height
} else {
size.height + gap
}
})
.collect::<Vec<_>>(),
};
// Prepare each item's origin by axis if state.items_sizes != self.item_sizes {
let item_origins = match self.axis { state.items_sizes = self.item_sizes.clone();
Axis::Horizontal => item_sizes // Prepare each item's size by axis
.iter() state.sizes = self
.scan(px(0.), |cumulative_x, size| { .item_sizes
let x = *cumulative_x; .iter()
*cumulative_x += *size; .enumerate()
Some(x) .map(|(i, size)| {
}) let size = size.along(self.axis);
.collect::<Vec<_>>(), if i + 1 == self.items_count {
Axis::Vertical => item_sizes size
.iter() } else {
.scan(px(0.), |cumulative_y, size| { size + gap
let y = *cumulative_y; }
*cumulative_y += *size; })
Some(y) .collect::<Vec<_>>();
})
.collect::<Vec<_>>(),
};
// println!("layout: {} {:?}", item_sizes.len(), start.elapsed());
let (layout_id, _) = self // Prepare each item's origin by axis
.base state.origins = state
.request_layout(global_id, inspector_id, window, cx); .sizes
.iter()
.scan(px(0.), |cumulative, size| match self.axis {
Axis::Horizontal => {
let x = *cumulative;
*cumulative += *size;
Some(x)
}
Axis::Vertical => {
let y = *cumulative;
*cumulative += *size;
Some(y)
}
})
.collect::<Vec<_>>();
state.container_size = Size {
width: px(self.item_sizes.iter().map(|size| size.width.0).sum::<f32>()),
height: px(self
.item_sizes
.iter()
.map(|size| size.height.0)
.sum::<f32>()),
};
}
let (layout_id, _) = self
.base
.request_layout(global_id, inspector_id, window, cx);
((layout_id, state.clone()), state)
},
);
( (
layout_id, layout_id,
VirtualListFrameState { VirtualListFrameState {
items: SmallVec::new(), items: SmallVec::new(),
item_sizes, size_layout,
item_origins,
}, },
) )
} }
@ -295,17 +306,17 @@ impl Element for VirtualList {
Axis::Vertical => border.top + padding.top + border.bottom + padding.bottom, Axis::Vertical => border.top + padding.top + border.bottom + padding.bottom,
}; };
let item_sizes = &layout.item_sizes; let item_sizes = &layout.size_layout.sizes;
let item_origins = &layout.item_origins; let item_origins = &layout.size_layout.origins;
let content_size = match self.axis { let content_size = match self.axis {
Axis::Horizontal => Size { Axis::Horizontal => Size {
width: px(item_sizes.iter().map(|size| size.0).sum::<f32>()) + padding_size, width: layout.size_layout.container_size.width + padding_size,
height: (first_item_size.height + padding_size).max(padded_bounds.size.height), height: (first_item_size.height + padding_size).max(padded_bounds.size.height),
}, },
Axis::Vertical => Size { Axis::Vertical => Size {
width: (first_item_size.width + padding_size).max(padded_bounds.size.width), width: (first_item_size.width + padding_size).max(padded_bounds.size.width),
height: px(item_sizes.iter().map(|size| size.0).sum::<f32>()) + padding_size, height: layout.size_layout.container_size.height + padding_size,
}, },
}; };