diff --git a/crates/re_entity_db/src/time_histogram_per_timeline.rs b/crates/re_entity_db/src/time_histogram_per_timeline.rs index d7c38d07e0b2..c2452e2bc406 100644 --- a/crates/re_entity_db/src/time_histogram_per_timeline.rs +++ b/crates/re_entity_db/src/time_histogram_per_timeline.rs @@ -100,12 +100,9 @@ impl TimeHistogramPerTimeline { }); } else { for (timeline, time_value) in timepoint.iter() { - let remaining_count = self - .times - .entry(*timeline) - .or_default() - .decrement(time_value.as_i64(), n); - if remaining_count == 0 { + let hist = self.times.entry(*timeline).or_default(); + hist.decrement(time_value.as_i64(), n); + if hist.is_empty() { self.times.remove(timeline); } } diff --git a/crates/re_entity_db/tests/clear.rs b/crates/re_entity_db/tests/clear.rs index 1ea4e5c69dbb..0cc5827c03ff 100644 --- a/crates/re_entity_db/tests/clear.rs +++ b/crates/re_entity_db/tests/clear.rs @@ -1,7 +1,7 @@ // https://github.com/rust-lang/rust-clippy/issues/10011 #![cfg(test)] -use re_data_store::{DataStore, LatestAtQuery}; +use re_data_store::{DataStore, DataStoreStats, LatestAtQuery}; use re_entity_db::EntityDb; use re_log_types::{ example_components::{MyColor, MyIndex, MyPoint}, @@ -367,3 +367,59 @@ fn clears() -> anyhow::Result<()> { Ok(()) } + +/// Test for GC behavior following clear. This functionality is expected by blueprints. +#[test] +fn clear_and_gc() -> anyhow::Result<()> { + re_log::setup_logging(); + + let mut db = EntityDb::new(StoreId::random(re_log_types::StoreKind::Recording)); + + let timepoint = TimePoint::default().with(Timeline::new_sequence("blueprint"), 0); + let entity_path: EntityPath = "space_view".into(); + + // Insert a component, then clear it, then GC. + { + // EntityTree is Empty when we start + assert_eq!(db.tree().num_children_and_fields(), 0); + + let point = MyPoint::new(1.0, 2.0); + + let row = DataRow::from_component_batches( + RowId::new(), + timepoint.clone(), + entity_path.clone(), + [&[point] as _], + )?; + + db.add_data_row(row)?; + + db.gc_everything_but_the_latest_row(); + + let stats = DataStoreStats::from_store(db.store()); + assert_eq!(stats.temporal.num_rows, 1); + + let clear = DataRow::from_component_batches( + RowId::new(), + timepoint.clone(), + entity_path.clone(), + Clear::recursive() + .as_component_batches() + .iter() + .map(|b| b.as_ref()), + )?; + + db.add_data_row(clear)?; + + db.gc_everything_but_the_latest_row(); + + // No rows should remain because the table should have been purged + let stats = DataStoreStats::from_store(db.store()); + assert_eq!(stats.temporal.num_rows, 0); + + // EntityTree should be empty again when we end since everything was GC'd + assert_eq!(db.tree().num_children_and_fields(), 0); + } + + Ok(()) +} diff --git a/crates/re_entity_db/tests/time_histograms.rs b/crates/re_entity_db/tests/time_histograms.rs index 0aacceca630d..b1282f33ba68 100644 --- a/crates/re_entity_db/tests/time_histograms.rs +++ b/crates/re_entity_db/tests/time_histograms.rs @@ -668,6 +668,10 @@ fn assert_recursive_histogram<'a>( let histo = db.time_histogram(timeline); if let Some(expected) = expected_times { + if expected.is_empty() { + assert!(histo.is_none()); + continue; + } let histo = histo.unwrap(); let ranges = histo.range(i64::MIN.., 0).collect::>(); let expected: Vec<_> = expected.to_vec(); @@ -692,9 +696,13 @@ fn assert_histogram_for_component<'a>( .and_then(|tree| tree.time_histogram_for_component(timeline, component_name)); if let Some(expected) = expected_times { + let expected: Vec<_> = expected.to_vec(); + if expected.is_empty() { + assert!(histo.is_none()); + continue; + } let histo = histo.unwrap(); let ranges = histo.range(i64::MIN.., 0).collect::>(); - let expected: Vec<_> = expected.to_vec(); similar_asserts::assert_eq!(expected, ranges); } else { assert!(histo.is_none());