Skip to main content

re_viewer_context/view/
visualizer_system.rs

1use std::collections::BTreeMap;
2
3use parking_lot::Mutex;
4use re_chunk_store::MissingChunkReporter;
5use vec1::Vec1;
6
7use re_chunk::{ArchetypeName, ComponentType};
8use re_sdk_types::blueprint::components::VisualizerInstructionId;
9use re_sdk_types::{Archetype, ComponentDescriptor, ComponentIdentifier, ComponentSet};
10
11use crate::{
12    IdentifiedViewSystem, ViewContext, ViewContextCollection, ViewQuery, ViewSystemExecutionError,
13    ViewSystemIdentifier,
14};
15
16#[derive(Debug, Clone, Default)]
17pub struct SortedComponentSet(linked_hash_map::LinkedHashMap<ComponentDescriptor, ()>);
18
19impl SortedComponentSet {
20    pub fn insert(&mut self, k: ComponentDescriptor) -> Option<()> {
21        self.0.insert(k, ())
22    }
23
24    pub fn extend(&mut self, iter: impl IntoIterator<Item = ComponentDescriptor>) {
25        self.0.extend(iter.into_iter().map(|k| (k, ())));
26    }
27
28    pub fn iter(&self) -> linked_hash_map::Keys<'_, ComponentDescriptor, ()> {
29        self.0.keys()
30    }
31
32    pub fn contains(&self, k: &ComponentDescriptor) -> bool {
33        self.0.contains_key(k)
34    }
35}
36
37impl FromIterator<ComponentDescriptor> for SortedComponentSet {
38    fn from_iter<I: IntoIterator<Item = ComponentDescriptor>>(iter: I) -> Self {
39        Self(iter.into_iter().map(|k| (k, ())).collect())
40    }
41}
42
43pub type DatatypeSet = std::collections::BTreeSet<arrow::datatypes::DataType>;
44
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct AnyPhysicalDatatypeRequirement {
47    /// The required component that this requirement is targeting.
48    pub target_component: ComponentIdentifier,
49
50    /// The semantic type the visualizer is working with.
51    ///
52    /// Matches with the semantic type are generally preferred.
53    pub semantic_type: ComponentType,
54
55    /// All supported physical Arrow data types.
56    ///
57    /// Has to contain the physical data type that is covered by the Rerun semantic type.
58    pub physical_types: DatatypeSet,
59
60    /// If false, ignores all static components.
61    ///
62    /// This is useful if you rely on ranges queries as done by the time series view.
63    pub allow_static_data: bool,
64}
65
66impl From<AnyPhysicalDatatypeRequirement> for RequiredComponents {
67    fn from(req: AnyPhysicalDatatypeRequirement) -> Self {
68        Self::AnyPhysicalDatatype(req)
69    }
70}
71
72/// Specifies how component requirements should be evaluated for visualizer entity matching.
73#[derive(Debug, Clone, PartialEq, Eq, Default)]
74pub enum RequiredComponents {
75    /// No component requirements - all entities are candidates.
76    #[default]
77    None,
78
79    /// Entity must have _all_ of these components.
80    AllComponents(ComponentSet),
81
82    /// Entity must have _any one_ of these components.
83    AnyComponent(ComponentSet),
84
85    /// Entity must have _any one_ of these physical Arrow data types.
86    ///
87    /// For instance, we may not put views into the "recommended" section or visualizer entities proactively unless they support the native type.
88    AnyPhysicalDatatype(AnyPhysicalDatatypeRequirement),
89}
90
91// TODO(grtlr): Eventually we will want to hide these fields to prevent visualizers doing too much shenanigans.
92pub struct VisualizerQueryInfo {
93    /// This is not required, but if it is found, it is a strong indication that this
94    /// system should be active (if also the `required_components` are found).
95    pub relevant_archetype: Option<ArchetypeName>,
96
97    /// Returns the minimal set of components that the system _requires_ in order to be instantiated.
98    pub required: RequiredComponents,
99
100    /// Returns the list of components that the system _queries_.
101    ///
102    /// Must include required components.
103    /// Order should reflect order in archetype docs & user code as well as possible.
104    ///
105    /// Note that we need full descriptors here in order to write overrides from the UI.
106    pub queried: SortedComponentSet, // TODO(grtlr, wumpf): This can probably be removed?
107}
108
109impl VisualizerQueryInfo {
110    pub fn from_archetype<A: Archetype>() -> Self {
111        Self {
112            relevant_archetype: A::name().into(),
113            required: RequiredComponents::AllComponents(
114                A::required_components()
115                    .iter()
116                    .map(|c| c.component)
117                    .collect(),
118            ),
119            queried: A::all_components().iter().cloned().collect(),
120        }
121    }
122
123    pub fn empty() -> Self {
124        Self {
125            relevant_archetype: Default::default(),
126            required: RequiredComponents::None,
127            queried: SortedComponentSet::default(),
128        }
129    }
130
131    /// Returns the component _identifiers_ for all queried components.
132    pub fn queried_components(&self) -> impl Iterator<Item = ComponentIdentifier> {
133        self.queried.iter().map(|desc| desc.component)
134    }
135}
136
137/// Severity level for visualizer diagnostics.
138///
139/// Sorts from least concern to highest.
140#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
141pub enum VisualizerReportSeverity {
142    /// Something went wrong on an optional component.
143    ///
144    /// We can often still show something using the default.
145    Warning,
146
147    /// Something went wrong on a required component (or otherwise fatally).
148    ///
149    /// The entity usually can't be shown.
150    Error,
151
152    /// It's not just a single visualizer instruction that failed, but the visualizer as a whole tanked.
153    OverallVisualizerError,
154}
155
156/// Contextual information about where/why a diagnostic occurred.
157#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
158pub struct VisualizerReportContext {
159    /// The component that caused the issue (if applicable).
160    ///
161    /// In presence of mappings this is the target mapping, i.e. the visualizer's "slot name".
162    pub component: Option<ComponentIdentifier>,
163
164    /// Additional free-form context
165    pub extra: Option<String>,
166}
167
168impl re_byte_size::SizeBytes for VisualizerReportContext {
169    fn heap_size_bytes(&self) -> u64 {
170        self.extra.heap_size_bytes()
171    }
172}
173
174/// A diagnostic message (error or warning) from a visualizer for a single instruction.
175///
176/// Collected into [`crate::VisualizerTypeReport::PerInstructionReport`].
177///
178/// # Sub-types of per-instruction failures
179///
180/// * **Cross-component / "global" reason** — the entity can't be shown for a reason
181///   not tied to one component (e.g. a broken transform chain, `Pinhole` interplay).
182///   In this case [`context.component`](VisualizerReportContext::component) is `None`.
183///
184/// * **A specific component didn't make sense**
185///   ([`context.component`](VisualizerReportContext::component) is `Some`):
186///   - *Selector failure* — the component mapping/selector couldn't resolve:
187///     the referenced component doesn't exist, the jq selector string is syntactically
188///     invalid, or it points at something that doesn't exist.
189///   - *Bad data* — the selector resolved, but the resulting data is malformed,
190///     has an unexpected type, or is otherwise unusable.
191///
192/// For a high-level failure handling overview, see the `re_viewer` crate documentation.
193#[derive(Debug, Clone, PartialEq, Eq, Hash)]
194pub struct VisualizerInstructionReport {
195    pub severity: VisualizerReportSeverity,
196    pub context: VisualizerReportContext,
197
198    /// Short message suitable for inline display
199    pub summary: String,
200
201    /// Optional detailed explanation
202    pub details: Option<String>,
203}
204
205impl re_byte_size::SizeBytes for VisualizerInstructionReport {
206    fn heap_size_bytes(&self) -> u64 {
207        self.summary.heap_size_bytes()
208            + self.details.heap_size_bytes()
209            + self.context.heap_size_bytes()
210    }
211}
212
213impl VisualizerInstructionReport {
214    /// Create a new error report
215    pub fn error(summary: impl Into<String>) -> Self {
216        Self {
217            severity: VisualizerReportSeverity::Error,
218            summary: summary.into(),
219            details: None,
220            context: VisualizerReportContext::default(),
221        }
222    }
223
224    /// Create a new warning report
225    pub fn warning(summary: impl Into<String>) -> Self {
226        Self {
227            severity: VisualizerReportSeverity::Warning,
228            summary: summary.into(),
229            details: None,
230            context: VisualizerReportContext::default(),
231        }
232    }
233}
234
235/// Result of running [`VisualizerSystem::execute`].
236#[derive(Default)]
237pub struct VisualizerExecutionOutput {
238    /// Draw data produced by the visualizer.
239    ///
240    /// It's the view's responsibility to queue this data for rendering.
241    pub draw_data: Vec<re_renderer::QueueableDrawData>,
242
243    /// Reports (errors and warnings) encountered during execution, mapped to the visualizer instructions that caused them.
244    ///
245    /// Reports from last frame will be shown in the UI for the respective visualizer instruction.
246    /// For errors that prevent any visualization at all, return a
247    /// [`ViewSystemExecutionError`] instead.
248    ///
249    /// It's mutex protected to make it easier to append errors while processing instructions in parallel.
250    pub reports_per_instruction:
251        Mutex<BTreeMap<VisualizerInstructionId, Vec1<VisualizerInstructionReport>>>,
252
253    /// Used to indicate that some chunks were missing
254    missing_chunk_reporter: MissingChunkReporter,
255    //
256    // TODO(andreas): We should put other output here as well instead of passing around visualizer
257    // structs themselves which is rather surprising.
258    // Same applies to context systems.
259    // This mechanism could easily replace `VisualizerSystem::data`!
260}
261
262impl VisualizerExecutionOutput {
263    /// Indicate that the view should show a loading indicator because data is missing.
264    pub fn set_missing_chunks(&self) {
265        self.missing_chunk_reporter.report_missing_chunk();
266    }
267
268    /// Were any required chunks missing?
269    pub fn any_missing_chunks(&self) -> bool {
270        self.missing_chunk_reporter.any_missing()
271    }
272
273    /// Can be used to report missing chunks.
274    pub fn missing_chunk_reporter(&self) -> &MissingChunkReporter {
275        &self.missing_chunk_reporter
276    }
277
278    /// Marks the given visualizer instruction as having encountered an error during visualization.
279    pub fn report_error_for(
280        &self,
281        instruction_id: VisualizerInstructionId,
282        error: impl Into<String>,
283    ) {
284        // TODO(RR-3506): enforce supplying context information.
285        let report = VisualizerInstructionReport::error(error);
286        self.reports_per_instruction
287            .lock()
288            .entry(instruction_id)
289            .and_modify(|v| v.push(report.clone()))
290            .or_insert_with(|| vec1::vec1![report]);
291    }
292
293    /// Marks the given visualizer instruction as having encountered a warning during visualization.
294    pub fn report_warning_for(
295        &self,
296        instruction_id: VisualizerInstructionId,
297        warning: impl Into<String>,
298    ) {
299        // TODO(RR-3506): enforce supplying context information.
300        let report = VisualizerInstructionReport::warning(warning);
301        self.reports_per_instruction
302            .lock()
303            .entry(instruction_id)
304            .and_modify(|v| v.push(report.clone()))
305            .or_insert_with(|| vec1::vec1![report]);
306    }
307
308    /// Report a detailed diagnostic for a visualizer instruction.
309    pub fn report(
310        &self,
311        instruction_id: VisualizerInstructionId,
312        report: VisualizerInstructionReport,
313    ) {
314        self.reports_per_instruction
315            .lock()
316            .entry(instruction_id)
317            .and_modify(|v| v.push(report.clone()))
318            .or_insert_with(|| vec1::vec1![report]);
319    }
320
321    pub fn with_draw_data(
322        mut self,
323        draw_data: impl IntoIterator<Item = re_renderer::QueueableDrawData>,
324    ) -> Self {
325        self.draw_data.extend(draw_data);
326        self
327    }
328}
329
330/// Element of a scene derived from a single archetype query.
331///
332/// Is populated after scene contexts and has access to them.
333pub trait VisualizerSystem: Send + Sync + std::any::Any {
334    // TODO(andreas): This should be able to list out the ContextSystems it needs.
335
336    /// Information about which components are queried by the visualizer.
337    ///
338    /// Warning: this method is called on registration of the visualizer system in order
339    /// to stear store subscribers. If subsequent calls to this method return different results,
340    /// they may not be taken into account.
341    fn visualizer_query_info(&self, app_options: &crate::AppOptions) -> VisualizerQueryInfo;
342
343    /// Queries the chunk store and performs data conversions to make it ready for display.
344    ///
345    /// Mustn't query any data outside of the archetype.
346    fn execute(
347        &mut self,
348        ctx: &ViewContext<'_>,
349        query: &ViewQuery<'_>,
350        context_systems: &ViewContextCollection,
351    ) -> Result<VisualizerExecutionOutput, ViewSystemExecutionError>;
352
353    /// Optionally retrieves a chunk store reference from the scene element.
354    ///
355    /// This is useful for retrieving data that is common to several visualizers of a [`crate::ViewClass`].
356    /// For example, if most visualizers produce ui elements, a concrete [`crate::ViewClass`]
357    /// can pick those up in its [`crate::ViewClass::ui`] method by iterating over all visualizers.
358    fn data(&self) -> Option<&dyn std::any::Any> {
359        None
360    }
361}
362
363pub struct VisualizerCollection {
364    pub systems: BTreeMap<ViewSystemIdentifier, Box<dyn VisualizerSystem>>,
365}
366
367impl VisualizerCollection {
368    #[inline]
369    pub fn get<T: VisualizerSystem + IdentifiedViewSystem + 'static>(
370        &self,
371    ) -> Result<&T, ViewSystemExecutionError> {
372        self.systems
373            .get(&T::identifier())
374            .and_then(|s| (s.as_ref() as &dyn std::any::Any).downcast_ref())
375            .ok_or_else(|| {
376                ViewSystemExecutionError::VisualizerSystemNotFound(T::identifier().as_str())
377            })
378    }
379
380    #[inline]
381    pub fn get_by_type_identifier(
382        &self,
383        name: ViewSystemIdentifier,
384    ) -> Result<&dyn VisualizerSystem, ViewSystemExecutionError> {
385        self.systems
386            .get(&name)
387            .map(|s| s.as_ref())
388            .ok_or_else(|| ViewSystemExecutionError::VisualizerSystemNotFound(name.as_str()))
389    }
390
391    #[inline]
392    pub fn iter(&self) -> impl Iterator<Item = &dyn VisualizerSystem> {
393        self.systems.values().map(|s| s.as_ref())
394    }
395
396    #[inline]
397    pub fn iter_with_identifiers(
398        &self,
399    ) -> impl Iterator<Item = (ViewSystemIdentifier, &dyn VisualizerSystem)> {
400        self.systems.iter().map(|s| (*s.0, s.1.as_ref()))
401    }
402
403    /// Iterate over all visualizer data that can be downcast to the given type.
404    pub fn iter_visualizer_data<SpecificData: 'static>(
405        &self,
406    ) -> impl Iterator<Item = &'_ SpecificData> {
407        self.iter()
408            .filter_map(|visualizer| visualizer.data()?.downcast_ref::<SpecificData>())
409    }
410}