use itertools::{Either, Itertools as _};
use re_types_core::reflection::ComponentTypeSet;
use crate::Chunk;
use super::SplitColumnsOptions;
#[inline]
fn wants_own_chunk(
descriptor: &re_types_core::ComponentDescriptor,
own_chunks: &ComponentTypeSet,
) -> bool {
descriptor
.component_type
.is_some_and(|component_type| own_chunks.contains(&component_type))
}
fn is_dedicated_own_chunk(chunk: &Chunk, own_chunks: &ComponentTypeSet) -> bool {
let mut columns = chunk.components().values();
let (Some(column), None) = (columns.next(), columns.next()) else {
return false; };
wants_own_chunk(&column.descriptor, own_chunks)
}
pub fn may_merge(lhs: &Chunk, rhs: &Chunk, own_chunks: &ComponentTypeSet) -> bool {
if !is_dedicated_own_chunk(lhs, own_chunks) && !is_dedicated_own_chunk(rhs, own_chunks) {
return true;
}
has_exactly_same_components(lhs, rhs)
}
fn has_exactly_same_components(lhs: &Chunk, rhs: &Chunk) -> bool {
lhs.components().len() == rhs.components().len()
&& lhs.components().values().all(|column| {
rhs.components()
.contains_component(column.descriptor.component)
})
}
pub fn split(chunk: &Chunk, options: &SplitColumnsOptions) -> Option<Vec<Chunk>> {
let SplitColumnsOptions {
own_chunks,
split_size_ratio: _, } = options;
let (wants_own_chunk, rest): (Vec<_>, Vec<_>) = chunk
.components()
.values()
.map(|column| &column.descriptor)
.partition_map(|descriptor| {
if wants_own_chunk(descriptor, own_chunks) {
Either::Left(descriptor.component)
} else {
Either::Right(descriptor.component)
}
});
if wants_own_chunk.is_empty() || (wants_own_chunk.len() == 1 && rest.is_empty()) {
return None; }
let mut splits: Vec<Chunk> = wants_own_chunk
.iter()
.map(|component| chunk.components_sliced(&[*component]))
.collect();
if !rest.is_empty() {
splits.push(chunk.components_sliced(&rest));
}
re_log::trace!(
entity = %chunk.entity_path(),
num_splits = splits.len(),
"splitting out components that want their own chunk"
);
Some(splits)
}
#[cfg(test)]
mod tests {
use super::*;
use re_log_types::{EntityPath, TimePoint, Timeline};
use re_sdk_types::archetypes::VideoStream;
use re_sdk_types::components::{IsKeyframe, VideoSample};
use re_types_core::{Component as _, ComponentBatch, ComponentDescriptor};
use crate::RowId;
fn own_chunks() -> ComponentTypeSet {
let mut own_chunks = ComponentTypeSet::default();
own_chunks.insert(IsKeyframe::name());
own_chunks
}
fn options() -> SplitColumnsOptions {
SplitColumnsOptions {
own_chunks: own_chunks(),
..Default::default()
}
}
fn video_chunk(entity_path: &str, timepoint: TimePoint) -> Chunk {
let sample = VideoSample::from(vec![0u8; 64 * 1024]);
let is_keyframe = IsKeyframe::from(true);
Chunk::builder(EntityPath::from(entity_path))
.with_component_batches(
RowId::new(),
timepoint,
[
(
VideoStream::descriptor_sample(),
&[sample] as &dyn ComponentBatch,
),
(
VideoStream::descriptor_is_keyframe(),
&[is_keyframe] as &dyn ComponentBatch,
),
],
)
.build()
.expect("failed to build chunk")
}
fn keyframe_chunk() -> Chunk {
Chunk::builder(EntityPath::from("video"))
.with_component_batches(
RowId::new(),
TimePoint::from([(Timeline::new_sequence("frame"), 1)]),
[(
VideoStream::descriptor_is_keyframe(),
&[IsKeyframe::from(true)] as &dyn ComponentBatch,
)],
)
.build()
.expect("failed to build chunk")
}
fn sample_chunk() -> Chunk {
Chunk::builder(EntityPath::from("video"))
.with_component_batches(
RowId::new(),
TimePoint::from([(Timeline::new_sequence("frame"), 1)]),
[(
VideoStream::descriptor_sample(),
&[VideoSample::from(vec![0u8; 1024])] as &dyn ComponentBatch,
)],
)
.build()
.expect("failed to build chunk")
}
#[test]
fn refuses_to_merge_a_marker_chunk_with_anything_else() {
let marker = keyframe_chunk();
let mixed = video_chunk(
"video",
TimePoint::from([(Timeline::new_sequence("frame"), 1)]),
);
assert!(
marker.concatenable(&mixed),
"the guard is only needed because `concatenable` allows this"
);
assert!(!may_merge(&marker, &mixed, &own_chunks()));
assert!(!may_merge(&mixed, &marker, &own_chunks()));
assert!(!may_merge(&marker, &sample_chunk(), &own_chunks()));
}
#[test]
fn allows_merging_chunks_that_hold_the_same_components() {
assert!(may_merge(
&keyframe_chunk(),
&keyframe_chunk(),
&own_chunks()
));
assert!(may_merge(&sample_chunk(), &sample_chunk(), &own_chunks()));
let timepoint = TimePoint::from([(Timeline::new_sequence("frame"), 1)]);
assert!(may_merge(
&video_chunk("video", timepoint.clone()),
&video_chunk("video", timepoint),
&own_chunks(),
));
}
#[test]
fn splits_is_keyframe_out_of_its_archetype() {
let timepoint = TimePoint::from([(Timeline::new_sequence("frame"), 1)]);
let chunk = video_chunk("video", timepoint);
let splits = split(&chunk, &options()).expect("should split");
assert_eq!(splits.len(), 2);
assert_eq!(splits[0].components().len(), 1);
assert!(
splits[0]
.components()
.contains_component(VideoStream::descriptor_is_keyframe().component),
"the marker comes first, in a chunk of its own"
);
assert!(
splits[1]
.components()
.contains_component(VideoStream::descriptor_sample().component)
);
for split in &splits {
assert_eq!(split.num_rows(), chunk.num_rows());
}
}
#[test]
fn splits_from_a_static_chunk_too() {
let chunk = video_chunk("video", TimePoint::default());
assert!(chunk.is_static());
assert_eq!(split(&chunk, &options()).expect("should split").len(), 2);
}
#[test]
fn leaves_a_dedicated_chunk_alone() {
let chunk = Chunk::builder(EntityPath::from("video"))
.with_component_batches(
RowId::new(),
TimePoint::from([(Timeline::new_sequence("frame"), 1)]),
[(
VideoStream::descriptor_is_keyframe(),
&[IsKeyframe::from(true)] as &dyn ComponentBatch,
)],
)
.build()
.expect("failed to build chunk");
assert!(split(&chunk, &options()).is_none());
}
#[test]
fn leaves_unmarked_components_alone() {
let chunk = Chunk::builder(EntityPath::from("video"))
.with_component_batches(
RowId::new(),
TimePoint::from([(Timeline::new_sequence("frame"), 1)]),
[(
VideoStream::descriptor_sample(),
&[VideoSample::from(vec![0u8; 1024])] as &dyn ComponentBatch,
)],
)
.build()
.expect("failed to build chunk");
assert!(split(&chunk, &options()).is_none());
}
#[test]
fn leaves_untyped_columns_alone() {
let chunk = Chunk::builder(EntityPath::from("video"))
.with_component_batches(
RowId::new(),
TimePoint::from([(Timeline::new_sequence("frame"), 1)]),
[
(
ComponentDescriptor {
archetype: Some("rerun.archetypes.VideoStream".into()),
component: "VideoStream:is_keyframe".into(),
component_type: None,
},
&[IsKeyframe::from(true)] as &dyn ComponentBatch,
),
(
VideoStream::descriptor_sample(),
&[VideoSample::from(vec![0u8; 1024])] as &dyn ComponentBatch,
),
],
)
.build()
.expect("failed to build chunk");
assert!(split(&chunk, &options()).is_none());
}
}