From d9deef0fbf80634426f40bc0686004ba170e6236 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Fri, 2 Aug 2024 11:08:32 +0200 Subject: [PATCH] Vastly improved support for deserialized iteration (#7024) Whether we like it or not, sometimes we've got to deserialize. Up until now, the new APIs only provided a nice UX for the deserialization-free paths, while the deserialized path was particularly painful to use. The source of the pain is, as so often, the lack of lending iterators in the stdlib. This PR fixes that. Essentially, this: ```rust let all_fill_mode_chunks = results.get_optional_chunks(&FillMode::name()); let all_fill_modes = results.iter_as(timeline, FillMode::name()); let mut all_fill_mode_iters = all_fill_mode_chunks .iter() .map(|chunk| chunk.iter_component::()) .collect_vec(); let mut all_fill_modes_indexed = { let all_fill_modes = all_fill_mode_iters.iter_mut().flat_map(|it| it.into_iter()); let all_fill_modes_indices = all_fill_mode_chunks.iter().flat_map(|chunk| { chunk.iter_component_indices(&timeline, &FillMode::name()) }); itertools::izip!(all_fill_modes_indices, all_fill_modes) }; ``` becomes this: ```rust let all_fill_modes = results.iter_as(timeline, FillMode::name()).component::(); ``` --- Cargo.lock | 2 + crates/store/re_chunk/src/iter.rs | 79 +++++++++++++------ crates/store/re_chunk/src/lib.rs | 1 + crates/store/re_log_types/Cargo.toml | 1 + .../re_log_types/src/example_components.rs | 10 ++- crates/store/re_query2/Cargo.toml | 1 + crates/store/re_query2/examples/range.rs | 43 +++++----- crates/store/re_query2/tests/range.rs | 55 +++++++------ .../viewer/re_space_view/src/results_ext2.rs | 16 +++- .../src/visualizers/boxes3d.rs | 22 +----- .../src/visualizers/ellipsoids.rs | 25 ++---- .../src/visualizers/meshes.rs | 31 ++------ .../visualizers/utilities/entity_iterator.rs | 23 +++++- .../src/point_visualizer_system.rs | 1 - 14 files changed, 164 insertions(+), 146 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 874a1168eafe..d0dc3925ad7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4620,6 +4620,7 @@ dependencies = [ "ahash", "anyhow", "backtrace", + "bytemuck", "clean-path", "criterion", "crossbeam", @@ -4724,6 +4725,7 @@ dependencies = [ "ahash", "anyhow", "backtrace", + "bytemuck", "criterion", "indent", "indexmap 2.1.0", diff --git a/crates/store/re_chunk/src/iter.rs b/crates/store/re_chunk/src/iter.rs index 98d941c18392..d7c7c468ee0c 100644 --- a/crates/store/re_chunk/src/iter.rs +++ b/crates/store/re_chunk/src/iter.rs @@ -529,44 +529,77 @@ impl Chunk { /// The actual iterator implementation for [`Chunk::iter_component`]. pub struct ChunkComponentIter { - values: Vec, + values: Arc>, offsets: IO, } -/// The intermediate state for [`ChunkComponentIter`]. +/// The underlying item type for [`ChunkComponentIter`]. /// -/// Required so that we can return references to the inner data. -pub struct ChunkComponentIterRef<'a, C, IO> { - values: &'a [C], - offsets: &'a mut IO, +/// This allows us to cheaply carry slices of deserialized data, while working around the +/// limitations of Rust's Iterator trait and ecosystem. +/// +/// See [`ChunkComponentIterItem::as_slice`]. +#[derive(Clone, PartialEq)] +pub struct ChunkComponentIterItem { + values: Arc>, + index: usize, + len: usize, } -impl<'a, C: Component, IO: Iterator> IntoIterator - for &'a mut ChunkComponentIter -{ - type Item = &'a [C]; +impl PartialEq<[C]> for ChunkComponentIterItem { + fn eq(&self, rhs: &[C]) -> bool { + self.as_slice().eq(rhs) + } +} - type IntoIter = ChunkComponentIterRef<'a, C, IO>; +impl PartialEq> for ChunkComponentIterItem { + fn eq(&self, rhs: &Vec) -> bool { + self.as_slice().eq(rhs) + } +} + +impl Eq for ChunkComponentIterItem {} +// NOTE: No `C: Default`! +impl Default for ChunkComponentIterItem { #[inline] - fn into_iter(self) -> Self::IntoIter { - ChunkComponentIterRef { - values: &self.values, - offsets: &mut self.offsets, + fn default() -> Self { + Self { + values: Arc::new(Vec::new()), + index: 0, + len: 0, } } } -impl<'a, C: Component, IO: Iterator> Iterator - for ChunkComponentIterRef<'a, C, IO> -{ - type Item = &'a [C]; +impl ChunkComponentIterItem { + #[inline] + pub fn as_slice(&self) -> &[C] { + &self.values[self.index..self.index + self.len] + } +} + +impl std::ops::Deref for ChunkComponentIterItem { + type Target = [C]; + + #[inline] + fn deref(&self) -> &Self::Target { + self.as_slice() + } +} + +impl> Iterator for ChunkComponentIter { + type Item = ChunkComponentIterItem; #[inline] fn next(&mut self) -> Option { self.offsets .next() - .map(move |(idx, len)| &self.values[idx..idx + len]) + .map(move |(index, len)| ChunkComponentIterItem { + values: Arc::clone(&self.values), + index, + len, + }) } } @@ -591,7 +624,7 @@ impl Chunk { ) -> ChunkComponentIter + '_> { let Some(list_array) = self.components.get(&C::name()) else { return ChunkComponentIter { - values: vec![], + values: Arc::new(vec![]), offsets: Either::Left(std::iter::empty()), }; }; @@ -614,7 +647,7 @@ impl Chunk { ); } return ChunkComponentIter { - values: vec![], + values: Arc::new(vec![]), offsets: Either::Left(std::iter::empty()), }; } @@ -622,7 +655,7 @@ impl Chunk { // NOTE: No need for validity checks here, `iter_offsets` already takes care of that. ChunkComponentIter { - values, + values: Arc::new(values), offsets: Either::Right(self.iter_component_offsets(&C::name())), } } diff --git a/crates/store/re_chunk/src/lib.rs b/crates/store/re_chunk/src/lib.rs index 0f2693f41ced..5e645ba45d72 100644 --- a/crates/store/re_chunk/src/lib.rs +++ b/crates/store/re_chunk/src/lib.rs @@ -24,6 +24,7 @@ pub use self::builder::{ChunkBuilder, ChunkTimelineBuilder}; pub use self::chunk::{Chunk, ChunkError, ChunkResult, ChunkTimeline}; pub use self::helpers::{ChunkShared, UnitChunkShared}; pub use self::id::{ChunkId, RowId}; +pub use self::iter::{ChunkComponentIter, ChunkComponentIterItem, ChunkIndicesIter}; pub use self::latest_at::LatestAtQuery; pub use self::range::RangeQuery; pub use self::transport::TransportChunk; diff --git a/crates/store/re_log_types/Cargo.toml b/crates/store/re_log_types/Cargo.toml index f3a34a2e50f0..40ae5049b94d 100644 --- a/crates/store/re_log_types/Cargo.toml +++ b/crates/store/re_log_types/Cargo.toml @@ -60,6 +60,7 @@ arrow2 = { workspace = true, features = [ "compute_concatenate", ] } backtrace.workspace = true +bytemuck.workspace = true clean-path.workspace = true document-features.workspace = true fixed = { workspace = true, default-features = false } diff --git a/crates/store/re_log_types/src/example_components.rs b/crates/store/re_log_types/src/example_components.rs index 8fa4613b6825..6eae2e54a8f4 100644 --- a/crates/store/re_log_types/src/example_components.rs +++ b/crates/store/re_log_types/src/example_components.rs @@ -40,7 +40,8 @@ impl re_types_core::Archetype for MyPoints { // ---------------------------------------------------------------------------- -#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] pub struct MyPoint { pub x: f32, pub y: f32, @@ -148,7 +149,8 @@ impl Loggable for MyPoint { // ---------------------------------------------------------------------------- -#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] pub struct MyPoint64 { pub x: f64, pub y: f64, @@ -256,7 +258,7 @@ impl Loggable for MyPoint64 { // ---------------------------------------------------------------------------- -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, bytemuck::Pod, bytemuck::Zeroable)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[repr(transparent)] pub struct MyColor(pub u32); @@ -381,7 +383,7 @@ impl Loggable for MyLabel { // ---------------------------------------------------------------------------- -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, bytemuck::Pod, bytemuck::Zeroable)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[repr(transparent)] pub struct MyIndex(pub u64); diff --git a/crates/store/re_query2/Cargo.toml b/crates/store/re_query2/Cargo.toml index 477939a60322..cf9be8fe46c4 100644 --- a/crates/store/re_query2/Cargo.toml +++ b/crates/store/re_query2/Cargo.toml @@ -55,6 +55,7 @@ thiserror.workspace = true [dev-dependencies] +bytemuck.workspace = true criterion.workspace = true mimalloc.workspace = true rand = { workspace = true, features = ["std", "std_rng"] } diff --git a/crates/store/re_query2/examples/range.rs b/crates/store/re_query2/examples/range.rs index acfe88500c8c..4492638293e9 100644 --- a/crates/store/re_query2/examples/range.rs +++ b/crates/store/re_query2/examples/range.rs @@ -43,30 +43,21 @@ fn main() -> anyhow::Result<()> { // once for the whole column, and will then return references into that data. // This is why you have to process the data in two-steps: the iterator needs to have somewhere // to reference to. - let mut all_points_iters = all_points_chunks - .iter() - .map(|chunk| chunk.iter_component::()) - .collect_vec(); - let all_points_indexed = { - let all_points = all_points_iters.iter_mut().flat_map(|it| it.into_iter()); - let all_points_indices = all_points_chunks - .iter() - .flat_map(|chunk| chunk.iter_component_indices(&query.timeline(), &MyPoint::name())); - izip!(all_points_indices, all_points) - }; - let mut all_labels_iters = all_labels_chunks + let all_points_indexed = all_points_chunks.iter().flat_map(|chunk| { + izip!( + chunk.iter_component_indices(&query.timeline(), &MyPoint::name()), + chunk.iter_component::() + ) + }); + let all_labels_indexed = all_labels_chunks .unwrap_or_default() .iter() - .map(|chunk| chunk.iter_component::()) - .collect_vec(); - let all_labels_indexed = { - let all_labels = all_labels_iters.iter_mut().flat_map(|it| it.into_iter()); - let all_labels_indices = all_labels_chunks - .unwrap_or_default() - .iter() - .flat_map(|chunk| chunk.iter_component_indices(&query.timeline(), &MyLabel::name())); - izip!(all_labels_indices, all_labels) - }; + .flat_map(|chunk| { + izip!( + chunk.iter_component_indices(&query.timeline(), &MyLabel::name()), + chunk.iter_component::() + ) + }); // Or, if you want every last bit of performance you can get, you can manipulate the raw // data directly: @@ -74,7 +65,7 @@ fn main() -> anyhow::Result<()> { .unwrap_or_default() .iter() .flat_map(|chunk| { - itertools::izip!( + izip!( chunk.iter_component_indices(&query.timeline(), &MyColor::name()), chunk.iter_primitive::(&MyColor::name()), ) @@ -90,8 +81,10 @@ fn main() -> anyhow::Result<()> { eprintln!("results:"); for ((data_time, row_id), points, colors, labels) in all_frames { - let colors = colors.unwrap_or(&[]).iter().map(|c| Some(MyColor(*c))); - let labels = labels.unwrap_or(&[]).iter().cloned().map(Some); + let points = points.as_slice(); + let colors = colors.unwrap_or_default().iter().map(|c| Some(MyColor(*c))); + let labels = labels.unwrap_or_default(); + let labels = labels.as_slice().iter().cloned().map(Some); // Apply your instance-level joining logic, if any: let results = diff --git a/crates/store/re_query2/tests/range.rs b/crates/store/re_query2/tests/range.rs index b6d86262cfac..920630e86595 100644 --- a/crates/store/re_query2/tests/range.rs +++ b/crates/store/re_query2/tests/range.rs @@ -3,7 +3,7 @@ use std::sync::Arc; -use itertools::Itertools as _; +use itertools::Itertools; use re_chunk::{RowId, Timeline}; use re_chunk_store::{ @@ -1039,42 +1039,41 @@ fn query_and_compare( ); let all_points_chunks = cached.get_required(&MyPoint::name()).unwrap(); - let mut all_points_iters = all_points_chunks + let all_points_indexed = all_points_chunks .iter() - .map(|chunk| chunk.iter_component::()) + .flat_map(|chunk| { + itertools::izip!( + chunk.iter_component_indices(&query.timeline(), &MyPoint::name()), + chunk.iter_component::() + ) + }) + .collect_vec(); + // Only way I've managed to make `rustc` realize there's a `PartialEq` available. + let all_points_indexed = all_points_indexed + .iter() + .map(|(index, points)| (*index, points.as_slice())) .collect_vec(); - let all_points_indexed = { - let all_points = all_points_iters.iter_mut().flat_map(|it| it.into_iter()); - let all_points_indices = all_points_chunks.iter().flat_map(|chunk| { - chunk.iter_component_indices(&query.timeline(), &MyPoint::name()) - }); - itertools::izip!(all_points_indices, all_points) - }; let all_colors_chunks = cached.get(&MyColor::name()).unwrap_or_default(); - let mut all_colors_iters = all_colors_chunks + let all_colors_indexed = all_colors_chunks .iter() - .map(|chunk| chunk.iter_component::()) + .flat_map(|chunk| { + itertools::izip!( + chunk.iter_component_indices(&query.timeline(), &MyColor::name()), + chunk.iter_primitive::(&MyColor::name()), + ) + }) + .collect_vec(); + // Only way I've managed to make `rustc` realize there's a `PartialEq` available. + let all_colors_indexed = all_colors_indexed + .iter() + .map(|(index, colors)| (*index, bytemuck::cast_slice(colors))) .collect_vec(); - let all_colors_indexed = { - let all_colors = all_colors_iters.iter_mut().flat_map(|it| it.into_iter()); - let all_colors_indices = all_colors_chunks.iter().flat_map(|chunk| { - chunk.iter_component_indices(&query.timeline(), &MyColor::name()) - }); - itertools::izip!(all_colors_indices, all_colors) - }; eprintln!("{query:?}"); eprintln!("{store}"); - similar_asserts::assert_eq!( - expected_all_points_indexed, - all_points_indexed.collect_vec(), - ); - - similar_asserts::assert_eq!( - expected_all_colors_indexed, - all_colors_indexed.collect_vec(), - ); + similar_asserts::assert_eq!(expected_all_points_indexed, all_points_indexed); + similar_asserts::assert_eq!(expected_all_colors_indexed, all_colors_indexed); } } diff --git a/crates/viewer/re_space_view/src/results_ext2.rs b/crates/viewer/re_space_view/src/results_ext2.rs index 2921a09ab56a..3ae919f1c360 100644 --- a/crates/viewer/re_space_view/src/results_ext2.rs +++ b/crates/viewer/re_space_view/src/results_ext2.rs @@ -364,7 +364,7 @@ impl<'a> RangeResultsExt for HybridResults<'a> { // --- -use re_chunk::{RowId, TimeInt, 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`]. @@ -375,6 +375,20 @@ pub struct HybridResultsChunkIter<'a> { } impl<'a> HybridResultsChunkIter<'a> { + /// Iterate as indexed deserialized batches. + /// + /// See [`Chunk::iter_component`] for more information. + pub fn component( + &'a self, + ) -> impl Iterator)> + 'a { + self.chunks.iter().flat_map(move |chunk| { + itertools::izip!( + chunk.iter_component_indices(&self.timeline, &self.component_name), + chunk.iter_component::(), + ) + }) + } + /// Iterate as indexed primitives. /// /// See [`Chunk::iter_primitive`] for more information. diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs b/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs index 5d4556c684a6..6cf1cad14522 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs @@ -1,5 +1,3 @@ -use itertools::Itertools as _; - use re_entity_db::InstancePathHash; use re_log_types::Instance; use re_renderer::{ @@ -269,24 +267,12 @@ impl VisualizerSystem for Boxes3DVisualizer { let all_keypoint_ids = results.iter_as(timeline, KeypointId::name()); // Deserialized because it's a union. - let all_fill_mode_chunks = results.get_optional_chunks(&FillMode::name()); - let mut all_fill_mode_iters = all_fill_mode_chunks - .iter() - .map(|chunk| chunk.iter_component::()) - .collect_vec(); - let mut all_fill_modes_indexed = { - let all_fill_modes = - all_fill_mode_iters.iter_mut().flat_map(|it| it.into_iter()); - let all_fill_modes_indices = all_fill_mode_chunks.iter().flat_map(|chunk| { - chunk.iter_component_indices(&timeline, &FillMode::name()) - }); - itertools::izip!(all_fill_modes_indices, all_fill_modes) - }; - + let all_fill_modes = results.iter_as(timeline, FillMode::name()); // fill mode is currently a non-repeated component - let fill_mode: FillMode = all_fill_modes_indexed + let fill_mode: FillMode = all_fill_modes + .component::() .next() - .and_then(|(_, fill_modes)| fill_modes.first().copied()) + .and_then(|(_, fill_modes)| fill_modes.as_slice().first().copied()) .unwrap_or_default(); match fill_mode { diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs b/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs index 4cf622d8fe5a..33738b0b7500 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs @@ -1,5 +1,3 @@ -use itertools::Itertools as _; - use re_entity_db::InstancePathHash; use re_log_types::Instance; use re_renderer::{ @@ -298,30 +296,17 @@ impl VisualizerSystem for Ellipsoids3DVisualizer { ); let all_colors = results.iter_as(timeline, Color::name()); let all_line_radii = results.iter_as(timeline, Radius::name()); + // Deserialized because it's a union. + let all_fill_modes = results.iter_as(timeline, FillMode::name()); let all_labels = results.iter_as(timeline, Text::name()); let all_class_ids = results.iter_as(timeline, ClassId::name()); let all_keypoint_ids = results.iter_as(timeline, KeypointId::name()); - // Deserialized because it's a union. - let all_fill_mode_chunks = results.get_optional_chunks(&FillMode::name()); - let mut all_fill_mode_iters = all_fill_mode_chunks - .iter() - .map(|chunk| chunk.iter_component::()) - .collect_vec(); - let all_fill_modes_indexed = { - let all_fill_modes = - all_fill_mode_iters.iter_mut().flat_map(|it| it.into_iter()); - let all_fill_modes_indices = all_fill_mode_chunks.iter().flat_map(|chunk| { - chunk.iter_component_indices(&timeline, &FillMode::name()) - }); - itertools::izip!(all_fill_modes_indices, all_fill_modes) - }; - let data = re_query2::range_zip_1x6( all_half_sizes_indexed, all_colors.primitive::(), all_line_radii.primitive::(), - all_fill_modes_indexed, + all_fill_modes.component::(), all_labels.string(), all_class_ids.primitive::(), all_keypoint_ids.primitive::(), @@ -332,7 +317,7 @@ impl VisualizerSystem for Ellipsoids3DVisualizer { half_sizes, colors, line_radii, - fill_mode, + fill_modes, labels, class_ids, keypoint_ids, @@ -343,7 +328,7 @@ impl VisualizerSystem for Ellipsoids3DVisualizer { line_radii: line_radii .map_or(&[], |line_radii| bytemuck::cast_slice(line_radii)), // fill mode is currently a non-repeated component - fill_mode: fill_mode + fill_mode: fill_modes .unwrap_or_default() .first() .copied() 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 60d3ead85b02..e013537cf477 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/meshes.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/meshes.rs @@ -1,5 +1,3 @@ -use itertools::Itertools as _; - use re_chunk_store::RowId; use re_log_types::{hash::Hash64, Instance, TimeInt}; use re_renderer::renderer::MeshInstance; @@ -52,7 +50,7 @@ struct Mesh3DComponentData<'a> { triangle_indices: Option<&'a [TriangleIndices]>, albedo_factor: Option<&'a AlbedoFactor>, - albedo_texture: Option<&'a TensorData>, + albedo_texture: Option, class_ids: &'a [ClassId], } @@ -108,7 +106,8 @@ impl Mesh3DVisualizer { vertex_texcoords: (!vertex_texcoords.is_empty()) .then_some(vertex_texcoords), albedo_factor: data.albedo_factor.copied(), - albedo_texture: data.albedo_texture.cloned(), + // NOTE: not actually cloning anything. + albedo_texture: data.albedo_texture.clone(), class_ids: (!data.class_ids.is_empty()) .then(|| data.class_ids.to_owned()), }, @@ -202,25 +201,10 @@ impl VisualizerSystem for Mesh3DVisualizer { let all_vertex_texcoords = results.iter_as(timeline, Texcoord2D::name()); let all_triangle_indices = results.iter_as(timeline, TriangleIndices::name()); let all_albedo_factors = results.iter_as(timeline, AlbedoFactor::name()); - let all_class_ids = results.iter_as(timeline, ClassId::name()); - // TODO(#6386): we have to deserialize here because `TensorData` is still a complex // type at this point. - let all_albedo_textures_chunks = results.get_optional_chunks(&TensorData::name()); - let mut all_albedo_textures_iters = all_albedo_textures_chunks - .iter() - .map(|chunk| chunk.iter_component::()) - .collect_vec(); - let all_albedo_textures_indexed = { - let all_albedo_textures = all_albedo_textures_iters - .iter_mut() - .flat_map(|it| it.into_iter()); - let all_albedo_textures_indices = - all_albedo_textures_chunks.iter().flat_map(|chunk| { - chunk.iter_component_indices(&timeline, &TensorData::name()) - }); - itertools::izip!(all_albedo_textures_indices, all_albedo_textures) - }; + let all_albedo_textures = results.iter_as(timeline, TensorData::name()); + let all_class_ids = results.iter_as(timeline, ClassId::name()); let query_result_hash = results.query_result_hash(); @@ -231,7 +215,7 @@ impl VisualizerSystem for Mesh3DVisualizer { all_vertex_texcoords.primitive_array::<2, f32>(), all_triangle_indices.primitive_array::<3, u32>(), all_albedo_factors.primitive::(), - all_albedo_textures_indexed, + all_albedo_textures.component::(), all_class_ids.primitive::(), ) .map( @@ -263,7 +247,8 @@ impl VisualizerSystem for Mesh3DVisualizer { bytemuck::cast_slice(albedo_factors) }) .first(), - albedo_texture: albedo_textures.and_then(|v| v.first()), + // NOTE: not actually cloning anything. + albedo_texture: albedo_textures.unwrap_or_default().first().cloned(), class_ids: class_ids .map_or(&[], |class_ids| bytemuck::cast_slice(class_ids)), } 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 7435ff82d7a9..8a305f899aa4 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 @@ -310,19 +310,36 @@ where // --- -use re_chunk::{Chunk, ComponentName, RowId}; +use re_chunk::{Chunk, ChunkComponentIterItem, ComponentName, RowId}; use re_chunk_store::external::{re_chunk, re_chunk::external::arrow2}; +/// Iterate `chunks` as indexed deserialized batches. +/// +/// See [`Chunk::iter_component`] for more information. +#[allow(unused)] +pub fn iter_component<'a, C: re_types::Component>( + chunks: &'a std::borrow::Cow<'a, [Chunk]>, + timeline: Timeline, + component_name: ComponentName, +) -> impl Iterator)> + 'a { + chunks.iter().flat_map(move |chunk| { + itertools::izip!( + chunk.iter_component_indices(&timeline, &component_name), + chunk.iter_component::() + ) + }) +} + /// Iterate `chunks` as indexed primitives. /// /// See [`Chunk::iter_primitive`] for more information. #[allow(unused)] pub fn iter_primitive<'a, T: arrow2::types::NativeType>( - chunks: impl IntoIterator + 'a, + chunks: &'a std::borrow::Cow<'a, [Chunk]>, timeline: Timeline, component_name: ComponentName, ) -> impl Iterator + 'a { - chunks.into_iter().flat_map(move |chunk| { + chunks.iter().flat_map(move |chunk| { itertools::izip!( chunk.iter_component_indices(&timeline, &component_name), chunk.iter_primitive::(&component_name) diff --git a/crates/viewer/re_space_view_time_series/src/point_visualizer_system.rs b/crates/viewer/re_space_view_time_series/src/point_visualizer_system.rs index f6eb1b62de5b..b5b8243f0c41 100644 --- a/crates/viewer/re_space_view_time_series/src/point_visualizer_system.rs +++ b/crates/viewer/re_space_view_time_series/src/point_visualizer_system.rs @@ -336,7 +336,6 @@ impl SeriesPointSystem { let marker_shape = all_marker_shapes_chunks[0] .iter_component::() - .into_iter() .next() .and_then(|marker_shapes| marker_shapes.first().copied());