Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pure refactor] Make selection state independent of blueprint #2035

Merged
merged 5 commits into from
May 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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