Skip to content

Commit 109f8fa

Browse files
IsseWemilk
authored andcommitted
Larger than ram time panel UI (#12338)
### Related * Closes RR-3133 * Closes RR-3001 ### What Display loaded/unloaded state of chunks in the time panel. https://github.com/user-attachments/assets/5f543b77-1001-464a-b162-82e015f32f4e --------- Co-authored-by: Emil Ernerfeldt <[email protected]>
1 parent 30affb3 commit 109f8fa

File tree

14 files changed

+711
-70
lines changed

14 files changed

+711
-70
lines changed

Cargo.lock

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9837,15 +9837,19 @@ dependencies = [
98379837
"itertools 0.14.0",
98389838
"nohash-hasher",
98399839
"rand 0.9.2",
9840+
"re_chunk",
98409841
"re_chunk_store",
98419842
"re_context_menu",
98429843
"re_data_ui",
98439844
"re_entity_db",
98449845
"re_format",
98459846
"re_int_histogram",
98469847
"re_log",
9848+
"re_log_encoding",
98479849
"re_log_types",
98489850
"re_sdk_types",
9851+
"re_sorbet",
9852+
"re_span",
98499853
"re_test_context",
98509854
"re_tracing",
98519855
"re_ui",

crates/store/re_chunk/src/chunk.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -492,8 +492,6 @@ impl Chunk {
492492
&self,
493493
timeline: &TimelineName,
494494
) -> Vec<(TimeInt, u64)> {
495-
re_tracing::profile_function!();
496-
497495
if self.is_static() {
498496
return vec![(TimeInt::STATIC, self.num_events_cumulative())];
499497
}
@@ -527,8 +525,6 @@ impl Chunk {
527525
&self,
528526
time_column: &TimeColumn,
529527
) -> Vec<(TimeInt, u64)> {
530-
re_tracing::profile_function!();
531-
532528
debug_assert!(time_column.is_sorted());
533529

534530
// NOTE: This is used on some very hot paths (time panel rendering).
@@ -578,8 +574,6 @@ impl Chunk {
578574
&self,
579575
time_column: &TimeColumn,
580576
) -> Vec<(TimeInt, u64)> {
581-
re_tracing::profile_function!();
582-
583577
debug_assert!(!time_column.is_sorted());
584578

585579
// NOTE: This is used on some very hot paths (time panel rendering).

crates/store/re_entity_db/src/rrd_manifest_index.rs

Lines changed: 122 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use re_log_types::{AbsoluteTimeRange, StoreKind};
1313

1414
use crate::sorted_range_map::SortedRangeMap;
1515

16+
mod time_range_merger;
17+
1618
/// Errors that can occur during prefetching.
1719
#[derive(thiserror::Error, Debug)]
1820
pub enum PrefetchError {
@@ -48,6 +50,19 @@ pub enum LoadState {
4850
Loaded,
4951
}
5052

53+
impl LoadState {
54+
pub fn is_loaded(&self) -> bool {
55+
!self.is_unloaded()
56+
}
57+
58+
pub fn is_unloaded(&self) -> bool {
59+
match self {
60+
Self::Unloaded | Self::InTransit => true,
61+
Self::Loaded => false,
62+
}
63+
}
64+
}
65+
5166
/// How to calculate which chunks to prefetch.
5267
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5368
pub struct ChunkPrefetchOptions {
@@ -410,37 +425,124 @@ impl RrdManifestIndex {
410425
)?)
411426
}
412427

413-
#[must_use]
414-
pub fn time_ranges_all_chunks(
428+
/// Creates an iterator of time ranges which are loaded on a specific timeline.
429+
///
430+
/// The ranges are guaranteed to be ordered and non-overlapping.
431+
pub fn loaded_ranges_on_timeline(
415432
&self,
416433
timeline: &Timeline,
417-
) -> Vec<(LoadState, AbsoluteTimeRange)> {
418-
re_tracing::profile_function!();
419-
420-
let mut time_ranges_all_chunks = Vec::new();
434+
) -> impl Iterator<Item = AbsoluteTimeRange> {
435+
let mut scratch = Vec::new();
436+
let mut ranges = Vec::new();
421437

438+
// First we merge ranges for individual components, since chunks' time ranges
439+
// often have gaps which we don't want to display other components' chunks
440+
// loaded state in.
422441
for timelines in self.native_temporal_map.values() {
423-
let Some(entity_component_chunks) = timelines.get(timeline) else {
442+
let Some(data) = timelines.get(timeline) else {
424443
continue;
425444
};
426445

427-
for chunks in entity_component_chunks.values() {
428-
for (chunk_id, entry) in chunks {
429-
let RrdManifestTemporalMapEntry { time_range, .. } = entry;
446+
for chunks in data.values() {
447+
scratch.extend(chunks.iter().filter_map(|(c, range)| {
448+
let state = self.remote_chunk_info(c)?.state;
430449

431-
let Some(info) = self.remote_chunks.get(chunk_id) else {
432-
continue;
433-
};
434-
debug_assert!(
435-
time_range.min <= time_range.max,
436-
"Unexpected negative time range in RRD manifest"
437-
);
438-
time_ranges_all_chunks.push((info.state, *time_range));
439-
}
450+
Some(time_range_merger::TimeRange {
451+
range: range.time_range,
452+
loaded: state.is_loaded(),
453+
})
454+
}));
455+
456+
ranges.extend(time_range_merger::merge_ranges(&scratch));
457+
458+
scratch.clear();
440459
}
441460
}
442461

443-
time_ranges_all_chunks
462+
time_range_merger::merge_ranges(&ranges)
463+
.into_iter()
464+
.filter(|r| r.loaded)
465+
.map(|r| r.range)
466+
}
467+
468+
/// If `component` is some, this returns all unloaded temporal entries for that specific
469+
/// component on the given timeline.
470+
///
471+
/// If not, this returns all unloaded temporal entries for `entity`'s components and its
472+
/// descendants' unloaded temporal entries.
473+
pub fn unloaded_temporal_entries_for(
474+
&self,
475+
timeline: &re_chunk::Timeline,
476+
entity: &re_chunk::EntityPath,
477+
component: Option<re_chunk::ComponentIdentifier>,
478+
) -> Vec<RrdManifestTemporalMapEntry> {
479+
re_tracing::profile_function!();
480+
481+
if let Some(component) = component {
482+
let Some(entity_ranges_per_timeline) = self.native_temporal_map.get(entity) else {
483+
return Vec::new();
484+
};
485+
486+
let Some(entity_ranges) = entity_ranges_per_timeline.get(timeline) else {
487+
return Vec::new();
488+
};
489+
490+
let Some(component_ranges) = entity_ranges.get(&component) else {
491+
return Vec::new();
492+
};
493+
494+
component_ranges
495+
.iter()
496+
.filter(|(chunk, _)| self.is_chunk_unloaded(chunk))
497+
.map(|(_, entry)| *entry)
498+
.collect()
499+
} else {
500+
// If we don't have a specific component we want to include the entity's children
501+
let mut res = Vec::new();
502+
503+
if let Some(tree) = self.entity_tree.subtree(entity) {
504+
tree.visit_children_recursively(|child| {
505+
self.unloaded_temporal_entries_for_entity(&mut res, timeline, child);
506+
});
507+
} else {
508+
#[cfg(debug_assertions)]
509+
re_log::warn_once!(
510+
"[DEBUG] Missing entity tree for {entity} while fetching temporal entities"
511+
);
512+
513+
self.unloaded_temporal_entries_for_entity(&mut res, timeline, entity);
514+
}
515+
516+
res
517+
}
518+
}
519+
520+
/// Fills `ranges` with unloaded temporal entries for this exact entity (descendants aren't included).
521+
fn unloaded_temporal_entries_for_entity(
522+
&self,
523+
ranges: &mut Vec<RrdManifestTemporalMapEntry>,
524+
timeline: &re_chunk::Timeline,
525+
entity: &re_chunk::EntityPath,
526+
) {
527+
re_tracing::profile_function!();
528+
529+
if let Some(entity_ranges_per_timeline) = self.native_temporal_map.get(entity)
530+
&& let Some(entity_ranges) = entity_ranges_per_timeline.get(timeline)
531+
{
532+
for (_, entry) in entity_ranges
533+
.values()
534+
.flatten()
535+
.filter(|(chunk, _)| self.is_chunk_unloaded(chunk))
536+
{
537+
ranges.push(*entry);
538+
}
539+
}
540+
}
541+
542+
fn is_chunk_unloaded(&self, chunk_id: &ChunkId) -> bool {
543+
self.remote_chunks
544+
.get(chunk_id)
545+
.is_none_or(|c| c.state.is_unloaded())
444546
}
445547

446548
pub fn full_uncompressed_size(&self) -> Option<u64> {

0 commit comments

Comments
 (0)