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}