diff --git a/Cargo.lock b/Cargo.lock index eeaace5cb208..458fa86c31d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4772,6 +4772,7 @@ dependencies = [ "egui_plot", "image", "itertools 0.11.0", + "nohash-hasher", "once_cell", "poll-promise", "re_analytics", @@ -4789,6 +4790,7 @@ dependencies = [ "re_memory", "re_renderer", "re_smart_channel", + "re_space_view", "re_space_view_bar_chart", "re_space_view_spatial", "re_space_view_tensor", diff --git a/crates/re_space_view/src/data_query.rs b/crates/re_space_view/src/data_query.rs index 246c0d8f36da..a49deeb3e71c 100644 --- a/crates/re_space_view/src/data_query.rs +++ b/crates/re_space_view/src/data_query.rs @@ -1,58 +1,5 @@ -use re_data_store::{EntityPath, EntityProperties, EntityPropertyMap}; -use re_viewer_context::{DataResult, EntitiesPerSystemPerClass, StoreContext}; -use slotmap::SlotMap; -use smallvec::SmallVec; - -slotmap::new_key_type! { - /// Identifier for a [`DataResultNode`] - pub struct DataResultHandle; -} - -/// A hierarchical tree of [`DataResult`]s -pub struct DataResultTree { - pub data_results: SlotMap, - pub root_handle: Option, -} - -impl DataResultTree { - /// Depth-first traversal of the tree, calling `visitor` on each result. - pub fn visit(&self, visitor: &mut impl FnMut(DataResultHandle)) { - if let Some(root_handle) = self.root_handle { - self.visit_recursive(root_handle, visitor); - } - } - - /// Look up a [`DataResult`] in the tree based on its handle. - pub fn lookup_result(&self, handle: DataResultHandle) -> Option<&DataResult> { - self.data_results.get(handle).map(|node| &node.data_result) - } - - /// Look up a [`DataResultNode`] in the tree based on its handle. - pub fn lookup_node(&self, handle: DataResultHandle) -> Option<&DataResultNode> { - self.data_results.get(handle) - } - - fn visit_recursive( - &self, - handle: DataResultHandle, - visitor: &mut impl FnMut(DataResultHandle), - ) { - if let Some(result) = self.data_results.get(handle) { - visitor(handle); - - for child in &result.children { - self.visit_recursive(*child, visitor); - } - } - } -} - -/// A single node in the [`DataResultTree`] -#[derive(Debug)] -pub struct DataResultNode { - pub data_result: DataResult, - pub children: SmallVec<[DataResultHandle; 4]>, -} +use re_data_store::{EntityProperties, EntityPropertyMap}; +use re_viewer_context::{DataQueryResult, EntitiesPerSystemPerClass, StoreContext}; pub struct EntityOverrides { pub root: EntityProperties, @@ -70,8 +17,8 @@ pub trait PropertyResolver { /// The common trait implemented for data queries /// -/// Both interfaces return [`DataResult`]s, which are self-contained description of the data -/// to be added to a `SpaceView` including both the [`EntityPath`] and context for any overrides. +/// Both interfaces return [`re_viewer_context::DataResult`]s, which are self-contained description of the data +/// to be added to a `SpaceView` including both the [`re_log_types::EntityPath`] and context for any overrides. pub trait DataQuery { /// Execute a full query, returning a `DataResultTree` containing all results. /// @@ -83,20 +30,5 @@ pub trait DataQuery { property_resolver: &impl PropertyResolver, ctx: &StoreContext<'_>, entities_per_system_per_class: &EntitiesPerSystemPerClass, - ) -> DataResultTree; - - /// Find a single [`DataResult`] within the context of the query. - /// - /// `auto_properties` is a map containing any heuristic-derived auto properties for the given `SpaceView`. - /// - /// This is used when finding the result for a single entity such as in - /// a selection panel. - fn resolve( - &self, - property_resolver: &impl PropertyResolver, - ctx: &StoreContext<'_>, - entities_per_system_per_class: &EntitiesPerSystemPerClass, - entity_path: &EntityPath, - as_group: bool, - ) -> DataResult; + ) -> DataQueryResult; } diff --git a/crates/re_space_view/src/data_query_blueprint.rs b/crates/re_space_view/src/data_query_blueprint.rs index 3722bcd7e425..6143c771e05c 100644 --- a/crates/re_space_view/src/data_query_blueprint.rs +++ b/crates/re_space_view/src/data_query_blueprint.rs @@ -3,15 +3,13 @@ use once_cell::sync::Lazy; use re_data_store::{EntityProperties, EntityTree}; use re_log_types::{EntityPath, EntityPathExpr}; use re_viewer_context::{ - DataResult, EntitiesPerSystem, EntitiesPerSystemPerClass, SpaceViewClassName, + DataQueryId, DataQueryResult, DataResult, DataResultHandle, DataResultNode, DataResultTree, + EntitiesPerSystem, EntitiesPerSystemPerClass, SpaceViewClassName, }; use slotmap::SlotMap; use smallvec::SmallVec; -use crate::{ - blueprint::QueryExpressions, DataQuery, DataResultHandle, DataResultNode, DataResultTree, - EntityOverrides, PropertyResolver, -}; +use crate::{blueprint::QueryExpressions, DataQuery, EntityOverrides, PropertyResolver}; /// An implementation of [`DataQuery`] that is built from a collection of [`QueryExpressions`] /// @@ -26,7 +24,7 @@ use crate::{ /// and for which there is a valid `ViewPart` system. This keeps recursive expressions from incorrectly /// picking up irrelevant data within the tree. pub struct DataQueryBlueprint { - pub blueprint_path: EntityPath, + pub id: DataQueryId, pub space_view_class_name: SpaceViewClassName, pub expressions: QueryExpressions, } @@ -41,7 +39,7 @@ impl DataQuery for DataQueryBlueprint { property_resolver: &impl PropertyResolver, ctx: &re_viewer_context::StoreContext<'_>, entities_per_system_per_class: &EntitiesPerSystemPerClass, - ) -> DataResultTree { + ) -> DataQueryResult { re_tracing::profile_function!(); static EMPTY_ENTITY_LIST: Lazy = Lazy::new(Default::default); @@ -66,56 +64,9 @@ impl DataQuery for DataQueryBlueprint { ) }); - DataResultTree { - data_results, - root_handle, - } - } - - fn resolve( - &self, - property_resolver: &impl PropertyResolver, - ctx: &re_viewer_context::StoreContext<'_>, - entities_per_system_per_class: &EntitiesPerSystemPerClass, - entity_path: &re_log_types::EntityPath, - as_group: bool, - ) -> re_viewer_context::DataResult { - re_tracing::profile_function!(); - let overrides = property_resolver.resolve_entity_overrides(ctx); - - let view_parts = if let Some(per_system_entity_list) = - entities_per_system_per_class.get(&self.space_view_class_name) - { - per_system_entity_list - .iter() - .filter_map(|(part, ents)| { - if ents.contains(entity_path) { - Some(*part) - } else { - None - } - }) - .collect() - } else { - Default::default() - }; - - let mut resolved_properties = overrides.root.clone(); - for prefix in EntityPath::incremental_walk(None, entity_path) { - resolved_properties = resolved_properties.with_child(&overrides.group.get(&prefix)); - } - - // TODO(jleibs): This needs to be updated to accommodate for groups - DataResult { - entity_path: entity_path.clone(), - view_parts, - is_group: as_group, - individual_properties: overrides.individual.get_opt(entity_path).cloned(), - resolved_properties, - override_path: self - .blueprint_path - .join(&Self::OVERRIDES_PREFIX.into()) - .join(entity_path), + DataQueryResult { + id: self.id, + tree: DataResultTree::new(data_results, root_handle), } } } @@ -214,7 +165,7 @@ impl<'a> QueryExpressionEvaluator<'a> { resolved_properties = resolved_properties.with_child(props); } - let base_entity_path = self.blueprint.blueprint_path.clone(); + let base_entity_path = self.blueprint.id.as_entity_path().clone(); let prefix = EntityPath::from(DataQueryBlueprint::OVERRIDES_PREFIX); let override_path = base_entity_path.join(&prefix).join(&entity_path); @@ -387,7 +338,7 @@ mod tests { for (input, outputs) in scenarios { let query = DataQueryBlueprint { - blueprint_path: EntityPath::root(), + id: DataQueryId::random(), space_view_class_name: "3D".into(), expressions: input .into_iter() @@ -396,11 +347,11 @@ mod tests { .into(), }; - let result_tree = query.execute_query(&resolver, &ctx, &entities_per_system_per_class); + let query_result = query.execute_query(&resolver, &ctx, &entities_per_system_per_class); let mut visited = vec![]; - result_tree.visit(&mut |handle| { - let result = result_tree.lookup_result(handle).unwrap(); + query_result.tree.visit(&mut |handle| { + let result = query_result.tree.lookup_result(handle).unwrap(); if result.is_group && result.entity_path != EntityPath::root() { visited.push(format!("{}/", result.entity_path)); } else { diff --git a/crates/re_space_view/src/lib.rs b/crates/re_space_view/src/lib.rs index 1a79e8f5853f..c7b05496a090 100644 --- a/crates/re_space_view/src/lib.rs +++ b/crates/re_space_view/src/lib.rs @@ -11,9 +11,7 @@ mod space_view_contents; mod unreachable_transform_reason; pub use blueprint::QueryExpressions; -pub use data_query::{ - DataQuery, DataResultHandle, DataResultNode, DataResultTree, EntityOverrides, PropertyResolver, -}; +pub use data_query::{DataQuery, EntityOverrides, PropertyResolver}; pub use data_query_blueprint::DataQueryBlueprint; pub use screenshot::ScreenshotMode; pub use space_view_contents::{DataBlueprintGroup, SpaceViewContents}; diff --git a/crates/re_space_view/src/space_view_contents.rs b/crates/re_space_view/src/space_view_contents.rs index 66b1d5d0ff44..0b68180544eb 100644 --- a/crates/re_space_view/src/space_view_contents.rs +++ b/crates/re_space_view/src/space_view_contents.rs @@ -3,15 +3,14 @@ use std::collections::{BTreeMap, BTreeSet}; use nohash_hasher::IntMap; use re_data_store::{EntityPath, EntityProperties}; use re_viewer_context::{ - DataBlueprintGroupHandle, DataResult, EntitiesPerSystemPerClass, PerSystemEntities, - SpaceViewId, StoreContext, ViewSystemName, + DataBlueprintGroupHandle, DataQueryResult, DataResult, DataResultHandle, DataResultNode, + DataResultTree, EntitiesPerSystemPerClass, PerSystemEntities, SpaceViewId, StoreContext, + ViewSystemName, }; use slotmap::SlotMap; use smallvec::{smallvec, SmallVec}; -use crate::{ - DataQuery, DataResultHandle, DataResultNode, DataResultTree, EntityOverrides, PropertyResolver, -}; +use crate::{DataQuery, EntityOverrides, PropertyResolver}; /// A grouping of several data-blueprints. #[derive(Clone, serde::Deserialize, serde::Serialize)] @@ -484,7 +483,7 @@ impl DataQuery for SpaceViewContents { property_resolver: &impl PropertyResolver, ctx: &StoreContext<'_>, _entities_per_system_per_class: &EntitiesPerSystemPerClass, - ) -> DataResultTree { + ) -> DataQueryResult { re_tracing::profile_function!(); let overrides = property_resolver.resolve_entity_overrides(ctx); let mut data_results = SlotMap::::default(); @@ -495,74 +494,11 @@ impl DataQuery for SpaceViewContents { &overrides.root, &mut data_results, )); - DataResultTree { - data_results, - root_handle, - } - } - - fn resolve( - &self, - property_resolver: &impl PropertyResolver, - ctx: &StoreContext<'_>, - _entities_per_system_per_class: &EntitiesPerSystemPerClass, - entity_path: &EntityPath, - as_group: bool, - ) -> DataResult { - re_tracing::profile_function!(); - let overrides = property_resolver.resolve_entity_overrides(ctx); - - let view_parts = self - .per_system_entities() - .iter() - .filter_map(|(part, ents)| { - if ents.contains(entity_path) { - Some(*part) - } else { - None - } - }) - .collect(); - - // Start with the root override - let mut resolved_properties = overrides.root; - - // Merge in any group overrides - for prefix in EntityPath::incremental_walk(None, entity_path) { - if let Some(props) = overrides.group.get_opt(&prefix) { - resolved_properties = resolved_properties.with_child(props); - } - } - - if as_group { - DataResult { - entity_path: entity_path.clone(), - view_parts, - is_group: true, - resolved_properties, - individual_properties: overrides.group.get_opt(entity_path).cloned(), - override_path: self - .entity_path() - .join(&SpaceViewContents::GROUP_OVERRIDES_PREFIX.into()) - .join(entity_path), - } - } else { - // Finally apply the individual overrides - if let Some(props) = overrides.individual.get_opt(entity_path) { - resolved_properties = resolved_properties.with_child(props); - } - - DataResult { - entity_path: entity_path.clone(), - view_parts, - is_group: false, - resolved_properties, - individual_properties: overrides.individual.get_opt(entity_path).cloned(), - override_path: self - .entity_path() - .join(&SpaceViewContents::INDIVIDUAL_OVERRIDES_PREFIX.into()) - .join(entity_path), - } + DataQueryResult { + // Create a fake `DataQueryId` based on the SpaceView since each + // SpaceView contains a single "Query" based on its contents. + id: self.space_view_id.uuid().into(), + tree: DataResultTree::new(data_results, root_handle), } } } diff --git a/crates/re_viewer/Cargo.toml b/crates/re_viewer/Cargo.toml index 03ac037eb107..1a472b1c42c6 100644 --- a/crates/re_viewer/Cargo.toml +++ b/crates/re_viewer/Cargo.toml @@ -51,6 +51,7 @@ re_log.workspace = true re_memory.workspace = true re_renderer = { workspace = true, default-features = false } re_smart_channel.workspace = true +re_space_view.workspace = true re_space_view_bar_chart.workspace = true re_space_view_spatial.workspace = true re_space_view_tensor.workspace = true @@ -87,6 +88,7 @@ egui.workspace = true image = { workspace = true, default-features = false, features = ["png"] } itertools = { workspace = true } once_cell = { workspace = true } +nohash-hasher.workspace = true poll-promise = { workspace = true, features = ["web"] } rfd.workspace = true ron.workspace = true diff --git a/crates/re_viewer/src/app_state.rs b/crates/re_viewer/src/app_state.rs index db1531f01007..3d309deb87b8 100644 --- a/crates/re_viewer/src/app_state.rs +++ b/crates/re_viewer/src/app_state.rs @@ -3,6 +3,7 @@ use ahash::HashMap; use re_data_store::StoreDb; use re_log_types::{LogMsg, StoreId, TimeRangeF}; use re_smart_channel::ReceiveSet; +use re_space_view::DataQuery as _; use re_viewer_context::{ AppOptions, Caches, CommandSender, ComponentUiRegistry, PlayState, RecordingConfig, SelectionState, SpaceViewClassRegistry, StoreContext, ViewerContext, @@ -114,6 +115,26 @@ impl AppState { &rec_cfg.time_ctrl.current_query(), ); + // Execute the queries for every `SpaceView` + let query_results = { + re_tracing::profile_scope!("query_results"); + viewport + .blueprint + .space_views + .values_mut() + .map(|space_view| { + ( + space_view.query_id(), + space_view.contents.execute_query( + space_view, + store_context, + &entities_per_system_per_class, + ), + ) + }) + .collect::<_>() + }; + let mut ctx = ViewerContext { app_options, cache, @@ -122,6 +143,7 @@ impl AppState { store_db, store_context, entities_per_system_per_class: &entities_per_system_per_class, + query_results: &query_results, rec_cfg, re_ui, render_ctx, @@ -141,6 +163,29 @@ impl AppState { viewport.on_frame_start(&mut ctx, &spaces_info); + // TODO(jleibs): Running the queries a second time is annoying, but we need + // to do this or else the auto_properties aren't right since they get populated + // in on_frame_start, but on_frame_start also needs the queries. + let updated_query_results = { + re_tracing::profile_scope!("updated_query_results"); + viewport + .blueprint + .space_views + .values_mut() + .map(|space_view| { + ( + space_view.query_id(), + space_view.contents.execute_query( + space_view, + store_context, + &entities_per_system_per_class, + ), + ) + }) + .collect::<_>() + }; + ctx.query_results = &updated_query_results; + time_panel.show_panel(&mut ctx, ui, app_blueprint.time_panel_expanded); selection_panel.show_panel( &mut ctx, diff --git a/crates/re_viewer/src/ui/selection_panel.rs b/crates/re_viewer/src/ui/selection_panel.rs index 004423568ea2..359a223f257f 100644 --- a/crates/re_viewer/src/ui/selection_panel.rs +++ b/crates/re_viewer/src/ui/selection_panel.rs @@ -15,7 +15,7 @@ use re_viewer_context::{ gpu_bridge::colormap_dropdown_button_ui, Item, SpaceViewClassName, SpaceViewId, UiVerbosity, ViewerContext, }; -use re_viewport::{external::re_space_view::DataQuery as _, Viewport, ViewportBlueprint}; +use re_viewport::{Viewport, ViewportBlueprint}; use crate::ui::visible_history::visible_history_ui; @@ -491,61 +491,59 @@ fn blueprint_ui( let entity_path = &instance_path.entity_path; let as_group = false; - let data_result = space_view.contents.resolve( - space_view, - ctx.store_context, - ctx.entities_per_system_per_class, - &instance_path.entity_path, - as_group, - ); + let query_result = ctx.lookup_query_result(space_view.query_id()); + if let Some(data_result) = query_result + .tree + .lookup_result_by_path_and_group(entity_path, as_group) + .cloned() + { + let mut props = data_result + .individual_properties + .clone() + .unwrap_or_default(); + entity_props_ui( + ctx, + ui, + &space_view_class, + Some(entity_path), + &mut props, + &data_result.resolved_properties, + ); + data_result.save_override(Some(props), ctx); + } + } + } + } + } + Item::DataBlueprintGroup(space_view_id, data_blueprint_group_handle) => { + if let Some(space_view) = viewport.blueprint.space_view_mut(space_view_id) { + if let Some(group) = space_view.contents.group_mut(*data_blueprint_group_handle) { + let group_path = group.group_path.clone(); + let as_group = true; + + let query_result = ctx.lookup_query_result(space_view.query_id()); + if let Some(data_result) = query_result + .tree + .lookup_result_by_path_and_group(&group_path, as_group) + .cloned() + { + let space_view_class = *space_view.class_name(); let mut props = data_result .individual_properties .clone() .unwrap_or_default(); + entity_props_ui( ctx, ui, &space_view_class, - Some(entity_path), + None, &mut props, &data_result.resolved_properties, ); data_result.save_override(Some(props), ctx); } - } - } - } - - Item::DataBlueprintGroup(space_view_id, data_blueprint_group_handle) => { - if let Some(space_view) = viewport.blueprint.space_view_mut(space_view_id) { - if let Some(group) = space_view.contents.group_mut(*data_blueprint_group_handle) { - let group_path = group.group_path.clone(); - let as_group = true; - - let data_result = space_view.contents.resolve( - space_view, - ctx.store_context, - ctx.entities_per_system_per_class, - &group_path, - as_group, - ); - - let space_view_class = *space_view.class_name(); - let mut props = data_result - .individual_properties - .clone() - .unwrap_or_default(); - - entity_props_ui( - ctx, - ui, - &space_view_class, - None, - &mut props, - &data_result.resolved_properties, - ); - data_result.save_override(Some(props), ctx); } else { ctx.selection_state_mut().clear_current(); } diff --git a/crates/re_viewer_context/src/blueprint_id.rs b/crates/re_viewer_context/src/blueprint_id.rs index c6db29118927..260e73865d50 100644 --- a/crates/re_viewer_context/src/blueprint_id.rs +++ b/crates/re_viewer_context/src/blueprint_id.rs @@ -87,6 +87,11 @@ impl BlueprintId { pub fn registry() -> &'static EntityPath { T::registry() } + + #[inline] + pub fn uuid(&self) -> uuid::Uuid { + self.id + } } impl From for BlueprintId { diff --git a/crates/re_viewer_context/src/lib.rs b/crates/re_viewer_context/src/lib.rs index a65dc082fe32..a635c9b17ecf 100644 --- a/crates/re_viewer_context/src/lib.rs +++ b/crates/re_viewer_context/src/lib.rs @@ -9,6 +9,7 @@ mod caches; mod command_sender; mod component_ui_registry; mod item; +mod query_context; mod selection_history; mod selection_state; mod space_view; @@ -34,6 +35,7 @@ pub use command_sender::{ pub use component_ui_registry::{ComponentUiRegistry, UiVerbosity}; pub use item::{resolve_mono_instance_path, resolve_mono_instance_path_item, Item, ItemCollection}; use nohash_hasher::{IntMap, IntSet}; +pub use query_context::{DataQueryResult, DataResultHandle, DataResultNode, DataResultTree}; use re_log_types::EntityPath; pub use selection_history::SelectionHistory; pub use selection_state::{ diff --git a/crates/re_viewer_context/src/query_context.rs b/crates/re_viewer_context/src/query_context.rs new file mode 100644 index 000000000000..e37cfcf1aba6 --- /dev/null +++ b/crates/re_viewer_context/src/query_context.rs @@ -0,0 +1,152 @@ +use ahash::HashMap; +use once_cell::sync::Lazy; +use re_log_types::EntityPath; +use slotmap::SlotMap; +use smallvec::SmallVec; + +use crate::{DataQueryId, DataResult, ViewerContext}; + +slotmap::new_key_type! { + /// Identifier for a [`DataResultNode`] + pub struct DataResultHandle; +} + +/// The result of executing a single data query +pub struct DataQueryResult { + /// The [`DataQueryId`] for the query that generated this result + pub id: DataQueryId, + + /// The [`DataResultTree`] for the query + pub tree: DataResultTree, +} + +impl Clone for DataQueryResult { + fn clone(&self) -> Self { + re_tracing::profile_function!(); + Self { + id: self.id, + tree: self.tree.clone(), + } + } +} + +impl Default for DataQueryResult { + fn default() -> Self { + Self { + id: DataQueryId::invalid(), + tree: DataResultTree::default(), + } + } +} + +/// A hierarchical tree of [`DataResult`]s +#[derive(Clone, Default)] +pub struct DataResultTree { + data_results: SlotMap, + // TODO(jleibs): Decide if we really want to compute this per-query. + // at the moment we only look up a single path per frame for the selection panel. It's probably + // less over-head to just walk the tree once instead of pre-computing an entire map we use for + // a single lookup. + data_results_by_path: HashMap<(EntityPath, bool), DataResultHandle>, + root_handle: Option, +} + +/// A single node in the [`DataResultTree`] +#[derive(Clone, Debug)] +pub struct DataResultNode { + pub data_result: DataResult, + pub children: SmallVec<[DataResultHandle; 4]>, +} + +impl DataResultTree { + pub fn new( + data_results: SlotMap, + root_handle: Option, + ) -> Self { + re_tracing::profile_function!(); + let data_results_by_path = data_results + .iter() + .map(|(handle, node)| { + ( + ( + node.data_result.entity_path.clone(), + node.data_result.is_group, + ), + handle, + ) + }) + .collect(); + + Self { + data_results, + data_results_by_path, + root_handle, + } + } + + pub fn root_handle(&self) -> Option { + self.root_handle + } + + pub fn root_node(&self) -> Option<&DataResultNode> { + self.root_handle + .and_then(|handle| self.data_results.get(handle)) + } + + /// Depth-first traversal of the tree, calling `visitor` on each result. + pub fn visit(&self, visitor: &mut impl FnMut(DataResultHandle)) { + if let Some(root_handle) = self.root_handle { + self.visit_recursive(root_handle, visitor); + } + } + + /// Look up a [`DataResult`] in the tree based on its handle. + pub fn lookup_result(&self, handle: DataResultHandle) -> Option<&DataResult> { + self.data_results.get(handle).map(|node| &node.data_result) + } + + /// Look up a [`DataResultNode`] in the tree based on its handle. + pub fn lookup_node(&self, handle: DataResultHandle) -> Option<&DataResultNode> { + self.data_results.get(handle) + } + + /// Look up a [`DataResultNode`] in the tree based on an [`EntityPath`]. + pub fn lookup_result_by_path_and_group( + &self, + path: &EntityPath, + is_group: bool, + ) -> Option<&DataResult> { + self.data_results_by_path + .get(&(path.clone(), is_group)) + .and_then(|handle| self.lookup_result(*handle)) + } + + fn visit_recursive( + &self, + handle: DataResultHandle, + visitor: &mut impl FnMut(DataResultHandle), + ) { + if let Some(result) = self.data_results.get(handle) { + visitor(handle); + + for child in &result.children { + self.visit_recursive(*child, visitor); + } + } + } +} + +static EMPTY_QUERY: Lazy = Lazy::::new(Default::default); + +impl ViewerContext<'_> { + pub fn lookup_query_result(&self, id: DataQueryId) -> &DataQueryResult { + self.query_results.get(&id).unwrap_or_else(|| { + if cfg!(debug_assertions) { + re_log::warn!("Tried looking up a query that doesn't exist: {:?}", id); + } else { + re_log::debug!("Tried looking up a query that doesn't exist: {:?}", id); + } + &EMPTY_QUERY + }) + } +} diff --git a/crates/re_viewer_context/src/space_view/view_query.rs b/crates/re_viewer_context/src/space_view/view_query.rs index 8d3219cac897..9ee11e2c3e83 100644 --- a/crates/re_viewer_context/src/space_view/view_query.rs +++ b/crates/re_viewer_context/src/space_view/view_query.rs @@ -19,7 +19,7 @@ use crate::{ /// /// In the future `resolved_properties` will be replaced by a `StoreView` that contains /// the relevant data overrides for the given query. -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct DataResult { /// Where to retrieve the data from. // TODO(jleibs): This should eventually become a more generalized (StoreView + EntityPath) reference to handle diff --git a/crates/re_viewer_context/src/viewer_context.rs b/crates/re_viewer_context/src/viewer_context.rs index 0a7a17a9a40e..24543471654c 100644 --- a/crates/re_viewer_context/src/viewer_context.rs +++ b/crates/re_viewer_context/src/viewer_context.rs @@ -1,12 +1,14 @@ +use ahash::HashMap; use re_data_store::store_db::StoreDb; use re_data_store::{EntityTree, TimeHistogramPerTimeline}; use re_log_types::TimeRange; -use crate::EntitiesPerSystemPerClass; +use crate::query_context::DataQueryResult; use crate::{ item::resolve_mono_instance_path_item, AppOptions, Caches, CommandSender, ComponentUiRegistry, Item, ItemCollection, SelectionState, SpaceViewClassRegistry, StoreContext, TimeControl, }; +use crate::{DataQueryId, EntitiesPerSystemPerClass}; /// Common things needed by many parts of the viewer. pub struct ViewerContext<'a> { @@ -34,6 +36,9 @@ pub struct ViewerContext<'a> { /// Mapping from class and system to entities for the store pub entities_per_system_per_class: &'a EntitiesPerSystemPerClass, + /// All the query results for this frame + pub query_results: &'a HashMap, + /// UI config for the current recording (found in [`StoreDb`]). pub rec_cfg: &'a mut RecordingConfig, diff --git a/crates/re_viewport/src/space_view.rs b/crates/re_viewport/src/space_view.rs index 2445f44789d7..8d20ca5908fd 100644 --- a/crates/re_viewport/src/space_view.rs +++ b/crates/re_viewport/src/space_view.rs @@ -2,14 +2,12 @@ use nohash_hasher::IntMap; use re_data_store::{EntityPath, EntityProperties, EntityTree, TimeInt, VisibleHistory}; use re_data_store::{EntityPropertiesComponent, EntityPropertyMap}; use re_renderer::ScreenshotProcessor; -use re_space_view::{ - DataQuery, EntityOverrides, PropertyResolver, ScreenshotMode, SpaceViewContents, -}; +use re_space_view::{EntityOverrides, PropertyResolver, ScreenshotMode, SpaceViewContents}; use re_space_view_time_series::TimeSeriesSpaceView; use re_viewer_context::{ - DataResult, DynSpaceViewClass, EntitiesPerSystem, PerSystemDataResults, SpaceViewClassName, - SpaceViewHighlights, SpaceViewId, SpaceViewState, SpaceViewSystemRegistry, StoreContext, - ViewerContext, + DataQueryId, DataResult, DynSpaceViewClass, EntitiesPerSystem, PerSystemDataResults, + SpaceViewClassName, SpaceViewHighlights, SpaceViewId, SpaceViewState, SpaceViewSystemRegistry, + StoreContext, ViewerContext, }; use crate::{ @@ -238,16 +236,16 @@ impl SpaceViewBlueprint { let class = self.class(ctx.space_view_class_registry); - let data_results = - self.contents - .execute_query(self, ctx.store_context, ctx.entities_per_system_per_class); + // TODO(jleibs): Sort out borrow-checker to avoid the need to clone here + // while still being able to pass &ViewerContext down the chain. + let query_result = ctx.lookup_query_result(self.query_id()).clone(); let mut per_system_data_results = PerSystemDataResults::default(); { re_tracing::profile_scope!("per_system_data_results"); - data_results.visit(&mut |handle| { - if let Some(result) = data_results.lookup_result(handle) { + query_result.tree.visit(&mut |handle| { + if let Some(result) = query_result.tree.lookup_result(handle) { for system in &result.view_parts { per_system_data_results .entry(*system) @@ -356,10 +354,16 @@ impl SpaceViewBlueprint { *self.contents.per_system_entities_mut() = per_system_entities; } + #[inline] pub fn entity_path(&self) -> EntityPath { self.id.as_entity_path() } + #[inline] + pub fn query_id(&self) -> DataQueryId { + self.id.uuid().into() + } + pub fn root_data_result(&self, ctx: &StoreContext<'_>) -> DataResult { let entity_path = self.entity_path(); @@ -443,27 +447,11 @@ impl PropertyResolver for SpaceViewBlueprint { mod tests { use re_data_store::StoreDb; use re_log_types::{DataCell, DataRow, RowId, StoreId, TimePoint}; - use re_space_view::DataResultTree; + use re_space_view::DataQuery as _; use re_viewer_context::{EntitiesPerSystemPerClass, StoreContext}; use super::*; - fn find_in_tree<'a>( - tree: &'a DataResultTree, - path: &EntityPath, - is_group: bool, - ) -> Option<&'a DataResult> { - let mut return_result = None; - tree.visit(&mut |handle| { - if let Some(result) = tree.lookup_result(handle) { - if result.entity_path == *path && result.is_group == is_group { - return_result = Some(result); - } - } - }); - return_result - } - fn save_override(props: EntityProperties, path: &EntityPath, store: &mut StoreDb) { let component = EntityPropertiesComponent { props }; let row = DataRow::from_cells1_sized( @@ -516,17 +504,24 @@ mod tests { all_recordings: vec![], }; - let result_tree = space_view.contents.execute_query( + let query_result = space_view.contents.execute_query( &space_view, &ctx, &entities_per_system_per_class, ); - let parent = find_in_tree(&result_tree, &EntityPath::from("parent"), false).unwrap(); - let child1 = - find_in_tree(&result_tree, &EntityPath::from("parent/skip/child1"), false).unwrap(); - let child2 = - find_in_tree(&result_tree, &EntityPath::from("parent/skip/child2"), false).unwrap(); + let parent = query_result + .tree + .lookup_result_by_path_and_group(&EntityPath::from("parent"), false) + .unwrap(); + let child1 = query_result + .tree + .lookup_result_by_path_and_group(&EntityPath::from("parent/skip/child1"), false) + .unwrap(); + let child2 = query_result + .tree + .lookup_result_by_path_and_group(&EntityPath::from("parent/skip/child2"), false) + .unwrap(); for result in [parent, child1, child2] { assert_eq!(result.resolved_properties, EntityProperties::default(),); @@ -547,19 +542,28 @@ mod tests { all_recordings: vec![], }; - let result_tree = space_view.contents.execute_query( + let query_result = space_view.contents.execute_query( &space_view, &ctx, &entities_per_system_per_class, ); - let parent_group = - find_in_tree(&result_tree, &EntityPath::from("parent"), true).unwrap(); - let parent = find_in_tree(&result_tree, &EntityPath::from("parent"), false).unwrap(); - let child1 = - find_in_tree(&result_tree, &EntityPath::from("parent/skip/child1"), false).unwrap(); - let child2 = - find_in_tree(&result_tree, &EntityPath::from("parent/skip/child2"), false).unwrap(); + let parent_group = query_result + .tree + .lookup_result_by_path_and_group(&EntityPath::from("parent"), true) + .unwrap(); + let parent = query_result + .tree + .lookup_result_by_path_and_group(&EntityPath::from("parent"), false) + .unwrap(); + let child1 = query_result + .tree + .lookup_result_by_path_and_group(&EntityPath::from("parent/skip/child1"), false) + .unwrap(); + let child2 = query_result + .tree + .lookup_result_by_path_and_group(&EntityPath::from("parent/skip/child2"), false) + .unwrap(); assert!(!parent.resolved_properties.visible); @@ -585,17 +589,24 @@ mod tests { all_recordings: vec![], }; - let result_tree = space_view.contents.execute_query( + let query_result = space_view.contents.execute_query( &space_view, &ctx, &entities_per_system_per_class, ); - let parent = find_in_tree(&result_tree, &EntityPath::from("parent"), false).unwrap(); - let child1 = - find_in_tree(&result_tree, &EntityPath::from("parent/skip/child1"), false).unwrap(); - let child2 = - find_in_tree(&result_tree, &EntityPath::from("parent/skip/child2"), false).unwrap(); + let parent = query_result + .tree + .lookup_result_by_path_and_group(&EntityPath::from("parent"), false) + .unwrap(); + let child1 = query_result + .tree + .lookup_result_by_path_and_group(&EntityPath::from("parent/skip/child1"), false) + .unwrap(); + let child2 = query_result + .tree + .lookup_result_by_path_and_group(&EntityPath::from("parent/skip/child2"), false) + .unwrap(); for result in [parent, child1, child2] { assert!(!result.resolved_properties.visible); @@ -624,17 +635,24 @@ mod tests { all_recordings: vec![], }; - let result_tree = space_view.contents.execute_query( + let query_result = space_view.contents.execute_query( &space_view, &ctx, &entities_per_system_per_class, ); - let parent = find_in_tree(&result_tree, &EntityPath::from("parent"), false).unwrap(); - let child1 = - find_in_tree(&result_tree, &EntityPath::from("parent/skip/child1"), false).unwrap(); - let child2 = - find_in_tree(&result_tree, &EntityPath::from("parent/skip/child2"), false).unwrap(); + let parent = query_result + .tree + .lookup_result_by_path_and_group(&EntityPath::from("parent"), false) + .unwrap(); + let child1 = query_result + .tree + .lookup_result_by_path_and_group(&EntityPath::from("parent/skip/child1"), false) + .unwrap(); + let child2 = query_result + .tree + .lookup_result_by_path_and_group(&EntityPath::from("parent/skip/child2"), false) + .unwrap(); for result in [parent, child1, child2] { assert!(result.resolved_properties.visible_history.enabled); @@ -658,17 +676,24 @@ mod tests { all_recordings: vec![], }; - let result_tree = space_view.contents.execute_query( + let query_result = space_view.contents.execute_query( &space_view, &ctx, &entities_per_system_per_class, ); - let parent = find_in_tree(&result_tree, &EntityPath::from("parent"), false).unwrap(); - let child1 = - find_in_tree(&result_tree, &EntityPath::from("parent/skip/child1"), false).unwrap(); - let child2 = - find_in_tree(&result_tree, &EntityPath::from("parent/skip/child2"), false).unwrap(); + let parent = query_result + .tree + .lookup_result_by_path_and_group(&EntityPath::from("parent"), false) + .unwrap(); + let child1 = query_result + .tree + .lookup_result_by_path_and_group(&EntityPath::from("parent/skip/child1"), false) + .unwrap(); + let child2 = query_result + .tree + .lookup_result_by_path_and_group(&EntityPath::from("parent/skip/child2"), false) + .unwrap(); for result in [parent, child1] { assert!(result.resolved_properties.visible_history.enabled); diff --git a/crates/re_viewport/src/viewport_blueprint_ui.rs b/crates/re_viewport/src/viewport_blueprint_ui.rs index cde0aba079c1..cacd3bd8b698 100644 --- a/crates/re_viewport/src/viewport_blueprint_ui.rs +++ b/crates/re_viewport/src/viewport_blueprint_ui.rs @@ -3,10 +3,12 @@ use itertools::Itertools; use re_data_store::InstancePath; use re_data_ui::item_ui; -use re_space_view::{DataQuery as _, DataResultHandle, DataResultNode, DataResultTree}; use re_ui::list_item::ListItem; use re_ui::ReUi; -use re_viewer_context::{HoverHighlight, Item, SpaceViewId, ViewerContext}; +use re_viewer_context::{ + DataResultHandle, DataResultNode, DataResultTree, HoverHighlight, Item, SpaceViewId, + ViewerContext, +}; use crate::{ space_view_heuristics::all_possible_space_views, viewport_blueprint::TreeActions, @@ -150,11 +152,11 @@ impl ViewportBlueprint<'_> { }; debug_assert_eq!(space_view.id, *space_view_id); - let result_tree = space_view.contents.execute_query( - space_view, - ctx.store_context, - ctx.entities_per_system_per_class, - ); + // TODO(jleibs): Sort out borrow-checker to avoid the need to clone here + // while still being able to pass &ViewerContext down the chain. + let query_result = ctx.lookup_query_result(space_view.query_id()).clone(); + + let result_tree = &query_result.tree; let mut visibility_changed = false; let mut visible = self.tree.is_visible(tile_id); @@ -162,8 +164,7 @@ impl ViewportBlueprint<'_> { let item = Item::SpaceView(space_view.id); let default_open = result_tree - .root_handle - .and_then(|handle| result_tree.lookup_node(handle)) + .root_node() .map_or(false, Self::default_open_for_data_result); let collapsing_header_id = ui.id().with(space_view.id); @@ -187,14 +188,14 @@ impl ViewportBlueprint<'_> { response | vis_response }) .show_collapsing(ui, collapsing_header_id, default_open, |_, ui| { - if let Some(result_handle) = result_tree.root_handle { + if let Some(result_handle) = result_tree.root_handle() { // TODO(jleibs): handle the case where the only result // in the tree is a single path (no groups). This should never // happen for a SpaceViewContents. Self::space_view_blueprint_ui( ctx, ui, - &result_tree, + result_tree, result_handle, space_view, visible_child,