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

Make it possible to change the contents of a view multiple times per frame #8854

Merged
merged 3 commits into from
Jan 30, 2025
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
9 changes: 5 additions & 4 deletions crates/viewer/re_selection_panel/src/selection_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use re_data_ui::{
DataUi,
};
use re_entity_db::{EntityPath, InstancePath};
use re_log_types::{ComponentPath, EntityPathFilter, ResolvedEntityPathFilter};
use re_log_types::{ComponentPath, EntityPathFilter, EntityPathSubs, ResolvedEntityPathFilter};
use re_types::blueprint::components::Interactive;
use re_ui::{
icons,
Expand Down Expand Up @@ -383,11 +383,12 @@ The last rule matching `/world/house` is `+ /world/**`, so it is included.
ctx,
ui,
*view_id,
&view.contents.entity_path_filter,
view.contents.entity_path_filter(),
&view.space_origin,
) {
view.contents
.set_entity_path_filter(ctx, &new_entity_path_filter);
let path_subs = EntityPathSubs::new_with_origin(&view.space_origin);
let query_filter = new_entity_path_filter.resolve_forgiving(&path_subs);
view.contents.set_entity_path_filter(ctx, query_filter);
}
})
.header_response
Expand Down
2 changes: 1 addition & 1 deletion crates/viewer/re_selection_panel/src/view_entity_picker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ fn add_entities_ui(ctx: &ViewerContext<'_>, ui: &mut egui::Ui, view: &ViewBluepr

let tree = &ctx.recording().tree();
let query_result = ctx.lookup_query_result(view.id);
let entity_path_filter = &view.contents.entity_path_filter;
let entity_path_filter = view.contents.entity_path_filter();
let entities_add_info = create_entity_add_info(ctx, tree, view, query_result);

list_item::list_item_scope(ui, "view_entity_picker", |ui| {
Expand Down
2 changes: 1 addition & 1 deletion crates/viewer/re_viewport_blueprint/src/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ impl ViewBlueprint {
let contents = ViewContents::new(
new_id,
self.class_identifier,
self.contents.entity_path_filter.clone(),
self.contents.entity_path_filter().clone(),
);

Self {
Expand Down
129 changes: 75 additions & 54 deletions crates/viewer/re_viewport_blueprint/src/view_contents.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::sync::Arc;

use nohash_hasher::{IntMap, IntSet};
use parking_lot::Mutex;
use re_log_types::{ResolvedEntityPathFilter, ResolvedEntityPathRule};
use slotmap::SlotMap;
use smallvec::SmallVec;
Expand Down Expand Up @@ -37,8 +40,17 @@ use crate::{ViewBlueprint, ViewProperty};
pub struct ViewContents {
pub blueprint_entity_path: EntityPath,

pub view_class_identifier: ViewClassIdentifier,
pub entity_path_filter: ResolvedEntityPathFilter,
view_class_identifier: ViewClassIdentifier,

/// Deserialized entity path filter.
///
/// Consider this read-only.
entity_path_filter: ResolvedEntityPathFilter,

/// Update entity path filter.
///
/// Mutations go to this value and should be saved to the blueprint store when they occur.
new_entity_path_filter: Arc<Mutex<ResolvedEntityPathFilter>>,
Comment on lines +48 to +53
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is such a common pattern in imgui. We should at some point think of coming up with a higher-level abstraction for it 🤔 (but not now)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

}

impl ViewContents {
Expand Down Expand Up @@ -83,10 +95,13 @@ impl ViewContents {
blueprint_archetypes::ViewContents::name().short_name(),
));

let new_entity_path_filter = Arc::new(Mutex::new(entity_path_filter.clone()));

Self {
blueprint_entity_path,
view_class_identifier,
entity_path_filter,
new_entity_path_filter,
}
}

Expand Down Expand Up @@ -120,10 +135,13 @@ impl ViewContents {
let entity_path_filter =
EntityPathFilter::from_query_expressions(query).resolve_forgiving(subst_env);

let new_entity_path_filter = Arc::new(Mutex::new(entity_path_filter.clone()));

Self {
blueprint_entity_path: property.blueprint_store_path,
view_class_identifier,
entity_path_filter,
new_entity_path_filter,
}
}

Expand All @@ -137,30 +155,15 @@ impl ViewContents {
ctx.save_blueprint_archetype(
&self.blueprint_entity_path,
&blueprint_archetypes::ViewContents::new(
self.entity_path_filter.iter_unresolved_expressions(),
self.new_entity_path_filter
.lock()
.iter_unresolved_expressions(),
),
);
}

/// Set the entity path filter. WARNING: a single mutation is allowed per frame, or data will be
/// lost.
//TODO(#8518): address this massive foot-gun.
pub fn set_entity_path_filter(
&self,
ctx: &ViewerContext<'_>,
new_entity_path_filter: &EntityPathFilter,
) {
if &self.entity_path_filter.unresolved() == 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 entity_path_filter(&self) -> &ResolvedEntityPathFilter {
&self.entity_path_filter
}

pub fn build_resolver<'a>(
Expand All @@ -187,69 +190,87 @@ impl ViewContents {
}
}

/// Perform arbitrary mutation on the entity path filter. WARNING: a single mutation is allowed
/// per frame, or data will be lost.
/// Sets the entity path filter to the provided one.
pub fn set_entity_path_filter(
&self,
ctx: &ViewerContext<'_>,
entity_path_filter: ResolvedEntityPathFilter,
) {
*self.new_entity_path_filter.lock() = entity_path_filter;
self.save_entity_path_filter_to_blueprint(ctx);
}

/// Perform arbitrary mutation on the entity path filter.
///
/// This method exists because of the single mutation per frame limitation. It currently is the
/// only way to perform multiple mutations on the entity path filter in a single frame.
//TODO(#8518): address this massive foot-gun.
/// Using this method avoids triggering multiple writes to the blueprint store.
pub fn mutate_entity_path_filter(
&self,
ctx: &ViewerContext<'_>,
f: impl FnOnce(&mut ResolvedEntityPathFilter),
) {
let mut new_entity_path_filter = self.entity_path_filter.clone();
f(&mut new_entity_path_filter);
self.set_entity_path_filter(ctx, &new_entity_path_filter.unresolved());
f(&mut self.new_entity_path_filter.lock());
self.save_entity_path_filter_to_blueprint(ctx);
}

/// Remove a subtree and any existing rules that it would match. WARNING: a single mutation is
/// allowed per frame, or data will be lost.
/// Remove a subtree and any existing rules that it would match.
///
/// Because most-specific matches win, if we only add a subtree exclusion
/// it can still be overridden by existing inclusions. This method ensures
/// that not only do we add a subtree exclusion, but clear out any existing
/// inclusions or (now redundant) exclusions that would match the subtree.
//TODO(#8518): address this massive foot-gun.
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.unresolved());
self.new_entity_path_filter
.lock()
.remove_subtree_and_matching_rules(path);

self.save_entity_path_filter_to_blueprint(ctx);
}

/// Directly add an exclusion rule to the [`EntityPathFilter`]. WARNING: a single mutation is
/// allowed per frame, or data will be lost.
/// Directly add an exclusion rule to the [`EntityPathFilter`].
///
/// This is a direct modification of the filter and will not do any simplification
/// related to overlapping or conflicting rules.
///
/// If you are trying to remove an entire subtree, prefer using [`Self::remove_subtree_and_matching_rules`].
//TODO(#8518): address this massive foot-gun.
pub fn raw_add_entity_exclusion(&self, ctx: &ViewerContext<'_>, rule: ResolvedEntityPathRule) {
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.unresolved());
self.new_entity_path_filter
.lock()
.add_rule(RuleEffect::Exclude, rule);

self.save_entity_path_filter_to_blueprint(ctx);
}

/// Directly add an inclusion rule to the [`EntityPathFilter`]. WARNING: a single mutation is
/// allowed per frame, or data will be lost.
/// Directly add an inclusion rule to the [`EntityPathFilter`].
///
/// This is a direct modification of the filter and will not do any simplification
/// related to overlapping or conflicting rules.
//TODO(#8518): address this massive foot-gun.
pub fn raw_add_entity_inclusion(&self, ctx: &ViewerContext<'_>, rule: ResolvedEntityPathRule) {
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.unresolved());
self.new_entity_path_filter
.lock()
.add_rule(RuleEffect::Include, rule);

self.save_entity_path_filter_to_blueprint(ctx);
}

