Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Time panel chunkification #6934

Merged
merged 48 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
8404179
remove old data density graph ui
jprochazk Jul 18, 2024
0534aa0
wip
jprochazk Jul 18, 2024
bb4e116
Merge branch 'main' into jan/time-panel-chunkification
jprochazk Jul 19, 2024
e3a252b
Revert "wip"
jprochazk Jul 19, 2024
e823464
add new APIs
jprochazk Jul 19, 2024
4bd129c
update data UI to not use `EntityInfo`
jprochazk Jul 19, 2024
81f33eb
update blueprint to not use `EntityInfo`
jprochazk Jul 19, 2024
dae84a8
use entity subtree size in `entity_tree_stats_ui`
jprochazk Jul 19, 2024
fda44be
update time UI to not use `SubtreeInfo`
jprochazk Jul 19, 2024
71bad34
remove time histogram usage from component data ui
jprochazk Jul 19, 2024
339c66f
remove `EntityInfo` usage from selection panel
jprochazk Jul 19, 2024
bcc92d4
remove `EntityInfo` usage from 2d/3d space views
jprochazk Jul 19, 2024
28a130d
actually chunkify time panel
jprochazk Jul 19, 2024
a186a00
rm dead code
jprochazk Jul 19, 2024
9293509
remove time histogram usage from EntityLatestAtResults data ui
jprochazk Jul 19, 2024
8e8560e
remove `EntityInfo` from entity tree visitors
jprochazk Jul 19, 2024
a19cd6b
remove some dead code
jprochazk Jul 19, 2024
c27b0b2
add query method
jprochazk Jul 19, 2024
5753834
remove `EntityTree.entity`
jprochazk Jul 19, 2024
b5a140e
remove `SubtreeInfo`
jprochazk Jul 19, 2024
6322bba
Merge branch 'main' into jan/time-panel-chunkification
jprochazk Jul 22, 2024
d4b65f1
docs
jprochazk Jul 22, 2024
10dc774
more docs
jprochazk Jul 22, 2024
ee7509a
docs docs
jprochazk Jul 22, 2024
473dc59
simplify + docs
jprochazk Jul 22, 2024
1bfbfeb
Merge branch 'main' into jan/time-panel-chunkification
jprochazk Jul 22, 2024
b153073
btreemap is sorted by keys
jprochazk Jul 22, 2024
292989f
Merge branch 'main' into jan/time-panel-chunkification
jprochazk Jul 22, 2024
b96a5e0
rename `all_components*` queries
jprochazk Jul 22, 2024
2aebfd0
more renamings
jprochazk Jul 22, 2024
e702215
invert
jprochazk Jul 22, 2024
7e6b5cc
refactor component/data query APIs
jprochazk Jul 22, 2024
e7e57ea
use `u64` everywhere
jprochazk Jul 22, 2024
0d88df1
more static/temporal semantics mess
jprochazk Jul 22, 2024
1bf8ade
refactor into combinators
jprochazk Jul 22, 2024
5ffb090
clarify temporal/static in `num_events` APIs
jprochazk Jul 22, 2024
0291f80
rationale
jprochazk Jul 22, 2024
e3083b6
specify units
jprochazk Jul 22, 2024
4082b9c
clarify `size_of_entity_on_timeline`
jprochazk Jul 22, 2024
5d21b1c
typo
jprochazk Jul 22, 2024
5ef30e9
use `u64` for entity size
jprochazk Jul 22, 2024
b2001e5
add back unused param
jprochazk Jul 22, 2024
eb98e81
Update crates/store/re_chunk_store/src/query.rs
jprochazk Jul 23, 2024
9eea627
Update crates/store/re_chunk_store/src/query.rs
jprochazk Jul 23, 2024
12dc339
Update crates/store/re_chunk_store/src/query.rs
jprochazk Jul 23, 2024
c95c182
Update crates/store/re_chunk_store/src/query.rs
jprochazk Jul 23, 2024
82127ba
docs
jprochazk Jul 23, 2024
63c806b
Merge branch 'main' into jan/time-panel-chunkification
jprochazk Jul 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
307 changes: 307 additions & 0 deletions crates/store/re_chunk_store/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ use std::{

use itertools::Itertools;
use re_chunk::{Chunk, LatestAtQuery, RangeQuery};
use re_log_types::ResolvedTimeRange;
use re_log_types::{EntityPath, TimeInt, Timeline};
use re_types_core::SizeBytes as _;
use re_types_core::{ComponentName, ComponentNameSet};

use crate::{store::ChunkIdSetPerTime, ChunkStore};
Expand Down Expand Up @@ -61,6 +63,47 @@ impl ChunkStore {
}
}

