Skip to content

Commit

Permalink
[pure refactor] Make selection state independent of blueprint (#2035)
Browse files Browse the repository at this point in the history
* selection history no longer depends on blueprint

* selection state almost independent of blueprint now

* selection state is now free of blueprint dependencies

* spelling
  • Loading branch information
Wumpf authored May 3, 2023
1 parent 40fc2fd commit d223a5b
Show file tree
Hide file tree
Showing 10 changed files with 377 additions and 364 deletions.
2 changes: 1 addition & 1 deletion crates/re_viewer/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,7 @@ impl eframe::App for App {
log_db,
)
.selection_state
.on_frame_start(blueprint);
.on_frame_start(|item| blueprint.is_item_valid(item));

{
// TODO(andreas): store the re_renderer somewhere else.
Expand Down
29 changes: 3 additions & 26 deletions crates/re_viewer/src/misc/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,29 +29,6 @@ impl std::fmt::Debug for Item {
}

impl Item {
/// If `false`, the selection is referring to data that is no longer present.
pub(crate) fn is_valid(&self, blueprint: &crate::ui::Blueprint) -> bool {
match self {
Item::ComponentPath(_) => true,
Item::InstancePath(space_view_id, _) => space_view_id
.map(|space_view_id| blueprint.viewport.space_view(&space_view_id).is_some())
.unwrap_or(true),
Item::SpaceView(space_view_id) => {
blueprint.viewport.space_view(space_view_id).is_some()
}
Item::DataBlueprintGroup(space_view_id, data_blueprint_group_handle) => {
if let Some(space_view) = blueprint.viewport.space_view(space_view_id) {
space_view
.data_blueprint
.group(*data_blueprint_group_handle)
.is_some()
} else {
false
}
}
}
}

pub fn kind(self: &Item) -> &'static str {
match self {
Item::InstancePath(space_view_id, instance_path) => {
Expand Down Expand Up @@ -133,8 +110,8 @@ impl ItemCollection {
None
}

/// Remove all invalid selections.
pub fn purge_invalid(&mut self, blueprint: &crate::ui::Blueprint) {
self.items.retain(|selection| selection.is_valid(blueprint));
/// Retains elements that fulfil a certain condition.
pub fn retain(&mut self, f: impl Fn(&Item) -> bool) {
self.items.retain(|item| f(item));
}
}
9 changes: 7 additions & 2 deletions crates/re_viewer/src/misc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub(crate) mod mesh_loader;
pub mod queries;
mod selection_state;
pub(crate) mod space_info;
mod space_view_highlights;
pub(crate) mod time_control;
pub(crate) mod time_control_ui;
mod transform_cache;
Expand All @@ -32,11 +33,15 @@ pub use {
app_options::*,
item::{Item, ItemCollection},
selection_state::{
HoverHighlight, HoveredSpace, InteractionHighlight, OptionalSpaceViewEntityHighlight,
SelectionHighlight, SelectionState, SpaceViewHighlights, SpaceViewOutlineMasks,
HoverHighlight, HoveredSpace, InteractionHighlight, SelectionHighlight, SelectionState,
},
};

pub use space_view_highlights::{
highlights_for_space_view, OptionalSpaceViewEntityHighlight, SpaceViewHighlights,
SpaceViewOutlineMasks,
};

// ----------------------------------------------------------------------------

pub fn help_hover_button(ui: &mut egui::Ui) -> egui::Response {
Expand Down
273 changes: 5 additions & 268 deletions crates/re_viewer/src/misc/selection_state.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
use ahash::{HashMap, HashSet};
use egui::NumExt;
use lazy_static::lazy_static;
use nohash_hasher::IntMap;
use ahash::HashSet;

use re_data_store::EntityPath;
use re_log_types::{component_types::InstanceKey, EntityPathHash};
use re_renderer::OutlineMaskPreference;

use crate::ui::{Blueprint, SelectionHistory, SpaceView, SpaceViewId};

use super::{Item, ItemCollection};
use crate::ui::SelectionHistory;

#[derive(Clone, Default, Debug, PartialEq)]
pub enum HoveredSpace {
Expand Down Expand Up @@ -85,82 +79,6 @@ impl InteractionHighlight {
}
}

/// Highlights of a specific entity path in a specific space view.
///
/// Using this in bulk on many instances is faster than querying single objects.
#[derive(Default)]
pub struct SpaceViewEntityHighlight {
overall: InteractionHighlight,
instances: ahash::HashMap<InstanceKey, InteractionHighlight>,
}

#[derive(Copy, Clone)]
pub struct OptionalSpaceViewEntityHighlight<'a>(Option<&'a SpaceViewEntityHighlight>);

impl<'a> OptionalSpaceViewEntityHighlight<'a> {
pub fn index_highlight(&self, instance_key: InstanceKey) -> InteractionHighlight {
match self.0 {
Some(entity_highlight) => entity_highlight
.instances
.get(&instance_key)
.cloned()
.unwrap_or_default()
.max(entity_highlight.overall),
None => InteractionHighlight::default(),
}
}
}

#[derive(Default)]
pub struct SpaceViewOutlineMasks {
pub overall: OutlineMaskPreference,
pub instances: ahash::HashMap<InstanceKey, OutlineMaskPreference>,
pub any_selection_highlight: bool,
}

lazy_static! {
static ref SPACEVIEW_OUTLINE_MASK_NONE: SpaceViewOutlineMasks =
SpaceViewOutlineMasks::default();
}

impl SpaceViewOutlineMasks {
pub fn index_outline_mask(&self, instance_key: InstanceKey) -> OutlineMaskPreference {
self.instances
.get(&instance_key)
.cloned()
.unwrap_or_default()
.with_fallback_to(self.overall)
}
}

/// Highlights in a specific space view.
///
/// Using this in bulk on many objects is faster than querying single objects.
#[derive(Default)]
pub struct SpaceViewHighlights {
highlighted_entity_paths: IntMap<EntityPathHash, SpaceViewEntityHighlight>,
outlines_masks: IntMap<EntityPathHash, SpaceViewOutlineMasks>,
}

impl SpaceViewHighlights {
pub fn entity_highlight(
&self,
entity_path_hash: EntityPathHash,
) -> OptionalSpaceViewEntityHighlight<'_> {
OptionalSpaceViewEntityHighlight(self.highlighted_entity_paths.get(&entity_path_hash))
}

pub fn entity_outline_mask(&self, entity_path_hash: EntityPathHash) -> &SpaceViewOutlineMasks {
self.outlines_masks
.get(&entity_path_hash)
.unwrap_or(&SPACEVIEW_OUTLINE_MASK_NONE)
}

pub fn any_outlines(&self) -> bool {
!self.outlines_masks.is_empty()
}
}

/// Selection and hover state
#[derive(Default, serde::Deserialize, serde::Serialize)]
#[serde(default)]
Expand All @@ -173,7 +91,7 @@ pub struct SelectionState {

/// History of selections (what was selected previously).
#[serde(skip)]
history: SelectionHistory,
pub history: SelectionHistory,

/// What objects are hovered? Read from this.
#[serde(skip)]
Expand All @@ -194,10 +112,10 @@ pub struct SelectionState {

impl SelectionState {
/// Called at the start of each frame
pub fn on_frame_start(&mut self, blueprint: &Blueprint) {
pub fn on_frame_start(&mut self, item_retain_condition: impl Fn(&Item) -> bool) {
crate::profile_function!();

self.history.on_frame_start(blueprint);
self.history.retain(&item_retain_condition);

self.hovered_space_previous_frame =
std::mem::replace(&mut self.hovered_space_this_frame, HoveredSpace::None);
Expand Down Expand Up @@ -284,15 +202,6 @@ impl SelectionState {
self.hovered_space_this_frame = space;
}

pub fn selection_ui(
&mut self,
re_ui: &re_ui::ReUi,
ui: &mut egui::Ui,
blueprint: &mut Blueprint,
) -> Option<ItemCollection> {
self.history.selection_ui(re_ui, ui, blueprint)
}

pub fn highlight_for_ui_element(&self, test: &Item) -> HoverHighlight {
let hovered = self
.hovered_previous_frame
Expand Down Expand Up @@ -328,176 +237,4 @@ impl SelectionState {
HoverHighlight::None
}
}

pub fn highlights_for_space_view(
&self,
space_view_id: SpaceViewId,
space_views: &HashMap<SpaceViewId, SpaceView>,
) -> SpaceViewHighlights {
crate::profile_function!();

let mut highlighted_entity_paths =
IntMap::<EntityPathHash, SpaceViewEntityHighlight>::default();
let mut outlines_masks = IntMap::<EntityPathHash, SpaceViewOutlineMasks>::default();

let mut selection_mask_index: u8 = 0;
let mut hover_mask_index: u8 = 0;
let mut next_selection_mask = || {
// We don't expect to overflow u8, but if we do, don't use the "background mask".
selection_mask_index = selection_mask_index.wrapping_add(1).at_least(1);
OutlineMaskPreference::some(0, selection_mask_index)
};
let mut next_hover_mask = || {
// We don't expect to overflow u8, but if we do, don't use the "background mask".
hover_mask_index = hover_mask_index.wrapping_add(1).at_least(1);
OutlineMaskPreference::some(hover_mask_index, 0)
};

for current_selection in self.selection.iter() {
match current_selection {
Item::ComponentPath(_) | Item::SpaceView(_) => {}

Item::DataBlueprintGroup(group_space_view_id, group_handle) => {
if *group_space_view_id == space_view_id {
if let Some(space_view) = space_views.get(group_space_view_id) {
// Everything in the same group should receive the same selection outline.
// (Due to the way outline masks work in re_renderer, we can't leave the hover channel empty)
let selection_mask = next_selection_mask();

space_view.data_blueprint.visit_group_entities_recursively(
*group_handle,
&mut |entity_path: &EntityPath| {
highlighted_entity_paths
.entry(entity_path.hash())
.or_default()
.overall
.selection = SelectionHighlight::SiblingSelection;
let outline_mask_ids =
outlines_masks.entry(entity_path.hash()).or_default();
outline_mask_ids.overall =
selection_mask.with_fallback_to(outline_mask_ids.overall);
outline_mask_ids.any_selection_highlight = true;
},
);
}
}
}

Item::InstancePath(selected_space_view_context, selected_instance) => {
{
let highlight = if *selected_space_view_context == Some(space_view_id) {
SelectionHighlight::Selection
} else {
SelectionHighlight::SiblingSelection
};

let highlighted_entity = highlighted_entity_paths
.entry(selected_instance.entity_path.hash())
.or_default();
let highlight_target = if let Some(selected_index) =
selected_instance.instance_key.specific_index()
{
&mut highlighted_entity
.instances
.entry(selected_index)
.or_default()
.selection
} else {
&mut highlighted_entity.overall.selection
};
*highlight_target = (*highlight_target).max(highlight);
}
{
let outline_mask_ids = outlines_masks
.entry(selected_instance.entity_path.hash())
.or_default();
outline_mask_ids.any_selection_highlight = true;
let outline_mask_target = if let Some(selected_index) =
selected_instance.instance_key.specific_index()
{
outline_mask_ids
.instances
.entry(selected_index)
.or_default()
} else {
&mut outline_mask_ids.overall
};
*outline_mask_target =
next_selection_mask().with_fallback_to(*outline_mask_target);
}
}
};
}

for current_hover in self.hovered_previous_frame.iter() {
match current_hover {
Item::ComponentPath(_) | Item::SpaceView(_) => {}

Item::DataBlueprintGroup(group_space_view_id, group_handle) => {
// Unlike for selected objects/data we are more picky for data blueprints with our hover highlights
// since they are truly local to a space view.
if *group_space_view_id == space_view_id {
if let Some(space_view) = space_views.get(group_space_view_id) {
// Everything in the same group should receive the same selection outline.
let hover_mask = next_hover_mask();

space_view.data_blueprint.visit_group_entities_recursively(
*group_handle,
&mut |entity_path: &EntityPath| {
highlighted_entity_paths
.entry(entity_path.hash())
.or_default()
.overall
.hover = HoverHighlight::Hovered;
let mask =
outlines_masks.entry(entity_path.hash()).or_default();
mask.overall = hover_mask.with_fallback_to(mask.overall);
},
);
}
}
}

Item::InstancePath(_, selected_instance) => {
{
let highlighted_entity = highlighted_entity_paths
.entry(selected_instance.entity_path.hash())
.or_default();

let highlight_target = if let Some(selected_index) =
selected_instance.instance_key.specific_index()
{
&mut highlighted_entity
.instances
.entry(selected_index)
.or_default()
.hover
} else {
&mut highlighted_entity.overall.hover
};
*highlight_target = HoverHighlight::Hovered;
}
{
let outlined_entity = outlines_masks
.entry(selected_instance.entity_path.hash())
.or_default();
let outline_mask_target = if let Some(selected_index) =
selected_instance.instance_key.specific_index()
{
outlined_entity.instances.entry(selected_index).or_default()
} else {
&mut outlined_entity.overall
};
*outline_mask_target =
next_hover_mask().with_fallback_to(*outline_mask_target);
}
}
};
}

SpaceViewHighlights {
highlighted_entity_paths,
outlines_masks,
}
}
}
Loading

0 comments on commit d223a5b

Please sign in to comment.