Skip to main content

re_viewer_context/view/
visualizer_system.rs

1use std::any::TypeId;
2use std::collections::BTreeMap;
3use std::collections::HashMap;
4
5use parking_lot::Mutex;
6use re_chunk_store::MissingChunkReporter;
7use vec1::Vec1;
8
9use re_chunk::ArchetypeName;
10use re_sdk_types::blueprint::components::VisualizerInstructionId;
11use re_sdk_types::{ComponentDescriptor, ComponentIdentifier, ViewClassIdentifier};
12
13use crate::{
14    BufferAndFormatConstraint, SingleRequiredComponentConstraint, ViewContext,
15    ViewContextCollection, ViewQuery, ViewSystemExecutionError, ViewSystemIdentifier,
16    ViewerDiagnostic, ViewerReportSeverity, VisualizabilityConstraints,
17};
18
19#[derive(Debug, Clone, Default)]
20pub struct SortedComponentSet(linked_hash_map::LinkedHashMap<ComponentDescriptor, ()>);
21
22impl SortedComponentSet {
23    pub fn insert(&mut self, k: ComponentDescriptor) -> Option<()> {
24        self.0.insert(k, ())
25    }
26
27    pub fn extend(&mut self, iter: impl IntoIterator<Item = ComponentDescriptor>) {
28        self.0.extend(iter.into_iter().map(|k| (k, ())));
29    }
30
31    pub fn iter(&self) -> linked_hash_map::Keys<'_, ComponentDescriptor, ()> {
32        self.0.keys()
33    }
34
35    pub fn contains(&self, k: &ComponentDescriptor) -> bool {
36        self.0.contains_key(k)
37    }
38}
39
40impl FromIterator<ComponentDescriptor> for SortedComponentSet {
41    fn from_iter<I: IntoIterator<Item = ComponentDescriptor>>(iter: I) -> Self {
42        Self(iter.into_iter().map(|k| (k, ())).collect())
43    }
44}
45
46// TODO(grtlr): Eventually we will want to hide these fields to prevent visualizers doing too much shenanigans.
47pub struct VisualizerQueryInfo {
48    /// This is not required, but if it is found, it is a strong indication that this
49    /// system should be active (if also the `required_components` are found).
50    ///
51    /// This information results in the "indicated visualizer" list.
52    pub relevant_archetype: Option<ArchetypeName>,
53
54    /// Returns the minimal set of components that the system _requires_ in order to be instantiated.
55    pub constraints: VisualizabilityConstraints,
56
57    /// Returns the list of components that the system _queries_.
58    ///
59    /// Must include required components.
60    /// Order should reflect order in archetype docs & user code as well as possible.
61    ///
62    /// We use this to determine which components should be shown in the UI.
63    pub queried: SortedComponentSet,
64}
65
66impl VisualizerQueryInfo {
67    /// Creates a query info for a visualizer that requires both a buffer and a format component.
68    ///
69    /// Both components have to be part of the queried components.
70    /// See [`BufferAndFormatConstraint`] for more details.
71    pub fn buffer_and_format<Buffer: re_sdk_types::Component, Format: re_sdk_types::Component>(
72        buffer_descriptor: &ComponentDescriptor,
73        format_descriptor: &ComponentDescriptor,
74        all_queried_components: &[ComponentDescriptor],
75    ) -> Self {
76        let query_info = Self {
77            relevant_archetype: format_descriptor.archetype,
78            constraints: BufferAndFormatConstraint::new::<Buffer, Format>(
79                buffer_descriptor,
80                format_descriptor,
81            )
82            .into(),
83            queried: all_queried_components.iter().cloned().collect(),
84        };
85
86        re_log::debug_assert!(
87            query_info
88                .queried
89                .iter()
90                .any(|desc| desc == buffer_descriptor),
91            "The buffer component must be part of the queried components."
92        );
93        re_log::debug_assert!(
94            query_info
95                .queried
96                .iter()
97                .any(|desc| desc == format_descriptor),
98            "The format component must be part of the queried components."
99        );
100
101        query_info
102    }
103
104    /// Creates a query info for a visualizer that requires a single component.
105    ///
106    /// The target component has to be part of the queried components.
107    /// See [`SingleRequiredComponentConstraint`] for more details.
108    pub fn single_required_component<C: re_sdk_types::Component>(
109        target_component_descriptor: &ComponentDescriptor,
110        all_queried_components: &[ComponentDescriptor],
111    ) -> Self {
112        let query_info = Self {
113            relevant_archetype: target_component_descriptor.archetype,
114            constraints: SingleRequiredComponentConstraint::new::<C>(target_component_descriptor)
115                .into(),
116            queried: all_queried_components.iter().cloned().collect(),
117        };
118
119        re_log::debug_assert!(
120            query_info
121                .queried
122                .iter()
123                .any(|desc| desc == target_component_descriptor),
124            "The required component must be part of the queried components."
125        );
126
127        query_info
128    }
129
130    pub fn empty() -> Self {
131        Self {
132            relevant_archetype: Default::default(),
133            constraints: VisualizabilityConstraints::None,
134            queried: SortedComponentSet::default(),
135        }
136    }
137
138    /// Returns the component _identifiers_ for all queried components.
139    pub fn queried_components(&self) -> impl Iterator<Item = ComponentIdentifier> {
140        self.queried.iter().map(|desc| desc.component)
141    }
142}
143
144/// Contextual information about where/why a diagnostic occurred.
145#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, re_byte_size::SizeBytes)]
146pub struct VisualizerReportContext {
147    /// The component that caused the issue (if applicable).
148    ///
149    /// In presence of mappings this is the target mapping, i.e. the visualizer's "slot name".
150    pub component: Option<ComponentIdentifier>,
151
152    /// Additional free-form context
153    pub extra: Option<String>,
154}
155
156/// A diagnostic message (error or warning) from a visualizer for a single instruction.
157///
158/// Collected into [`crate::VisualizerTypeReport::PerInstructionReport`].
159///
160/// # Sub-types of per-instruction failures
161///
162/// * **Cross-component / "global" reason** — the entity can't be shown for a reason
163///   not tied to one component (e.g. a broken transform chain, `Pinhole` interplay).
164///   In this case [`context.component`](VisualizerReportContext::component) is `None`.
165///
166/// * **A specific component didn't make sense**
167///   ([`context.component`](VisualizerReportContext::component) is `Some`):
168///   - *Selector failure* — the component mapping/selector couldn't resolve:
169///     the referenced component doesn't exist, the jq selector string is syntactically
170///     invalid, or it points at something that doesn't exist.
171///   - *Bad data* — the selector resolved, but the resulting data is malformed,
172///     has an unexpected type, or is otherwise unusable.
173///
174/// For a high-level failure handling overview, see the `re_viewer` crate documentation.
175#[derive(Debug, Clone, PartialEq, Eq, Hash, re_byte_size::SizeBytes)]
176pub struct VisualizerInstructionReport {
177    pub diagnostic: ViewerDiagnostic,
178    pub context: VisualizerReportContext,
179}
180
181/// Result of running [`VisualizerSystem::execute`].
182///
183/// Contains two kinds of output:
184/// - [`Self::draw_data`]: GPU render commands, queued by the view for rendering.
185/// - Typed visualizer data: CPU-side typed data read back by the view's own code
186///   (picking, labels, bounding boxes, camera info, plot series, etc.);
187///   see [`Self::with_visualizer_data`].
188#[derive(Default)]
189pub struct VisualizerExecutionOutput {
190    /// GPU render commands produced by the visualizer.
191    ///
192    /// It's the view's responsibility to queue this data for rendering.
193    pub draw_data: Vec<re_renderer::QueueableDrawData>,
194
195    /// The view class this visualizer has affinity to, i.e. the value returned by
196    /// [`VisualizerSystem::affinity`].
197    ///
198    /// Populated automatically by the execution framework after calling [`VisualizerSystem::execute`].
199    pub affinity: Option<ViewClassIdentifier>,
200
201    /// Per-instruction diagnostics (errors and warnings) encountered during execution.
202    ///
203    /// Shown in the UI for the respective visualizer instruction.
204    /// For errors that prevent any visualization at all, return a
205    /// [`ViewSystemExecutionError`] instead.
206    ///
207    /// Mutex-protected to allow parallel appending across instructions.
208    pub reports_per_instruction:
209        Mutex<BTreeMap<VisualizerInstructionId, Vec1<VisualizerInstructionReport>>>,
210
211    /// Used to indicate that some chunks were missing
212    missing_chunk_reporter: MissingChunkReporter,
213
214    /// CPU-side typed data produced by the visualizer for consumption by the view.
215    ///
216    /// Keyed by type — each type can appear at most once. A visualizer can store multiple
217    /// independent values of different types (e.g. both `SpatialViewVisualizerData` and
218    /// a visualizer-specific output struct).
219    ///
220    /// Use [`Self::with_visualizer_data`] to store and [`crate::SystemExecutionOutput::visualizer_data`]
221    /// or [`crate::SystemExecutionOutput::iter_visualizer_data`] to retrieve.
222    visualizer_data: HashMap<TypeId, Box<dyn std::any::Any + Send + Sync>>,
223}
224
225impl VisualizerExecutionOutput {
226    /// Indicate that the view should show a loading indicator because data is missing.
227    pub fn set_missing_chunks(&self) {
228        self.missing_chunk_reporter.report_missing_chunk();
229    }
230
231    /// Were any required chunks missing?
232    pub fn any_missing_chunks(&self) -> bool {
233        self.missing_chunk_reporter.any_missing()
234    }
235
236    /// Can be used to report missing chunks.
237    pub fn missing_chunk_reporter(&self) -> &MissingChunkReporter {
238        &self.missing_chunk_reporter
239    }
240
241    /// Report a message for a visualizer instruction with the given severity but no component context.
242    ///
243    /// Use [`Self::report`] instead when component-specific context is available.
244    pub fn report_unspecified_source(
245        &self,
246        instruction_id: VisualizerInstructionId,
247        severity: ViewerReportSeverity,
248        summary: impl Into<String>,
249    ) {
250        self.report(
251            instruction_id,
252            VisualizerInstructionReport {
253                diagnostic: ViewerDiagnostic {
254                    severity,
255                    summary: summary.into(),
256                    details: None,
257                },
258                context: VisualizerReportContext::default(),
259            },
260        );
261    }
262
263    /// Report a detailed diagnostic for a visualizer instruction.
264    pub fn report(
265        &self,
266        instruction_id: VisualizerInstructionId,
267        report: VisualizerInstructionReport,
268    ) {
269        self.reports_per_instruction
270            .lock()
271            .entry(instruction_id)
272            .and_modify(|v| v.push(report.clone()))
273            .or_insert_with(|| vec1::vec1![report]);
274    }
275
276    pub fn with_draw_data(
277        mut self,
278        draw_data: impl IntoIterator<Item = re_renderer::QueueableDrawData>,
279    ) -> Self {
280        self.draw_data.extend(draw_data);
281        self
282    }
283
284    /// Add a typed output payload for this visualizer.
285    ///
286    /// Each type can be stored at most once. Calling this multiple times with the same type
287    /// overwrites the previous value.
288    pub fn with_visualizer_data<T: std::any::Any + Send + Sync>(mut self, data: T) -> Self {
289        self.visualizer_data
290            .insert(TypeId::of::<T>(), Box::new(data));
291        self
292    }
293
294    /// Retrieve a typed output payload previously stored with [`Self::with_visualizer_data`].
295    pub fn get_visualizer_data<T: std::any::Any>(&self) -> Option<&T> {
296        self.visualizer_data
297            .get(&TypeId::of::<T>())
298            .and_then(|b| b.downcast_ref::<T>())
299    }
300}
301
302/// Element of a scene derived from a single archetype query.
303///
304/// Is populated after scene contexts and has access to them.
305pub trait VisualizerSystem: Send + Sync + std::any::Any {
306    // TODO(andreas): This should be able to list out the ContextSystems it needs.
307
308    /// Information about which components are queried by the visualizer.
309    ///
310    /// Warning: this method is called on registration of the visualizer system in order
311    /// to stear store subscribers. If subsequent calls to this method return different results,
312    /// they may not be taken into account.
313    fn visualizer_query_info(&self, app_options: &crate::AppOptions) -> VisualizerQueryInfo;
314
315    /// View class that this visualizer prefers to be used in, eg: 2D or 3D.
316    /// Useful for visualizers that can be displayed in 2D and 3D, so heuristics
317    /// can determine the best view to use for the visualizer.
318    fn affinity(&self) -> Option<ViewClassIdentifier> {
319        None
320    }
321
322    /// Queries the chunk store and performs data conversions to make it ready for display.
323    ///
324    /// Mustn't query any data outside of the archetype.
325    /// All output data should be placed in the returned [`VisualizerExecutionOutput`].
326    fn execute(
327        &self,
328        ctx: &ViewContext<'_>,
329        query: &ViewQuery<'_>,
330        context_systems: &ViewContextCollection,
331    ) -> Result<VisualizerExecutionOutput, ViewSystemExecutionError>;
332
333    /// Optional custom UI shown in the selection panel for this visualizer instruction.
334    ///
335    /// Returns `true` if the custom UI replaces the default per-component value UI.
336    /// Visualizers that need source-mapping selectors render them themselves as part of
337    /// this UI. Returns `false` to fall back to the default per-component UI; the default
338    /// impl renders nothing and returns `false`.
339    fn selection_ui(
340        &self,
341        _ctx: &ViewContext<'_>,
342        _ui: &mut egui::Ui,
343        _data_result: &crate::DataResult,
344        _instruction: &crate::VisualizerInstruction,
345        _type_report: Option<&crate::VisualizerTypeReport>,
346    ) -> bool {
347        false
348    }
349}
350
351pub struct VisualizerCollection {
352    pub systems: BTreeMap<ViewSystemIdentifier, Box<dyn VisualizerSystem>>,
353}
354
355impl VisualizerCollection {
356    #[inline]
357    pub fn get_by_type_identifier(
358        &self,
359        name: ViewSystemIdentifier,
360    ) -> Result<&dyn VisualizerSystem, ViewSystemExecutionError> {
361        self.systems
362            .get(&name)
363            .map(|s| s.as_ref())
364            .ok_or_else(|| ViewSystemExecutionError::VisualizerSystemNotFound(name.as_str()))
365    }
366
367    #[inline]
368    pub fn iter(&self) -> impl Iterator<Item = &dyn VisualizerSystem> {
369        self.systems.values().map(|s| s.as_ref())
370    }
371
372    #[inline]
373    pub fn iter_with_identifiers(
374        &self,
375    ) -> impl Iterator<Item = (ViewSystemIdentifier, &dyn VisualizerSystem)> {
376        self.systems.iter().map(|s| (*s.0, s.1.as_ref()))
377    }
378
379    pub fn contains_visualizer_type(&self, name: ViewSystemIdentifier) -> bool {
380        self.systems.contains_key(&name)
381    }
382}