re_types_core/
loggable.rs

1use std::borrow::Cow;
2
3use nohash_hasher::IntSet;
4
5use re_byte_size::SizeBytes;
6
7use crate::{ComponentDescriptor, DeserializationResult, SerializationResult};
8
9#[expect(unused_imports, clippy::unused_trait_names)] // used in docstrings
10use crate::{Archetype, ComponentBatch};
11
12// ---
13
14/// A [`Loggable`] represents a single instance in an array of loggable data.
15///
16/// Internally, Arrow, and by extension Rerun, only deal with arrays of data.
17/// We refer to individual entries in these arrays as instances.
18///
19/// A [`Loggable`] has no semantics (such as a name, for example): it's just data.
20/// If you want to encode semantics, then you're looking for a [`Component`], which extends [`Loggable`].
21///
22/// Implementing the [`Loggable`] trait automatically derives the [`ComponentBatch`] implementation,
23/// which makes it possible to work with lists' worth of data in a generic fashion.
24pub trait Loggable: 'static + Send + Sync + Clone + Sized + SizeBytes {
25    /// The underlying [`arrow::datatypes::DataType`], excluding datatype extensions.
26    fn arrow_datatype() -> arrow::datatypes::DataType;
27
28    // Returns an empty Arrow array that matches this `Loggable`'s underlying datatype.
29    #[inline]
30    fn arrow_empty() -> arrow::array::ArrayRef {
31        arrow::array::new_empty_array(&Self::arrow_datatype())
32    }
33
34    /// Given an iterator of owned or reference values to the current [`Loggable`], serializes
35    /// them into an Arrow array.
36    ///
37    /// When using Rerun's builtin components & datatypes, this can only fail if the data
38    /// exceeds the maximum number of entries in an Arrow array (2^31 for standard arrays,
39    /// 2^63 for large arrays).
40    #[inline]
41    fn to_arrow<'a>(
42        data: impl IntoIterator<Item = impl Into<std::borrow::Cow<'a, Self>>>,
43    ) -> SerializationResult<arrow::array::ArrayRef>
44    where
45        Self: 'a,
46    {
47        Self::to_arrow_opt(data.into_iter().map(|v| Some(v)))
48    }
49
50    /// Given an iterator of options of owned or reference values to the current
51    /// [`Loggable`], serializes them into an Arrow array.
52    ///
53    /// When using Rerun's builtin components & datatypes, this can only fail if the data
54    /// exceeds the maximum number of entries in an Arrow array (2^31 for standard arrays,
55    /// 2^63 for large arrays).
56    fn to_arrow_opt<'a>(
57        data: impl IntoIterator<Item = Option<impl Into<std::borrow::Cow<'a, Self>>>>,
58    ) -> SerializationResult<arrow::array::ArrayRef>
59    where
60        Self: 'a;
61
62    /// Given an Arrow array, deserializes it into a collection of [`Loggable`]s.
63    #[inline]
64    fn from_arrow(data: &dyn arrow::array::Array) -> DeserializationResult<Vec<Self>> {
65        Self::from_arrow_opt(data)?
66            .into_iter()
67            .map(|opt| opt.ok_or_else(crate::DeserializationError::missing_data))
68            .collect::<DeserializationResult<Vec<_>>>()
69    }
70
71    /// Given an Arrow array, deserializes it into a collection of optional [`Loggable`]s.
72    #[inline]
73    fn from_arrow_opt(
74        data: &dyn arrow::array::Array,
75    ) -> crate::DeserializationResult<Vec<Option<Self>>> {
76        Self::from_arrow(data).map(|v| v.into_iter().map(Some).collect())
77    }
78
79    /// Verifies that the given Arrow array can be deserialized into a collection of [`Self`]s.
80    ///
81    /// Calls [`Self::from_arrow`] and returns an error if it fails.
82    fn verify_arrow_array(data: &dyn arrow::array::Array) -> crate::DeserializationResult<()> {
83        Self::from_arrow(data).map(|_| ())
84    }
85}
86
87/// A [`Component`] describes semantic data that can be used by any number of [`Archetype`]s.
88///
89/// Implementing the [`Component`] trait automatically derives the [`ComponentBatch`] implementation,
90/// which makes it possible to work with lists' worth of data in a generic fashion.
91pub trait Component: Loggable {
92    /// The fully-qualified name of this component, e.g. `rerun.components.Position2D`.
93    fn name() -> ComponentName;
94}
95
96// ---
97
98pub type UnorderedComponentDescriptorSet = IntSet<ComponentDescriptor>;
99
100pub type ComponentDescriptorSet = std::collections::BTreeSet<ComponentDescriptor>;
101
102re_string_interner::declare_new_type!(
103    /// The fully-qualified name of a [`Component`], e.g. `rerun.components.Position2D`.
104    #[cfg_attr(feature = "serde", derive(::serde::Deserialize, ::serde::Serialize))]
105    pub struct ComponentName;
106);
107
108// TODO(cmc): The only reason this exists is for convenience, and the only reason we need this
109// convenience is because we're still in this weird half-way in-between state where some things
110// are still indexed by name. Remove this entirely once we've ported everything to descriptors.
111impl From<ComponentName> for Cow<'static, ComponentDescriptor> {
112    #[inline]
113    fn from(name: ComponentName) -> Self {
114        name.sanity_check();
115        Cow::Owned(ComponentDescriptor::new(name))
116    }
117}
118
119// TODO(cmc): The only reason this exists is for convenience, and the only reason we need this
120// convenience is because we're still in this weird half-way in-between state where some things
121// are still indexed by name. Remove this entirely once we've ported everything to descriptors.
122impl From<&ComponentName> for Cow<'static, ComponentDescriptor> {
123    #[inline]
124    fn from(name: &ComponentName) -> Self {
125        name.sanity_check();
126        Cow::Owned(ComponentDescriptor::new(*name))
127    }
128}
129
130impl ComponentName {
131    /// Runs some asserts in debug mode to make sure the name is not weird.
132    #[inline]
133    #[track_caller]
134    pub fn sanity_check(&self) {
135        let full_name = self.0.as_str();
136        debug_assert!(
137            !full_name.starts_with("rerun.components.rerun.components."),
138            "DEBUG ASSERT: Found component with full name {full_name:?}. Maybe some bad round-tripping?"
139        );
140    }
141
142    /// Returns the fully-qualified name, e.g. `rerun.components.Position2D`.
143    ///
144    /// This is the default `Display` implementation for [`ComponentName`].
145    #[inline]
146    pub fn full_name(&self) -> &'static str {
147        self.sanity_check();
148        self.0.as_str()
149    }
150
151    /// Returns the unqualified name, e.g. `Position2D`.
152    ///
153    /// Used for most UI elements.
154    ///
155    /// ```
156    /// # use re_types_core::ComponentName;
157    /// assert_eq!(ComponentName::from("rerun.components.Position2D").short_name(), "Position2D");
158    /// ```
159    #[inline]
160    pub fn short_name(&self) -> &'static str {
161        self.sanity_check();
162        let full_name = self.0.as_str();
163        if let Some(short_name) = full_name.strip_prefix("rerun.blueprint.components.") {
164            short_name
165        } else if let Some(short_name) = full_name.strip_prefix("rerun.components.") {
166            short_name
167        } else if let Some(short_name) = full_name.strip_prefix("rerun.controls.") {
168            short_name
169        } else if let Some(short_name) = full_name.strip_prefix("rerun.") {
170            short_name
171        } else {
172            full_name
173        }
174    }
175
176    /// Is this an indicator component for an archetype?
177    pub fn is_indicator_component(&self) -> bool {
178        self.ends_with("Indicator")
179    }
180
181    /// If this is an indicator component, for which archetype?
182    pub fn indicator_component_archetype_short_name(&self) -> Option<String> {
183        self.short_name()
184            .strip_suffix("Indicator")
185            .map(|name| name.to_owned())
186    }
187
188    /// Web URL to the Rerun documentation for this component.
189    pub fn doc_url(&self) -> Option<String> {
190        if let Some(archetype_name_pascal_case) = self.indicator_component_archetype_short_name() {
191            // Link indicator components to their archetype.
192            // This code should be correct as long as this url passes our link checker:
193            // https://rerun.io/docs/reference/types/archetypes/line_strips3d
194
195            let archetype_name_snake_case = re_case::to_snake_case(&archetype_name_pascal_case);
196            let base_url = "https://rerun.io/docs/reference/types/archetypes";
197            Some(format!("{base_url}/{archetype_name_snake_case}"))
198        } else if let Some(component_name_pascal_case) =
199            self.full_name().strip_prefix("rerun.components.")
200        {
201            // This code should be correct as long as this url passes our link checker:
202            // https://rerun.io/docs/reference/types/components/line_strip2d
203
204            let component_name_snake_case = re_case::to_snake_case(component_name_pascal_case);
205            let base_url = "https://rerun.io/docs/reference/types/components";
206            Some(format!("{base_url}/{component_name_snake_case}"))
207        } else {
208            None // A user component
209        }
210    }
211
212    /// Determine if component matches a string
213    ///
214    /// Valid matches are case invariant matches of either the full name or the short name.
215    pub fn matches(&self, other: &str) -> bool {
216        self.0.as_str() == other
217            || self.full_name().to_lowercase() == other.to_lowercase()
218            || self.short_name().to_lowercase() == other.to_lowercase()
219    }
220}
221
222// ---
223
224impl re_byte_size::SizeBytes for ComponentName {
225    #[inline]
226    fn heap_size_bytes(&self) -> u64 {
227        0
228    }
229}
230
231re_string_interner::declare_new_type!(
232    /// The fully-qualified name of a [`Datatype`], e.g. `rerun.datatypes.Vec2D`.
233    #[cfg_attr(feature = "serde", derive(::serde::Deserialize, ::serde::Serialize))]
234    pub struct DatatypeName;
235);
236
237impl DatatypeName {
238    /// Returns the fully-qualified name, e.g. `rerun.datatypes.Vec2D`.
239    ///
240    /// This is the default `Display` implementation for [`DatatypeName`].
241    #[inline]
242    pub fn full_name(&self) -> &'static str {
243        self.0.as_str()
244    }
245
246    /// Returns the unqualified name, e.g. `Vec2D`.
247    ///
248    /// Used for most UI elements.
249    ///
250    /// ```
251    /// # use re_types_core::DatatypeName;
252    /// assert_eq!(DatatypeName::from("rerun.datatypes.Vec2D").short_name(), "Vec2D");
253    /// ```
254    #[inline]
255    pub fn short_name(&self) -> &'static str {
256        let full_name = self.0.as_str();
257        if let Some(short_name) = full_name.strip_prefix("rerun.datatypes.") {
258            short_name
259        } else if let Some(short_name) = full_name.strip_prefix("rerun.") {
260            short_name
261        } else {
262            full_name
263        }
264    }
265}
266
267impl re_byte_size::SizeBytes for DatatypeName {
268    #[inline]
269    fn heap_size_bytes(&self) -> u64 {
270        0
271    }
272}