use nohash_hasher::IntMap;
use re_types_blueprint::blueprint::components::VisualizerOverrides;
use slotmap::SlotMap;
use smallvec::SmallVec;
use re_entity_db::{external::re_chunk_store::LatestAtQuery, EntityDb, EntityTree};
use re_log_types::{
path::RuleEffect, EntityPath, EntityPathFilter, EntityPathRule, EntityPathSubs, Timeline,
};
use re_types::{
blueprint::{
archetypes as blueprint_archetypes, components as blueprint_components,
components::QueryExpression,
},
Archetype as _, SpaceViewClassIdentifier,
};
use re_types_core::ComponentName;
use re_viewer_context::{
ApplicableEntities, DataQueryResult, DataResult, DataResultHandle, DataResultNode,
DataResultTree, IndicatedEntities, OverridePath, PerVisualizer, PropertyOverrides, QueryRange,
SpaceViewClassRegistry, SpaceViewId, ViewStates, ViewerContext, VisualizableEntities,
};
use crate::{SpaceViewBlueprint, ViewProperty};
pub struct SpaceViewContents {
pub blueprint_entity_path: EntityPath,
pub view_class_identifier: SpaceViewClassIdentifier,
pub entity_path_filter: EntityPathFilter,
}
impl SpaceViewContents {
pub fn is_equivalent(&self, other: &Self) -> bool {
self.view_class_identifier.eq(&other.view_class_identifier)
&& self.entity_path_filter.eq(&other.entity_path_filter)
}
pub fn entity_path_filter_is_superset_of(&self, other: &Self) -> bool {
if self.view_class_identifier != other.view_class_identifier {
return false;
}
self.entity_path_filter
.is_superset_of(&other.entity_path_filter)
}
}
impl SpaceViewContents {
pub fn new(
id: SpaceViewId,
view_class_identifier: SpaceViewClassIdentifier,
entity_path_filter: EntityPathFilter,
) -> Self {
let blueprint_entity_path = id.as_entity_path().join(&EntityPath::from_single_string(
blueprint_archetypes::SpaceViewContents::name().short_name(),
));
Self {
blueprint_entity_path,
view_class_identifier,
entity_path_filter,
}
}
pub fn from_db_or_default(
view_id: SpaceViewId,
blueprint_db: &EntityDb,
query: &LatestAtQuery,
view_class_identifier: SpaceViewClassIdentifier,
space_env: &EntityPathSubs,
) -> Self {
let property = ViewProperty::from_archetype::<blueprint_archetypes::SpaceViewContents>(
blueprint_db,
query,
view_id,
);
let expressions = match property.component_array_or_empty::<QueryExpression>() {
Ok(expressions) => expressions,
Err(err) => {
re_log::warn_once!(
"Failed to load SpaceViewContents for {:?} from blueprint store at {:?}: {}",
view_id,
property.blueprint_store_path,
err
);
Default::default()
}
};
let query = expressions.iter().map(|qe| qe.0.as_str());
let entity_path_filter =
EntityPathFilter::from_query_expressions_forgiving(query, space_env);
Self {
blueprint_entity_path: property.blueprint_store_path,
view_class_identifier,
entity_path_filter,
}
}
pub fn save_to_blueprint_store(&self, ctx: &ViewerContext<'_>) {
ctx.save_blueprint_archetype(
&self.blueprint_entity_path,
&blueprint_archetypes::SpaceViewContents::new(
self.entity_path_filter.iter_expressions(),
),
);
}
pub fn set_entity_path_filter(
&self,
ctx: &ViewerContext<'_>,
new_entity_path_filter: &EntityPathFilter,
) {
if &self.entity_path_filter == new_entity_path_filter {
return;
}
ctx.save_blueprint_component(
&self.blueprint_entity_path,
&new_entity_path_filter
.iter_expressions()
.map(|s| QueryExpression(s.into()))
.collect::<Vec<_>>(),
);
}
pub fn build_resolver<'a>(
&self,
space_view_class_registry: &'a re_viewer_context::SpaceViewClassRegistry,
space_view: &'a SpaceViewBlueprint,
applicable_entities_per_visualizer: &'a PerVisualizer<ApplicableEntities>,
visualizable_entities_per_visualizer: &'a PerVisualizer<VisualizableEntities>,
indicated_entities_per_visualizer: &'a PerVisualizer<IndicatedEntities>,
) -> DataQueryPropertyResolver<'a> {
let base_override_root = &self.blueprint_entity_path;
let individual_override_root =
base_override_root.join(&DataResult::INDIVIDUAL_OVERRIDES_PREFIX.into());
let recursive_override_root =
base_override_root.join(&DataResult::RECURSIVE_OVERRIDES_PREFIX.into());
DataQueryPropertyResolver {
space_view_class_registry,
space_view,
individual_override_root,
recursive_override_root,
applicable_entities_per_visualizer,
visualizable_entities_per_visualizer,
indicated_entities_per_visualizer,
}
}
pub fn remove_subtree_and_matching_rules(&self, ctx: &ViewerContext<'_>, path: EntityPath) {
let mut new_entity_path_filter = self.entity_path_filter.clone();
new_entity_path_filter.remove_subtree_and_matching_rules(path);
self.set_entity_path_filter(ctx, &new_entity_path_filter);
}
pub fn raw_add_entity_exclusion(&self, ctx: &ViewerContext<'_>, rule: EntityPathRule) {
let mut new_entity_path_filter = self.entity_path_filter.clone();
new_entity_path_filter.add_rule(RuleEffect::Exclude, rule);
self.set_entity_path_filter(ctx, &new_entity_path_filter);
}
pub fn raw_add_entity_inclusion(&self, ctx: &ViewerContext<'_>, rule: EntityPathRule) {
let mut new_entity_path_filter = self.entity_path_filter.clone();
new_entity_path_filter.add_rule(RuleEffect::Include, rule);
self.set_entity_path_filter(ctx, &new_entity_path_filter);
}
pub fn remove_filter_rule_for(&self, ctx: &ViewerContext<'_>, ent_path: &EntityPath) {
let mut new_entity_path_filter = self.entity_path_filter.clone();
new_entity_path_filter.remove_rule_for(ent_path);
self.set_entity_path_filter(ctx, &new_entity_path_filter);
}
pub fn execute_query(
&self,
ctx: &re_viewer_context::StoreContext<'_>,
visualizable_entities_for_visualizer_systems: &PerVisualizer<VisualizableEntities>,
) -> DataQueryResult {
re_tracing::profile_function!();
let mut data_results = SlotMap::<DataResultHandle, DataResultNode>::default();
let executor = QueryExpressionEvaluator {
visualizable_entities_for_visualizer_systems,
entity_path_filter: self.entity_path_filter.clone(),
recursive_override_base_path: self
.blueprint_entity_path
.join(&DataResult::RECURSIVE_OVERRIDES_PREFIX.into()),
individual_override_base_path: self
.blueprint_entity_path
.join(&DataResult::INDIVIDUAL_OVERRIDES_PREFIX.into()),
};
let mut num_matching_entities = 0;
let mut num_visualized_entities = 0;
let root_handle = {
re_tracing::profile_scope!("add_entity_tree_to_data_results_recursive");
executor.add_entity_tree_to_data_results_recursive(
ctx.recording.tree(),
&mut data_results,
&mut num_matching_entities,
&mut num_visualized_entities,
)
};
DataQueryResult {
tree: DataResultTree::new(data_results, root_handle),
num_matching_entities,
num_visualized_entities,
}
}
}
struct QueryExpressionEvaluator<'a> {
visualizable_entities_for_visualizer_systems: &'a PerVisualizer<VisualizableEntities>,
entity_path_filter: EntityPathFilter,
recursive_override_base_path: EntityPath,
individual_override_base_path: EntityPath,
}
impl<'a> QueryExpressionEvaluator<'a> {
fn add_entity_tree_to_data_results_recursive(
&self,
tree: &EntityTree,
data_results: &mut SlotMap<DataResultHandle, DataResultNode>,
num_matching_entities: &mut usize,
num_visualized_entities: &mut usize,
) -> Option<DataResultHandle> {
if !self
.entity_path_filter
.is_anything_in_subtree_included(&tree.path)
{
return None;
}
let entity_path = &tree.path;
let matches_filter = self.entity_path_filter.matches(entity_path);
*num_matching_entities += matches_filter as usize;
let visualizers: SmallVec<[_; 4]> = if matches_filter {
self.visualizable_entities_for_visualizer_systems
.iter()
.filter_map(|(visualizer, ents)| ents.contains(entity_path).then_some(*visualizer))
.collect()
} else {
Default::default()
};
*num_visualized_entities += !visualizers.is_empty() as usize;
let children: SmallVec<[_; 4]> = tree
.children
.values()
.filter_map(|subtree| {
self.add_entity_tree_to_data_results_recursive(
subtree,
data_results,
num_matching_entities,
num_visualized_entities,
)
})
.collect();
let exact_included = self.entity_path_filter.matches_exactly(entity_path);
if exact_included || !children.is_empty() || !visualizers.is_empty() {
Some(data_results.insert(DataResultNode {
data_result: DataResult {
entity_path: entity_path.clone(),
visualizers,
tree_prefix_only: !matches_filter,
property_overrides: PropertyOverrides {
resolved_component_overrides: IntMap::default(), recursive_path: self.recursive_override_base_path.join(entity_path),
individual_path: self.individual_override_base_path.join(entity_path),
query_range: QueryRange::default(), },
},
children,
}))
} else {
None
}
}
}
pub struct DataQueryPropertyResolver<'a> {
space_view_class_registry: &'a re_viewer_context::SpaceViewClassRegistry,
space_view: &'a SpaceViewBlueprint,
individual_override_root: EntityPath,
recursive_override_root: EntityPath,
applicable_entities_per_visualizer: &'a PerVisualizer<ApplicableEntities>,
visualizable_entities_per_visualizer: &'a PerVisualizer<VisualizableEntities>,
indicated_entities_per_visualizer: &'a PerVisualizer<IndicatedEntities>,
}
impl DataQueryPropertyResolver<'_> {
#[allow(clippy::too_many_arguments)]
fn update_overrides_recursive(
&self,
blueprint: &EntityDb,
blueprint_query: &LatestAtQuery,
active_timeline: &Timeline,
query_result: &mut DataQueryResult,
default_query_range: &QueryRange,
recursive_property_overrides: &IntMap<ComponentName, OverridePath>,
handle: DataResultHandle,
) {
let blueprint_engine = blueprint.storage_engine();
if let Some((child_handles, recursive_property_overrides)) =
query_result.tree.lookup_node_mut(handle).map(|node| {
let individual_override_path = self
.individual_override_root
.join(&node.data_result.entity_path);
let recursive_override_path = self
.recursive_override_root
.join(&node.data_result.entity_path);
if !node.data_result.visualizers.is_empty() {
re_tracing::profile_scope!("Update visualizers from overrides");
if let Some(viz_override) = blueprint
.latest_at_component::<VisualizerOverrides>(
&individual_override_path,
blueprint_query,
)
.map(|(_index, value)| value)
{
node.data_result.visualizers =
viz_override.0.iter().map(Into::into).collect();
} else {
node.data_result.visualizers = self
.space_view
.class(self.space_view_class_registry)
.choose_default_visualizers(
&node.data_result.entity_path,
self.applicable_entities_per_visualizer,
self.visualizable_entities_per_visualizer,
self.indicated_entities_per_visualizer,
);
}
}
let mut recursive_property_overrides =
std::borrow::Cow::Borrowed(recursive_property_overrides);
if let Some(recursive_override_subtree) =
blueprint.tree().subtree(&recursive_override_path)
{
for component_name in blueprint_engine
.store()
.all_components_for_entity(&recursive_override_subtree.path)
.unwrap_or_default()
{
if let Some(component_data) = blueprint
.storage_engine()
.cache()
.latest_at(blueprint_query, &recursive_override_path, [component_name])
.component_batch_raw(&component_name)
{
if !component_data.is_empty() {
recursive_property_overrides.to_mut().insert(
component_name,
OverridePath::blueprint_path(recursive_override_path.clone()),
);
}
}
}
}
let resolved_component_overrides = &mut node
.data_result
.property_overrides
.resolved_component_overrides;
*resolved_component_overrides = (*recursive_property_overrides).clone();
if let Some(individual_override_subtree) =
blueprint.tree().subtree(&individual_override_path)
{
for component_name in blueprint_engine
.store()
.all_components_for_entity(&individual_override_subtree.path)
.unwrap_or_default()
{
if let Some(component_data) = blueprint
.storage_engine()
.cache()
.latest_at(blueprint_query, &individual_override_path, [component_name])
.component_batch_raw(&component_name)
{
if !component_data.is_empty() {
resolved_component_overrides.insert(
component_name,
OverridePath::blueprint_path(individual_override_path.clone()),
);
}
}
}
}
use re_types::Loggable as _;
let latest_at_results = blueprint.latest_at(
blueprint_query,
&recursive_override_path,
std::iter::once(blueprint_components::VisibleTimeRange::name()),
);
let visible_time_ranges =
latest_at_results.component_batch::<blueprint_components::VisibleTimeRange>();
let time_range = visible_time_ranges.as_ref().and_then(|ranges| {
ranges
.iter()
.find(|range| range.timeline.as_str() == active_timeline.name().as_str())
});
node.data_result.property_overrides.query_range = time_range.map_or_else(
|| default_query_range.clone(),
|time_range| QueryRange::TimeRange(time_range.0.range.clone()),
);
(node.children.clone(), recursive_property_overrides)
})
{
for child in child_handles {
self.update_overrides_recursive(
blueprint,
blueprint_query,
active_timeline,
query_result,
default_query_range,
&recursive_property_overrides,
child,
);
}
}
}
pub fn update_overrides(
&self,
blueprint: &EntityDb,
blueprint_query: &LatestAtQuery,
active_timeline: &Timeline,
space_view_class_registry: &SpaceViewClassRegistry,
query_result: &mut DataQueryResult,
view_states: &mut ViewStates,
) {
re_tracing::profile_function!();
if let Some(root) = query_result.tree.root_handle() {
let recursive_property_overrides = Default::default();
let class = self.space_view.class(space_view_class_registry);
let view_state = view_states.get_mut_or_create(self.space_view.id, class);
let default_query_range = self.space_view.query_range(
blueprint,
blueprint_query,
active_timeline,
space_view_class_registry,
view_state,
);
self.update_overrides_recursive(
blueprint,
blueprint_query,
active_timeline,
query_result,
&default_query_range,
&recursive_property_overrides,
root,
);
}
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use re_chunk::{Chunk, RowId};
use re_entity_db::EntityDb;
use re_log_types::{example_components::MyPoint, StoreId, TimePoint, Timeline};
use re_viewer_context::{StoreContext, StoreHub, VisualizableEntities};
use super::*;
#[test]
fn test_query_results() {
let space_env = Default::default();
let mut recording = EntityDb::new(StoreId::random(re_log_types::StoreKind::Recording));
let blueprint = EntityDb::new(StoreId::random(re_log_types::StoreKind::Blueprint));
let timeline_frame = Timeline::new_sequence("frame");
let timepoint = TimePoint::from_iter([(timeline_frame, 10)]);
for entity_path in ["parent", "parent/skipped/child1", "parent/skipped/child2"] {
let row_id = RowId::new();
let point = MyPoint::new(1.0, 2.0);
let chunk = Chunk::builder(entity_path.into())
.with_component_batch(row_id, timepoint.clone(), &[point] as _)
.build()
.unwrap();
recording.add_chunk(&Arc::new(chunk)).unwrap();
}
let mut visualizable_entities_for_visualizer_systems =
PerVisualizer::<VisualizableEntities>::default();
visualizable_entities_for_visualizer_systems
.0
.entry("Points3D".into())
.or_insert_with(|| {
VisualizableEntities(
[
EntityPath::from("parent"),
EntityPath::from("parent/skipped/child1"),
]
.into_iter()
.collect(),
)
});
let ctx = StoreContext {
app_id: re_log_types::ApplicationId::unknown(),
blueprint: &blueprint,
default_blueprint: None,
recording: &recording,
bundle: &Default::default(),
caches: &Default::default(),
hub: &StoreHub::test_hub(),
};
struct Scenario {
filter: &'static str,
outputs: Vec<&'static str>,
}
let scenarios: Vec<Scenario> = vec![
Scenario {
filter: "+ /**",
outputs: vec![
"/**",
"/parent",
"/parent/skipped",
"/parent/skipped/child1", ],
},
Scenario {
filter: "+ parent/skipped/**",
outputs: vec![
"/**",
"/parent/**", "/parent/skipped",
"/parent/skipped/child1", ],
},
Scenario {
filter: r"+ parent
+ parent/skipped/child2",
outputs: vec![
"/**", "/parent",
"/parent/skipped/**", "/parent/skipped/child2",
],
},
Scenario {
filter: r"+ parent/skipped
+ parent/skipped/child2
+ parent/**",
outputs: vec![
"/**",
"/parent",
"/parent/skipped", "/parent/skipped/child1", "/parent/skipped/child2",
],
},
Scenario {
filter: r"+ parent/skipped
+ parent/skipped/child2
+ parent/**
- parent",
outputs: vec![
"/**",
"/parent/**", "/parent/skipped", "/parent/skipped/child1", "/parent/skipped/child2",
],
},
Scenario {
filter: r"+ parent/**
- parent/skipped/**",
outputs: vec!["/**", "/parent"], },
Scenario {
filter: r"+ parent/**
+ parent/skipped/child2
- parent/skipped/child1",
outputs: vec![
"/**",
"/parent",
"/parent/skipped",
"/parent/skipped/child2", ],
},
Scenario {
filter: r"+ not/found",
outputs: vec![],
},
];
for (i, Scenario { filter, outputs }) in scenarios.into_iter().enumerate() {
let contents = SpaceViewContents::new(
SpaceViewId::random(),
"3D".into(),
EntityPathFilter::parse_forgiving(filter, &space_env),
);
let query_result =
contents.execute_query(&ctx, &visualizable_entities_for_visualizer_systems);
let mut visited = vec![];
query_result.tree.visit(&mut |node| {
let result = &node.data_result;
if result.entity_path == EntityPath::root() {
visited.push("/**".to_owned());
} else if result.tree_prefix_only {
visited.push(format!("{}/**", result.entity_path));
assert!(result.visualizers.is_empty());
} else {
visited.push(result.entity_path.to_string());
}
true
});
assert_eq!(visited, outputs, "Scenario {i}, filter: {filter}");
}
}
}