Skip to content

Commit 38059c0

Browse files
committed
query result list: paginate instead of infinite scrolling
this simplifies the rendering ode considerably, and also leads to better performance, so it's a win-win.
1 parent 4d3c49c commit 38059c0

File tree

3 files changed

+70
-204
lines changed

3 files changed

+70
-204
lines changed

gui/src/main.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,8 +281,6 @@ fn span(
281281
info!("Right-clicked {id}");
282282
}
283283
header_res.header_response.context_menu(|ui| {
284-
if ui.button("Pop out").clicked() {};
285-
286284
#[allow(irrefutable_let_patterns)]
287285
if let SpanContext::QueryResults { locating_state, trace_provider } = ctx {
288286
let enabled = locating_state.borrow().can_start_new();

gui/src/search/mod.rs

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ use std::{
1111
};
1212

1313
use crate::{
14-
LogState, TraceProvider, enbitvec::EnBitVec, notifications::draw_x, rect,
15-
search::query_window::QueryLayoutCache, spawn_task,
14+
LogState, TraceProvider, notifications::draw_x, rect, search::query_window::PaginatedResults,
15+
spawn_task,
1616
};
1717
use crossbeam::channel::Receiver;
1818
use egui::{
1919
Color32, CornerRadius, Margin, Pos2, Rect, Response, RichText, Sense, Separator, Shape, Stroke,
2020
TextEdit, Ui, epaint::RectShape, pos2, vec2,
2121
};
22-
use entrace_query::lua_api::setup_lua_on_arc_rwlock;
22+
use entrace_query::{QueryError, lua_api::setup_lua_on_arc_rwlock};
2323
use mlua::{FromLua, Lua, Value};
2424
use tracing::{error, info};
2525
#[derive(Debug, Clone)]
@@ -30,8 +30,7 @@ pub struct PartialQueryResult {
3030
#[derive(Debug)]
3131
pub struct QueryResult {
3232
pub ids: Vec<u32>,
33-
pub layout_cache: QueryLayoutCache,
34-
cull_open_state: EnBitVec,
33+
pub pages: PaginatedResults,
3534
}
3635
#[derive(Debug)]
3736
pub enum Query {
@@ -200,12 +199,7 @@ impl SearchState {
200199
}
201200
}
202201
let ids_len = total_ids.len();
203-
let cull_open_state = EnBitVec::repeat(false, ids_len);
204-
let qr = QueryResult {
205-
ids: total_ids,
206-
cull_open_state,
207-
layout_cache: QueryLayoutCache::new(),
208-
};
202+
let qr = QueryResult { ids: total_ids, pages: PaginatedResults::new(ids_len) };
209203
tx.send(Ok(qr)).ok();
210204
});
211205
}

gui/src/search/query_window.rs

Lines changed: 65 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,15 @@
1-
use egui::{Context, ScrollArea, Ui, vec2};
1+
use std::{cmp::min, fmt::Write, ops::Range};
2+
3+
use egui::{Context, Layout, ScrollArea, Ui, Vec2, Widget};
24
use entrace_core::display_error_context;
35
use tracing::{error, info};
46

57
use crate::{
6-
App, LogState, LogStatus, SpanContext, row_height,
8+
App, LogState, LogStatus, SpanContext,
79
search::{Query, QueryError, QueryResult, QueryTiming, search_settings_dialog},
810
span,
911
};
1012

11-
#[derive(Debug)]
12-
pub enum LayoutCommand {
13-
Space(f32),
14-
Span(u32),
15-
}
16-
#[derive(Debug)]
17-
pub struct QueryLayoutCache {
18-
/// this is a bool instead of an option, so we can re-use the same [commands](QueryLayoutCache::commands) allocation in multiple successive
19-
/// frames, since it is going to be similar in length
20-
pub is_valid: bool,
21-
pub commands: Vec<LayoutCommand>,
22-
pub last_scroll_offset: Option<f32>,
23-
pub last_inner_height: Option<f32>,
24-
}
25-
26-
impl Default for QueryLayoutCache {
27-
fn default() -> Self {
28-
Self::new()
29-
}
30-
}
31-
32-
impl QueryLayoutCache {
33-
pub fn new() -> Self {
34-
Self {
35-
is_valid: false,
36-
commands: Vec::with_capacity(32),
37-
last_scroll_offset: None,
38-
last_inner_height: None,
39-
}
40-
}
41-
/// Convenience method for [QueryLayoutCache.commands::push]
42-
pub fn push(&mut self, command: LayoutCommand) {
43-
self.commands.push(command);
44-
}
45-
pub fn invalidate(&mut self) {
46-
self.is_valid = false;
47-
self.commands.clear();
48-
}
49-
}
50-
5113
pub fn query_windows(ui: &mut Ui, ctx: &Context, app: &mut App) {
5214
search_settings_dialog(ui, &mut app.search_state);
5315
for i in 0..app.search_state.queries.len() {
@@ -118,159 +80,71 @@ pub fn query_windows(ui: &mut Ui, ctx: &Context, app: &mut App) {
11880
i += 1;
11981
}
12082
}
121-
122-
pub fn query_result_list(ui: &mut Ui, result: &mut QueryResult, log: &mut LogState) {
123-
ui.label(format!("Got {} spans.", result.ids.len()));
124-
let scroller = ScrollArea::new([false, true])
125-
.auto_shrink([false, false])
126-
.stick_to_bottom(false)
127-
.show(ui, |ui| match result.layout_cache.is_valid {
128-
true => result_list_cached(ui, result, log),
129-
false => result_list_no_cache(ui, result, log),
130-
});
131-
132-
let cache = &mut result.layout_cache;
133-
let height = scroller.inner_rect.height();
134-
let new_offset = scroller.state.offset.y;
135-
if matches!(cache.last_inner_height, Some(last) if last != height)
136-
|| matches!(cache.last_scroll_offset, Some(last) if last != new_offset)
137-
{
138-
cache.invalidate();
139-
}
140-
141-
cache.last_inner_height = Some(height);
142-
cache.last_scroll_offset = Some(new_offset);
83+
#[derive(Debug)]
84+
pub struct PaginatedResults {
85+
cur_page: usize,
86+
page_size: usize,
87+
nr_entries: usize,
88+
page_entry_text: String,
14389
}
144-
fn result_list_cached(ui: &mut Ui, result: &mut QueryResult, log: &mut LogState) {
145-
let log_reader = log.trace_provider.read().unwrap();
146-
let cache = &mut result.layout_cache;
147-
let mut ctx = SpanContext::QueryResults {
148-
locating_state: &log.locating_state,
149-
trace_provider: log.trace_provider.clone(),
150-
};
151-
let mut invalidate_cache = false;
152-
for command in &cache.commands {
153-
match command {
154-
LayoutCommand::Space(x) => {
155-
ui.allocate_space(vec2(10.0, *x));
156-
}
157-
LayoutCommand::Span(span_id) => {
158-
let resp = span(ui, &mut ctx, &log_reader, *span_id);
159-
if let Some(resp) = resp.header_response
160-
&& resp.clicked()
161-
{
162-
invalidate_cache = true;
163-
result.cull_open_state.toggle(*span_id as usize);
164-
}
165-
}
90+
impl PaginatedResults {
91+
pub fn new(nr_entries: usize) -> PaginatedResults {
92+
PaginatedResults {
93+
cur_page: 0,
94+
page_size: 100,
95+
page_entry_text: String::from("0"),
96+
nr_entries,
16697
}
16798
}
168-
if invalidate_cache {
169-
cache.invalidate();
99+
pub fn cur_range(&self) -> Range<usize> {
100+
let start = self.cur_page * self.page_size;
101+
start..min(start + self.page_size, self.nr_entries)
170102
}
171-
}
172-
fn result_list_no_cache(ui: &mut Ui, result: &mut QueryResult, log: &mut LogState) {
173-
let log_reader = log.trace_provider.read().unwrap();
174-
let row_height = row_height(ui);
175-
let cache = &mut result.layout_cache;
176-
cache.invalidate();
177-
let mut invalidate_cache = false;
178-
let mut _non_culled_cnt = 0;
179-
//let skippable_space = ui.clip_rect().min.y - ui.cursor().min.y;
180-
//let skippable_rows = (skippable_space / row_height).max(0.0) as usize;
181-
//ui.allocate_space(vec2(ui.available_width(), row_height * skippable_rows as f32));
182-
//println!("Skipped rows: {skippable_rows}");
183-
enum Region {
184-
BeforeVisible(f32),
185-
Visible,
186-
AfterVisible(f32),
103+
pub fn set_page(&mut self, new: usize) {
104+
self.cur_page = min(new, self.page_cnt().saturating_sub(1));
105+
self.page_entry_text.clear();
106+
write!(self.page_entry_text, "{}", self.cur_page).ok();
187107
}
188-
let mut region = Region::BeforeVisible(0.0);
189-
//let mut region = Region::Visible;
190-
let mut idx = 0;
191-
let mut ctx = SpanContext::QueryResults {
192-
locating_state: &log.locating_state,
193-
trace_provider: log.trace_provider.clone(),
194-
};
195-
while idx < result.ids.len() {
196-
let span_id = result.ids[idx];
197-
198-
match region {
199-
Region::BeforeVisible(space_before) => {
200-
let span_end_lower_bound =
201-
ui.cursor().min.y + space_before + row_height + ui.spacing().item_spacing.y;
202-
let still_before_visible = span_end_lower_bound < ui.clip_rect().min.y;
203-
//info!(span_end_lower_bound, clip_min = ui.clip_rect().min.y, "BeforeVisible");
204-
if !still_before_visible {
205-
//println!("{idx} is (partially) visible. culled height before: {space_before}");
206-
ui.allocate_space(vec2(10.0, space_before.max(0.0)));
207-
cache.push(LayoutCommand::Space(space_before.max(0.0)));
208-
region = Region::Visible;
209-
continue;
210-
}
211-
212-
if !result.cull_open_state.get(span_id as usize).unwrap_or(true) {
213-
region = Region::BeforeVisible(space_before + row_height);
214-
} else {
215-
// if this span is open, then its children should be visible eveen
216-
// if the header is not. so draw it.
217-
// To draw it in the correct space, we allocate the (closed) space
218-
// before, then go again from 0.
219-
ui.allocate_space(vec2(10.0, space_before.max(0.0)));
220-
cache.push(LayoutCommand::Space(space_before.max(0.0)));
221-
// println!(
222-
// "{idx} is open-culled, so allocated the current carry of {space_before} \
223-
// before"
224-
// );
225-
span(ui, &mut ctx, &log_reader, span_id);
226-
cache.push(LayoutCommand::Span(span_id));
227-
region = Region::BeforeVisible(0.0);
228-
}
229-
}
230-
Region::Visible => {
231-
//println!("Visible({idx})");
232-
let resp = span(ui, &mut ctx, &log_reader, span_id);
233-
cache.push(LayoutCommand::Span(span_id));
234-
_non_culled_cnt += 1;
235-
if let Some(resp) = resp.header_response {
236-
if ui.clip_rect().max.y <= resp.rect.min.y {
237-
region = Region::AfterVisible(0.0);
238-
idx += 1;
239-
continue;
240-
}
241-
if resp.clicked() {
242-
invalidate_cache = true;
243-
result.cull_open_state.toggle(idx);
244-
}
245-
}
246-
if idx == result.ids.len().saturating_sub(1) {
247-
ui.allocate_space(vec2(10.0, row_height * 3.0));
248-
cache.push(LayoutCommand::Space(row_height * 3.0));
249-
}
250-
}
251-
Region::AfterVisible(after_space) => {
252-
if !result.cull_open_state.get(idx).unwrap_or(true) {
253-
region = Region::AfterVisible(after_space + row_height);
254-
} else {
255-
ui.allocate_space(vec2(10.0, after_space.max(0.0)));
256-
cache.push(LayoutCommand::Space(after_space.max(0.0)));
257-
258-
//println!("{idx} is open-culled, so allocated the current carry of {after_space} before");
259-
span(ui, &mut ctx, &log_reader, span_id);
260-
cache.push(LayoutCommand::Span(span_id));
261-
region = Region::AfterVisible(0.0);
262-
}
263-
}
264-
}
265-
idx += 1;
266-
}
267-
if let Region::AfterVisible(after_space) = region {
268-
ui.allocate_space(vec2(10.0, after_space));
269-
cache.push(LayoutCommand::Space(after_space.max(0.0)));
108+
pub fn page_cnt(&self) -> usize {
109+
self.nr_entries.div_ceil(self.page_size)
270110
}
271-
if invalidate_cache {
272-
cache.invalidate();
273-
}
274-
cache.is_valid = true;
275-
//println!("Not culled: {non_culled_cnt}");
111+
}
112+
pub fn result_list_pagination(ui: &mut Ui, result: &mut QueryResult) {
113+
ui.allocate_ui_with_layout(Vec2::ZERO, Layout::left_to_right(egui::Align::Center), |ui| {
114+
if ui.button("<").clicked() {
115+
result.pages.set_page(result.pages.cur_page.saturating_sub(1));
116+
}
117+
ui.label("Page ");
118+
let ed = egui::TextEdit::singleline(&mut result.pages.page_entry_text)
119+
.desired_width(0.0)
120+
.clip_text(false)
121+
.ui(ui);
122+
if ed.lost_focus()
123+
&& let Ok(x) = str::parse::<usize>(&result.pages.page_entry_text)
124+
{
125+
result.pages.set_page(x);
126+
}
127+
ui.label(format!("/ {}", result.pages.page_cnt()));
128+
if ui.button(">").clicked() {
129+
result.pages.set_page(result.pages.cur_page.saturating_add(1));
130+
}
131+
});
132+
}
133+
pub fn query_result_list(ui: &mut Ui, result: &mut QueryResult, log: &mut LogState) {
134+
ui.label(format!("Got {} spans.", result.ids.len()));
135+
result_list_pagination(ui, result);
136+
ScrollArea::new([false, true]).auto_shrink([false, false]).stick_to_bottom(false).show(
137+
ui,
138+
|ui| {
139+
let result_range = result.pages.cur_range();
140+
let log_reader = log.trace_provider.read().unwrap();
141+
let mut ctx = SpanContext::QueryResults {
142+
locating_state: &log.locating_state,
143+
trace_provider: log.trace_provider.clone(),
144+
};
145+
for id in result_range {
146+
span(ui, &mut ctx, &log_reader, result.ids[id]);
147+
}
148+
},
149+
);
276150
}

0 commit comments

Comments
 (0)