/// Retrieve all the [`ComponentName`]s that have been written to for a given [`EntityPath`].
///
/// Static components are always included in the results.
///
/// Returns `None` if the entity has never had any data logged to it.
pub fn all_components_on_all_timelines(
&self,
entity_path: &EntityPath,
) -> Option<ComponentNameSet> {
re_tracing::profile_function!();

self.query_id.fetch_add(1, Ordering::Relaxed);

let static_components: Option<ComponentNameSet> = self
.static_chunk_ids_per_entity
.get(entity_path)
.map(|static_chunks_per_component| {
static_chunks_per_component.keys().copied().collect()
});

let temporal_components: Option<ComponentNameSet> = self
.temporal_chunk_ids_per_entity_per_component
.get(entity_path)
.map(|temporal_chunk_ids_per_timeline| {
temporal_chunk_ids_per_timeline
.iter()
.flat_map(|(_, temporal_chunk_ids_per_component)| {
temporal_chunk_ids_per_component.keys().copied()
})
.collect()
});

match (static_components, temporal_components) {
(None, None) => None,
(None, comps @ Some(_)) | (comps @ Some(_), None) => comps,
(Some(static_comps), Some(temporal_comps)) => {
Some(static_comps.into_iter().chain(temporal_comps).collect())
}
}
}

/// Check whether a given entity has a specific [`ComponentName`] either on the specified
/// timeline, or in its static data.
#[inline]
Expand All @@ -75,6 +118,56 @@ impl ChunkStore {
.map_or(false, |components| components.contains(component_name))
}

pub fn entity_has_component_on_any_timeline(
&self,
entity_path: &EntityPath,
component_name: &ComponentName,
) -> bool {
re_tracing::profile_function!();

if self
.static_chunk_ids_per_entity
.get(entity_path)
.and_then(|static_chunks_per_component| static_chunks_per_component.get(component_name))
.and_then(|id| self.chunks_per_chunk_id.get(id))
.is_some()
{
return true;
}

for temporal_chunk_ids_per_component in self
.temporal_chunk_ids_per_entity_per_component
.get(entity_path)
.iter()
.flat_map(|temporal_chunk_ids_per_timeline| temporal_chunk_ids_per_timeline.values())
{
if temporal_chunk_ids_per_component
.get(component_name)
.is_some()
{
return true;
}
}

false
}

/// Returns true if the given entity has a specific component with static data.
#[inline]
pub fn entity_has_static_component(
&self,
entity_path: &EntityPath,
component_name: &ComponentName,
) -> bool {
re_tracing::profile_function!();

let Some(static_components) = self.static_chunk_ids_per_entity.get(entity_path) else {
return false;
};

static_components.contains_key(component_name)
}

/// Find the earliest time at which something was logged for a given entity on the specified
/// timeline.
///
Expand Down Expand Up @@ -413,3 +506,217 @@ impl ChunkStore {
.collect()
}
}

