Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic component placeholders #7447

Merged
merged 5 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions crates/build/re_types_builder/src/codegen/rust/api.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::{BTreeMap, HashSet};
use std::collections::{BTreeMap, HashMap, HashSet};

use anyhow::Context as _;
use camino::{Utf8Path, Utf8PathBuf};
Expand Down Expand Up @@ -57,6 +57,7 @@ impl CodeGenerator for RustCodeGenerator {
arrow_registry: &ArrowRegistry,
) -> BTreeMap<Utf8PathBuf, String> {
let mut files_to_write: BTreeMap<Utf8PathBuf, String> = Default::default();
let mut extension_contents_for_fqname: HashMap<String, String> = Default::default();

for object_kind in ObjectKind::ALL {
self.generate_folder(
Expand All @@ -65,11 +66,17 @@ impl CodeGenerator for RustCodeGenerator {
arrow_registry,
object_kind,
&mut files_to_write,
&mut extension_contents_for_fqname,
);
}

generate_blueprint_validation(reporter, objects, &mut files_to_write);
generate_reflection(reporter, objects, &mut files_to_write);
generate_reflection(
reporter,
objects,
&extension_contents_for_fqname,
&mut files_to_write,
);

files_to_write
}
Expand All @@ -83,6 +90,7 @@ impl RustCodeGenerator {
arrow_registry: &ArrowRegistry,
object_kind: ObjectKind,
files_to_write: &mut BTreeMap<Utf8PathBuf, String>,
extension_contents_for_fqname: &mut HashMap<String, String>,
) {
let crates_root_path = self.workspace_path.join("crates");

Expand All @@ -104,9 +112,14 @@ impl RustCodeGenerator {
let filename = format!("{filename_stem}.rs");

let filepath = module_path.join(filename);

let mut code = generate_object_file(reporter, objects, arrow_registry, obj, &filepath);

if let Ok(extension_contents) =
std::fs::read_to_string(module_path.join(format!("{filename_stem}_ext.rs")))
{
extension_contents_for_fqname.insert(obj.fqname.clone(), extension_contents);
}

if crate_name == "re_types_core" {
code = code.replace("::re_types_core", "crate");
}
Expand Down
36 changes: 32 additions & 4 deletions crates/build/re_types_builder/src/codegen/rust/reflection.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use std::collections::{BTreeMap, BTreeSet};
use std::collections::{BTreeMap, BTreeSet, HashMap};

use camino::Utf8PathBuf;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};

use crate::{
codegen::{autogen_warning, Target},
ObjectKind, Objects, Reporter, ATTR_RERUN_COMPONENT_REQUIRED,
ObjectKind, Objects, Reporter, ATTR_RERUN_COMPONENT_REQUIRED, ATTR_RUST_DERIVE,
ATTR_RUST_DERIVE_ONLY,
};

use super::util::{append_tokens, doc_as_lines};
Expand All @@ -15,6 +16,7 @@ use super::util::{append_tokens, doc_as_lines};
pub fn generate_reflection(
reporter: &Reporter,
objects: &Objects,
extension_contents_for_fqname: &HashMap<String, String>,
files_to_write: &mut BTreeMap<Utf8PathBuf, String>,
) {
// Put into its own subfolder since codegen is set up in a way that it thinks that everything
Expand All @@ -23,7 +25,12 @@ pub fn generate_reflection(
let path = Utf8PathBuf::from("crates/viewer/re_viewer/src/reflection/mod.rs");

let mut imports = BTreeSet::new();
let component_reflection = generate_component_reflection(reporter, objects, &mut imports);
let component_reflection = generate_component_reflection(
reporter,
objects,
extension_contents_for_fqname,
&mut imports,
);
let archetype_reflection = generate_archetype_reflection(reporter, objects);

let mut code = format!("// {}\n\n", autogen_warning!());
Expand Down Expand Up @@ -77,6 +84,7 @@ pub fn generate_reflection(
fn generate_component_reflection(
reporter: &Reporter,
objects: &Objects,
extension_contents_for_fqname: &HashMap<String, String>,
imports: &mut BTreeSet<String>,
) -> TokenStream {
let mut quoted_pairs = Vec::new();
Expand Down Expand Up @@ -111,11 +119,31 @@ fn generate_component_reflection(
obj.is_experimental(),
)
.join("\n");

// Emit custom placeholder if there's a default implementation
let auto_derive_default = obj.is_enum() // All enums have default values currently!
|| obj
.try_get_attr::<String>(ATTR_RUST_DERIVE_ONLY)
.or_else(|| obj.try_get_attr::<String>(ATTR_RUST_DERIVE))
.map_or(false, |derives| derives.contains("Default"));
let has_custom_default_impl =
extension_contents_for_fqname
.get(&obj.fqname)
.map_or(false, |contents| {
contents.contains(&format!("impl Default for {}", &obj.name))
|| contents.contains(&format!("impl Default for super::{}", &obj.name))
});
let custom_placeholder = if auto_derive_default || has_custom_default_impl {
quote! { Some(#type_name::default().to_arrow()?) }
} else {
quote! { None }
};

let quoted_reflection = quote! {
ComponentReflection {
docstring_md: #docstring_md,

placeholder: Some(#type_name::default().to_arrow()?),
custom_placeholder: #custom_placeholder,
}
};
quoted_pairs.push(quote! { (#quoted_name, #quoted_reflection) });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ table Blob (
"attr.arrow.transparent",
"attr.python.aliases": "bytes, npt.NDArray[np.uint8]",
"attr.python.array_aliases": "bytes, npt.NDArray[np.uint8]",
"attr.rust.derive": "Default, PartialEq, Eq",
"attr.rust.derive": "PartialEq, Eq",
"attr.rust.repr": "transparent"
) {
data: rerun.datatypes.Blob (order: 100);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ table ImageBuffer (
"attr.arrow.transparent",
"attr.python.aliases": "bytes, npt.NDArray[np.uint8]",
"attr.python.array_aliases": "bytes, npt.NDArray[np.uint8]",
"attr.rust.derive": "Default, PartialEq, Eq",
"attr.rust.derive": "PartialEq, Eq",
"attr.rust.repr": "transparent"
) {
buffer: rerun.datatypes.Blob (order: 100);
Expand Down
2 changes: 1 addition & 1 deletion crates/store/re_types/src/components/blob.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/store/re_types/src/components/image_buffer.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 0 additions & 9 deletions crates/store/re_types/src/components/show_labels_ext.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
use super::ShowLabels;

impl Default for ShowLabels {
#[inline]
fn default() -> Self {
// We don't actually use this default -- visualizers choose a fallback value --
// but it is necessary to satisfy `re_viewer::reflection::generate_component_reflection()`.
Self(true.into())
}
}

impl From<ShowLabels> for bool {
#[inline]
fn from(value: ShowLabels) -> Self {
Expand Down
7 changes: 0 additions & 7 deletions crates/store/re_types/src/datatypes/blob_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,6 @@ impl From<Vec<u8>> for Blob {
}
}

impl Default for Blob {
#[inline]
fn default() -> Self {
Self(Vec::new().into())
}
}

impl std::ops::Deref for Blob {
type Target = re_types_core::ArrowBuffer<u8>;

Expand Down
161 changes: 157 additions & 4 deletions crates/store/re_types_core/src/reflection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,156 @@ impl Reflection {
}
}

/// Computes a placeholder for a given arrow datatype.
///
/// With the exception of a few unsupported types,
/// a placeholder is an array of the given datatype with a single element.
/// This single element is (recursively if necessary) a sort of arbitrary zero value
/// which can be used as a starting point.
/// E.g. the default for a an integer array is an array containing a single zero.
///
/// For unsupported types this yields an empty array instead.
///
/// See also [`ComponentReflection::custom_placeholder`].
pub fn generic_placeholder_for_datatype(
datatype: &arrow2::datatypes::DataType,
) -> Box<dyn arrow2::array::Array> {
use arrow2::{
array,
datatypes::{DataType, IntervalUnit},
types,
};

match datatype {
DataType::Null => Box::new(array::NullArray::new(datatype.clone(), 1)),
DataType::Boolean => Box::new(array::BooleanArray::from_slice([false])),
DataType::Int8 => Box::new(array::Int8Array::from_slice([0])),
DataType::Int16 => Box::new(array::Int16Array::from_slice([0])),

DataType::Int32
| DataType::Date32
| DataType::Time32(_)
| DataType::Interval(IntervalUnit::YearMonth) => {
// TODO(andreas): Do we have to further distinguish these types? They do share the physical type.
Box::new(array::Int32Array::from_slice([0]))
}
DataType::Int64
| DataType::Date64
| DataType::Timestamp(_, _)
| DataType::Time64(_)
| DataType::Duration(_) => {
// TODO(andreas): Do we have to further distinguish these types? They do share the physical type.
Box::new(array::Int64Array::from_slice([0]))
}

DataType::UInt8 => Box::new(array::UInt8Array::from_slice([0])),
DataType::UInt16 => Box::new(array::UInt16Array::from_slice([0])),
DataType::UInt32 => Box::new(array::UInt32Array::from_slice([0])),
DataType::UInt64 => Box::new(array::UInt64Array::from_slice([0])),
DataType::Float16 => Box::new(array::Float16Array::from_slice([types::f16::from_f32(0.0)])),
DataType::Float32 => Box::new(array::Float32Array::from_slice([0.0])),
DataType::Float64 => Box::new(array::Float64Array::from_slice([0.0])),

DataType::Interval(IntervalUnit::DayTime) => {
Box::new(array::DaysMsArray::from_slice([types::days_ms::new(0, 0)]))
}
DataType::Interval(IntervalUnit::MonthDayNano) => {
Box::new(array::MonthsDaysNsArray::from_slice([
types::months_days_ns::new(0, 0, 0),
]))
}

DataType::Binary => Box::new(array::BinaryArray::<i32>::from_slice([[]])),
DataType::FixedSizeBinary(size) => Box::new(array::FixedSizeBinaryArray::from_iter(
std::iter::once(Some(vec![0; *size])),
*size,
)),
DataType::LargeBinary => Box::new(array::BinaryArray::<i64>::from_slice([[]])),
DataType::Utf8 => Box::new(array::Utf8Array::<i32>::from_slice([""])),
DataType::LargeUtf8 => Box::new(array::Utf8Array::<i64>::from_slice([""])),
DataType::List(field) => {
let inner = generic_placeholder_for_datatype(field.data_type());
let offsets = arrow2::offset::Offsets::try_from_lengths(std::iter::once(inner.len()))
.expect("failed to create offsets buffer");
Box::new(array::ListArray::<i32>::new(
datatype.clone(),
offsets.into(),
inner,
None,
))
}

// TODO(andreas): Unsupported type.
// What we actually want here is an array containing a single array of size `size`.
// But it's a bit tricky to build, because it doesn't look like we can concatenate `size` many arrays.
DataType::FixedSizeList(_field, size) => {
Box::new(array::FixedSizeListArray::new_null(datatype.clone(), *size))
}

DataType::LargeList(field) => {
let inner = generic_placeholder_for_datatype(field.data_type());
let offsets = arrow2::offset::Offsets::try_from_lengths(std::iter::once(inner.len()))
.expect("failed to create offsets buffer");
Box::new(array::ListArray::<i64>::new(
datatype.clone(),
offsets.into(),
inner,
None,
))
}
DataType::Struct(fields) => {
let inners = fields
.iter()
.map(|field| generic_placeholder_for_datatype(field.data_type()));
Box::new(array::StructArray::new(
datatype.clone(),
inners.collect(),
None,
))
}
DataType::Union(fields, _types, _union_mode) => {
if let Some(first_field) = fields.first() {
let first_field = generic_placeholder_for_datatype(first_field.data_type());
let first_field_len = first_field.len(); // Should be 1, but let's play this safe!
let other_fields = fields
.iter()
.skip(1)
.map(|field| array::new_empty_array(field.data_type().clone()));
let fields = std::iter::once(first_field).chain(other_fields);

Box::new(array::UnionArray::new(
datatype.clone(),
std::iter::once(0).collect(), // Single element of type 0.
fields.collect(),
Some(std::iter::once(first_field_len as i32).collect()),
))
} else {
// Pathological case: a union with no fields can't have a placeholder with a single element?
array::new_empty_array(datatype.clone())
}
}

// TODO(andreas): Unsupported types. Fairly complex to build and we don't use it so far.
DataType::Map(_field, _) => Box::new(array::MapArray::new_empty(datatype.clone())),

// TODO(andreas): Unsupported type. Has only `try_new` meaning we'd have to handle all error cases.
// But also we don't use this today anyways.
DataType::Dictionary(_integer_type, _arc, _sorted) => {
array::new_empty_array(datatype.clone()) // Rust type varies per integer type, use utility instead.
}

DataType::Decimal(_, _) => Box::new(array::Int128Array::from_slice([0])),

DataType::Decimal256(_, _) => {
Box::new(array::Int256Array::from_slice([types::i256::from_words(
0, 0,
)]))
}

DataType::Extension(_, datatype, _) => generic_placeholder_for_datatype(datatype),
}
}

/// Runtime reflection about components.
pub type ComponentReflectionMap = nohash_hasher::IntMap<ComponentName, ComponentReflection>;

Expand All @@ -60,12 +210,15 @@ pub struct ComponentReflection {
/// Markdown docstring for the component.
pub docstring_md: &'static str,

/// Placeholder value, used whenever no fallback was provided explicitly.
/// Custom placeholder value, used when not fallback was provided.
///
/// This is usually the default value of the component, serialized.
/// This is usually the default value of the component (if any), serialized.
///
/// This is useful as a base fallback value when displaying UI.
pub placeholder: Option<Box<dyn arrow2::array::Array>>,
/// Placeholders are useful as a base fallback value when displaying UI,
/// especially when it's necessary to have a starting value for edit ui.
/// Typically, this is only used when `FallbackProvider`s are not available.
/// If there's no custom placeholder, a placeholder can be derived from the arrow datatype.
pub custom_placeholder: Option<Box<dyn arrow2::array::Array>>,
}

/// Runtime reflection about archetypes.
Expand Down
Loading
Loading