Skip to content

Commit

Permalink
When duplicating a space-view copy the nested blueprint tree (#5076)
Browse files Browse the repository at this point in the history
### What
 - Resolves: #4977

When we duplicate the space-view we now copy the entire subtree and
write it into the new view. We do the same, recursively for the
DataQueries as well (which is where the component overrides live).

There's definitely some cleanup that could be done here but this seems
to work for now to solve the problem of duplicating all the overrides,
etc.

### 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/5076/index.html)
* Using examples from latest `main` build:
[app.rerun.io](https://app.rerun.io/pr/5076/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/5076/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
* [x] If applicable, add a new check to the [release
checklist](tests/python/release_checklist)!

- [PR Build Summary](https://build.rerun.io/pr/5076)
- [Docs
preview](https://rerun.io/preview/60420debd92234b2537b29d870aefe44ad6aab54/docs)
<!--DOCS-PREVIEW-->
- [Examples
preview](https://rerun.io/preview/60420debd92234b2537b29d870aefe44ad6aab54/examples)
<!--EXAMPLES-PREVIEW-->
- [Recent benchmark results](https://build.rerun.io/graphs/crates.html)
- [Wasm size tracking](https://build.rerun.io/graphs/sizes.html)
  • Loading branch information
jleibs authored Feb 7, 2024
1 parent bff8898 commit 79d8021
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 25 deletions.
10 changes: 10 additions & 0 deletions crates/re_log_types/src/path/entity_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,16 @@ impl From<&EntityPath> for re_types_core::datatypes::EntityPath {
}
}

impl<Idx> std::ops::Index<Idx> for EntityPath
where
Idx: std::slice::SliceIndex<[EntityPathPart]>,
{
type Output = Idx::Output;

fn index(&self, index: Idx) -> &Self::Output {
&self.parts[index]
}
}
// ----------------------------------------------------------------------------

use re_types_core::Loggable;
Expand Down
55 changes: 51 additions & 4 deletions crates/re_space_view/src/data_query_blueprint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@ use crate::{
///
/// If you want a new space view otherwise identical to an existing one, use
/// [`DataQueryBlueprint::duplicate`].
#[derive(PartialEq, Eq)]
pub struct DataQueryBlueprint {
pub id: DataQueryId,
pub space_view_class_identifier: SpaceViewClassIdentifier,
pub entity_path_filter: EntityPathFilter,

/// Pending blueprint writes for nested components from duplicate.
pending_writes: Vec<DataRow>,
}

impl DataQueryBlueprint {
Expand All @@ -61,6 +63,7 @@ impl DataQueryBlueprint {
id: DataQueryId::random(),
space_view_class_identifier,
entity_path_filter,
pending_writes: Default::default(),
}
}

Expand All @@ -82,6 +85,7 @@ impl DataQueryBlueprint {
id,
space_view_class_identifier,
entity_path_filter,
pending_writes: Default::default(),
})
}

Expand All @@ -92,18 +96,61 @@ impl DataQueryBlueprint {
/// Otherwise, incremental calls to `set_` functions will write just the necessary component
/// update directly to the store.
pub fn save_to_blueprint_store(&self, ctx: &ViewerContext<'_>) {
// Save any pending writes from a duplication.
ctx.command_sender
.send_system(SystemCommand::UpdateBlueprint(
ctx.store_context.blueprint.store_id().clone(),
self.pending_writes.clone(),
));

ctx.save_blueprint_component(
&self.id.as_entity_path(),
QueryExpressions::from(&self.entity_path_filter),
);
}

/// Creates a new [`DataQueryBlueprint`] with a the same contents, but a different [`DataQueryId`]
pub fn duplicate(&self) -> Self {
pub fn duplicate(&self, blueprint: &EntityDb, query: &LatestAtQuery) -> Self {
let mut pending_writes = Vec::new();

let current_path = self.id.as_entity_path();
let new_id = DataQueryId::random();
let new_path = new_id.as_entity_path();

// Create pending write operations to duplicate the entire subtree
// TODO(jleibs): This should be a helper somewhere.
if let Some(tree) = blueprint.tree().subtree(&current_path) {
tree.visit_children_recursively(&mut |path, info| {
let sub_path: EntityPath = new_path
.iter()
.chain(&path[current_path.len()..])
.cloned()
.collect();

if let Ok(row) = DataRow::from_cells(
RowId::new(),
blueprint_timepoint_for_writes(),
sub_path,
1,
info.components.keys().filter_map(|component| {
blueprint
.store()
.latest_at(query, path, *component, &[*component])
.and_then(|result| result.2[0].clone())
}),
) {
if row.num_cells() > 0 {
pending_writes.push(row);
}
}
});
}

Self {
id: DataQueryId::random(),
id: new_id,
space_view_class_identifier: self.space_view_class_identifier,
entity_path_filter: self.entity_path_filter.clone(),
pending_writes,
}
}

Expand Down Expand Up @@ -502,7 +549,7 @@ impl DataQueryPropertyResolver<'_> {
.blueprint
.store()
.latest_at(query, &override_path, *component, &[*component])
.and_then(|result| result.2[0].clone())
.and_then(|(_, _, cells)| cells[0].clone())
{
if !component_data.is_empty() {
component_overrides.insert(
Expand Down
84 changes: 70 additions & 14 deletions crates/re_space_view/src/space_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ use re_entity_db::{EntityPropertiesComponent, EntityPropertyMap};
use crate::DataQueryBlueprint;
use re_log_types::{DataRow, EntityPathFilter, EntityPathRule, RowId};
use re_query::query_archetype;
use re_types::blueprint::archetypes as blueprint_archetypes;
use re_types::{
blueprint::components::{EntitiesDeterminedByUser, SpaceViewOrigin, Visible},
components::Name,
};
use re_types_core::archetypes::Clear;
use re_types_core::Archetype as _;
use re_viewer_context::{
blueprint_timepoint_for_writes, DataQueryId, DataResult, DynSpaceViewClass, PerSystemEntities,
PropertyOverrides, SpaceViewClassIdentifier, SpaceViewId, SpaceViewState, StoreContext,
Expand Down Expand Up @@ -69,6 +71,9 @@ pub struct SpaceViewBlueprint {

/// True if this space view is visible in the UI.
pub visible: bool,

/// Pending blueprint writes for nested components from duplicate.
pending_writes: Vec<DataRow>,
}

impl SpaceViewBlueprint {
Expand All @@ -91,6 +96,7 @@ impl SpaceViewBlueprint {
queries: vec![query],
entities_determined_by_user: false,
visible: true,
pending_writes: Default::default(),
}
}

Expand Down Expand Up @@ -141,7 +147,7 @@ impl SpaceViewBlueprint {
) -> Option<Self> {
re_tracing::profile_function!();

let re_types::blueprint::archetypes::SpaceViewBlueprint {
let blueprint_archetypes::SpaceViewBlueprint {
display_name,
class_identifier,
space_origin,
Expand Down Expand Up @@ -189,6 +195,7 @@ impl SpaceViewBlueprint {
queries,
entities_determined_by_user,
visible,
pending_writes: Default::default(),
})
}

Expand All @@ -209,21 +216,24 @@ impl SpaceViewBlueprint {
queries,
entities_determined_by_user,
visible,
pending_writes,
} = self;

let mut arch =
re_types::blueprint::archetypes::SpaceViewBlueprint::new(class_identifier.as_str())
.with_space_origin(space_origin)
.with_entities_determined_by_user(*entities_determined_by_user)
.with_contents(queries.iter().map(|q| q.id))
.with_visible(*visible);
let mut arch = blueprint_archetypes::SpaceViewBlueprint::new(class_identifier.as_str())
.with_space_origin(space_origin)
.with_entities_determined_by_user(*entities_determined_by_user)
.with_contents(queries.iter().map(|q| q.id))
.with_visible(*visible);

if let Some(display_name) = display_name {
arch = arch.with_display_name(display_name.clone());
}

let mut deltas = vec![];
// Start with the pending writes, which explicitly filtered out the `SpaceViewBlueprint`
// components from the top level.
let mut deltas = pending_writes.clone();

// Add all the additional components from the archetype
if let Ok(row) =
DataRow::from_archetype(RowId::new(), timepoint.clone(), id.as_entity_path(), &arch)
{
Expand All @@ -244,18 +254,64 @@ impl SpaceViewBlueprint {
/// Creates a new [`SpaceViewBlueprint`] with the same contents, but a different [`SpaceViewId`]
///
/// Also duplicates all the queries in the space view.
///
/// Note that this function is a very partial implementation of proper space view cloning. See
/// `re_viewport::ViewportBlueprint::duplicate_space_view`.
pub fn duplicate(&self) -> Self {
pub fn duplicate(&self, blueprint: &EntityDb, query: &LatestAtQuery) -> Self {
let mut pending_writes = Vec::new();

let current_path = self.entity_path();
let new_id = SpaceViewId::random();
let new_path = new_id.as_entity_path();

// Create pending write operations to duplicate the entire subtree
// TODO(jleibs): This should be a helper somewhere.
if let Some(tree) = blueprint.tree().subtree(&current_path) {
tree.visit_children_recursively(&mut |path, info| {
let sub_path: EntityPath = new_path
.iter()
.chain(&path[current_path.len()..])
.cloned()
.collect();

if let Ok(row) = DataRow::from_cells(
RowId::new(),
blueprint_timepoint_for_writes(),
sub_path,
1,
info.components
.keys()
// It's important that we don't include the SpaceViewBlueprint's components
// since those will be updated separately and may contain different data.
.filter(|component| {
*path != current_path
|| !blueprint_archetypes::SpaceViewBlueprint::all_components()
.contains(component)
})
.filter_map(|component| {
blueprint
.store()
.latest_at(query, path, *component, &[*component])
.and_then(|(_, _, cells)| cells[0].clone())
}),
) {
if row.num_cells() > 0 {
pending_writes.push(row);
}
}
});
}

Self {
id: SpaceViewId::random(),
id: new_id,
display_name: self.display_name.clone(),
class_identifier: self.class_identifier,
space_origin: self.space_origin.clone(),
queries: self.queries.iter().map(|q| q.duplicate()).collect(),
queries: self
.queries
.iter()
.map(|q| q.duplicate(blueprint, query))
.collect(),
entities_determined_by_user: self.entities_determined_by_user,
visible: self.visible,
pending_writes,
}
}

Expand Down
8 changes: 1 addition & 7 deletions crates/re_viewport/src/viewport_blueprint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,15 +238,9 @@ impl ViewportBlueprint {
return None;
};

let new_space_view = space_view.duplicate();
let new_space_view = space_view.duplicate(ctx.store_context.blueprint, ctx.blueprint_query);
let new_space_view_id = new_space_view.id;

// copy entity properties from the old space view
let data_result = space_view.root_data_result(ctx.store_context, ctx.blueprint_query);
let new_data_result =
new_space_view.root_data_result(ctx.store_context, ctx.blueprint_query);
new_data_result.save_override(data_result.individual_properties().cloned(), ctx);

self.add_space_views(std::iter::once(new_space_view), ctx, None);

Some(new_space_view_id)
Expand Down
3 changes: 3 additions & 0 deletions tests/python/release_checklist/check_plot_overrides.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
* Override the `plots/cos` entity Visible time range
* Verify all 3 offset modes operate as expected
### Overrides are cloned
* After overriding things on both the space-view and the entity, clone the space-view.
If nothing weird happens, you can close this recording.
"""

Expand Down

0 comments on commit 79d8021

Please sign in to comment.