re_chunk_store/
drop_time_range.rs

1use re_chunk::{ChunkId, TimelineName};
2use re_log_types::AbsoluteTimeRange;
3
4use crate::{ChunkStore, ChunkStoreEvent};
5
6impl ChunkStore {
7    /// Drop all events that are in the given range on the given timeline.
8    ///
9    /// Note that matching events will be dropped from all timelines they appear on.
10    ///
11    /// Static chunks are unaffected.
12    ///
13    /// Used to implement undo (erase the last event from the blueprint db).
14    pub fn drop_time_range(
15        &mut self,
16        timeline: &TimelineName,
17        drop_range: AbsoluteTimeRange,
18    ) -> Vec<ChunkStoreEvent> {
19        re_tracing::profile_function!();
20
21        if drop_range.max() < drop_range.min() {
22            return Default::default();
23        }
24
25        // Prepare the changes:
26
27        let mut chunk_ids_to_drop = vec![];
28        let mut new_chunks = vec![];
29
30        for (chunk_id, chunk) in &self.chunks_per_chunk_id {
31            let Some(time_column) = chunk.timelines().get(timeline) else {
32                // static chunk, or chunk that doesn't overlap this timeline
33                continue; // keep it
34            };
35
36            let chunk_range = time_column.time_range();
37
38            if drop_range.contains_range(chunk_range) {
39                // The whole chunk should be dropped!
40                chunk_ids_to_drop.push(*chunk_id);
41            } else if drop_range.intersects(chunk_range) {
42                let chunk = chunk.sorted_by_timeline_if_unsorted(timeline);
43
44                let num_rows = chunk.num_rows();
45
46                // Get the sorted times:
47                #[expect(clippy::unwrap_used)] // We already know the chunk has the timeline
48                let time_column = chunk.timelines().get(timeline).unwrap();
49                let times = time_column.times_raw();
50
51                let drop_range_min = drop_range.min().as_i64();
52                let drop_range_max = drop_range.max().as_i64();
53
54                let min_idx = times.partition_point(|&time| time < drop_range_min);
55                let max_idx = times.partition_point(|&time| time <= drop_range_max);
56
57                {
58                    // Sanity check:
59                    debug_assert!(min_idx <= max_idx);
60                    debug_assert!(drop_range_min <= times[min_idx]);
61                    if 0 < min_idx {
62                        debug_assert!(times[min_idx - 1] < drop_range_min);
63                    }
64                    if max_idx < num_rows {
65                        debug_assert!(drop_range_max < times[max_idx]);
66                        if 0 < max_idx {
67                            debug_assert!(times[max_idx - 1] <= drop_range_max);
68                        }
69                    }
70                }
71
72                if min_idx < max_idx {
73                    chunk_ids_to_drop.push(*chunk_id);
74                    if 0 < min_idx {
75                        new_chunks.push(chunk.row_sliced(0, min_idx).with_id(ChunkId::new()));
76                    }
77                    if max_idx < num_rows {
78                        new_chunks.push(
79                            chunk
80                                .row_sliced(max_idx, num_rows - max_idx)
81                                .with_id(ChunkId::new()),
82                        );
83                    }
84                }
85            }
86        }
87
88        // ------------------
89        // Apply the changes:
90
91        let generation = self.generation();
92        let mut events: Vec<ChunkStoreEvent> = vec![];
93
94        for chunk_id in chunk_ids_to_drop {
95            for diff in self.remove_chunk(chunk_id) {
96                events.push(ChunkStoreEvent {
97                    store_id: self.id.clone(),
98                    store_generation: generation.clone(),
99                    event_id: self
100                        .event_id
101                        .fetch_add(1, std::sync::atomic::Ordering::Relaxed),
102                    diff,
103                });
104            }
105        }
106        for mut chunk in new_chunks {
107            chunk.sort_if_unsorted();
108            #[expect(clippy::unwrap_used)] // The chunk came from the store, so it should be fine
109            events.append(&mut self.insert_chunk(&chunk.into()).unwrap());
110        }
111
112        events
113    }
114}