Skip to content

Commit 2b2b0db

Browse files
authored
When duplicating a SpaceViewBlueprint, also duplicate its Query (#4549)
### What - Resolves: #4456 The crux of the problem is cloning the SpaceView created a new SpaceView with the same DataQuery reference (which is where the property overrides are stored). Since cloning the internal IDs of a SpaceView or DataQuery can have unintended consequences, I removed Clone from these types and replaced it with a `duplicate` method with clearer semantics. Also had to clean up the heuristic path to get rid of the one otherwise unnecessary clone. ### Checklist * [x] I have read and agree to [Contributor Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and the [Code of Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md) * [x] I've included a screenshot or gif (if applicable) * [x] I have tested the web demo (if applicable): * Using newly built examples: [app.rerun.io](https://app.rerun.io/pr/4549/index.html) * Using examples from latest `main` build: [app.rerun.io](https://app.rerun.io/pr/4549/index.html?manifest_url=https://app.rerun.io/version/main/examples_manifest.json) * Using full set of examples from `nightly` build: [app.rerun.io](https://app.rerun.io/pr/4549/index.html?manifest_url=https://app.rerun.io/version/nightly/examples_manifest.json) * [x] The PR title and labels are set such as to maximize their usefulness for the next release's CHANGELOG - [PR Build Summary](https://build.rerun.io/pr/4549) - [Docs preview](https://rerun.io/preview/f128e543a4e06edee55f1a089b1b6a9837ca43b0/docs) <!--DOCS-PREVIEW--> - [Examples preview](https://rerun.io/preview/f128e543a4e06edee55f1a089b1b6a9837ca43b0/examples) <!--EXAMPLES-PREVIEW--> - [Recent benchmark results](https://build.rerun.io/graphs/crates.html) - [Wasm size tracking](https://build.rerun.io/graphs/sizes.html)
1 parent 0987ae2 commit 2b2b0db

File tree

4 files changed

+67
-25
lines changed

4 files changed

+67
-25
lines changed

crates/re_space_view/src/data_query_blueprint.rs

+17-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,14 @@ use crate::{
2929
/// The results of recursive expressions are only included if they are found within the [`EntityTree`]
3030
/// and for which there is a valid `ViewPart` system. This keeps recursive expressions from incorrectly
3131
/// picking up irrelevant data within the tree.
32-
#[derive(Clone, PartialEq, Eq)]
32+
///
33+
/// Note: [`DataQueryBlueprint`] doesn't implement Clone because it stores an internal
34+
/// uuid used for identifying the path of its data in the blueprint store. It's ambiguous
35+
/// whether the intent is for a clone to write to the same place.
36+
///
37+
/// If you want a new space view otherwise identical to an existing one, use
38+
/// [`DataQueryBlueprint::duplicate`].
39+
#[derive(PartialEq, Eq)]
3340
pub struct DataQueryBlueprint {
3441
pub id: DataQueryId,
3542
pub space_view_class_identifier: SpaceViewClassIdentifier,
@@ -78,6 +85,15 @@ impl DataQueryBlueprint {
7885
})
7986
}
8087

88+
/// Creates a new [`DataQueryBlueprint`] with a the same contents, but a different [`DataQueryId`]
89+
pub fn duplicate(&self) -> Self {
90+
Self {
91+
id: DataQueryId::random(),
92+
space_view_class_identifier: self.space_view_class_identifier,
93+
expressions: self.expressions.clone(),
94+
}
95+
}
96+
8197
pub fn clear(&self, ctx: &ViewerContext<'_>) {
8298
let clear = Clear::recursive();
8399
ctx.save_blueprint_component(&self.id.as_entity_path(), clear.is_recursive);

crates/re_viewer/src/ui/selection_panel.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -522,8 +522,7 @@ fn blueprint_ui(
522522
.clicked()
523523
{
524524
if let Some(space_view) = viewport.blueprint.space_view(space_view_id) {
525-
let mut new_space_view = space_view.clone();
526-
new_space_view.id = SpaceViewId::random();
525+
let new_space_view = space_view.duplicate();
527526
viewport.blueprint.add_space_views(std::iter::once(new_space_view), ctx, &mut viewport.deferred_tree_actions);
528527
viewport.blueprint.mark_user_interaction(ctx);
529528
}

crates/re_viewport/src/space_view.rs

+21-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,13 @@ use crate::viewport_blueprint::add_delta_from_single_component;
2222
// ----------------------------------------------------------------------------
2323

2424
/// A view of a space.
25-
#[derive(Clone)]
25+
///
26+
/// Note: [`SpaceViewBlueprint`] doesn't implement Clone because it stores an internal
27+
/// uuid used for identifying the path of its data in the blueprint store. It's ambiguous
28+
/// whether the intent is for a clone to write to the same place.
29+
///
30+
/// If you want a new space view otherwise identical to an existing one, use
31+
/// [`SpaceViewBlueprint::duplicate`].
2632
pub struct SpaceViewBlueprint {
2733
pub id: SpaceViewId,
2834
pub display_name: String,
@@ -173,6 +179,20 @@ impl SpaceViewBlueprint {
173179
));
174180
}
175181

182+
/// Creates a new [`SpaceViewBlueprint`] with a the same contents, but a different [`SpaceViewId`]
183+
///
184+
/// Also duplicates all of the queries in the space view.
185+
pub fn duplicate(&self) -> Self {
186+
Self {
187+
id: SpaceViewId::random(),
188+
display_name: self.display_name.clone(),
189+
class_identifier: self.class_identifier,
190+
space_origin: self.space_origin.clone(),
191+
queries: self.queries.iter().map(|q| q.duplicate()).collect(),
192+
entities_determined_by_user: self.entities_determined_by_user,
193+
}
194+
}
195+
176196
pub fn clear(&self, ctx: &ViewerContext<'_>) {
177197
let clear = Clear::recursive();
178198
ctx.save_blueprint_component(&self.entity_path(), clear.is_recursive);

crates/re_viewport/src/space_view_heuristics.rs

+28-21
Original file line numberDiff line numberDiff line change
@@ -311,35 +311,42 @@ pub fn default_created_space_views(
311311

312312
// `AutoSpawnHeuristic::SpawnClassWithHighestScoreForRoot` means we're competing with other candidates for the same root.
313313
if let AutoSpawnHeuristic::SpawnClassWithHighestScoreForRoot(score) = spawn_heuristic {
314-
let mut should_spawn_new = true;
314+
// [`SpaceViewBlueprint`]s don't implement clone so wrap in an option so we can
315+
// track whether or not we've consumed it.
316+
let mut candidate_still_considered = Some(candidate);
317+
315318
for (prev_candidate, prev_spawn_heuristic) in &mut space_views {
316-
if prev_candidate.space_origin == candidate.space_origin {
317-
#[allow(clippy::match_same_arms)]
318-
match prev_spawn_heuristic {
319-
AutoSpawnHeuristic::SpawnClassWithHighestScoreForRoot(prev_score) => {
320-
// If we're competing with a candidate for the same root, we either replace a lower score, or we yield.
321-
should_spawn_new = false;
322-
if *prev_score < score {
323-
// Replace the previous candidate with this one.
324-
*prev_candidate = candidate.clone();
325-
*prev_spawn_heuristic = spawn_heuristic;
326-
} else {
327-
// We have a lower score, so we don't spawn.
319+
if let Some(candidate) = candidate_still_considered.take() {
320+
if prev_candidate.space_origin == candidate.space_origin {
321+
#[allow(clippy::match_same_arms)]
322+
match prev_spawn_heuristic {
323+
AutoSpawnHeuristic::SpawnClassWithHighestScoreForRoot(prev_score) => {
324+
// If we're competing with a candidate for the same root, we either replace a lower score, or we yield.
325+
if *prev_score < score {
326+
// Replace the previous candidate with this one.
327+
*prev_candidate = candidate;
328+
*prev_spawn_heuristic = spawn_heuristic;
329+
}
330+
331+
// Either way we're done with this candidate.
328332
break;
329333
}
330-
}
331-
AutoSpawnHeuristic::AlwaysSpawn => {
332-
// We can live side by side with always-spawn candidates.
333-
}
334-
AutoSpawnHeuristic::NeverSpawn => {
335-
// Never spawn candidates should not be in the list, this is weird!
336-
// But let's not fail on this since our heuristics are not perfect anyways.
334+
AutoSpawnHeuristic::AlwaysSpawn => {
335+
// We can live side by side with always-spawn candidates.
336+
}
337+
AutoSpawnHeuristic::NeverSpawn => {
338+
// Never spawn candidates should not be in the list, this is weird!
339+
// But let's not fail on this since our heuristics are not perfect anyways.
340+
}
337341
}
338342
}
343+
344+
// If we didn't hit the break condition, continue to consider the candidate
345+
candidate_still_considered = Some(candidate);
339346
}
340347
}
341348

342-
if should_spawn_new {
349+
if let Some(candidate) = candidate_still_considered {
343350
// Spatial views with images get extra treatment as well.
344351
if is_spatial_2d_class(candidate.class_identifier()) {
345352
#[derive(Hash, PartialEq, Eq)]

0 commit comments

Comments
 (0)