Skip to main content

re_chunk/
range.rs

1use re_log_types::{AbsoluteTimeRange, TimeInt, TimelineName};
2use re_types_core::ComponentIdentifier;
3
4use crate::Chunk;
5
6// --- Range ---
7
8/// A query over a time range, for a given timeline.
9///
10/// Get all the data within this time interval, plus the latest one before the start of the
11/// interval.
12///
13/// Motivation: all data is considered alive until the next logging to the same component path.
14#[derive(Clone, PartialEq, Eq, Hash)]
15pub struct RangeQuery {
16    pub timeline: TimelineName,
17    pub range: AbsoluteTimeRange,
18    pub options: RangeQueryOptions,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq, Hash)]
22pub struct RangeQueryOptions {
23    /// Should the results contain all extra timeline information available in the [`Chunk`]?
24    ///
25    /// While this information can be useful in some cases, it comes at a performance cost.
26    pub keep_extra_timelines: bool,
27
28    /// Should the results contain all extra component information available in the [`Chunk`]?
29    ///
30    /// While this information can be useful in some cases, it comes at a performance cost.
31    pub keep_extra_components: bool,
32
33    /// If true, the results will include one extra tick on each side of the range.
34    ///
35    /// Note: this is different from simply subtracting/adding one to the time range of the query,
36    /// as this will work even with non-contiguous time values, and even if these non-contiguous
37    /// jumps happen across multiple chunks.
38    ///
39    /// Consider for example this data:
40    /// ```text
41    /// ┌──────────────────────────────────┬───────────────┬──────────────────────┐
42    /// │ RowId                            ┆ frame_nr      ┆ Scalar               │
43    /// ╞══════════════════════════════════╪═══════════════╪══════════════════════╡
44    /// │ 17E9C11C655B21A9006568024DA10857 ┆ 0             ┆ [2]                  │
45    /// ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
46    /// │ 17E9C11C6560E8A6006568024DA10859 ┆ 2             ┆ [2.04]               │
47    /// ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
48    /// │ 17E9C11C656504F0006568024DA1085B ┆ 4             ┆ [2.08]               │
49    /// ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
50    /// │ 17E9C11C65693204006568024DA1085D ┆ 6             ┆ [2.12]               │
51    /// └──────────────────────────────────┴───────────────┴──────────────────────┘
52    /// ```
53    ///
54    /// * A `RangeQuery(#2, #4)` would yield frames #2 and #4.
55    /// * A `RangeQuery(#1, #5)` would still only yield frames #2 and #4.
56    /// * A `RangeQuery(#2, #4, include_extended_bounds=true)`, on the other hand, would yield all of
57    ///   frames #0, #2, #4 and #6.
58    pub include_extended_bounds: bool,
59}
60
61impl RangeQueryOptions {
62    pub const DEFAULT: Self = Self {
63        keep_extra_timelines: false,
64        keep_extra_components: false,
65        include_extended_bounds: false,
66    };
67}
68
69impl Default for RangeQueryOptions {
70    #[inline]
71    fn default() -> Self {
72        Self::DEFAULT
73    }
74}
75
76impl std::fmt::Debug for RangeQuery {
77    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78        let Self {
79            timeline,
80            range,
81            options,
82        } = self;
83
84        let RangeQueryOptions {
85            keep_extra_timelines,
86            keep_extra_components,
87            include_extended_bounds,
88        } = options;
89
90        f.write_fmt(format_args!(
91            "<ranging {:?}..={:?} on {:?}{}{}{}",
92            range.min(),
93            range.max(),
94            timeline,
95            if *keep_extra_timelines {
96                " keep_extra_timelines"
97            } else {
98                ""
99            },
100            if *keep_extra_components {
101                " keep_extra_components"
102            } else {
103                ""
104            },
105            if *include_extended_bounds {
106                " include_extended_bounds"
107            } else {
108                ""
109            },
110        ))
111    }
112}
113
114impl RangeQuery {
115    /// The returned query is guaranteed to never include [`TimeInt::STATIC`].
116    #[inline]
117    pub const fn new(timeline: TimelineName, range: AbsoluteTimeRange) -> Self {
118        Self {
119            timeline,
120            range,
121            options: RangeQueryOptions::DEFAULT,
122        }
123    }
124
125    /// The returned query is guaranteed to never include [`TimeInt::STATIC`].
126    ///
127    /// Keeps all extra timelines and components around.
128    #[inline]
129    pub const fn with_extras(timeline: TimelineName, range: AbsoluteTimeRange) -> Self {
130        Self {
131            timeline,
132            range,
133            options: RangeQueryOptions {
134                keep_extra_timelines: true,
135                keep_extra_components: true,
136                include_extended_bounds: false,
137            },
138        }
139    }
140
141    #[inline]
142    pub const fn everything(timeline: TimelineName) -> Self {
143        Self {
144            timeline,
145            range: AbsoluteTimeRange::EVERYTHING,
146            options: RangeQueryOptions::DEFAULT,
147        }
148    }
149
150    /// See [`RangeQueryOptions::keep_extra_timelines`] for more information.
151    #[inline]
152    pub fn keep_extra_timelines(mut self, toggle: bool) -> Self {
153        self.options.keep_extra_timelines = toggle;
154        self
155    }
156
157    /// See [`RangeQueryOptions::keep_extra_components`] for more information.
158    #[inline]
159    pub fn keep_extra_components(mut self, toggle: bool) -> Self {
160        self.options.keep_extra_components = toggle;
161        self
162    }
163
164    /// See [`RangeQueryOptions::include_extended_bounds`] for more information.
165    #[inline]
166    pub fn include_extended_bounds(mut self, toggle: bool) -> Self {
167        self.options.include_extended_bounds = toggle;
168        self
169    }
170
171    #[inline]
172    pub fn timeline(&self) -> &TimelineName {
173        &self.timeline
174    }
175
176    #[inline]
177    pub fn range(&self) -> AbsoluteTimeRange {
178        self.range
179    }
180
181    #[inline]
182    pub fn options(&self) -> RangeQueryOptions {
183        self.options.clone()
184    }
185}
186
187// ---
188
189impl Chunk {
190    /// Runs a [`RangeQuery`] filter on a [`Chunk`].
191    ///
192    /// This behaves as a row-based filter: the result is a new [`Chunk`] that is vertically
193    /// sliced, sorted and filtered in order to only contain the row(s) relevant for the
194    /// specified `query`.
195    ///
196    /// The resulting [`Chunk`] is guaranteed to contain all the same columns has the queried
197    /// chunk: there is no horizontal slicing going on.
198    ///
199    /// An empty [`Chunk`] (i.e. 0 rows, but N columns) is returned if the `query` yields nothing.
200    ///
201    /// Because the resulting chunk doesn't discard any column information, you can find extra relevant
202    /// information by inspecting the data, for examples timestamps on other timelines.
203    /// See [`Self::timeline_sliced`] and [`Self::component_sliced`] if you do want to filter this
204    /// extra data.
205    //
206    // TODO(RR-3865): Use arrow's `ListView` to avoid cloning data when the chunk requires sorting.
207    pub fn range(&self, query: &RangeQuery, component: ComponentIdentifier) -> Self {
208        if self.is_empty() {
209            return self.clone();
210        }
211
212        re_tracing::profile_function!(format!("{query:?}"));
213
214        let RangeQueryOptions {
215            keep_extra_timelines,
216            keep_extra_components,
217            include_extended_bounds,
218        } = query.options();
219
220        // Pre-slice the data if the caller allowed us: this will make further slicing
221        // (e.g. the range query itself) much cheaper to compute.
222        use std::borrow::Cow;
223        let chunk = if !keep_extra_timelines {
224            Cow::Owned(self.timeline_sliced(*query.timeline()))
225        } else {
226            Cow::Borrowed(self)
227        };
228        let chunk = if !keep_extra_components {
229            Cow::Owned(chunk.component_sliced(component))
230        } else {
231            chunk
232        };
233
234        if chunk.is_static() {
235            // NOTE: A given component for a given entity can only have one static entry associated
236            // with it, and this entry overrides everything else, which means it is functionally
237            // equivalent to just running a latest-at query.
238            chunk.latest_at(
239                &crate::LatestAtQuery::new(*query.timeline(), TimeInt::MAX),
240                component,
241            )
242        } else {
243            let Some(is_sorted_by_time) = chunk
244                .timelines
245                .get(query.timeline())
246                .map(|time_column| time_column.is_sorted())
247            else {
248                return chunk.emptied();
249            };
250
251            let (chunk, _densified) = chunk.densified(component);
252
253            let chunk = if is_sorted_by_time {
254                // Temporal, row-sorted, time-sorted chunk
255                chunk
256            } else {
257                // Temporal, unsorted chunk
258                chunk.sorted_by_timeline_if_unsorted(query.timeline())
259            };
260
261            let Some(times) = chunk
262                .timelines
263                .get(query.timeline())
264                .map(|time_column| time_column.times_raw())
265            else {
266                return chunk.emptied();
267            };
268
269            let mut start_index =
270                times.partition_point(|&time| time < query.range().min().as_i64());
271            let mut end_index = times.partition_point(|&time| time <= query.range().max().as_i64());
272
273            // See `RangeQueryOptions::include_extended_bounds` for more information.
274            if include_extended_bounds {
275                start_index = start_index.saturating_sub(1);
276                end_index = usize::min(self.num_rows(), end_index.saturating_add(1));
277            }
278
279            chunk.row_sliced_shallow(start_index, end_index.saturating_sub(start_index))
280        }
281    }
282}