diff --git a/crates/store/re_chunk/src/chunk.rs b/crates/store/re_chunk/src/chunk.rs index 0b50b6f418d6..da3817e163bc 100644 --- a/crates/store/re_chunk/src/chunk.rs +++ b/crates/store/re_chunk/src/chunk.rs @@ -211,6 +211,25 @@ impl Chunk { self } + /// Clones the chunk into a new chunk where all [`RowId`]s are [`RowId::ZERO`]. + pub fn zeroed(self) -> Self { + let row_ids = std::iter::repeat(RowId::ZERO) + .take(self.row_ids.len()) + .collect_vec(); + + #[allow(clippy::unwrap_used)] + let row_ids = ::to_arrow(&row_ids) + // Unwrap: native RowIds cannot fail to serialize. + .unwrap() + .as_any() + .downcast_ref::() + // Unwrap: RowId schema is known in advance to be a struct array -- always. + .unwrap() + .clone(); + + Self { row_ids, ..self } + } + /// Computes the time range covered by each individual component column on each timeline. /// /// This is different from the time range covered by the [`Chunk`] as a whole because component diff --git a/crates/viewer/re_space_view/src/lib.rs b/crates/viewer/re_space_view/src/lib.rs index 4401340cfb12..f87b110a6c33 100644 --- a/crates/viewer/re_space_view/src/lib.rs +++ b/crates/viewer/re_space_view/src/lib.rs @@ -8,7 +8,6 @@ mod heuristics; mod query; mod results_ext; mod screenshot; -mod time_key; mod view_property_ui; pub use heuristics::suggest_space_view_for_each_entity; @@ -19,7 +18,6 @@ pub use results_ext::{ HybridLatestAtResults, HybridResults, HybridResultsChunkIter, RangeResultsExt, }; pub use screenshot::ScreenshotMode; -pub use time_key::TimeKey; pub use view_property_ui::view_property_ui; pub mod external { diff --git a/crates/viewer/re_space_view/src/results_ext.rs b/crates/viewer/re_space_view/src/results_ext.rs index c4f6237a6a54..5be4920c8402 100644 --- a/crates/viewer/re_space_view/src/results_ext.rs +++ b/crates/viewer/re_space_view/src/results_ext.rs @@ -1,6 +1,8 @@ use std::borrow::Cow; use std::sync::Arc; +use itertools::Itertools as _; + use re_chunk_store::{Chunk, LatestAtQuery, RangeQuery, UnitChunkShared}; use re_log_types::external::arrow2::array::Array as ArrowArray; use re_log_types::hash::Hash64; @@ -9,7 +11,6 @@ use re_types_core::ComponentName; use re_viewer_context::{DataResult, QueryContext, ViewContext}; use crate::DataResultQuery as _; -use crate::TimeKey; // --- @@ -293,7 +294,9 @@ impl RangeResultsExt for HybridRangeResults { fn get_required_chunks(&self, component_name: &ComponentName) -> Option> { if let Some(unit) = self.overrides.get(component_name) { // Because this is an override we always re-index the data as static - let chunk = Arc::unwrap_or_clone(unit.clone().into_chunk()).into_static(); + let chunk = Arc::unwrap_or_clone(unit.clone().into_chunk()) + .into_static() + .zeroed(); Some(Cow::Owned(vec![chunk])) } else { self.results.get_required_chunks(component_name) @@ -302,27 +305,36 @@ impl RangeResultsExt for HybridRangeResults { #[inline] fn get_optional_chunks(&self, component_name: &ComponentName) -> Cow<'_, [Chunk]> { + re_tracing::profile_function!(); + if let Some(unit) = self.overrides.get(component_name) { // Because this is an override we always re-index the data as static - let chunk = Arc::unwrap_or_clone(unit.clone().into_chunk()).into_static(); + let chunk = Arc::unwrap_or_clone(unit.clone().into_chunk()) + .into_static() + .zeroed(); Cow::Owned(vec![chunk]) } else { - let chunks = self.results.get_optional_chunks(component_name); - - // If the data is not empty, return it. + re_tracing::profile_scope!("defaults"); - if !chunks.is_empty() { - return chunks; - } + // NOTE: Because this is a range query, we always need the defaults to come first, + // since range queries don't have any state to bootstrap from. + let defaults = self.defaults.get(component_name).map(|unit| { + // Because this is an default from the blueprint we always re-index the data as static + Arc::unwrap_or_clone(unit.clone().into_chunk()) + .into_static() + .zeroed() + }); - // Otherwise try to use the default data. + let chunks = self.results.get_optional_chunks(component_name); - let Some(unit) = self.defaults.get(component_name) else { - return Cow::Owned(Vec::new()); - }; - // Because this is an default from the blueprint we always re-index the data as static - let chunk = Arc::unwrap_or_clone(unit.clone().into_chunk()).into_static(); - Cow::Owned(vec![chunk]) + // TODO(cmc): this `collect_vec()` sucks, let's keep an eye on it and see if it ever + // becomes an issue. + Cow::Owned( + defaults + .into_iter() + .chain(chunks.iter().cloned()) + .collect_vec(), + ) } } } @@ -332,7 +344,9 @@ impl<'a> RangeResultsExt for HybridLatestAtResults<'a> { fn get_required_chunks(&self, component_name: &ComponentName) -> Option> { if let Some(unit) = self.overrides.get(component_name) { // Because this is an override we always re-index the data as static - let chunk = Arc::unwrap_or_clone(unit.clone().into_chunk()).into_static(); + let chunk = Arc::unwrap_or_clone(unit.clone().into_chunk()) + .into_static() + .zeroed(); Some(Cow::Owned(vec![chunk])) } else { self.results.get_required_chunks(component_name) @@ -343,24 +357,35 @@ impl<'a> RangeResultsExt for HybridLatestAtResults<'a> { fn get_optional_chunks(&self, component_name: &ComponentName) -> Cow<'_, [Chunk]> { if let Some(unit) = self.overrides.get(component_name) { // Because this is an override we always re-index the data as static - let chunk = Arc::unwrap_or_clone(unit.clone().into_chunk()).into_static(); + let chunk = Arc::unwrap_or_clone(unit.clone().into_chunk()) + .into_static() + .zeroed(); Cow::Owned(vec![chunk]) } else { - let chunks = self.results.get_optional_chunks(component_name); + let chunks = self + .results + .get_optional_chunks(component_name) + .iter() + // NOTE: Since this is a latest-at query that is being coerced into a range query, we + // need to make sure that every secondary column has an index smaller then the primary column + // (we use `(TimeInt::STATIC, RowId::ZERO)`), otherwise range zipping would yield unexpected + // results. + .map(|chunk| chunk.clone().into_static().zeroed()) + .collect_vec(); // If the data is not empty, return it. - if !chunks.is_empty() { - return chunks; + return Cow::Owned(chunks); } // Otherwise try to use the default data. - let Some(unit) = self.defaults.get(component_name) else { return Cow::Owned(Vec::new()); }; // Because this is an default from the blueprint we always re-index the data as static - let chunk = Arc::unwrap_or_clone(unit.clone().into_chunk()).into_static(); + let chunk = Arc::unwrap_or_clone(unit.clone().into_chunk()) + .into_static() + .zeroed(); Cow::Owned(vec![chunk]) } } @@ -386,11 +411,10 @@ impl<'a> RangeResultsExt for HybridResults<'a> { // --- -use re_chunk::{ChunkComponentIterItem, Timeline}; +use re_chunk::{ChunkComponentIterItem, RowId, TimeInt, Timeline}; use re_chunk_store::external::{re_chunk, re_chunk::external::arrow2}; /// The iterator type backing [`HybridResults::iter_as`]. -#[derive(Debug)] pub struct HybridResultsChunkIter<'a> { chunks: Cow<'a, [Chunk]>, timeline: Timeline, @@ -403,12 +427,10 @@ impl<'a> HybridResultsChunkIter<'a> { /// See [`Chunk::iter_component`] for more information. pub fn component( &'a self, - ) -> impl Iterator)> + 'a { + ) -> impl Iterator)> + 'a { self.chunks.iter().flat_map(move |chunk| { itertools::izip!( - chunk - .iter_component_indices(&self.timeline, &self.component_name) - .map(TimeKey::from), + chunk.iter_component_indices(&self.timeline, &self.component_name), chunk.iter_component::(), ) }) @@ -419,12 +441,10 @@ impl<'a> HybridResultsChunkIter<'a> { /// See [`Chunk::iter_primitive`] for more information. pub fn primitive( &'a self, - ) -> impl Iterator + 'a { + ) -> impl Iterator + 'a { self.chunks.iter().flat_map(move |chunk| { itertools::izip!( - chunk - .iter_component_indices(&self.timeline, &self.component_name) - .map(TimeKey::from), + chunk.iter_component_indices(&self.timeline, &self.component_name), chunk.iter_primitive::(&self.component_name) ) }) @@ -435,15 +455,13 @@ impl<'a> HybridResultsChunkIter<'a> { /// See [`Chunk::iter_primitive_array`] for more information. pub fn primitive_array( &'a self, - ) -> impl Iterator + 'a + ) -> impl Iterator + 'a where [T; N]: bytemuck::Pod, { self.chunks.iter().flat_map(move |chunk| { itertools::izip!( - chunk - .iter_component_indices(&self.timeline, &self.component_name) - .map(TimeKey::from), + chunk.iter_component_indices(&self.timeline, &self.component_name), chunk.iter_primitive_array::(&self.component_name) ) }) @@ -454,15 +472,13 @@ impl<'a> HybridResultsChunkIter<'a> { /// See [`Chunk::iter_primitive_array_list`] for more information. pub fn primitive_array_list( &'a self, - ) -> impl Iterator)> + 'a + ) -> impl Iterator)> + 'a where [T; N]: bytemuck::Pod, { self.chunks.iter().flat_map(move |chunk| { itertools::izip!( - chunk - .iter_component_indices(&self.timeline, &self.component_name) - .map(TimeKey::from), + chunk.iter_component_indices(&self.timeline, &self.component_name), chunk.iter_primitive_array_list::(&self.component_name) ) }) @@ -473,12 +489,10 @@ impl<'a> HybridResultsChunkIter<'a> { /// See [`Chunk::iter_string`] for more information. pub fn string( &'a self, - ) -> impl Iterator)> + 'a { + ) -> impl Iterator)> + 'a { self.chunks.iter().flat_map(|chunk| { itertools::izip!( - chunk - .iter_component_indices(&self.timeline, &self.component_name) - .map(TimeKey::from), + chunk.iter_component_indices(&self.timeline, &self.component_name), chunk.iter_string(&self.component_name) ) }) @@ -489,12 +503,10 @@ impl<'a> HybridResultsChunkIter<'a> { /// See [`Chunk::iter_buffer`] for more information. pub fn buffer( &'a self, - ) -> impl Iterator>)> + 'a { + ) -> impl Iterator>)> + 'a { self.chunks.iter().flat_map(|chunk| { itertools::izip!( - chunk - .iter_component_indices(&self.timeline, &self.component_name) - .map(TimeKey::from), + chunk.iter_component_indices(&self.timeline, &self.component_name), chunk.iter_buffer(&self.component_name) ) }) diff --git a/crates/viewer/re_space_view/src/time_key.rs b/crates/viewer/re_space_view/src/time_key.rs deleted file mode 100644 index 5bd9fc98379a..000000000000 --- a/crates/viewer/re_space_view/src/time_key.rs +++ /dev/null @@ -1,48 +0,0 @@ -use re_chunk_store::RowId; -use re_log_types::TimeInt; - -/// Time and row id for some piece of data. -/// -/// The `Ord` for this ignores `row_id`. -/// This is so that our zipping iterators will ignore the row id when comparing. -/// This is important for static data, especially when it comes to overrides. -/// If you override some static data (e.g. point cloud radius), the row id should be ignored, -/// otherwise the zipping iterators will say the override came _after_ the original data, -/// and so by latest-at semantics, the override will be ignored. -/// -/// See for more. -#[derive(Clone, Copy, Debug)] -pub struct TimeKey { - pub time: TimeInt, - pub row_id: RowId, -} - -impl From<(TimeInt, RowId)> for TimeKey { - #[inline] - fn from((time, row_id): (TimeInt, RowId)) -> Self { - Self { time, row_id } - } -} - -impl PartialEq for TimeKey { - #[inline] - fn eq(&self, other: &Self) -> bool { - self.time == other.time - } -} - -impl Eq for TimeKey {} - -impl PartialOrd for TimeKey { - #[inline] - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.time.cmp(&other.time)) - } -} - -impl Ord for TimeKey { - #[inline] - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.time.cmp(&other.time) - } -} diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/assets3d.rs b/crates/viewer/re_space_view_spatial/src/visualizers/assets3d.rs index f285a7e08279..4b9b4f55ea95 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/assets3d.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/assets3d.rs @@ -1,7 +1,7 @@ -use re_log_types::{hash::Hash64, Instance}; +use re_chunk_store::RowId; +use re_log_types::{hash::Hash64, Instance, TimeInt}; use re_renderer::renderer::MeshInstance; use re_renderer::RenderContext; -use re_space_view::TimeKey; use re_types::{ archetypes::Asset3D, components::{Blob, MediaType}, @@ -33,7 +33,7 @@ impl Default for Asset3DVisualizer { } struct Asset3DComponentData { - index: TimeKey, + index: (TimeInt, RowId), blob: ArrowBuffer, media_type: Option, @@ -58,7 +58,7 @@ impl Asset3DVisualizer { media_type: data.media_type.clone().map(Into::into), }; - let primary_row_id = data.index.row_id; + let primary_row_id = data.index.1; let picking_instance_hash = re_entity_db::InstancePathHash::entity_all(entity_path); let outline_mask_ids = ent_context.highlight.index_outline_mask(Instance::ALL); diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/depth_images.rs b/crates/viewer/re_space_view_spatial/src/visualizers/depth_images.rs index 108d522ecf9f..f5bea83170fd 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/depth_images.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/depth_images.rs @@ -282,7 +282,7 @@ impl VisualizerSystem for DepthImageVisualizer { Some(DepthImageComponentData { image: ImageInfo { - buffer_row_id: index.row_id, + buffer_row_id: index.1, buffer: buffer.clone().into(), format: first_copied(format.as_deref())?.0, kind: ImageKind::Depth, diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/encoded_image.rs b/crates/viewer/re_space_view_spatial/src/visualizers/encoded_image.rs index 69b67abd7860..8fd9ca18028c 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/encoded_image.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/encoded_image.rs @@ -144,7 +144,7 @@ impl EncodedImageVisualizer { let all_media_types = results.iter_as(timeline, MediaType::name()); let all_opacities = results.iter_as(timeline, Opacity::name()); - for (index, blobs, media_types, opacities) in re_query::range_zip_1x2( + for ((_time, tensor_data_row_id), blobs, media_types, opacities) in re_query::range_zip_1x2( all_blobs_indexed, all_media_types.string(), all_opacities.primitive::(), @@ -156,7 +156,7 @@ impl EncodedImageVisualizer { let image = ctx.viewer_ctx.cache.entry(|c: &mut ImageDecodeCache| { c.entry( - index.row_id, + tensor_data_row_id, blob, media_type.as_ref().map(|mt| mt.as_str()), ) diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/images.rs b/crates/viewer/re_space_view_spatial/src/visualizers/images.rs index f7347044ff96..fe5a4d57d008 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/images.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/images.rs @@ -165,7 +165,7 @@ impl ImageVisualizer { Some(ImageComponentData { image: ImageInfo { - buffer_row_id: index.row_id, + buffer_row_id: index.1, buffer: buffer.clone().into(), format: first_copied(formats.as_deref())?.0, kind: ImageKind::Color, diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/meshes.rs b/crates/viewer/re_space_view_spatial/src/visualizers/meshes.rs index 35ff280d7e40..91ac57ab236e 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/meshes.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/meshes.rs @@ -1,7 +1,7 @@ -use re_log_types::{hash::Hash64, Instance}; +use re_chunk_store::RowId; +use re_log_types::{hash::Hash64, Instance, TimeInt}; use re_renderer::renderer::MeshInstance; use re_renderer::RenderContext; -use re_space_view::TimeKey; use re_types::{ archetypes::Mesh3D, components::{ @@ -41,7 +41,7 @@ impl Default for Mesh3DVisualizer { } struct Mesh3DComponentData<'a> { - index: TimeKey, + index: (TimeInt, RowId), query_result_hash: Hash64, vertex_positions: &'a [Position3D], @@ -71,7 +71,7 @@ impl Mesh3DVisualizer { let entity_path = ctx.target_entity_path; for data in data { - let primary_row_id = data.index.row_id; + let primary_row_id = data.index.1; let picking_instance_hash = re_entity_db::InstancePathHash::entity_all(entity_path); let outline_mask_ids = ent_context.highlight.index_outline_mask(Instance::ALL); diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/segmentation_images.rs b/crates/viewer/re_space_view_spatial/src/visualizers/segmentation_images.rs index a07504ac121b..b87daf70af66 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/segmentation_images.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/segmentation_images.rs @@ -109,7 +109,7 @@ impl VisualizerSystem for SegmentationImageVisualizer { let buffer = buffers.first()?; Some(SegmentationImageComponentData { image: ImageInfo { - buffer_row_id: index.row_id, + buffer_row_id: index.1, buffer: buffer.clone().into(), format: first_copied(formats.as_deref())?.0, kind: ImageKind::Segmentation, diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/utilities/entity_iterator.rs b/crates/viewer/re_space_view_spatial/src/visualizers/utilities/entity_iterator.rs index d177fd037c08..af145fe0bfdc 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/utilities/entity_iterator.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/utilities/entity_iterator.rs @@ -4,7 +4,6 @@ use re_chunk_store::{LatestAtQuery, RangeQuery}; use re_log_types::{TimeInt, Timeline}; use re_space_view::{ latest_at_with_blueprint_resolved_data, range_with_blueprint_resolved_data, HybridResults, - TimeKey, }; use re_types::Archetype; use re_viewer_context::{ @@ -201,7 +200,7 @@ where // --- -use re_chunk::{Chunk, ChunkComponentIterItem, ComponentName}; +use re_chunk::{Chunk, ChunkComponentIterItem, ComponentName, RowId}; use re_chunk_store::external::{re_chunk, re_chunk::external::arrow2}; /// Iterate `chunks` as indexed deserialized batches. @@ -212,12 +211,10 @@ pub fn iter_component<'a, C: re_types::Component>( chunks: &'a std::borrow::Cow<'a, [Chunk]>, timeline: Timeline, component_name: ComponentName, -) -> impl Iterator)> + 'a { +) -> impl Iterator)> + 'a { chunks.iter().flat_map(move |chunk| { itertools::izip!( - chunk - .iter_component_indices(&timeline, &component_name) - .map(TimeKey::from), + chunk.iter_component_indices(&timeline, &component_name), chunk.iter_component::() ) }) @@ -231,12 +228,10 @@ pub fn iter_primitive<'a, T: arrow2::types::NativeType>( chunks: &'a std::borrow::Cow<'a, [Chunk]>, timeline: Timeline, component_name: ComponentName, -) -> impl Iterator + 'a { +) -> impl Iterator + 'a { chunks.iter().flat_map(move |chunk| { itertools::izip!( - chunk - .iter_component_indices(&timeline, &component_name) - .map(TimeKey::from), + chunk.iter_component_indices(&timeline, &component_name), chunk.iter_primitive::(&component_name) ) }) @@ -250,15 +245,13 @@ pub fn iter_primitive_array<'a, const N: usize, T: arrow2::types::NativeType>( chunks: &'a std::borrow::Cow<'a, [Chunk]>, timeline: Timeline, component_name: ComponentName, -) -> impl Iterator + 'a +) -> impl Iterator + 'a where [T; N]: bytemuck::Pod, { chunks.iter().flat_map(move |chunk| { itertools::izip!( - chunk - .iter_component_indices(&timeline, &component_name) - .map(TimeKey::from), + chunk.iter_component_indices(&timeline, &component_name), chunk.iter_primitive_array::(&component_name) ) }) @@ -272,15 +265,13 @@ pub fn iter_primitive_array_list<'a, const N: usize, T: arrow2::types::NativeTyp chunks: &'a std::borrow::Cow<'a, [Chunk]>, timeline: Timeline, component_name: ComponentName, -) -> impl Iterator)> + 'a +) -> impl Iterator)> + 'a where [T; N]: bytemuck::Pod, { chunks.iter().flat_map(move |chunk| { itertools::izip!( - chunk - .iter_component_indices(&timeline, &component_name) - .map(TimeKey::from), + chunk.iter_component_indices(&timeline, &component_name), chunk.iter_primitive_array_list::(&component_name) ) }) @@ -294,12 +285,10 @@ pub fn iter_string<'a>( chunks: &'a std::borrow::Cow<'a, [Chunk]>, timeline: Timeline, component_name: ComponentName, -) -> impl Iterator)> + 'a { +) -> impl Iterator)> + 'a { chunks.iter().flat_map(move |chunk| { itertools::izip!( - chunk - .iter_component_indices(&timeline, &component_name) - .map(TimeKey::from), + chunk.iter_component_indices(&timeline, &component_name), chunk.iter_string(&component_name) ) }) @@ -313,12 +302,10 @@ pub fn iter_buffer<'a, T: arrow2::types::NativeType>( chunks: &'a std::borrow::Cow<'a, [Chunk]>, timeline: Timeline, component_name: ComponentName, -) -> impl Iterator>)> + 'a { +) -> impl Iterator>)> + 'a { chunks.iter().flat_map(move |chunk| { itertools::izip!( - chunk - .iter_component_indices(&timeline, &component_name) - .map(TimeKey::from), + chunk.iter_component_indices(&timeline, &component_name), chunk.iter_buffer(&component_name) ) }) diff --git a/crates/viewer/re_space_view_text_log/src/visualizer_system.rs b/crates/viewer/re_space_view_text_log/src/visualizer_system.rs index 036506f026cc..f21437923e69 100644 --- a/crates/viewer/re_space_view_text_log/src/visualizer_system.rs +++ b/crates/viewer/re_space_view_text_log/src/visualizer_system.rs @@ -119,7 +119,7 @@ impl TextLogSystem { let all_frames = izip!(all_timepoints, all_frames); - for (timepoint, (index, bodies, levels, colors)) in all_frames { + for (timepoint, ((data_time, row_id), bodies, levels, colors)) in all_frames { let levels = levels.as_deref().unwrap_or(&[]).iter().cloned().map(Some); let colors = colors .unwrap_or(&[]) @@ -136,9 +136,9 @@ impl TextLogSystem { for (text, level, color) in results { self.entries.push(Entry { - row_id: index.row_id, + row_id, entity_path: data_result.entity_path.clone(), - time: index.time, + time: data_time, timepoint: timepoint.clone(), color, body: text.clone().into(), diff --git a/tests/python/release_checklist/check_latest_at_partial_updates.py b/tests/python/release_checklist/check_latest_at_partial_updates.py new file mode 100644 index 000000000000..afae940ed383 --- /dev/null +++ b/tests/python/release_checklist/check_latest_at_partial_updates.py @@ -0,0 +1,133 @@ +from __future__ import annotations + +import os +from argparse import Namespace +from uuid import uuid4 + +import rerun as rr +import rerun.blueprint as rrb + +README = """\ +# Latest-at: partial primary and secondary updates + +Checks that inter- and intra-timestamp partial updates are properly handled by latest-at queries, +end-to-end: all the way to the views and the renderer. + +For each frame, compare the view on the left with the expectation shown below. + +You might need to de-zoom a bit on each view (see [#6825](https://github.com/rerun-io/rerun/issues/6825) and +[#7281](https://github.com/rerun-io/rerun/issues/7281)). +""" + +FRAME_42 = """\ +Frame #42 should look like this: +* ![expected](https://static.rerun.io/check_latest_at_partial_updates_frame42/3ed69ef182d8e475a36fd9351669942f5092859f/480w.png) +""" + +FRAME_43 = """\ +Frame #43 should look like this: +* ![expected](https://static.rerun.io/check_latest_at_partial_updates_frame43/e86013ac21cc3b6bc17aceecc7cbb9e454128150/480w.png) +""" + +FRAME_44 = """\ +Frame #44 should look like this: +* ![expected](https://static.rerun.io/check_latest_at_partial_updates_frame44/df5d4bfe74bcf5fc12ad658f62f35908ceff80bf/480w.png) +""" + +FRAME_45 = """\ +Frame #45 should look like this: +* ![expected](https://static.rerun.io/check_latest_at_partial_updates_frame45/8c19fcbe9b7c59ed9e27452a5d2696eee84a4a55/480w.png) +""" + +FRAME_46 = """\ +Frame #46 should look like this: +* ![expected](https://static.rerun.io/check_latest_at_partial_updates_frame46/a7f7d8f5b07c1e3fe4ff66e42fd473d2f2edb04b/480w.png) +""" + + +def blueprint() -> rrb.BlueprintLike: + # TODO(#6825, #7281): set the camera properly so users don't have to manually de-zoom. + return rrb.Blueprint( + rrb.Horizontal( + contents=[ + rrb.Spatial3DView( + name="3D", + origin="/", + defaults=[ + rr.components.ColorBatch([255, 255, 0]), + rr.components.RadiusBatch([-10]), + ], + ), + rrb.Vertical( + rrb.TextDocumentView(origin="readme"), + rrb.TextDocumentView(origin="expected"), + row_shares=[1, 3], + ), + ] + ), + rrb.BlueprintPanel(state="collapsed"), + rrb.TimePanel(state="collapsed"), + rrb.SelectionPanel(state="collapsed"), + ) + + +def log_readme() -> None: + rr.log("readme", rr.TextDocument(README, media_type=rr.MediaType.MARKDOWN), static=True) + + +def log_points() -> None: + rr.set_time_sequence("frame", 42) + rr.log( + "expected", + rr.TextDocument(FRAME_42, media_type=rr.MediaType.MARKDOWN), + ) + rr.log("points", rr.Points3D([[0, 0, 0], [1, 1, 1]])) + + rr.set_time_sequence("frame", 43) + rr.log( + "expected", + rr.TextDocument(FRAME_43, media_type=rr.MediaType.MARKDOWN), + ) + rr.log_components("points", [rr.components.RadiusBatch(-20)]) + + rr.set_time_sequence("frame", 44) + rr.log( + "expected", + rr.TextDocument(FRAME_44, media_type=rr.MediaType.MARKDOWN), + ) + rr.log_components("points", [rr.components.ColorBatch([0, 0, 255])]) + + rr.set_time_sequence("frame", 45) + rr.log( + "expected", + rr.TextDocument(FRAME_45, media_type=rr.MediaType.MARKDOWN), + ) + rr.log("points", rr.Points3D([[0, 0, 1], [1, 1, 0]])) + + rr.set_time_sequence("frame", 46) + rr.log( + "expected", + rr.TextDocument(FRAME_46, media_type=rr.MediaType.MARKDOWN), + ) + rr.log("points", rr.Points3D([[0, 0, 0], [1, 1, 1]])) + rr.log_components("points", [rr.components.RadiusBatch(-30)]) + rr.log_components("points", [rr.components.ColorBatch([0, 255, 0])]) + rr.log("points", rr.Points3D([[0, 0, 1], [1, 1, 0]])) + + +def run(args: Namespace) -> None: + rr.script_setup(args, f"{os.path.basename(__file__)}", recording_id=uuid4()) + + rr.send_blueprint(blueprint()) + + log_readme() + log_points() + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser(description="Interactive release checklist") + rr.script_add_args(parser) + args = parser.parse_args() + run(args) diff --git a/tests/python/release_checklist/check_range_partial_updates.py b/tests/python/release_checklist/check_range_partial_updates.py new file mode 100644 index 000000000000..b335a9e79bcd --- /dev/null +++ b/tests/python/release_checklist/check_range_partial_updates.py @@ -0,0 +1,148 @@ +from __future__ import annotations + +import os +from argparse import Namespace +from uuid import uuid4 + +import rerun as rr +import rerun.blueprint as rrb + +README = """\ +# Range: partial primary and secondary updates + +Checks that inter- and intra-timestamp partial updates are properly handled by range queries, +end-to-end: all the way to the views and the renderer. + + +You might need to de-zoom the view a bit (see [#6825](https://github.com/rerun-io/rerun/issues/6825) and +[#7281](https://github.com/rerun-io/rerun/issues/7281)). + +* This entire panel should look like this: + - ![expected](https://static.rerun.io/check_range_partial_updates_frames/4aabe76ffd5753d8054c760675121444bbefe200/768w.png) +""" + + +def blueprint() -> rrb.BlueprintLike: + defaults = [ + rr.components.ColorBatch([255, 255, 0]), + rr.components.RadiusBatch([-10]), + ] + return rrb.Blueprint( + rrb.Grid( + contents=[ + rrb.Vertical( + rrb.Spatial3DView( + name="[42:42]", + origin="/", + time_ranges=rrb.VisibleTimeRange( + "frame", + start=rrb.TimeRangeBoundary.absolute(seq=42), + end=rrb.TimeRangeBoundary.absolute(seq=42), + ), + defaults=defaults, + ), + rrb.Spatial3DView( + name="[43:44]", + origin="/", + time_ranges=rrb.VisibleTimeRange( + "frame", + start=rrb.TimeRangeBoundary.absolute(seq=43), + end=rrb.TimeRangeBoundary.absolute(seq=44), + ), + defaults=defaults, + ), + rrb.Spatial3DView( + name="[42:44]", + origin="/", + time_ranges=rrb.VisibleTimeRange( + "frame", + start=rrb.TimeRangeBoundary.absolute(seq=42), + end=rrb.TimeRangeBoundary.absolute(seq=44), + ), + defaults=defaults, + ), + ), + rrb.Vertical( + rrb.Spatial3DView( + name="[43:45]", + origin="/", + time_ranges=rrb.VisibleTimeRange( + "frame", + start=rrb.TimeRangeBoundary.absolute(seq=43), + end=rrb.TimeRangeBoundary.absolute(seq=45), + ), + defaults=defaults, + ), + rrb.Spatial3DView( + name="[46:46]", + origin="/", + time_ranges=rrb.VisibleTimeRange( + "frame", + start=rrb.TimeRangeBoundary.absolute(seq=46), + end=rrb.TimeRangeBoundary.absolute(seq=46), + ), + defaults=defaults, + ), + rrb.Spatial3DView( + name="[-∞:+∞]", + origin="/", + time_ranges=rrb.VisibleTimeRange( + "frame", + start=rrb.TimeRangeBoundary.infinite(), + end=rrb.TimeRangeBoundary.infinite(), + ), + defaults=defaults, + ), + ), + rrb.TextDocumentView(origin="readme"), + ], + grid_columns=3, + ), + rrb.BlueprintPanel(state="collapsed"), + rrb.TimePanel(state="collapsed"), + rrb.SelectionPanel(state="collapsed"), + ) + + +def log_readme() -> None: + rr.log("readme", rr.TextDocument(README, media_type=rr.MediaType.MARKDOWN), static=True) + + +def log_points() -> None: + rr.set_time_sequence("frame", 42) + rr.log("points", rr.Points3D([[0, 0, 0], [1, 1, 1]], colors=[255, 0, 0])) + + rr.set_time_sequence("frame", 43) + rr.log_components("points", [rr.components.RadiusBatch(-20)]) + + rr.set_time_sequence("frame", 44) + rr.log_components("points", [rr.components.ColorBatch([0, 0, 255])]) + + rr.set_time_sequence("frame", 45) + rr.log("points", rr.Points3D([[0, 0, 1], [1, 1, 0]])) + rr.log_components("points", [rr.components.RadiusBatch(-40)]) + + rr.set_time_sequence("frame", 46) + rr.log_components("points", [rr.components.RadiusBatch(-40)]) + rr.log("points", rr.Points3D([[0, 2, 0], [1, 2, 1]])) + rr.log_components("points", [rr.components.RadiusBatch(-30)]) + rr.log_components("points", [rr.components.ColorBatch([0, 255, 0])]) + rr.log("points", rr.Points3D([[0, 0, 2], [2, 2, 0]])) + + +def run(args: Namespace) -> None: + rr.script_setup(args, f"{os.path.basename(__file__)}", recording_id=uuid4()) + + rr.send_blueprint(blueprint()) + + log_readme() + log_points() + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser(description="Interactive release checklist") + rr.script_add_args(parser) + args = parser.parse_args() + run(args)