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, LoggableBatch};
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 [`LoggableBatch`] 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    /// Returns the complete [`ComponentDescriptor`] for this [`Component`].
93    ///
94    /// Every component is uniquely identified by its [`ComponentDescriptor`].
95    //
96    // NOTE: Builtin Rerun components don't (yet) have anything but a `ComponentName` attached to
97    // them (other tags are injected at the Archetype level), therefore having a full
98    // `ComponentDescriptor` might seem overkill.
99    // It's not:
100    // * Users might still want to register Components with specific tags.
101    // * In the future, `ComponentDescriptor`s will very likely cover more than Archetype-related tags
102    //   (e.g. generics, metric units, etc).
103    fn descriptor() -> ComponentDescriptor;
104
105    /// The fully-qualified name of this component, e.g. `rerun.components.Position2D`.
106    ///
107    /// This is a trivial but useful helper for `Self::descriptor().component_name`.
108    ///
109    /// The default implementation already does the right thing: do not override unless you know
110    /// what you're doing.
111    /// `Self::name()` must exactly match the value returned by `Self::descriptor().component_name`,
112    /// or undefined behavior ensues.
113    //
114    // TODO(cmc): The only reason we keep this around is for convenience, and the only reason we need this
115    // convenience is because we're still in this weird half-way in-between state where some things
116    // are still indexed by name. Remove this entirely once we've ported everything to descriptors.
117    #[inline]
118    fn name() -> ComponentName {
119        Self::descriptor().component_name
120    }
121}
122
123// ---
124
125pub type UnorderedComponentNameSet = IntSet<ComponentName>;
126
127pub type ComponentNameSet = std::collections::BTreeSet<ComponentName>;
128
129re_string_interner::declare_new_type!(
130    /// The fully-qualified name of a [`Component`], e.g. `rerun.components.Position2D`.
131    #[cfg_attr(feature = "serde", derive(::serde::Deserialize, ::serde::Serialize))]
132    pub struct ComponentName;
133);
134
135// TODO(cmc): The only reason this exists is for convenience, and the only reason we need this
136// convenience is because we're still in this weird half-way in-between state where some things
137// are still indexed by name. Remove this entirely once we've ported everything to descriptors.
138impl From<ComponentName> for Cow<'static, ComponentDescriptor> {
139    #[inline]
140    fn from(name: ComponentName) -> Self {
141        name.sanity_check();
142        Cow::Owned(ComponentDescriptor::new(name))
143    }
144}
145
146// TODO(cmc): The only reason this exists is for convenience, and the only reason we need this
147// convenience is because we're still in this weird half-way in-between state where some things
148// are still indexed by name. Remove this entirely once we've ported everything to descriptors.
149impl From<&ComponentName> for Cow<'static, ComponentDescriptor> {
150    #[inline]
151    fn from(name: &ComponentName) -> Self {
152        name.sanity_check();
153        Cow::Owned(ComponentDescriptor::new(*name))
154    }
155}
156
157impl ComponentName {
158    /// Runs some asserts in debug mode to make sure the name is not weird.
159    #[inline]
160    #[track_caller]
161    pub fn sanity_check(&self) {
162        let full_name = self.0.as_str();
163        debug_assert!(
164            !full_name.starts_with("rerun.components.rerun.components."),
165            "DEBUG ASSERT: Found component with full name {full_name:?}. Maybe some bad round-tripping?"
166        );
167    }
168
169    /// Returns the fully-qualified name, e.g. `rerun.components.Position2D`.
170    ///
171    /// This is the default `Display` implementation for [`ComponentName`].
172    #[inline]
173    pub fn full_name(&self) -> &'static str {
174        self.sanity_check();
175        self.0.as_str()
176    }
177
178    /// Returns the unqualified name, e.g. `Position2D`.
179    ///
180    /// Used for most UI elements.
181    ///
182    /// ```
183    /// # use re_types_core::ComponentName;
184    /// assert_eq!(ComponentName::from("rerun.components.Position2D").short_name(), "Position2D");
185    /// ```
186    #[inline]
187    pub fn short_name(&self) -> &'static str {
188        self.sanity_check();
189        let full_name = self.0.as_str();
190        if let Some(short_name) = full_name.strip_prefix("rerun.blueprint.components.") {
191            short_name
192        } else if let Some(short_name) = full_name.strip_prefix("rerun.components.") {
193            short_name
194        } else if let Some(short_name) = full_name.strip_prefix("rerun.controls.") {
195            short_name
196        } else if let Some(short_name) = full_name.strip_prefix("rerun.") {
197            short_name
198        } else {
199            full_name
200        }
201    }
202
203    /// Is this an indicator component for an archetype?
204    pub fn is_indicator_component(&self) -> bool {
205        self.ends_with("Indicator")
206    }
207
208    /// If this is an indicator component, for which archetype?
209    pub fn indicator_component_archetype_short_name(&self) -> Option<String> {
210        self.short_name()
211            .strip_suffix("Indicator")
212            .map(|name| name.to_owned())
213    }
214
215    /// Web URL to the Rerun documentation for this component.
216    pub fn doc_url(&self) -> Option<String> {
217        if let Some(archetype_name_pascal_case) = self.indicator_component_archetype_short_name() {
218            // Link indicator components to their archetype.
219            // This code should be correct as long as this url passes our link checker:
220            // https://rerun.io/docs/reference/types/archetypes/line_strips3d
221
222            let archetype_name_snake_case = re_case::to_snake_case(&archetype_name_pascal_case);
223            let base_url = "https://rerun.io/docs/reference/types/archetypes";
224            Some(format!("{base_url}/{archetype_name_snake_case}"))
225        } else if let Some(component_name_pascal_case) =
226            self.full_name().strip_prefix("rerun.components.")
227        {
228            // This code should be correct as long as this url passes our link checker:
229            // https://rerun.io/docs/reference/types/components/line_strip2d
230
231            let component_name_snake_case = re_case::to_snake_case(component_name_pascal_case);
232            let base_url = "https://rerun.io/docs/reference/types/components";
233            Some(format!("{base_url}/{component_name_snake_case}"))
234        } else {
235            None // A user component
236        }
237    }
238
239    /// Determine if component matches a string
240    ///
241    /// Valid matches are case invariant matches of either the full name or the short name.
242    pub fn matches(&self, other: &str) -> bool {
243        self.0.as_str() == other
244            || self.full_name().to_lowercase() == other.to_lowercase()
245            || self.short_name().to_lowercase() == other.to_lowercase()
246    }
247}
248
249// ---
250
251impl re_byte_size::SizeBytes for ComponentName {
252    #[inline]
253    fn heap_size_bytes(&self) -> u64 {
254        0
255    }
256}
257
258re_string_interner::declare_new_type!(
259    /// The fully-qualified name of a [`Datatype`], e.g. `rerun.datatypes.Vec2D`.
260    #[cfg_attr(feature = "serde", derive(::serde::Deserialize, ::serde::Serialize))]
261    pub struct DatatypeName;
262);
263
264impl DatatypeName {
265    /// Returns the fully-qualified name, e.g. `rerun.datatypes.Vec2D`.
266    ///
267    /// This is the default `Display` implementation for [`DatatypeName`].
268    #[inline]
269    pub fn full_name(&self) -> &'static str {
270        self.0.as_str()
271    }
272
273    /// Returns the unqualified name, e.g. `Vec2D`.
274    ///
275    /// Used for most UI elements.
276    ///
277    /// ```
278    /// # use re_types_core::DatatypeName;
279    /// assert_eq!(DatatypeName::from("rerun.datatypes.Vec2D").short_name(), "Vec2D");
280    /// ```
281    #[inline]
282    pub fn short_name(&self) -> &'static str {
283        let full_name = self.0.as_str();
284        if let Some(short_name) = full_name.strip_prefix("rerun.datatypes.") {
285            short_name
286        } else if let Some(short_name) = full_name.strip_prefix("rerun.") {
287            short_name
288        } else {
289            full_name
290        }
291    }
292}
293
294impl re_byte_size::SizeBytes for DatatypeName {
295    #[inline]
296    fn heap_size_bytes(&self) -> u64 {
297        0
298    }
299}