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}