/// Remove rules for a given entity. WARNING: a single mutation is allowed per frame, or data
/// will be lost.
//TODO(#8518): address this massive foot-gun.
/// Remove rules for a given entity.
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.unresolved());
self.new_entity_path_filter.lock().remove_rule_for(ent_path);

self.save_entity_path_filter_to_blueprint(ctx);
}

/// Save the entity path filter.
fn save_entity_path_filter_to_blueprint(&self, ctx: &ViewerContext<'_>) {
ctx.save_blueprint_component(
&self.blueprint_entity_path,
&self
.new_entity_path_filter
.lock()
.unresolved()
.iter_expressions()
.map(|s| QueryExpression(s.into()))
.collect::<Vec<_>>(),
);
}

/// Build up the initial [`DataQueryResult`] for this [`ViewContents`]
Expand Down
4 changes: 2 additions & 2 deletions crates/viewer/re_viewport_blueprint/src/viewport_blueprint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ impl ViewportBlueprint {
|| entity_path.is_descendant_of(&view.space_origin)
|| view
.contents
.entity_path_filter
.entity_path_filter()
.matches(&instance_path.entity_path)
})
}
Expand Down Expand Up @@ -359,7 +359,7 @@ impl ViewportBlueprint {
.views
.values()
.filter(|view| view.class_identifier() == class_id)
.map(|view| &view.contents.entity_path_filter)
.map(|view| view.contents.entity_path_filter())
.collect::<Vec<_>>();
recommended_views.retain(|(query_filter, _)| {
existing_path_filters
Expand Down