From dd04366bf84d464c658e4f6b488bc5442d33376d Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Tue, 14 Jan 2025 15:02:40 +0100 Subject: [PATCH] Add `archetype_native` attribute and make `ContainerBlueprint` eager + partial (#8666) Introduces a new attribute that, when paired with an eager archetype, will also generate a native companion type with back-and-forth conversion methods. This is especially useful for blueprint archetypes (although there are definitely non-blueprint examples too), which are heavily used all across the viewer, and would be very painful to use otherwise. ```rust /// Whether we should generate an extra Rust object comprised of native Rust types. /// /// The generated object will have the name of the archetype, prefixed by `Native`, /// e.g. `NativePoints3D`. /// /// Applies only to *eager* archetypes. No-op otherwise. attribute "attr.rust.archetype_native"; ``` With this, we now should have all the tools required to port every remaining archetype (both blueprint and data). * Part of #8650 --- .../re_types_builder/src/codegen/rust/api.rs | 184 ++++++++- .../re_types_builder/src/codegen/rust/util.rs | 2 + crates/build/re_types_builder/src/lib.rs | 1 + crates/build/re_types_builder/src/objects.rs | 9 + .../re_types/definitions/attributes/rust.fbs | 8 + .../rerun/blueprint/archetypes/background.fbs | 2 +- .../archetypes/container_blueprint.fbs | 4 +- .../blueprint/archetypes/visual_bounds2d.fbs | 2 +- .../src/blueprint/archetypes/background.rs | 98 +++-- .../archetypes/container_blueprint.rs | 391 ++++++++++++------ .../re_types/src/blueprint/archetypes/mod.rs | 2 +- .../blueprint/archetypes/visual_bounds2d.rs | 77 ++-- .../re_viewport_blueprint/src/container.rs | 4 +- 13 files changed, 554 insertions(+), 230 deletions(-) diff --git a/crates/build/re_types_builder/src/codegen/rust/api.rs b/crates/build/re_types_builder/src/codegen/rust/api.rs index c60624a37b81..38a0eed4881e 100644 --- a/crates/build/re_types_builder/src/codegen/rust/api.rs +++ b/crates/build/re_types_builder/src/codegen/rust/api.rs @@ -25,8 +25,8 @@ use crate::{ ArrowRegistry, CodeGenerator, ElementType, Object, ObjectField, ObjectKind, Objects, Reporter, Type, ATTR_DEFAULT, ATTR_RERUN_COMPONENT_OPTIONAL, ATTR_RERUN_COMPONENT_RECOMMENDED, ATTR_RERUN_COMPONENT_REQUIRED, ATTR_RERUN_LOG_MISSING_AS_EMPTY, ATTR_RERUN_VIEW_IDENTIFIER, - ATTR_RUST_CUSTOM_CLAUSE, ATTR_RUST_DERIVE, ATTR_RUST_DERIVE_ONLY, ATTR_RUST_NEW_PUB_CRATE, - ATTR_RUST_REPR, + ATTR_RUST_ARCHETYPE_EAGER, ATTR_RUST_CUSTOM_CLAUSE, ATTR_RUST_DERIVE, ATTR_RUST_DERIVE_ONLY, + ATTR_RUST_NEW_PUB_CRATE, ATTR_RUST_REPR, }; use super::{ @@ -233,7 +233,15 @@ fn generate_mod_file( { let module_name = obj.snake_case_name(); let type_name = &obj.name; - code.push_str(&format!("pub use self::{module_name}::{type_name};\n")); + let native_type_name = format!("Native{type_name}"); + + if obj.requires_native_rust_archetype() { + code.push_str(&format!( + "pub use self::{module_name}::{{{type_name}, {native_type_name}}};\n" + )); + } else { + code.push_str(&format!("pub use self::{module_name}::{type_name};\n")); + } } // And then deprecated. if objects.iter().any(|obj| obj.deprecation_notice().is_some()) { @@ -245,10 +253,19 @@ fn generate_mod_file( { let module_name = obj.snake_case_name(); let type_name = &obj.name; + let native_type_name = format!("Native{type_name}"); + if obj.deprecation_notice().is_some() { code.push_str("#[allow(deprecated)]\n"); } - code.push_str(&format!("pub use self::{module_name}::{type_name};\n")); + + if obj.requires_native_rust_archetype() { + code.push_str(&format!( + "pub use self::{module_name}::{{{type_name}, {native_type_name}}};\n" + )); + } else { + code.push_str(&format!("pub use self::{module_name}::{type_name};\n")); + } } files_to_write.insert(path, code); @@ -264,6 +281,15 @@ fn quote_struct( ) -> TokenStream { assert!(obj.is_struct()); + // Certain eager archetypes might require the generation of an associated native archetype, as + // the internal viewer code heavily relies on it. + let obj_native = obj.requires_native_rust_archetype().then(|| { + let mut obj_native = obj.clone(); + obj_native.name = format!("Native{}", obj_native.name); + obj_native.attrs.remove(ATTR_RUST_ARCHETYPE_EAGER); + obj_native + }); + let Object { name, fields, .. } = obj; let name = format_ident!("{name}"); @@ -309,10 +335,12 @@ fn quote_struct( } else { quote! { pub struct #name { #(#quoted_fields,)* }} }; + let quoted_struct_native = quote_struct_native(reporter, objects, obj); let quoted_from_impl = quote_from_impl_from_obj(obj); - let quoted_trait_impls = quote_trait_impls_from_obj(reporter, arrow_registry, objects, obj); + let quoted_trait_impls = + quote_trait_impls_from_obj(reporter, arrow_registry, objects, obj, obj_native.as_ref()); let quoted_builder = quote_builder_from_obj(reporter, objects, obj); @@ -368,6 +396,8 @@ fn quote_struct( #quoted_deprecation_notice #quoted_struct + #quoted_struct_native + #quoted_trait_impls #quoted_from_impl @@ -380,6 +410,99 @@ fn quote_struct( tokens } +/// Certain eager archetypes might require the generation of an associated native archetype, as +/// the internal viewer code heavily relies on it. +fn quote_struct_native( + reporter: &Reporter, + objects: &Objects, + obj: &Object, +) -> Option { + assert!(obj.is_struct()); + + let obj_native = obj.requires_native_rust_archetype().then(|| { + let mut obj_native = obj.clone(); + obj_native.name = format!("Native{}", obj_native.name); + obj_native.attrs.remove(ATTR_RUST_ARCHETYPE_EAGER); + obj_native + }); + + let Object { name, .. } = obj; + + let name = format_ident!("{name}"); + + let derive_only = obj.is_attr_set(ATTR_RUST_DERIVE_ONLY); + let quoted_derive_clone_debug = if derive_only { + quote!() + } else { + quote_derive_clone_debug() + }; + + let is_tuple_struct = is_tuple_struct_from_obj(obj); + obj_native.as_ref().map(|obj_native| { + let native_name = format_ident!("{}", obj_native.name); + + let quoted_fields = obj_native + .fields + .iter() + .map(|obj_field| ObjectFieldTokenizer(reporter, obj_native, obj_field).quoted(objects)); + let quoted_struct = if is_tuple_struct { + quote! { pub struct #native_name(#(#quoted_fields,)*); } + } else { + quote! { pub struct #native_name { #(#quoted_fields,)* }} + }; + + let eager_fields_to_native_fields = obj.fields.iter().map(|field| { + let field_name = format_ident!("{}", field.name); + quote!(value.#field_name.clone().map(|batch| (batch.descriptor, batch.array))) + }); + let eager_to_native = quote! { + impl TryFrom<&#name> for #native_name { + type Error = crate::DeserializationError; + + #[rustfmt::skip] // so it doesn't take 1000 lines for no reason + fn try_from(value: &#name) -> Result { + use ::re_types_core::Archetype as _; + Self::from_arrow_components( + [ #(#eager_fields_to_native_fields),* ] + .into_iter() + .flatten(), + ) + } + } + }; + + let native_fields_to_eager_fields = obj_native.fields.iter().map(|field| { + let field_name = format_ident!("{}", field.name); + if field.is_nullable { + quote!(#field_name: value.#field_name.as_ref().and_then(|v| v.serialized())) + } else { + quote!(#field_name: value.#field_name.serialized()) + } + }); + let native_to_eager = quote! { + impl From<&#native_name> for #name { + #[rustfmt::skip] // so it doesn't take 1000 lines for no reason + #[inline] + fn from(value: &#native_name) -> Self { + Self { + #(#native_fields_to_eager_fields),* + } + } + } + }; + + quote! { + #[doc(hidden)] + #quoted_derive_clone_debug + #quoted_struct + + #eager_to_native + + #native_to_eager + } + }) +} + fn quote_union( reporter: &Reporter, arrow_registry: &ArrowRegistry, @@ -426,7 +549,8 @@ fn quote_union( } }); - let quoted_trait_impls = quote_trait_impls_from_obj(reporter, arrow_registry, objects, obj); + let quoted_trait_impls = + quote_trait_impls_from_obj(reporter, arrow_registry, objects, obj, None); let quoted_heap_size_bytes = { let quoted_matches = fields.iter().map(|obj_field| { @@ -574,7 +698,8 @@ fn quote_enum( } }); - let quoted_trait_impls = quote_trait_impls_from_obj(reporter, arrow_registry, objects, obj); + let quoted_trait_impls = + quote_trait_impls_from_obj(reporter, arrow_registry, objects, obj, None); let all = fields.iter().map(|field| { let name = format_ident!("{}", field.name); @@ -843,13 +968,14 @@ fn quote_trait_impls_from_obj( arrow_registry: &ArrowRegistry, objects: &Objects, obj: &Object, + obj_native: Option<&Object>, ) -> TokenStream { match obj.kind { ObjectKind::Datatype | ObjectKind::Component => { quote_trait_impls_for_datatype_or_component(objects, arrow_registry, obj) } - ObjectKind::Archetype => quote_trait_impls_for_archetype(obj), + ObjectKind::Archetype => quote_trait_impls_for_archetype(obj, obj_native), ObjectKind::View => quote_trait_impls_for_view(reporter, obj), } @@ -1049,7 +1175,7 @@ fn quote_trait_impls_for_datatype_or_component( } } -fn quote_trait_impls_for_archetype(obj: &Object) -> TokenStream { +fn quote_trait_impls_for_archetype(obj: &Object, obj_native: Option<&Object>) -> TokenStream { #![allow(clippy::collapsible_else_if)] let Object { @@ -1238,7 +1364,7 @@ fn quote_trait_impls_for_archetype(obj: &Object) -> TokenStream { }; // TODO(#7245): This goes away once all archetypes have been made eager. - let all_native_deserializers = { + let all_native_deserializers = |origin: TokenStream| { obj.fields.iter().map(|obj_field| { let obj_field_fqname = obj_field.fqname.as_str(); let field_name = format_ident!("{}", obj_field.name); @@ -1267,7 +1393,6 @@ fn quote_trait_impls_for_archetype(obj: &Object) -> TokenStream { } }; - // NOTE: An archetype cannot have overlapped component types by definition, so use the // component's fqname to do the mapping. let quoted_deser = if is_nullable && !is_plural { @@ -1281,7 +1406,7 @@ fn quote_trait_impls_for_archetype(obj: &Object) -> TokenStream { }; quote! { - if let Some(array) = arrays_by_descr.get(&Self::#descr_fn_name()) { + if let Some(array) = arrays_by_descr.get(&#origin::#descr_fn_name()) { <#component>::from_arrow_opt(&**array) .with_context(#obj_field_fqname)? #quoted_collection @@ -1291,7 +1416,7 @@ fn quote_trait_impls_for_archetype(obj: &Object) -> TokenStream { } } else if is_nullable { quote! { - if let Some(array) = arrays_by_descr.get(&Self::#descr_fn_name()) { + if let Some(array) = arrays_by_descr.get(&#origin::#descr_fn_name()) { Some({ <#component>::from_arrow_opt(&**array) .with_context(#obj_field_fqname)? @@ -1304,7 +1429,7 @@ fn quote_trait_impls_for_archetype(obj: &Object) -> TokenStream { } else { quote! {{ let array = arrays_by_descr - .get(&Self::#descr_fn_name()) + .get(&#origin::#descr_fn_name()) .ok_or_else(DeserializationError::missing_data) .with_context(#obj_field_fqname)?; @@ -1313,7 +1438,7 @@ fn quote_trait_impls_for_archetype(obj: &Object) -> TokenStream { }; quote!(let #field_name = #quoted_deser;) - }) + }).collect_vec() }; let all_eager_deserializers = { @@ -1334,9 +1459,33 @@ fn quote_trait_impls_for_archetype(obj: &Object) -> TokenStream { let all_deserializers = if obj.is_eager_rust_archetype() { quote!(#(#all_eager_deserializers;)*) } else { + let all_native_deserializers = all_native_deserializers(quote!(Self)); quote!(#(#all_native_deserializers;)*) }; + let from_arrow_components_native = obj_native.map(|obj_native| { + let native_name = format_ident!("{}", obj_native.name); + + let all_native_deserializers = all_native_deserializers(quote!(#name)); + quote! { + impl #native_name { + fn from_arrow_components( + arrow_data: impl IntoIterator, + ) -> DeserializationResult { + re_tracing::profile_function!(); + use ::re_types_core::{Loggable as _, ResultExt as _}; + + let arrays_by_descr: ::nohash_hasher::IntMap<_, _> = arrow_data.into_iter().collect(); + #(#all_native_deserializers;)* + + Ok(Self { + #(#quoted_field_names,)* + }) + } + } + } + }); + quote! { impl #name { #(#all_descriptor_methods)* @@ -1414,7 +1563,6 @@ fn quote_trait_impls_for_archetype(obj: &Object) -> TokenStream { use ::re_types_core::{Loggable as _, ResultExt as _}; let arrays_by_descr: ::nohash_hasher::IntMap<_, _> = arrow_data.into_iter().collect(); - #all_deserializers Ok(Self { @@ -1423,6 +1571,8 @@ fn quote_trait_impls_for_archetype(obj: &Object) -> TokenStream { } } + #from_arrow_components_native + impl ::re_types_core::AsComponents for #name { #as_components_impl } @@ -1474,7 +1624,7 @@ fn quote_from_impl_from_obj(obj: &Object) -> TokenStream { let self_field_access = if obj_is_tuple_struct { quote!(self.0) } else { - quote!(self.#quoted_obj_field_name ) + quote!(self.#quoted_obj_field_name) }; let deref_impl = quote! { impl std::ops::Deref for #quoted_obj_name { diff --git a/crates/build/re_types_builder/src/codegen/rust/util.rs b/crates/build/re_types_builder/src/codegen/rust/util.rs index 671b3a31b161..8fce97eba7ce 100644 --- a/crates/build/re_types_builder/src/codegen/rust/util.rs +++ b/crates/build/re_types_builder/src/codegen/rust/util.rs @@ -133,6 +133,8 @@ pub fn string_from_quoted( let line_is_attr = trimmed.starts_with("#[allow(") || trimmed.starts_with("#[inline]") + || trimmed.starts_with("#[doc(hidden)]") + || trimmed.starts_with("#[rustfmt::skip]") || trimmed.starts_with("#[derive"); if line_is_attr && (!prev_line_was_attr && !prev_line_was_docstring) { diff --git a/crates/build/re_types_builder/src/lib.rs b/crates/build/re_types_builder/src/lib.rs index b891af2e3bff..487963a74e43 100644 --- a/crates/build/re_types_builder/src/lib.rs +++ b/crates/build/re_types_builder/src/lib.rs @@ -198,6 +198,7 @@ pub const ATTR_CPP_NO_FIELD_CTORS: &str = "attr.cpp.no_field_ctors"; pub const ATTR_CPP_RENAME_FIELD: &str = "attr.cpp.rename_field"; pub const ATTR_RUST_ARCHETYPE_EAGER: &str = "attr.rust.archetype_eager"; +pub const ATTR_RUST_ARCHETYPE_NATIVE: &str = "attr.rust.archetype_native"; pub const ATTR_RUST_CUSTOM_CLAUSE: &str = "attr.rust.custom_clause"; pub const ATTR_RUST_DERIVE: &str = "attr.rust.derive"; pub const ATTR_RUST_DERIVE_ONLY: &str = "attr.rust.derive_only"; diff --git a/crates/build/re_types_builder/src/objects.rs b/crates/build/re_types_builder/src/objects.rs index 519460d3d83b..c835346cc3f5 100644 --- a/crates/build/re_types_builder/src/objects.rs +++ b/crates/build/re_types_builder/src/objects.rs @@ -13,6 +13,7 @@ use crate::{ root_as_schema, Docs, FbsBaseType, FbsEnum, FbsEnumVal, FbsField, FbsKeyValue, FbsObject, FbsSchema, FbsType, Reporter, ATTR_RERUN_COMPONENT_OPTIONAL, ATTR_RERUN_COMPONENT_RECOMMENDED, ATTR_RERUN_COMPONENT_REQUIRED, ATTR_RERUN_OVERRIDE_TYPE, ATTR_RUST_ARCHETYPE_EAGER, + ATTR_RUST_ARCHETYPE_NATIVE, }; // --- @@ -694,6 +695,10 @@ impl Object { pub fn is_eager_rust_archetype(&self) -> bool { self.is_archetype() && self.is_attr_set(ATTR_RUST_ARCHETYPE_EAGER) } + + pub fn requires_native_rust_archetype(&self) -> bool { + self.is_eager_rust_archetype() && self.is_attr_set(ATTR_RUST_ARCHETYPE_NATIVE) + } } pub fn is_testing_fqname(fqname: &str) -> bool { @@ -1426,6 +1431,10 @@ impl Attributes { pub fn has(&self, name: impl AsRef) -> bool { self.0.contains_key(name.as_ref()) } + + pub fn remove(&mut self, name: impl AsRef) { + self.0.remove(name.as_ref()); + } } fn filepath_from_declaration_file( diff --git a/crates/store/re_types/definitions/attributes/rust.fbs b/crates/store/re_types/definitions/attributes/rust.fbs index 0d9b7b2a3e37..87d5904f3e81 100644 --- a/crates/store/re_types/definitions/attributes/rust.fbs +++ b/crates/store/re_types/definitions/attributes/rust.fbs @@ -42,6 +42,14 @@ attribute "attr.rust.new_pub_crate"; /// object will be generated in `re_viewport/src/blueprint`. attribute "attr.rust.override_crate"; +/// Whether we should generate an extra Rust object comprised of native Rust types. +/// +/// The generated object will have the name of the archetype, prefixed by `Native`, +/// e.g. `NativePoints3D`. +/// +/// Applies only to *eager* archetypes. No-op otherwise. +attribute "attr.rust.archetype_native"; + /// The generated Rust object should be eagerly serialized, i.e. only comprised of Arrow arrays. /// /// Applies only to archetypes. No-op otherwise. diff --git a/crates/store/re_types/definitions/rerun/blueprint/archetypes/background.fbs b/crates/store/re_types/definitions/rerun/blueprint/archetypes/background.fbs index 4216f2f8fe6b..5464e214ee4a 100644 --- a/crates/store/re_types/definitions/rerun/blueprint/archetypes/background.fbs +++ b/crates/store/re_types/definitions/rerun/blueprint/archetypes/background.fbs @@ -5,7 +5,7 @@ namespace rerun.blueprint.archetypes; table Background ( "attr.python.aliases": "datatypes.Rgba32Like, blueprint_components.BackgroundKindLike", "attr.rerun.scope": "blueprint", - "attr.rust.derive": "Copy" + "attr.rust.archetype_eager" ) { // --- Required --- diff --git a/crates/store/re_types/definitions/rerun/blueprint/archetypes/container_blueprint.fbs b/crates/store/re_types/definitions/rerun/blueprint/archetypes/container_blueprint.fbs index 454ab1f93ff0..07232ba0b150 100644 --- a/crates/store/re_types/definitions/rerun/blueprint/archetypes/container_blueprint.fbs +++ b/crates/store/re_types/definitions/rerun/blueprint/archetypes/container_blueprint.fbs @@ -4,7 +4,9 @@ namespace rerun.blueprint.archetypes; /// The description of a container. table ContainerBlueprint ( - "attr.rerun.scope": "blueprint" + "attr.rerun.scope": "blueprint", + "attr.rust.archetype_eager", + "attr.rust.archetype_native" ) { // --- Required --- diff --git a/crates/store/re_types/definitions/rerun/blueprint/archetypes/visual_bounds2d.fbs b/crates/store/re_types/definitions/rerun/blueprint/archetypes/visual_bounds2d.fbs index 38c38541640c..ea210ab47b96 100644 --- a/crates/store/re_types/definitions/rerun/blueprint/archetypes/visual_bounds2d.fbs +++ b/crates/store/re_types/definitions/rerun/blueprint/archetypes/visual_bounds2d.fbs @@ -9,7 +9,7 @@ namespace rerun.blueprint.archetypes; /// based on the bounding-box of the data or other camera information present in the view. table VisualBounds2D ( "attr.rerun.scope": "blueprint", - "attr.rust.derive": "Copy" + "attr.rust.archetype_eager": "" ) { /// Controls the visible range of a 2D view. /// diff --git a/crates/store/re_types/src/blueprint/archetypes/background.rs b/crates/store/re_types/src/blueprint/archetypes/background.rs index 3aa96cdf3711..edb3d0f3287d 100644 --- a/crates/store/re_types/src/blueprint/archetypes/background.rs +++ b/crates/store/re_types/src/blueprint/archetypes/background.rs @@ -19,13 +19,13 @@ use ::re_types_core::{ComponentDescriptor, ComponentName}; use ::re_types_core::{DeserializationError, DeserializationResult}; /// **Archetype**: Configuration for the background of a view. -#[derive(Clone, Debug, Copy)] +#[derive(Clone, Debug, Default)] pub struct Background { /// The type of the background. - pub kind: crate::blueprint::components::BackgroundKind, + pub kind: Option, /// Color used for the solid background type. - pub color: Option, + pub color: Option, } impl Background { @@ -132,52 +132,24 @@ impl ::re_types_core::Archetype for Background { re_tracing::profile_function!(); use ::re_types_core::{Loggable as _, ResultExt as _}; let arrays_by_descr: ::nohash_hasher::IntMap<_, _> = arrow_data.into_iter().collect(); - let kind = { - let array = arrays_by_descr - .get(&Self::descriptor_kind()) - .ok_or_else(DeserializationError::missing_data) - .with_context("rerun.blueprint.archetypes.Background#kind")?; - ::from_arrow_opt(&**array) - .with_context("rerun.blueprint.archetypes.Background#kind")? - .into_iter() - .next() - .flatten() - .ok_or_else(DeserializationError::missing_data) - .with_context("rerun.blueprint.archetypes.Background#kind")? - }; - let color = if let Some(array) = arrays_by_descr.get(&Self::descriptor_color()) { - ::from_arrow_opt(&**array) - .with_context("rerun.blueprint.archetypes.Background#color")? - .into_iter() - .next() - .flatten() - } else { - None - }; + let kind = arrays_by_descr + .get(&Self::descriptor_kind()) + .map(|array| SerializedComponentBatch::new(array.clone(), Self::descriptor_kind())); + let color = arrays_by_descr + .get(&Self::descriptor_color()) + .map(|array| SerializedComponentBatch::new(array.clone(), Self::descriptor_color())); Ok(Self { kind, color }) } } impl ::re_types_core::AsComponents for Background { - fn as_component_batches(&self) -> Vec> { - re_tracing::profile_function!(); + #[inline] + fn as_serialized_batches(&self) -> Vec { use ::re_types_core::Archetype as _; [ - Some(Self::indicator()), - (Some(&self.kind as &dyn ComponentBatch)).map(|batch| { - ::re_types_core::ComponentBatchCowWithDescriptor { - batch: batch.into(), - descriptor_override: Some(Self::descriptor_kind()), - } - }), - (self - .color - .as_ref() - .map(|comp| (comp as &dyn ComponentBatch))) - .map(|batch| ::re_types_core::ComponentBatchCowWithDescriptor { - batch: batch.into(), - descriptor_override: Some(Self::descriptor_color()), - }), + Self::indicator().serialized(), + self.kind.clone(), + self.color.clone(), ] .into_iter() .flatten() @@ -192,15 +164,47 @@ impl Background { #[inline] pub fn new(kind: impl Into) -> Self { Self { - kind: kind.into(), + kind: try_serialize_field(Self::descriptor_kind(), [kind]), color: None, } } + /// Update only some specific fields of a `Background`. + #[inline] + pub fn update_fields() -> Self { + Self::default() + } + + /// Clear all the fields of a `Background`. + #[inline] + pub fn clear_fields() -> Self { + use ::re_types_core::Loggable as _; + Self { + kind: Some(SerializedComponentBatch::new( + crate::blueprint::components::BackgroundKind::arrow_empty(), + Self::descriptor_kind(), + )), + color: Some(SerializedComponentBatch::new( + crate::components::Color::arrow_empty(), + Self::descriptor_color(), + )), + } + } + + /// The type of the background. + #[inline] + pub fn with_kind( + mut self, + kind: impl Into, + ) -> Self { + self.kind = try_serialize_field(Self::descriptor_kind(), [kind]); + self + } + /// Color used for the solid background type. #[inline] pub fn with_color(mut self, color: impl Into) -> Self { - self.color = Some(color.into()); + self.color = try_serialize_field(Self::descriptor_color(), [color]); self } } @@ -210,10 +214,4 @@ impl ::re_byte_size::SizeBytes for Background { fn heap_size_bytes(&self) -> u64 { self.kind.heap_size_bytes() + self.color.heap_size_bytes() } - - #[inline] - fn is_pod() -> bool { - ::is_pod() - && >::is_pod() - } } diff --git a/crates/store/re_types/src/blueprint/archetypes/container_blueprint.rs b/crates/store/re_types/src/blueprint/archetypes/container_blueprint.rs index ccd1e4dbb08c..81eb205dd091 100644 --- a/crates/store/re_types/src/blueprint/archetypes/container_blueprint.rs +++ b/crates/store/re_types/src/blueprint/archetypes/container_blueprint.rs @@ -19,8 +19,52 @@ use ::re_types_core::{ComponentDescriptor, ComponentName}; use ::re_types_core::{DeserializationError, DeserializationResult}; /// **Archetype**: The description of a container. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct ContainerBlueprint { + /// The class of the view. + pub container_kind: Option, + + /// The name of the container. + pub display_name: Option, + + /// `ContainerId`s or `ViewId`s that are children of this container. + pub contents: Option, + + /// The layout shares of each column in the container. + /// + /// For [`components::ContainerKind::Horizontal`][crate::blueprint::components::ContainerKind::Horizontal] containers, the length of this list should always match the number of contents. + /// + /// Ignored for [`components::ContainerKind::Vertical`][crate::blueprint::components::ContainerKind::Vertical] containers. + pub col_shares: Option, + + /// The layout shares of each row of the container. + /// + /// For [`components::ContainerKind::Vertical`][crate::blueprint::components::ContainerKind::Vertical] containers, the length of this list should always match the number of contents. + /// + /// Ignored for [`components::ContainerKind::Horizontal`][crate::blueprint::components::ContainerKind::Horizontal] containers. + pub row_shares: Option, + + /// Which tab is active. + /// + /// Only applies to `Tabs` containers. + pub active_tab: Option, + + /// Whether this container is visible. + /// + /// Defaults to true if not specified. + pub visible: Option, + + /// How many columns this grid should have. + /// + /// If unset, the grid layout will be auto. + /// + /// Ignored for [`components::ContainerKind::Horizontal`][crate::blueprint::components::ContainerKind::Horizontal]/[`components::ContainerKind::Vertical`][crate::blueprint::components::ContainerKind::Vertical] containers. + pub grid_columns: Option, +} + +#[doc(hidden)] +#[derive(Clone, Debug)] +pub struct NativeContainerBlueprint { /// The class of the view. pub container_kind: crate::blueprint::components::ContainerKind, @@ -62,6 +106,49 @@ pub struct ContainerBlueprint { pub grid_columns: Option, } +impl TryFrom<&ContainerBlueprint> for NativeContainerBlueprint { + type Error = crate::DeserializationError; + + #[rustfmt::skip] + fn try_from(value: &ContainerBlueprint) -> Result { + use ::re_types_core::Archetype as _; + Self::from_arrow_components( + [ + value + .container_kind + .clone() + .map(|batch| (batch.descriptor, batch.array)), + value.display_name.clone().map(|batch| (batch.descriptor, batch.array)), + value.contents.clone().map(|batch| (batch.descriptor, batch.array)), + value.col_shares.clone().map(|batch| (batch.descriptor, batch.array)), + value.row_shares.clone().map(|batch| (batch.descriptor, batch.array)), + value.active_tab.clone().map(|batch| (batch.descriptor, batch.array)), + value.visible.clone().map(|batch| (batch.descriptor, batch.array)), + value.grid_columns.clone().map(|batch| (batch.descriptor, batch.array)), + ] + .into_iter() + .flatten(), + ) + } +} + +impl From<&NativeContainerBlueprint> for ContainerBlueprint { + #[rustfmt::skip] + #[inline] + fn from(value: &NativeContainerBlueprint) -> Self { + Self { + container_kind: value.container_kind.serialized(), + display_name: value.display_name.as_ref().and_then(|v| v.serialized()), + contents: value.contents.as_ref().and_then(|v| v.serialized()), + col_shares: value.col_shares.as_ref().and_then(|v| v.serialized()), + row_shares: value.row_shares.as_ref().and_then(|v| v.serialized()), + active_tab: value.active_tab.as_ref().and_then(|v| v.serialized()), + visible: value.visible.as_ref().and_then(|v| v.serialized()), + grid_columns: value.grid_columns.as_ref().and_then(|v| v.serialized()), + } + } +} + impl ContainerBlueprint { /// Returns the [`ComponentDescriptor`] for [`Self::container_kind`]. #[inline] @@ -237,6 +324,62 @@ impl ::re_types_core::Archetype for ContainerBlueprint { } #[inline] + fn from_arrow_components( + arrow_data: impl IntoIterator, + ) -> DeserializationResult { + re_tracing::profile_function!(); + use ::re_types_core::{Loggable as _, ResultExt as _}; + let arrays_by_descr: ::nohash_hasher::IntMap<_, _> = arrow_data.into_iter().collect(); + let container_kind = arrays_by_descr + .get(&Self::descriptor_container_kind()) + .map(|array| { + SerializedComponentBatch::new(array.clone(), Self::descriptor_container_kind()) + }); + let display_name = arrays_by_descr + .get(&Self::descriptor_display_name()) + .map(|array| { + SerializedComponentBatch::new(array.clone(), Self::descriptor_display_name()) + }); + let contents = arrays_by_descr + .get(&Self::descriptor_contents()) + .map(|array| SerializedComponentBatch::new(array.clone(), Self::descriptor_contents())); + let col_shares = arrays_by_descr + .get(&Self::descriptor_col_shares()) + .map(|array| { + SerializedComponentBatch::new(array.clone(), Self::descriptor_col_shares()) + }); + let row_shares = arrays_by_descr + .get(&Self::descriptor_row_shares()) + .map(|array| { + SerializedComponentBatch::new(array.clone(), Self::descriptor_row_shares()) + }); + let active_tab = arrays_by_descr + .get(&Self::descriptor_active_tab()) + .map(|array| { + SerializedComponentBatch::new(array.clone(), Self::descriptor_active_tab()) + }); + let visible = arrays_by_descr + .get(&Self::descriptor_visible()) + .map(|array| SerializedComponentBatch::new(array.clone(), Self::descriptor_visible())); + let grid_columns = arrays_by_descr + .get(&Self::descriptor_grid_columns()) + .map(|array| { + SerializedComponentBatch::new(array.clone(), Self::descriptor_grid_columns()) + }); + Ok(Self { + container_kind, + display_name, + contents, + col_shares, + row_shares, + active_tab, + visible, + grid_columns, + }) + } +} + +impl NativeContainerBlueprint { fn from_arrow_components( arrow_data: impl IntoIterator, ) -> DeserializationResult { @@ -245,7 +388,7 @@ impl ::re_types_core::Archetype for ContainerBlueprint { let arrays_by_descr: ::nohash_hasher::IntMap<_, _> = arrow_data.into_iter().collect(); let container_kind = { let array = arrays_by_descr - .get(&Self::descriptor_container_kind()) + .get(&ContainerBlueprint::descriptor_container_kind()) .ok_or_else(DeserializationError::missing_data) .with_context("rerun.blueprint.archetypes.ContainerBlueprint#container_kind")?; ::from_arrow_opt(&**array) @@ -256,29 +399,33 @@ impl ::re_types_core::Archetype for ContainerBlueprint { .ok_or_else(DeserializationError::missing_data) .with_context("rerun.blueprint.archetypes.ContainerBlueprint#container_kind")? }; - let display_name = - if let Some(array) = arrays_by_descr.get(&Self::descriptor_display_name()) { - ::from_arrow_opt(&**array) - .with_context("rerun.blueprint.archetypes.ContainerBlueprint#display_name")? - .into_iter() - .next() - .flatten() - } else { - None - }; - let contents = if let Some(array) = arrays_by_descr.get(&Self::descriptor_contents()) { - Some({ - ::from_arrow_opt(&**array) - .with_context("rerun.blueprint.archetypes.ContainerBlueprint#contents")? - .into_iter() - .map(|v| v.ok_or_else(DeserializationError::missing_data)) - .collect::>>() - .with_context("rerun.blueprint.archetypes.ContainerBlueprint#contents")? - }) + let display_name = if let Some(array) = + arrays_by_descr.get(&ContainerBlueprint::descriptor_display_name()) + { + ::from_arrow_opt(&**array) + .with_context("rerun.blueprint.archetypes.ContainerBlueprint#display_name")? + .into_iter() + .next() + .flatten() } else { None }; - let col_shares = if let Some(array) = arrays_by_descr.get(&Self::descriptor_col_shares()) { + let contents = + if let Some(array) = arrays_by_descr.get(&ContainerBlueprint::descriptor_contents()) { + Some({ + ::from_arrow_opt(&**array) + .with_context("rerun.blueprint.archetypes.ContainerBlueprint#contents")? + .into_iter() + .map(|v| v.ok_or_else(DeserializationError::missing_data)) + .collect::>>() + .with_context("rerun.blueprint.archetypes.ContainerBlueprint#contents")? + }) + } else { + None + }; + let col_shares = if let Some(array) = + arrays_by_descr.get(&ContainerBlueprint::descriptor_col_shares()) + { Some({ ::from_arrow_opt(&**array) .with_context("rerun.blueprint.archetypes.ContainerBlueprint#col_shares")? @@ -290,7 +437,9 @@ impl ::re_types_core::Archetype for ContainerBlueprint { } else { None }; - let row_shares = if let Some(array) = arrays_by_descr.get(&Self::descriptor_row_shares()) { + let row_shares = if let Some(array) = + arrays_by_descr.get(&ContainerBlueprint::descriptor_row_shares()) + { Some({ ::from_arrow_opt(&**array) .with_context("rerun.blueprint.archetypes.ContainerBlueprint#row_shares")? @@ -302,7 +451,9 @@ impl ::re_types_core::Archetype for ContainerBlueprint { } else { None }; - let active_tab = if let Some(array) = arrays_by_descr.get(&Self::descriptor_active_tab()) { + let active_tab = if let Some(array) = + arrays_by_descr.get(&ContainerBlueprint::descriptor_active_tab()) + { ::from_arrow_opt(&**array) .with_context("rerun.blueprint.archetypes.ContainerBlueprint#active_tab")? .into_iter() @@ -311,25 +462,27 @@ impl ::re_types_core::Archetype for ContainerBlueprint { } else { None }; - let visible = if let Some(array) = arrays_by_descr.get(&Self::descriptor_visible()) { - ::from_arrow_opt(&**array) - .with_context("rerun.blueprint.archetypes.ContainerBlueprint#visible")? - .into_iter() - .next() - .flatten() - } else { - None - }; - let grid_columns = - if let Some(array) = arrays_by_descr.get(&Self::descriptor_grid_columns()) { - ::from_arrow_opt(&**array) - .with_context("rerun.blueprint.archetypes.ContainerBlueprint#grid_columns")? + let visible = + if let Some(array) = arrays_by_descr.get(&ContainerBlueprint::descriptor_visible()) { + ::from_arrow_opt(&**array) + .with_context("rerun.blueprint.archetypes.ContainerBlueprint#visible")? .into_iter() .next() .flatten() } else { None }; + let grid_columns = if let Some(array) = + arrays_by_descr.get(&ContainerBlueprint::descriptor_grid_columns()) + { + ::from_arrow_opt(&**array) + .with_context("rerun.blueprint.archetypes.ContainerBlueprint#grid_columns")? + .into_iter() + .next() + .flatten() + } else { + None + }; Ok(Self { container_kind, display_name, @@ -344,73 +497,19 @@ impl ::re_types_core::Archetype for ContainerBlueprint { } impl ::re_types_core::AsComponents for ContainerBlueprint { - fn as_component_batches(&self) -> Vec> { - re_tracing::profile_function!(); + #[inline] + fn as_serialized_batches(&self) -> Vec { use ::re_types_core::Archetype as _; [ - Some(Self::indicator()), - (Some(&self.container_kind as &dyn ComponentBatch)).map(|batch| { - ::re_types_core::ComponentBatchCowWithDescriptor { - batch: batch.into(), - descriptor_override: Some(Self::descriptor_container_kind()), - } - }), - (self - .display_name - .as_ref() - .map(|comp| (comp as &dyn ComponentBatch))) - .map(|batch| ::re_types_core::ComponentBatchCowWithDescriptor { - batch: batch.into(), - descriptor_override: Some(Self::descriptor_display_name()), - }), - (self - .contents - .as_ref() - .map(|comp_batch| (comp_batch as &dyn ComponentBatch))) - .map(|batch| ::re_types_core::ComponentBatchCowWithDescriptor { - batch: batch.into(), - descriptor_override: Some(Self::descriptor_contents()), - }), - (self - .col_shares - .as_ref() - .map(|comp_batch| (comp_batch as &dyn ComponentBatch))) - .map(|batch| ::re_types_core::ComponentBatchCowWithDescriptor { - batch: batch.into(), - descriptor_override: Some(Self::descriptor_col_shares()), - }), - (self - .row_shares - .as_ref() - .map(|comp_batch| (comp_batch as &dyn ComponentBatch))) - .map(|batch| ::re_types_core::ComponentBatchCowWithDescriptor { - batch: batch.into(), - descriptor_override: Some(Self::descriptor_row_shares()), - }), - (self - .active_tab - .as_ref() - .map(|comp| (comp as &dyn ComponentBatch))) - .map(|batch| ::re_types_core::ComponentBatchCowWithDescriptor { - batch: batch.into(), - descriptor_override: Some(Self::descriptor_active_tab()), - }), - (self - .visible - .as_ref() - .map(|comp| (comp as &dyn ComponentBatch))) - .map(|batch| ::re_types_core::ComponentBatchCowWithDescriptor { - batch: batch.into(), - descriptor_override: Some(Self::descriptor_visible()), - }), - (self - .grid_columns - .as_ref() - .map(|comp| (comp as &dyn ComponentBatch))) - .map(|batch| ::re_types_core::ComponentBatchCowWithDescriptor { - batch: batch.into(), - descriptor_override: Some(Self::descriptor_grid_columns()), - }), + Self::indicator().serialized(), + self.container_kind.clone(), + self.display_name.clone(), + self.contents.clone(), + self.col_shares.clone(), + self.row_shares.clone(), + self.active_tab.clone(), + self.visible.clone(), + self.grid_columns.clone(), ] .into_iter() .flatten() @@ -425,7 +524,10 @@ impl ContainerBlueprint { #[inline] pub fn new(container_kind: impl Into) -> Self { Self { - container_kind: container_kind.into(), + container_kind: try_serialize_field( + Self::descriptor_container_kind(), + [container_kind], + ), display_name: None, contents: None, col_shares: None, @@ -436,10 +538,67 @@ impl ContainerBlueprint { } } + /// Update only some specific fields of a `ContainerBlueprint`. + #[inline] + pub fn update_fields() -> Self { + Self::default() + } + + /// Clear all the fields of a `ContainerBlueprint`. + #[inline] + pub fn clear_fields() -> Self { + use ::re_types_core::Loggable as _; + Self { + container_kind: Some(SerializedComponentBatch::new( + crate::blueprint::components::ContainerKind::arrow_empty(), + Self::descriptor_container_kind(), + )), + display_name: Some(SerializedComponentBatch::new( + crate::components::Name::arrow_empty(), + Self::descriptor_display_name(), + )), + contents: Some(SerializedComponentBatch::new( + crate::blueprint::components::IncludedContent::arrow_empty(), + Self::descriptor_contents(), + )), + col_shares: Some(SerializedComponentBatch::new( + crate::blueprint::components::ColumnShare::arrow_empty(), + Self::descriptor_col_shares(), + )), + row_shares: Some(SerializedComponentBatch::new( + crate::blueprint::components::RowShare::arrow_empty(), + Self::descriptor_row_shares(), + )), + active_tab: Some(SerializedComponentBatch::new( + crate::blueprint::components::ActiveTab::arrow_empty(), + Self::descriptor_active_tab(), + )), + visible: Some(SerializedComponentBatch::new( + crate::blueprint::components::Visible::arrow_empty(), + Self::descriptor_visible(), + )), + grid_columns: Some(SerializedComponentBatch::new( + crate::blueprint::components::GridColumns::arrow_empty(), + Self::descriptor_grid_columns(), + )), + } + } + + /// The class of the view. + #[inline] + pub fn with_container_kind( + mut self, + container_kind: impl Into, + ) -> Self { + self.container_kind = + try_serialize_field(Self::descriptor_container_kind(), [container_kind]); + self + } + /// The name of the container. #[inline] pub fn with_display_name(mut self, display_name: impl Into) -> Self { - self.display_name = Some(display_name.into()); + self.display_name = try_serialize_field(Self::descriptor_display_name(), [display_name]); self } @@ -449,7 +608,7 @@ impl ContainerBlueprint { mut self, contents: impl IntoIterator>, ) -> Self { - self.contents = Some(contents.into_iter().map(Into::into).collect()); + self.contents = try_serialize_field(Self::descriptor_contents(), contents); self } @@ -463,7 +622,7 @@ impl ContainerBlueprint { mut self, col_shares: impl IntoIterator>, ) -> Self { - self.col_shares = Some(col_shares.into_iter().map(Into::into).collect()); + self.col_shares = try_serialize_field(Self::descriptor_col_shares(), col_shares); self } @@ -477,7 +636,7 @@ impl ContainerBlueprint { mut self, row_shares: impl IntoIterator>, ) -> Self { - self.row_shares = Some(row_shares.into_iter().map(Into::into).collect()); + self.row_shares = try_serialize_field(Self::descriptor_row_shares(), row_shares); self } @@ -489,7 +648,7 @@ impl ContainerBlueprint { mut self, active_tab: impl Into, ) -> Self { - self.active_tab = Some(active_tab.into()); + self.active_tab = try_serialize_field(Self::descriptor_active_tab(), [active_tab]); self } @@ -501,7 +660,7 @@ impl ContainerBlueprint { mut self, visible: impl Into, ) -> Self { - self.visible = Some(visible.into()); + self.visible = try_serialize_field(Self::descriptor_visible(), [visible]); self } @@ -515,7 +674,7 @@ impl ContainerBlueprint { mut self, grid_columns: impl Into, ) -> Self { - self.grid_columns = Some(grid_columns.into()); + self.grid_columns = try_serialize_field(Self::descriptor_grid_columns(), [grid_columns]); self } } @@ -532,16 +691,4 @@ impl ::re_byte_size::SizeBytes for ContainerBlueprint { + self.visible.heap_size_bytes() + self.grid_columns.heap_size_bytes() } - - #[inline] - fn is_pod() -> bool { - ::is_pod() - && >::is_pod() - && >>::is_pod() - && >>::is_pod() - && >>::is_pod() - && >::is_pod() - && >::is_pod() - && >::is_pod() - } } diff --git a/crates/store/re_types/src/blueprint/archetypes/mod.rs b/crates/store/re_types/src/blueprint/archetypes/mod.rs index 92d1c164ee4e..e226ce3141b4 100644 --- a/crates/store/re_types/src/blueprint/archetypes/mod.rs +++ b/crates/store/re_types/src/blueprint/archetypes/mod.rs @@ -26,7 +26,7 @@ mod visible_time_ranges_ext; mod visual_bounds2d; pub use self::background::Background; -pub use self::container_blueprint::ContainerBlueprint; +pub use self::container_blueprint::{ContainerBlueprint, NativeContainerBlueprint}; pub use self::dataframe_query::DataframeQuery; pub use self::force_center::ForceCenter; pub use self::force_collision_radius::ForceCollisionRadius; diff --git a/crates/store/re_types/src/blueprint/archetypes/visual_bounds2d.rs b/crates/store/re_types/src/blueprint/archetypes/visual_bounds2d.rs index 8066f0e81f7d..6efa80af2144 100644 --- a/crates/store/re_types/src/blueprint/archetypes/visual_bounds2d.rs +++ b/crates/store/re_types/src/blueprint/archetypes/visual_bounds2d.rs @@ -25,12 +25,12 @@ use ::re_types_core::{DeserializationError, DeserializationResult}; /// /// If no visual bounds are set, it will be determined automatically, /// based on the bounding-box of the data or other camera information present in the view. -#[derive(Clone, Debug, Copy)] +#[derive(Clone, Debug, Default)] pub struct VisualBounds2D { /// Controls the visible range of a 2D view. /// /// Use this to control pan & zoom of the view. - pub range: crate::blueprint::components::VisualBounds2D, + pub range: Option, } impl VisualBounds2D { @@ -126,39 +126,21 @@ impl ::re_types_core::Archetype for VisualBounds2D { re_tracing::profile_function!(); use ::re_types_core::{Loggable as _, ResultExt as _}; let arrays_by_descr: ::nohash_hasher::IntMap<_, _> = arrow_data.into_iter().collect(); - let range = { - let array = arrays_by_descr - .get(&Self::descriptor_range()) - .ok_or_else(DeserializationError::missing_data) - .with_context("rerun.blueprint.archetypes.VisualBounds2D#range")?; - ::from_arrow_opt(&**array) - .with_context("rerun.blueprint.archetypes.VisualBounds2D#range")? - .into_iter() - .next() - .flatten() - .ok_or_else(DeserializationError::missing_data) - .with_context("rerun.blueprint.archetypes.VisualBounds2D#range")? - }; + let range = arrays_by_descr + .get(&Self::descriptor_range()) + .map(|array| SerializedComponentBatch::new(array.clone(), Self::descriptor_range())); Ok(Self { range }) } } impl ::re_types_core::AsComponents for VisualBounds2D { - fn as_component_batches(&self) -> Vec> { - re_tracing::profile_function!(); + #[inline] + fn as_serialized_batches(&self) -> Vec { use ::re_types_core::Archetype as _; - [ - Some(Self::indicator()), - (Some(&self.range as &dyn ComponentBatch)).map(|batch| { - ::re_types_core::ComponentBatchCowWithDescriptor { - batch: batch.into(), - descriptor_override: Some(Self::descriptor_range()), - } - }), - ] - .into_iter() - .flatten() - .collect() + [Self::indicator().serialized(), self.range.clone()] + .into_iter() + .flatten() + .collect() } } @@ -169,9 +151,39 @@ impl VisualBounds2D { #[inline] pub fn new(range: impl Into) -> Self { Self { - range: range.into(), + range: try_serialize_field(Self::descriptor_range(), [range]), } } + + /// Update only some specific fields of a `VisualBounds2D`. + #[inline] + pub fn update_fields() -> Self { + Self::default() + } + + /// Clear all the fields of a `VisualBounds2D`. + #[inline] + pub fn clear_fields() -> Self { + use ::re_types_core::Loggable as _; + Self { + range: Some(SerializedComponentBatch::new( + crate::blueprint::components::VisualBounds2D::arrow_empty(), + Self::descriptor_range(), + )), + } + } + + /// Controls the visible range of a 2D view. + /// + /// Use this to control pan & zoom of the view. + #[inline] + pub fn with_range( + mut self, + range: impl Into, + ) -> Self { + self.range = try_serialize_field(Self::descriptor_range(), [range]); + self + } } impl ::re_byte_size::SizeBytes for VisualBounds2D { @@ -179,9 +191,4 @@ impl ::re_byte_size::SizeBytes for VisualBounds2D { fn heap_size_bytes(&self) -> u64 { self.range.heap_size_bytes() } - - #[inline] - fn is_pod() -> bool { - ::is_pod() - } } diff --git a/crates/viewer/re_viewport_blueprint/src/container.rs b/crates/viewer/re_viewport_blueprint/src/container.rs index af0b03fb810f..f6873eaa7d67 100644 --- a/crates/viewer/re_viewport_blueprint/src/container.rs +++ b/crates/viewer/re_viewport_blueprint/src/container.rs @@ -76,7 +76,7 @@ impl ContainerBlueprint { // is the marker that the have been cleared and not an error. let container_kind = results.component_instance::(0)?; - let blueprint_archetypes::ContainerBlueprint { + let blueprint_archetypes::NativeContainerBlueprint { container_kind, display_name, contents, @@ -85,7 +85,7 @@ impl ContainerBlueprint { active_tab, visible, grid_columns, - } = blueprint_archetypes::ContainerBlueprint { + } = blueprint_archetypes::NativeContainerBlueprint { container_kind, display_name: results.component_instance(0), contents: results.component_batch(),