diff --git a/Cargo.lock b/Cargo.lock index fab880b3ac27..5b492c665e77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5036,6 +5036,7 @@ dependencies = [ "re_log", "re_log_types", "re_renderer", + "re_space_view", "re_tracing", "re_types", "re_ui", diff --git a/crates/re_space_view/src/lib.rs b/crates/re_space_view/src/lib.rs index 06724825d8e4..5369feb3fe47 100644 --- a/crates/re_space_view/src/lib.rs +++ b/crates/re_space_view/src/lib.rs @@ -13,3 +13,22 @@ pub use data_query::{DataQuery, EntityOverrideContext, PropertyResolver}; pub use data_query_blueprint::DataQueryBlueprint; pub use screenshot::ScreenshotMode; pub use unreachable_transform_reason::UnreachableTransformReason; + +// ----------- + +use re_data_store::external::re_arrow_store; + +/// Utility for implementing [`re_viewer_context::VisualizerAdditionalApplicabilityFilter`] using on the properties of a concrete component. +#[inline] +pub fn diff_component_filter( + event: &re_arrow_store::StoreEvent, + filter: impl Fn(&T) -> bool, +) -> bool { + let filter = &filter; + event.diff.cells.iter().any(|(component_name, cell)| { + component_name == &T::name() + && T::from_arrow(cell.as_arrow_ref()) + .map(|components| components.iter().any(filter)) + .unwrap_or(false) + }) +} diff --git a/crates/re_space_view_bar_chart/src/view_part_system.rs b/crates/re_space_view_bar_chart/src/view_part_system.rs index 90b5bf2b7e27..929e63a425b4 100644 --- a/crates/re_space_view_bar_chart/src/view_part_system.rs +++ b/crates/re_space_view_bar_chart/src/view_part_system.rs @@ -2,6 +2,7 @@ use std::collections::BTreeMap; use re_arrow_store::LatestAtQuery; use re_data_store::EntityPath; +use re_space_view::diff_component_filter; use re_types::{ archetypes::{BarChart, Tensor}, components::Color, @@ -9,8 +10,8 @@ use re_types::{ Archetype, ComponentNameSet, }; use re_viewer_context::{ - default_heuristic_filter, HeuristicFilterContext, IdentifiedViewSystem, - SpaceViewSystemExecutionError, ViewContextCollection, ViewPartSystem, ViewQuery, ViewerContext, + IdentifiedViewSystem, SpaceViewSystemExecutionError, ViewContextCollection, ViewPartSystem, + ViewQuery, ViewerContext, VisualizerAdditionalApplicabilityFilter, }; /// A bar chart system, with everything needed to render it. @@ -25,6 +26,16 @@ impl IdentifiedViewSystem for BarChartViewPartSystem { } } +struct BarChartVisualizerEntityFilter; + +impl VisualizerAdditionalApplicabilityFilter for BarChartVisualizerEntityFilter { + fn update_applicability(&mut self, event: &re_arrow_store::StoreEvent) -> bool { + diff_component_filter(event, |tensor: &re_types::components::TensorData| { + tensor.is_vector() + }) + } +} + impl ViewPartSystem for BarChartViewPartSystem { fn required_components(&self) -> ComponentNameSet { BarChart::required_components() @@ -42,28 +53,8 @@ impl ViewPartSystem for BarChartViewPartSystem { .collect() } - fn heuristic_filter( - &self, - store: &re_arrow_store::DataStore, - ent_path: &EntityPath, - _ctx: HeuristicFilterContext, - query: &LatestAtQuery, - entity_components: &ComponentNameSet, - ) -> bool { - if !default_heuristic_filter(entity_components, &self.indicator_components()) { - return false; - } - - // NOTE: We want to make sure we query at the right time, otherwise we might take into - // account a `Clear()` that actually only applies into the future, and then - // `is_vector` will righfully fail because of the empty tensor. - if let Some(tensor) = - store.query_latest_component::(ent_path, query) - { - tensor.is_vector() - } else { - false - } + fn applicability_filter(&self) -> Option> { + Some(Box::new(BarChartVisualizerEntityFilter)) } fn execute( diff --git a/crates/re_space_view_spatial/src/parts/images.rs b/crates/re_space_view_spatial/src/parts/images.rs index 6daaa56a0c8f..af200ed56fcd 100644 --- a/crates/re_space_view_spatial/src/parts/images.rs +++ b/crates/re_space_view_spatial/src/parts/images.rs @@ -12,6 +12,7 @@ use re_renderer::{ renderer::{DepthCloud, DepthClouds, RectangleOptions, TexturedRect}, Colormap, }; +use re_space_view::diff_component_filter; use re_types::{ archetypes::{DepthImage, Image, SegmentationImage}, components::{Color, DrawOrder, TensorData, ViewCoordinates}, @@ -21,7 +22,7 @@ use re_types::{ use re_viewer_context::{ default_heuristic_filter, gpu_bridge, DefaultColor, HeuristicFilterContext, SpaceViewClass, SpaceViewSystemExecutionError, TensorDecodeCache, TensorStatsCache, ViewPartSystem, ViewQuery, - ViewerContext, + ViewerContext, VisualizerAdditionalApplicabilityFilter, }; use re_viewer_context::{IdentifiedViewSystem, ViewContextCollection}; @@ -643,6 +644,16 @@ impl IdentifiedViewSystem for ImagesPart { } } +struct ImageVisualizerEntityFilter; + +impl VisualizerAdditionalApplicabilityFilter for ImageVisualizerEntityFilter { + fn update_applicability(&mut self, event: &re_arrow_store::StoreEvent) -> bool { + diff_component_filter(event, |tensor: &re_types::components::TensorData| { + tensor.is_shaped_like_an_image() + }) + } +} + impl ViewPartSystem for ImagesPart { fn required_components(&self) -> ComponentNameSet { let image: ComponentNameSet = Image::required_components() @@ -677,12 +688,16 @@ impl ViewPartSystem for ImagesPart { .collect() } + fn applicability_filter(&self) -> Option> { + Some(Box::new(ImageVisualizerEntityFilter)) + } + fn heuristic_filter( &self, - store: &re_arrow_store::DataStore, - ent_path: &EntityPath, + _store: &re_arrow_store::DataStore, + _ent_path: &EntityPath, ctx: HeuristicFilterContext, - query: &LatestAtQuery, + _query: &LatestAtQuery, entity_components: &ComponentNameSet, ) -> bool { if !default_heuristic_filter(entity_components, &self.indicator_components()) { @@ -697,14 +712,7 @@ impl ViewPartSystem for ImagesPart { return false; } - // NOTE: We want to make sure we query at the right time, otherwise we might take into - // account a `Clear()` that actually only applies into the future, and then - // `is_shaped_like_an_image` will righfully fail because of the empty tensor. - if let Some(tensor) = store.query_latest_component::(ent_path, query) { - tensor.is_shaped_like_an_image() - } else { - false - } + true } fn execute( diff --git a/crates/re_space_view_tensor/Cargo.toml b/crates/re_space_view_tensor/Cargo.toml index 0fc8aa55d132..929ce8f92f46 100644 --- a/crates/re_space_view_tensor/Cargo.toml +++ b/crates/re_space_view_tensor/Cargo.toml @@ -22,6 +22,7 @@ re_data_ui.workspace = true re_log_types.workspace = true re_log.workspace = true re_renderer.workspace = true +re_space_view.workspace = true re_tracing.workspace = true re_types.workspace = true re_ui.workspace = true diff --git a/crates/re_space_view_tensor/src/view_part_system.rs b/crates/re_space_view_tensor/src/view_part_system.rs index 0ecec2745fa5..0a9331a3c087 100644 --- a/crates/re_space_view_tensor/src/view_part_system.rs +++ b/crates/re_space_view_tensor/src/view_part_system.rs @@ -1,14 +1,14 @@ use re_arrow_store::{LatestAtQuery, VersionedComponent}; use re_data_store::EntityPath; use re_log_types::RowId; +use re_space_view::diff_component_filter; use re_types::{ archetypes::Tensor, components::TensorData, tensor_data::DecodedTensor, Archetype, ComponentNameSet, }; use re_viewer_context::{ - default_heuristic_filter, HeuristicFilterContext, IdentifiedViewSystem, - SpaceViewSystemExecutionError, TensorDecodeCache, ViewContextCollection, ViewPartSystem, - ViewQuery, ViewerContext, + IdentifiedViewSystem, SpaceViewSystemExecutionError, TensorDecodeCache, ViewContextCollection, + ViewPartSystem, ViewQuery, ViewerContext, VisualizerAdditionalApplicabilityFilter, }; #[derive(Default)] @@ -22,6 +22,16 @@ impl IdentifiedViewSystem for TensorSystem { } } +struct TensorVisualizerEntityFilter; + +impl VisualizerAdditionalApplicabilityFilter for TensorVisualizerEntityFilter { + fn update_applicability(&mut self, event: &re_arrow_store::StoreEvent) -> bool { + diff_component_filter(event, |tensor: &re_types::components::TensorData| { + !tensor.is_vector() + }) + } +} + impl ViewPartSystem for TensorSystem { fn required_components(&self) -> ComponentNameSet { Tensor::required_components() @@ -34,26 +44,8 @@ impl ViewPartSystem for TensorSystem { std::iter::once(Tensor::indicator().name()).collect() } - fn heuristic_filter( - &self, - store: &re_arrow_store::DataStore, - ent_path: &EntityPath, - _ctx: HeuristicFilterContext, - query: &LatestAtQuery, - entity_components: &ComponentNameSet, - ) -> bool { - if !default_heuristic_filter(entity_components, &self.indicator_components()) { - return false; - } - - // The tensor view can't display anything with less than two dimensions. - if let Some(tensor) = - store.query_latest_component::(ent_path, query) - { - !tensor.is_vector() - } else { - false - } + fn applicability_filter(&self) -> Option> { + Some(Box::new(TensorVisualizerEntityFilter)) } fn execute( diff --git a/crates/re_viewer_context/src/lib.rs b/crates/re_viewer_context/src/lib.rs index bb34fba38cf3..040d77a0759e 100644 --- a/crates/re_viewer_context/src/lib.rs +++ b/crates/re_viewer_context/src/lib.rs @@ -49,6 +49,7 @@ pub use space_view::{ SpaceViewHighlights, SpaceViewOutlineMasks, SpaceViewState, SpaceViewSystemExecutionError, SpaceViewSystemRegistrator, SystemExecutionOutput, ViewContextCollection, ViewContextSystem, ViewPartCollection, ViewPartSystem, ViewQuery, ViewSystemIdentifier, + VisualizerAdditionalApplicabilityFilter, }; pub use store_context::StoreContext; pub use tensor::{TensorDecodeCache, TensorStats, TensorStatsCache}; diff --git a/crates/re_viewer_context/src/space_view/mod.rs b/crates/re_viewer_context/src/space_view/mod.rs index d4a5385167c8..b8bd66b9db0d 100644 --- a/crates/re_viewer_context/src/space_view/mod.rs +++ b/crates/re_viewer_context/src/space_view/mod.rs @@ -33,6 +33,7 @@ pub use view_part_system::{ default_heuristic_filter, HeuristicFilterContext, ViewPartCollection, ViewPartSystem, }; pub use view_query::{DataResult, PerSystemDataResults, PropertyOverrides, ViewQuery}; +pub use visualizer_entity_subscriber::VisualizerAdditionalApplicabilityFilter; // --------------------------------------------------------------------------- diff --git a/crates/re_viewer_context/src/space_view/view_part_system.rs b/crates/re_viewer_context/src/space_view/view_part_system.rs index 408a65597ddd..296910365fe0 100644 --- a/crates/re_viewer_context/src/space_view/view_part_system.rs +++ b/crates/re_viewer_context/src/space_view/view_part_system.rs @@ -7,6 +7,7 @@ use re_types::ComponentNameSet; use crate::{ IdentifiedViewSystem, SpaceViewClassIdentifier, SpaceViewSystemExecutionError, ViewContextCollection, ViewQuery, ViewSystemIdentifier, ViewerContext, + VisualizerAdditionalApplicabilityFilter, }; /// This is additional context made available to the `heuristic_filter`. @@ -81,6 +82,13 @@ pub trait ViewPartSystem: Send + Sync + 'static { default_heuristic_filter(entity_components, &self.indicator_components()) } + /// Additional filter for applicability. + /// + /// If none is specified, applicability is solely determined by required components. + fn applicability_filter(&self) -> Option> { + None + } + /// Queries the data store and performs data conversions to make it ready for display. /// /// Mustn't query any data outside of the archetype. diff --git a/crates/re_viewer_context/src/space_view/visualizer_entity_subscriber.rs b/crates/re_viewer_context/src/space_view/visualizer_entity_subscriber.rs index 49b919c0d900..677bd0710ba2 100644 --- a/crates/re_viewer_context/src/space_view/visualizer_entity_subscriber.rs +++ b/crates/re_viewer_context/src/space_view/visualizer_entity_subscriber.rs @@ -28,17 +28,45 @@ pub struct VisualizerEntitySubscriber { required_components_indices: IntMap, per_store_mapping: HashMap, + + /// Additional filter for applicability. + applicability_filter: Box, +} + +/// Additional filter for applicability on top of the default check for required components. +pub trait VisualizerAdditionalApplicabilityFilter: Send + Sync { + /// Updates the internal applicability filter state based on the given events. + /// + /// Called for every update no matter whether the entity is already has all required components or not. + /// + /// Returns true if the entity changed in the event is now applicable to the visualizer, false otherwise. + /// Once a entity passes this filter, it can never go back to being filtered out. + /// **This implies that the filter does not _need_ to be stateful.** + /// It is perfectly fine to return `true` only if something in the diff is regarded as applicable and false otherwise. + /// (However, if necessary, the applicability filter *can* keep track of state.) + fn update_applicability(&mut self, _event: &re_arrow_store::StoreEvent) -> bool; +} + +struct DefaultVisualizerApplicabilityFilter; + +impl VisualizerAdditionalApplicabilityFilter for DefaultVisualizerApplicabilityFilter { + #[inline] + fn update_applicability(&mut self, _event: &re_arrow_store::StoreEvent) -> bool { + true + } } #[derive(Default)] struct VisualizerEntityMapping { /// For each entity, which of the required components are present. /// + /// Last bit is used for the applicability filter. + /// /// In order of `required_components`. /// If all bits are set, the entity is applicable to the visualizer. // TODO(andreas): We could just limit the number of required components to 32 or 64 and // then use a single u32/u64 as a bitmap. - required_component_bitmap_per_entity: IntMap, + required_component_and_filter_bitmap_per_entity: IntMap, /// Which entities the visualizer can be applied to. /// @@ -59,6 +87,9 @@ impl VisualizerEntitySubscriber { .map(|(i, name)| (name, i)) .collect(), per_store_mapping: Default::default(), + applicability_filter: visualizer + .applicability_filter() + .unwrap_or_else(|| Box::new(DefaultVisualizerApplicabilityFilter)), } } @@ -102,10 +133,10 @@ impl StoreSubscriber for VisualizerEntitySubscriber { .or_default(); let required_components_bitmap = store_mapping - .required_component_bitmap_per_entity + .required_component_and_filter_bitmap_per_entity .entry(event.diff.entity_path.hash()) .or_insert_with(|| { - BitVec::from_elem(self.required_components_indices.len(), false) + BitVec::from_elem(self.required_components_indices.len() + 1, false) }); if required_components_bitmap.all() { @@ -119,6 +150,15 @@ impl StoreSubscriber for VisualizerEntitySubscriber { } } + let bit_index_for_filter = self.required_components_indices.len(); + let custom_filter = required_components_bitmap[bit_index_for_filter]; + if !custom_filter { + required_components_bitmap.set( + bit_index_for_filter, + self.applicability_filter.update_applicability(event), + ); + } + if required_components_bitmap.all() { re_log::debug!( "Entity {:?} in store {:?} is now applicable to visualizer {:?}",