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 && !full_name.contains(':'),
166 "DEBUG ASSERT: Found component with full name {full_name:?}. Maybe some bad round-tripping?"
167 );
168 }
169
170 /// Returns the fully-qualified name, e.g. `rerun.components.Position2D`.
171 ///
172 /// This is the default `Display` implementation for [`ComponentName`].
173 #[inline]
174 pub fn full_name(&self) -> &'static str {
175 self.sanity_check();
176 self.0.as_str()
177 }
178
179 /// Returns the unqualified name, e.g. `Position2D`.
180 ///
181 /// Used for most UI elements.
182 ///
183 /// ```
184 /// # use re_types_core::ComponentName;
185 /// assert_eq!(ComponentName::from("rerun.components.Position2D").short_name(), "Position2D");
186 /// ```
187 #[inline]
188 pub fn short_name(&self) -> &'static str {
189 self.sanity_check();
190 let full_name = self.0.as_str();
191 if let Some(short_name) = full_name.strip_prefix("rerun.blueprint.components.") {
192 short_name
193 } else if let Some(short_name) = full_name.strip_prefix("rerun.components.") {
194 short_name
195 } else if let Some(short_name) = full_name.strip_prefix("rerun.controls.") {
196 short_name
197 } else if let Some(short_name) = full_name.strip_prefix("rerun.") {
198 short_name
199 } else {
200 full_name
201 }
202 }
203
204 /// Is this an indicator component for an archetype?
205 pub fn is_indicator_component(&self) -> bool {
206 (self.starts_with("rerun.components.") || self.starts_with("rerun.blueprint.components."))
207 && self.ends_with("Indicator")
208 }
209
210 /// If this is an indicator component, for which archetype?
211 pub fn indicator_component_archetype(&self) -> Option<String> {
212 if let Some(name) = self.strip_prefix("rerun.components.") {
213 if let Some(name) = name.strip_suffix("Indicator") {
214 return Some(name.to_owned());
215 }
216 }
217 None
218 }
219
220 /// Web URL to the Rerun documentation for this component.
221 pub fn doc_url(&self) -> Option<String> {
222 if let Some(archetype_name_pascal_case) = self.indicator_component_archetype() {
223 // Link indicator components to their archetype.
224 // This code should be correct as long as this url passes our link checker:
225 // https://rerun.io/docs/reference/types/archetypes/line_strips3d
226
227 let archetype_name_snake_case = re_case::to_snake_case(&archetype_name_pascal_case);
228 let base_url = "https://rerun.io/docs/reference/types/archetypes";
229 Some(format!("{base_url}/{archetype_name_snake_case}"))
230 } else if let Some(component_name_pascal_case) =
231 self.full_name().strip_prefix("rerun.components.")
232 {
233 // This code should be correct as long as this url passes our link checker:
234 // https://rerun.io/docs/reference/types/components/line_strip2d
235
236 let component_name_snake_case = re_case::to_snake_case(component_name_pascal_case);
237 let base_url = "https://rerun.io/docs/reference/types/components";
238 Some(format!("{base_url}/{component_name_snake_case}"))
239 } else {
240 None // A user component
241 }
242 }
243
244 /// Determine if component matches a string
245 ///
246 /// Valid matches are case invariant matches of either the full name or the short name.
247 pub fn matches(&self, other: &str) -> bool {
248 self.0.as_str() == other
249 || self.full_name().to_lowercase() == other.to_lowercase()
250 || self.short_name().to_lowercase() == other.to_lowercase()
251 }
252}
253
254// ---
255
256impl re_byte_size::SizeBytes for ComponentName {
257 #[inline]
258 fn heap_size_bytes(&self) -> u64 {
259 0
260 }
261}
262
263re_string_interner::declare_new_type!(
264 /// The fully-qualified name of a [`Datatype`], e.g. `rerun.datatypes.Vec2D`.
265 #[cfg_attr(feature = "serde", derive(::serde::Deserialize, ::serde::Serialize))]
266 pub struct DatatypeName;
267);
268
269impl DatatypeName {
270 /// Returns the fully-qualified name, e.g. `rerun.datatypes.Vec2D`.
271 ///
272 /// This is the default `Display` implementation for [`DatatypeName`].
273 #[inline]
274 pub fn full_name(&self) -> &'static str {
275 self.0.as_str()
276 }
277
278 /// Returns the unqualified name, e.g. `Vec2D`.
279 ///
280 /// Used for most UI elements.
281 ///
282 /// ```
283 /// # use re_types_core::DatatypeName;
284 /// assert_eq!(DatatypeName::from("rerun.datatypes.Vec2D").short_name(), "Vec2D");
285 /// ```
286 #[inline]
287 pub fn short_name(&self) -> &'static str {
288 let full_name = self.0.as_str();
289 if let Some(short_name) = full_name.strip_prefix("rerun.datatypes.") {
290 short_name
291 } else if let Some(short_name) = full_name.strip_prefix("rerun.") {
292 short_name
293 } else {
294 full_name
295 }
296 }
297}
298
299impl re_byte_size::SizeBytes for DatatypeName {
300 #[inline]
301 fn heap_size_bytes(&self) -> u64 {
302 0
303 }
304}