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}