// Queries returning `usize` and `bool`
impl ChunkStore {
/// Returns the number of events logged for a given component on the given entity path across all timelines.
pub fn num_events_on_timeline_for_component(
&self,
timeline: &Timeline,
entity_path: &EntityPath,
component_name: ComponentName,
) -> usize {
re_tracing::profile_function!();

self.query_id.fetch_add(1, Ordering::Relaxed);

self.num_events_for_static_component(entity_path, component_name)
+ self.num_events_on_timeline_for_temporal_component(
timeline,
entity_path,
component_name,
)
}

pub fn time_range_for_entity(
&self,
timeline: &Timeline,
entity_path: &EntityPath,
) -> Option<ResolvedTimeRange> {
re_tracing::profile_function!();

self.query_id.fetch_add(1, Ordering::Relaxed);

let temporal_chunk_ids_per_timeline =
self.temporal_chunk_ids_per_entity.get(entity_path)?;
let chunk_id_sets = temporal_chunk_ids_per_timeline.get(timeline)?;

let start = chunk_id_sets.per_start_time.keys().min()?;
let end = chunk_id_sets.per_end_time.keys().max()?;

Some(ResolvedTimeRange::new(*start, *end))
}

pub fn num_events_on_timeline_for_all_components(
&self,
timeline: &Timeline,
entity_path: &EntityPath,
) -> usize {
re_tracing::profile_function!();

self.query_id.fetch_add(1, Ordering::Relaxed);

let mut total_events = 0;

if let Some(static_chunks_per_component) = self.static_chunk_ids_per_entity.get(entity_path)
{
for chunk in static_chunks_per_component
.values()
.filter_map(|id| self.chunks_per_chunk_id.get(id))
{
total_events += chunk.num_events_cumulative();
}
}

if let Some(chunk_ids) = self
.temporal_chunk_ids_per_entity
.get(entity_path)
.and_then(|temporal_chunks_events_per_timeline| {
temporal_chunks_events_per_timeline.get(timeline)
})
{
for chunk in chunk_ids
.per_start_time
.values()
.flat_map(|ids| ids.iter().filter_map(|id| self.chunks_per_chunk_id.get(id)))
{
total_events += chunk.num_events_cumulative();
}
}

total_events
}

pub fn entity_has_data_on_timeline(
&self,
timeline: &Timeline,
entity_path: &EntityPath,
) -> bool {
re_tracing::profile_function!();

self.query_id.fetch_add(1, Ordering::Relaxed);

if self.static_chunk_ids_per_entity.get(entity_path).is_some() {
// Static data exists on all timelines
return true;
}

if let Some(temporal_chunk_ids_per_timeline) =
self.temporal_chunk_ids_per_entity.get(entity_path)
{
if temporal_chunk_ids_per_timeline.contains_key(timeline) {
return true;
}
}

false
}

pub fn num_events_for_static_component(
&self,
entity_path: &EntityPath,
component_name: ComponentName,
) -> usize {
re_tracing::profile_function!();

self.query_id.fetch_add(1, Ordering::Relaxed);

if let Some(static_chunk) = self
.static_chunk_ids_per_entity
.get(entity_path)
.and_then(|static_chunks_per_component| {
static_chunks_per_component.get(&component_name)
})
.and_then(|chunk_id| self.chunks_per_chunk_id.get(chunk_id))
{
// Static data overrides temporal data
static_chunk
.num_events_for_component(component_name)
.unwrap_or(0)
} else {
0
}
}

/// Returns the number of events logged for a given component on the given entity path across all timelines.
///
/// This ignores static data.
pub fn num_events_on_timeline_for_temporal_component(
&self,
timeline: &Timeline,
entity_path: &EntityPath,
component_name: ComponentName,
) -> usize {
re_tracing::profile_function!();

self.query_id.fetch_add(1, Ordering::Relaxed);

let Some(temporal_chunk_ids_per_timeline) = self
.temporal_chunk_ids_per_entity_per_component
.get(entity_path)
else {
return 0; // no events logged for the entity path
};

let Some(temporal_chunk_ids_per_component) = temporal_chunk_ids_per_timeline.get(timeline)
else {
return 0; // no events logged on this timeline
};

let Some(chunk_ids) = temporal_chunk_ids_per_component.get(&component_name) else {
return 0; // no events logged for the component on this timeline
};

let mut num_events = 0;
for chunk in chunk_ids
.per_start_time
.values()
.flat_map(|ids| ids.iter().filter_map(|id| self.chunks_per_chunk_id.get(id)))
{
num_events += chunk.num_events_for_component(component_name).unwrap_or(0);
}

num_events
}

pub fn size_of_entity_on_timeline(
&self,
timeline: &Timeline,
entity_path: &EntityPath,
) -> usize {
re_tracing::profile_function!();

self.query_id.fetch_add(1, Ordering::Relaxed);

let mut total_size = 0;

if let Some(static_chunks_per_component) = self.static_chunk_ids_per_entity.get(entity_path)
{
for chunk in static_chunks_per_component
.values()
.filter_map(|id| self.chunks_per_chunk_id.get(id))
{
total_size += Chunk::total_size_bytes(chunk) as usize;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see you've learned from my mistakes :D

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is really surprising that Arc implements heap_size_bytes, but returns 0 for it... 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. Unfortunately the two other alternatives I've tried (don't implement SizeBytes for Arc, only use static methods for SizeBytes) turned out to be worse for various reasons.

}
}

if let Some(chunk_id_sets) = self
.temporal_chunk_ids_per_entity
.get(entity_path)
.and_then(|temporal_chunk_ids_per_timeline| {
temporal_chunk_ids_per_timeline.get(timeline)
})
{
for chunk in chunk_id_sets
.per_start_time
.values()
.flat_map(|v| v.iter())
.filter_map(|id| self.chunks_per_chunk_id.get(id))
{
total_size += Chunk::total_size_bytes(chunk) as usize;
}
}

total_size
}
}
Loading
Loading