use arrow::buffer::ScalarBuffer as ArrowScalarBuffer;
use arrow::{
array::{
Array as ArrowArray, ArrayRef as ArrowArrayRef,
Int32DictionaryArray as ArrowInt32DictionaryArray, ListArray as ArrowListArray,
},
buffer::NullBuffer as ArrowNullBuffer,
datatypes::DataType as ArrowDataType,
};
use thiserror::Error;
use re_arrow_util::ArrowArrayDowncastRef as _;
use re_chunk_store::{ColumnDescriptor, ComponentColumnDescriptor, LatestAtQuery};
use re_dataframe::external::re_chunk::{TimeColumn, TimeColumnError};
use re_log_types::{EntityPath, TimeInt, Timeline};
use re_types_core::ComponentName;
use re_ui::UiExt;
use re_viewer_context::{UiLayout, ViewerContext};
#[derive(Error, Debug)]
pub(crate) enum DisplayRecordBatchError {
#[error("Bad column for timeline '{timeline}': {error}")]
BadTimeColumn {
timeline: String,
error: TimeColumnError,
},
#[error("Unexpected column data type for component '{0}': {1:?}")]
UnexpectedComponentColumnDataType(String, ArrowDataType),
}
#[derive(Debug)]
pub(crate) enum ComponentData {
Null,
ListArray(ArrowListArray),
DictionaryArray {
dict: ArrowInt32DictionaryArray,
values: ArrowListArray,
},
}
impl ComponentData {
fn try_new(
descriptor: &ComponentColumnDescriptor,
column_data: &ArrowArrayRef,
) -> Result<Self, DisplayRecordBatchError> {
match column_data.data_type() {
ArrowDataType::Null => Ok(Self::Null),
ArrowDataType::List(_) => Ok(Self::ListArray(
column_data
.downcast_array_ref::<ArrowListArray>()
.expect("`data_type` checked, failure is a bug in re_dataframe")
.clone(),
)),
ArrowDataType::Dictionary(_, _) => {
let dict = column_data
.downcast_array_ref::<ArrowInt32DictionaryArray>()
.expect("`data_type` checked, failure is a bug in re_dataframe")
.clone();
let values = dict
.values()
.downcast_array_ref::<ArrowListArray>()
.expect("`data_type` checked, failure is a bug in re_dataframe")
.clone();
Ok(Self::DictionaryArray { dict, values })
}
_ => Err(DisplayRecordBatchError::UnexpectedComponentColumnDataType(
descriptor.component_name.to_string(),
column_data.data_type().to_owned(),
)),
}
}
fn instance_count(&self, row_index: usize) -> u64 {
match self {
Self::Null => 0,
Self::ListArray(list_array) => {
if list_array.is_valid(row_index) {
list_array.value(row_index).len() as u64
} else {
0
}
}
Self::DictionaryArray { dict, values } => {
if let Some(key) = dict.key(row_index) {
values.value(key).len() as u64
} else {
0
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn data_ui(
&self,
ctx: &ViewerContext<'_>,
ui: &mut egui::Ui,
latest_at_query: &LatestAtQuery,
entity_path: &EntityPath,
component_name: ComponentName,
row_index: usize,
instance_index: Option<u64>,
) {
let data = match self {
Self::Null => {
if instance_index.is_none() {
ui.label("null");
}
return;
}
Self::ListArray(list_array) => list_array
.is_valid(row_index)
.then(|| list_array.value(row_index)),
Self::DictionaryArray { dict, values } => {
dict.key(row_index).map(|key| values.value(key))
}
};
if let Some(data) = data {
let data_to_display = if let Some(instance_index) = instance_index {
data.slice(instance_index as usize, 1)
} else {
data
};
ctx.component_ui_registry.ui_raw(
ctx,
ui,
UiLayout::List,
latest_at_query,
ctx.recording(),
entity_path,
component_name,
None,
data_to_display.as_ref(),
);
} else {
ui.label("-");
}
}
}
#[derive(Debug)]
pub(crate) enum DisplayColumn {
Timeline {
timeline: Timeline,
time_data: ArrowScalarBuffer<i64>,
time_nulls: Option<ArrowNullBuffer>,
},
Component {
entity_path: EntityPath,
component_name: ComponentName,
component_data: ComponentData,
},
}
impl DisplayColumn {
fn try_new(
column_descriptor: &ColumnDescriptor,
column_data: &ArrowArrayRef,
) -> Result<Self, DisplayRecordBatchError> {
match column_descriptor {
ColumnDescriptor::Time(desc) => {
let timeline = desc.timeline();
let (time_data, time_nulls) = TimeColumn::read_nullable_array(column_data)
.map_err(|err| DisplayRecordBatchError::BadTimeColumn {
timeline: timeline.name().as_str().to_owned(),
error: err,
})?;
Ok(Self::Timeline {
timeline,
time_data,
time_nulls,
})
}
ColumnDescriptor::Component(desc) => Ok(Self::Component {
entity_path: desc.entity_path.clone(),
component_name: desc.component_name,
component_data: ComponentData::try_new(desc, column_data)?,
}),
}
}
pub(crate) fn instance_count(&self, row_index: usize) -> u64 {
match self {
Self::Timeline { .. } => 1,
Self::Component { component_data, .. } => component_data.instance_count(row_index),
}
}
pub(crate) fn data_ui(
&self,
ctx: &ViewerContext<'_>,
ui: &mut egui::Ui,
latest_at_query: &LatestAtQuery,
row_index: usize,
instance_index: Option<u64>,
) {
if let Some(instance_index) = instance_index {
if instance_index >= self.instance_count(row_index) {
return;
}
}
match self {
Self::Timeline {
timeline,
time_data,
time_nulls,
} => {
if instance_index.is_some() {
return;
}
let is_valid = time_nulls
.as_ref()
.map_or(true, |nulls| nulls.is_valid(row_index));
if let (true, Some(value)) = (is_valid, time_data.get(row_index)) {
match TimeInt::try_from(*value) {
Ok(timestamp) => {
ui.label(timeline.typ().format(timestamp, ctx.app_options.time_zone));
}
Err(err) => {
ui.error_with_details_on_hover(err.to_string());
}
}
} else {
ui.label("-");
}
}
Self::Component {
entity_path,
component_name,
component_data,
} => {
component_data.data_ui(
ctx,
ui,
latest_at_query,
entity_path,
*component_name,
row_index,
instance_index,
);
}
}
}
pub(crate) fn try_decode_time(&self, row_index: usize) -> Option<TimeInt> {
match self {
Self::Timeline { time_data, .. } => {
let timestamp = time_data.get(row_index)?;
TimeInt::try_from(*timestamp).ok()
}
Self::Component { .. } => None,
}
}
}
#[derive(Debug)]
pub(crate) struct DisplayRecordBatch {
num_rows: usize,
columns: Vec<DisplayColumn>,
}
impl DisplayRecordBatch {
pub(crate) fn try_new(
row_data: &Vec<ArrowArrayRef>,
selected_columns: &[ColumnDescriptor],
) -> Result<Self, DisplayRecordBatchError> {
let num_rows = row_data.first().map(|arr| arr.len()).unwrap_or(0);
let columns: Result<Vec<_>, _> = selected_columns
.iter()
.zip(row_data)
.map(|(column_descriptor, column_data)| {
DisplayColumn::try_new(column_descriptor, column_data)
})
.collect();
Ok(Self {
num_rows,
columns: columns?,
})
}
pub(crate) fn num_rows(&self) -> usize {
self.num_rows
}
pub(crate) fn columns(&self) -> &[DisplayColumn] {
&self.columns
}
}