use re_entity_db::TimeHistogram;
use re_log_types::{ResolvedTimeRange, TimeInt, TimeType};
#[derive(Clone, Debug)]
pub(crate) struct TimelineAxis {
pub ranges: vec1::Vec1<ResolvedTimeRange>,
}
impl TimelineAxis {
#[inline]
pub fn new(time_type: TimeType, times: &TimeHistogram) -> Self {
re_tracing::profile_function!();
assert!(!times.is_empty());
let gap_threshold = gap_size_heuristic(time_type, times);
Self {
ranges: create_ranges(times, gap_threshold),
}
}
#[inline]
pub fn sum_time_lengths(&self) -> u64 {
self.ranges.iter().map(|t| t.abs_length()).sum()
}
#[inline]
pub fn min(&self) -> TimeInt {
self.ranges.first().min()
}
}
fn gap_size_heuristic(time_type: TimeType, times: &TimeHistogram) -> u64 {
re_tracing::profile_function!();
assert!(!times.is_empty());
if times.total_count() <= 2 {
return u64::MAX;
}
let total_time_span = times.min_key().unwrap().abs_diff(times.max_key().unwrap());
if total_time_span == 0 {
return u64::MAX;
}
let max_collapses = ((times.total_count() - 1) / 3).min(20) as usize;
let min_gap_size: u64 = match time_type {
TimeType::Sequence => 9,
TimeType::Time => TimeInt::from_milliseconds(100.try_into().unwrap()).as_i64() as _,
};
let mut gap_sizes =
collect_candidate_gaps(times, min_gap_size, max_collapses).unwrap_or_default();
gap_sizes.sort_unstable();
let min_collapse_fraction = (2.0 / (times.total_count() - 1) as f64).max(0.35);
let mut gap_threshold = u64::MAX;
let mut uncollapsed_time = total_time_span;
for &gap in gap_sizes.iter().rev().take(max_collapses) {
let gap_fraction = gap as f64 / uncollapsed_time as f64;
if gap_fraction > min_collapse_fraction {
gap_threshold = gap;
uncollapsed_time -= gap;
} else {
break; }
}
gap_threshold
}
fn collect_candidate_gaps(
times: &TimeHistogram,
min_gap_size: u64,
max_collapses: usize,
) -> Option<Vec<u64>> {
re_tracing::profile_function!();
let max_gap_size = times.max_key().unwrap() - times.min_key().unwrap();
let mut granularity = max_gap_size as u64;
let mut gaps = collect_gaps_with_granularity(times, granularity, min_gap_size)?;
while gaps.len() < max_collapses && min_gap_size < granularity {
granularity /= 2;
gaps = collect_gaps_with_granularity(times, granularity, min_gap_size)?;
}
Some(gaps)
}
fn collect_gaps_with_granularity(
times: &TimeHistogram,
granularity: u64,
min_gap_size: u64,
) -> Option<Vec<u64>> {
re_tracing::profile_function!();
let mut non_gap_time_span = 0;
let mut gaps = vec![];
let mut last_range: Option<re_int_histogram::RangeI64> = None;
for (range, _count) in times.range(.., granularity) {
non_gap_time_span += range.length();
if let Some(last_range) = last_range {
let gap_size = last_range.max.abs_diff(range.min);
if min_gap_size < gap_size {
gaps.push(gap_size);
}
}
last_range = Some(range);
}
if min_gap_size * 100 < non_gap_time_span {
return None;
}
Some(gaps)
}
fn create_ranges(times: &TimeHistogram, gap_threshold: u64) -> vec1::Vec1<ResolvedTimeRange> {
re_tracing::profile_function!();
let mut it = times.range(.., gap_threshold);
let first_range = it.next().unwrap().0;
let mut ranges = vec1::vec1![ResolvedTimeRange::new(first_range.min, first_range.max,)];
for (new_range, _count) in it {
if ranges.last_mut().max().as_i64().abs_diff(new_range.min) < gap_threshold {
ranges.last_mut().set_max(new_range.max);
} else {
ranges.push(ResolvedTimeRange::new(new_range.min, new_range.max));
}
}
ranges
}
#[cfg(test)]
mod tests {
use super::*;
use re_chunk_store::ResolvedTimeRange;
fn ranges(times: &[i64]) -> vec1::Vec1<ResolvedTimeRange> {
let mut time_histogram = TimeHistogram::default();
for &time in times {
time_histogram.increment(time, 1);
}
TimelineAxis::new(TimeType::Sequence, &time_histogram).ranges
}
#[test]
fn test_time_axis() {
assert_eq!(1, ranges(&[1]).len());
assert_eq!(1, ranges(&[1, 2, 3, 4]).len());
assert_eq!(1, ranges(&[10, 20, 30, 40]).len());
assert_eq!(1, ranges(&[1, 2, 3, 11, 12, 13]).len(), "Too small gap");
assert_eq!(2, ranges(&[10, 20, 30, 110, 120, 130]).len());
assert_eq!(1, ranges(&[10, 1000]).len(), "not enough numbers");
assert_eq!(
2,
ranges(&[
i64::MIN / 2,
1_000_000_000,
2_000_000_000,
3_000_000_000,
4_000_000_000,
5_000_000_000,
6_000_000_000,
])
.len()
);
assert_eq!(
3,
ranges(&[
i64::MIN / 2,
1_000_000_000,
2_000_000_000,
3_000_000_000,
4_000_000_000,
5_000_000_000,
100_000_000_000,
])
.len()
);
}
}