From a54c80d3aecf2b5513c388ce40e742490789e375 Mon Sep 17 00:00:00 2001 From: Antoine Beyeler <49431240+abey79@users.noreply.github.com> Date: Tue, 28 Jan 2025 10:37:24 +0100 Subject: [PATCH] Improve performance for Blueprint & Streams Panel for many entities (#8808) --- .../re_blueprint_tree/src/blueprint_tree.rs | 16 +++++++--- crates/viewer/re_time_panel/src/lib.rs | 29 +++++++++++-------- .../viewer/re_ui/src/list_item/list_item.rs | 23 +++++++++++++++ 3 files changed, 52 insertions(+), 16 deletions(-) diff --git a/crates/viewer/re_blueprint_tree/src/blueprint_tree.rs b/crates/viewer/re_blueprint_tree/src/blueprint_tree.rs index 38b921c25c20..36bc26934d3a 100644 --- a/crates/viewer/re_blueprint_tree/src/blueprint_tree.rs +++ b/crates/viewer/re_blueprint_tree/src/blueprint_tree.rs @@ -174,6 +174,7 @@ impl BlueprintTree { let item_response = ui .list_item() + .render_offscreen(false) .selected(ctx.selection().contains_item(&item)) .draggable(true) // allowed for consistency but results in an invalid drag .drop_target_style(self.is_candidate_drop_parent_container(&container_data.id)) @@ -272,6 +273,7 @@ impl BlueprintTree { .. } = ui .list_item() + .render_offscreen(false) .selected(ctx.selection().contains_item(&item)) .draggable(true) .drop_target_style(self.is_candidate_drop_parent_container(&container_data.id)) @@ -360,6 +362,7 @@ impl BlueprintTree { .. } = ui .list_item() + .render_offscreen(false) .selected(ctx.selection().contains_item(&item)) .draggable(true) .force_hovered(is_item_hovered) @@ -375,10 +378,13 @@ impl BlueprintTree { } if !view_data.projection_trees.is_empty() { - ui.list_item().interactive(false).show_flat( - ui, - list_item::LabelContent::new("Projections:").italics(true), - ); + ui.list_item() + .render_offscreen(false) + .interactive(false) + .show_flat( + ui, + list_item::LabelContent::new("Projections:").italics(true), + ); for projection in &view_data.projection_trees { self.data_result_ui(ctx, viewport_blueprint, ui, projection, view_visible); @@ -481,6 +487,7 @@ impl BlueprintTree { DataResultKind::OriginProjectionPlaceholder => { if ui .list_item() + .render_offscreen(false) .show_hierarchical( ui, list_item::LabelContent::new("$origin") @@ -507,6 +514,7 @@ impl BlueprintTree { let list_item = ui .list_item() + .render_offscreen(false) .draggable(true) .selected(is_selected) .force_hovered(is_item_hovered); diff --git a/crates/viewer/re_time_panel/src/lib.rs b/crates/viewer/re_time_panel/src/lib.rs index 6dacd912ea0e..69a110f676ad 100644 --- a/crates/viewer/re_time_panel/src/lib.rs +++ b/crates/viewer/re_time_panel/src/lib.rs @@ -760,6 +760,7 @@ impl TimePanel { .. } = ui .list_item() + .render_offscreen(false) .selected(is_selected) .draggable(true) .force_hovered(is_item_hovered) @@ -925,6 +926,7 @@ impl TimePanel { let response = ui .list_item() + .render_offscreen(false) .selected(ctx.selection().contains_item(&item.to_item())) .force_hovered( ctx.selection_state() @@ -975,18 +977,21 @@ impl TimePanel { format!("{} times", re_format::format_uint(num_messages)) }; - ui.list_item().interactive(false).show_flat( - ui, - list_item::LabelContent::new(format!( - "{kind} component, logged {num_messages}" - )) - .truncate(false) - .with_icon(if is_static { - &re_ui::icons::COMPONENT_STATIC - } else { - &re_ui::icons::COMPONENT_TEMPORAL - }), - ); + ui.list_item() + .interactive(false) + .render_offscreen(false) + .show_flat( + ui, + list_item::LabelContent::new(format!( + "{kind} component, logged {num_messages}" + )) + .truncate(false) + .with_icon(if is_static { + &re_ui::icons::COMPONENT_STATIC + } else { + &re_ui::icons::COMPONENT_TEMPORAL + }), + ); // Static components are not displayed at all on the timeline, so cannot be // previewed there. So we display their content in this tooltip instead. diff --git a/crates/viewer/re_ui/src/list_item/list_item.rs b/crates/viewer/re_ui/src/list_item/list_item.rs index c4904cddee44..051c7c33cbe2 100644 --- a/crates/viewer/re_ui/src/list_item/list_item.rs +++ b/crates/viewer/re_ui/src/list_item/list_item.rs @@ -49,6 +49,7 @@ pub struct ListItem { force_background: Option, pub collapse_openness: Option, height: f32, + render_offscreen: bool, } impl Default for ListItem { @@ -62,6 +63,7 @@ impl Default for ListItem { force_background: None, collapse_openness: None, height: DesignTokens::list_item_height(), + render_offscreen: true, } } } @@ -140,6 +142,18 @@ impl ListItem { self } + /// Controls whether [`Self`] calls [`ListItemContent::ui`] when the item is not currently + /// visible. + /// + /// Skipping rendering can increase performances for long lists that are mostly out of view, but + /// this will prevent any side effects from [`ListItemContent::ui`] from occurring. For this + /// reason, this is an opt-in optimization. + #[inline] + pub fn render_offscreen(mut self, render_offscreen: bool) -> Self { + self.render_offscreen = render_offscreen; + self + } + /// Draw the item as part of a flat list. /// /// *Important*: must be called while nested in a [`super::list_item_scope`]. @@ -274,6 +288,7 @@ impl ListItem { force_background, collapse_openness, height, + render_offscreen, } = self; let collapse_extra = if collapse_openness.is_some() { @@ -329,6 +344,14 @@ impl ListItem { // allocate past the available width. response.rect = rect; + let should_render = render_offscreen || ui.is_rect_visible(rect); + if !should_render { + return ListItemResponse { + response, + collapse_response: None, + }; + } + // override_hover should not affect the returned response let mut style_response = response.clone(); if force_hovered {