Skip to main content

re_log_types/
lib.rs

1//! The different types that make up the rerun log format.
2//!
3//! ## Feature flags
4#![doc = document_features::document_features!()]
5//!
6//! ## Mono-components
7//!
8//! Some components, mostly transform related ones, are "mono-components".
9//! This means that Rerun makes assumptions that depend on this component
10//! only taking on a singular value for all instances of an Entity. Where possible,
11//! exposed APIs will force these components to be logged as a singular instance.
12//! However, it is an error with undefined behavior to manually use lower-level
13//! APIs to log a batched mono-component.
14//!
15//! This requirement is especially apparent with transforms:
16//! Each entity must have a unique transform chain,
17//! e.g. the entity `foo/bar/baz` is has the transform that is the product of
18//! `foo.transform * foo/bar.transform * foo/bar/baz.transform`.
19
20pub mod arrow_msg;
21mod entry_id;
22mod entry_name;
23pub mod example_components;
24pub mod hash;
25mod index;
26pub mod path;
27
28// mod data_cell;
29// mod data_row;
30// mod data_table;
31mod instance;
32mod vec_deque_ext;
33
34use std::sync::Arc;
35
36use arrow::array::RecordBatch as ArrowRecordBatch;
37use re_build_info::CrateVersion;
38use re_byte_size::SizeBytes;
39
40pub use re_types_core::TimelineName;
41
42pub use self::arrow_msg::{ArrowMsg, ArrowRecordBatchReleaseCallback};
43pub use self::entry_id::{EntryId, EntryIdOrName};
44pub use self::entry_name::{EntryName, InvalidEntryNameError};
45pub use self::index::{
46    AbsoluteTimeRange, AbsoluteTimeRangeF, Duration, NonMinI64, TimeCell, TimeInt, TimePoint,
47    TimeReal, TimeType, Timeline, TimelinePoint, Timestamp, TimestampFormat, TimestampFormatKind,
48    TryFromIntError,
49};
50pub use self::instance::Instance;
51pub use self::path::*;
52pub use self::vec_deque_ext::{VecDequeInsertionExt, VecDequeRemovalExt, VecDequeSortingExt};
53
54pub mod external {
55    pub use {arrow, re_tuid, re_types_core};
56}
57
58#[macro_export]
59macro_rules! impl_into_enum {
60    ($from_ty: ty, $enum_name: ident, $to_enum_variant: ident) => {
61        impl From<$from_ty> for $enum_name {
62            #[inline]
63            fn from(value: $from_ty) -> Self {
64                Self::$to_enum_variant(value)
65            }
66        }
67    };
68}
69
70// ----------------------------------------------------------------------------
71
72/// What kind of store this is.
73///
74/// `Recording` stores contain user-data logged via the SDK.
75///
76/// `Blueprint` stores contain the data which describes the layout of the visualization. It can be
77/// logged using the Blueprint API, and is also generated by the viewer as it is interacted with.
78#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
79#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
80pub enum StoreKind {
81    /// A recording of user-data.
82    Recording,
83
84    /// Data associated with the blueprint state.
85    Blueprint,
86}
87
88impl std::fmt::Display for StoreKind {
89    #[inline]
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        match self {
92            Self::Recording => "Recording".fmt(f),
93            Self::Blueprint => "Blueprint".fmt(f),
94        }
95    }
96}
97
98impl std::str::FromStr for StoreKind {
99    type Err = String;
100
101    fn from_str(s: &str) -> Result<Self, Self::Err> {
102        match s.to_lowercase().as_str() {
103            "recording" => Ok(Self::Recording),
104            "blueprint" => Ok(Self::Blueprint),
105            unknown => Err(format!("{unknown:?} is not a valid StoreKind")),
106        }
107    }
108}
109
110#[test]
111fn store_kind_str_roundtrip() {
112    {
113        let kind = StoreKind::Recording;
114        assert_eq!(kind, kind.to_string().parse().unwrap());
115    }
116
117    {
118        let kind = StoreKind::Blueprint;
119        assert_eq!(kind, kind.to_string().parse().unwrap());
120    }
121}
122
123/// A unique id per store.
124///
125/// The kind of store is part of the id, and can be either a [`StoreKind::Recording`] or a
126/// [`StoreKind::Blueprint`].
127///
128/// ## Uniqueness
129///
130/// All data associated with a given `StoreId`, regardless of its source, is considered to belong
131/// to the same logical recording. As such, when ingested in the viewer, they will be pooled into
132/// a single store and displayed as a single, in-viewer recording. This can be very confusing if it
133/// happens inadvertently. However, the ability to pool data sharing the same `StoreId` can be very
134/// powerful. As a result, we do not want to _enforce_ uniqueness. Still, it is important to
135/// understand what a `StoreId` is, and, to avoid footguns, default to make them unique.
136///
137/// In the context of the logging SDK, the application id is a mandatory, user-defined string. By
138/// default, the recording id is a UUID, which ensures unique-by-default behavior. The user
139/// can override the recording id though. In that case, the user is responsible for making the
140/// application id/recording id pair unique or not, based on their needs.
141///
142/// In the context of remote recordings (aka a dataset's segment), the application id is the
143/// dataset entry id, and the recording id is the segment id. The former is a UUID, and the latter
144/// is, by definition, unique within the dataset entry. As a result, the uniqueness of the `StoreId`
145/// is always guaranteed in this case.
146#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
147#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
148pub struct StoreId {
149    kind: StoreKind,
150    application_id: ApplicationId,
151    recording_id: RecordingId,
152}
153
154/// More compact debug representation of a [`StoreId`].
155impl std::fmt::Debug for StoreId {
156    #[inline]
157    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158        f.debug_tuple("StoreId")
159            .field(&self.kind)
160            .field(&self.application_id.as_str())
161            .field(&self.recording_id.as_str())
162            .finish()
163    }
164}
165
166impl StoreId {
167    #[inline]
168    pub fn new(
169        kind: StoreKind,
170        application_id: impl Into<ApplicationId>,
171        recording_id: impl Into<RecordingId>,
172    ) -> Self {
173        Self {
174            kind,
175            application_id: application_id.into(),
176            recording_id: recording_id.into(),
177        }
178    }
179
180    #[inline]
181    pub fn recording(
182        application_id: impl Into<ApplicationId>,
183        recording_id: impl Into<RecordingId>,
184    ) -> Self {
185        Self::new(StoreKind::Recording, application_id, recording_id)
186    }
187
188    /// Create a [`Self`] for the default blueprint of a given application id
189    #[inline]
190    pub fn default_blueprint(application_id: ApplicationId) -> Self {
191        let recording_id = application_id.as_recording_id();
192        Self::new(StoreKind::Blueprint, application_id, recording_id)
193    }
194
195    /// Generate a random [`StoreId`] with the provided application id.
196    ///
197    /// Note: the application id is required here because fully random store ids are often a
198    /// logically incorrect thing to build (the notable exceptions being tests).
199    #[inline]
200    pub fn random(kind: StoreKind, application_id: impl Into<ApplicationId>) -> Self {
201        Self {
202            kind,
203            recording_id: RecordingId::random(),
204            application_id: application_id.into(),
205        }
206    }
207
208    #[inline]
209    pub fn empty_recording() -> Self {
210        Self::new(StoreKind::Recording, "<EMPTY>", "<EMPTY>")
211    }
212
213    #[inline]
214    pub fn with_recording_id(self, recording_id: impl Into<RecordingId>) -> Self {
215        Self {
216            kind: self.kind,
217            recording_id: recording_id.into(),
218            application_id: self.application_id,
219        }
220    }
221
222    #[inline]
223    pub fn with_application_id(self, application_id: impl Into<ApplicationId>) -> Self {
224        Self {
225            kind: self.kind,
226            recording_id: self.recording_id,
227            application_id: application_id.into(),
228        }
229    }
230
231    #[inline]
232    pub fn from_uuid(kind: StoreKind, application_id: ApplicationId, uuid: uuid::Uuid) -> Self {
233        Self::new(kind, application_id, uuid.simple().to_string())
234    }
235
236    #[inline]
237    pub fn is_empty_recording(&self) -> bool {
238        self.kind == StoreKind::Recording && self.recording_id.as_str() == "<EMPTY>"
239    }
240
241    #[inline]
242    pub fn is_default_blueprint(&self) -> bool {
243        self.kind == StoreKind::Blueprint
244            && self.application_id.as_str() == self.recording_id.as_str()
245    }
246
247    #[inline]
248    pub fn kind(&self) -> StoreKind {
249        self.kind
250    }
251
252    #[inline]
253    pub fn is_recording(&self) -> bool {
254        self.kind == StoreKind::Recording
255    }
256
257    #[inline]
258    pub fn is_blueprint(&self) -> bool {
259        self.kind == StoreKind::Blueprint
260    }
261
262    #[inline]
263    pub fn recording_id(&self) -> &RecordingId {
264        &self.recording_id
265    }
266
267    #[inline]
268    pub fn application_id(&self) -> &ApplicationId {
269        &self.application_id
270    }
271}
272
273// ----------------------------------------------------------------------------
274
275/// The user-chosen name of the application doing the logging.
276///
277/// Application IDs are really schema names.
278/// Every recording using the same schema (approximately!) could share the same blueprint.
279///
280/// In the context of a remote recording, this is the dataset entry id.
281#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
282#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
283pub struct ApplicationId(Arc<String>);
284
285impl From<&str> for ApplicationId {
286    fn from(s: &str) -> Self {
287        Self(Arc::new(s.to_owned()))
288    }
289}
290
291impl From<String> for ApplicationId {
292    fn from(s: String) -> Self {
293        Self(Arc::new(s))
294    }
295}
296
297impl SizeBytes for ApplicationId {
298    #[inline]
299    fn heap_size_bytes(&self) -> u64 {
300        self.0.heap_size_bytes()
301    }
302}
303
304impl ApplicationId {
305    /// The default [`ApplicationId`] if the user hasn't set one.
306    ///
307    /// Currently: `"unknown_app_id"`.
308    pub fn unknown() -> Self {
309        static UNKNOWN_APP_ID: std::sync::LazyLock<ApplicationId> =
310            std::sync::LazyLock::new(|| ApplicationId(Arc::new("unknown_app_id".to_owned())));
311
312        UNKNOWN_APP_ID.clone()
313    }
314
315    pub fn as_str(&self) -> &str {
316        self.0.as_str()
317    }
318
319    /// A randomly generated app id
320    pub fn random() -> Self {
321        Self(Arc::new(format!("app_{}", uuid::Uuid::new_v4().simple())))
322    }
323
324    fn as_recording_id(&self) -> RecordingId {
325        RecordingId(Arc::clone(&self.0))
326    }
327}
328
329impl std::fmt::Display for ApplicationId {
330    #[inline]
331    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
332        self.0.fmt(f)
333    }
334}
335
336// ----------------------------------------------------------------------------
337
338/// The recording id for a recording.
339///
340/// In the context of a recording from the logging SDK, it is by default a uuid, but it is not
341/// required to be so. It may be a user-chosen name as well. In the context of a remote recording,
342/// this is the segment id.
343#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
344#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
345pub struct RecordingId(Arc<String>);
346
347impl From<&str> for RecordingId {
348    fn from(s: &str) -> Self {
349        Self(Arc::new(s.to_owned()))
350    }
351}
352
353impl From<String> for RecordingId {
354    fn from(s: String) -> Self {
355        Self(Arc::new(s))
356    }
357}
358
359impl SizeBytes for RecordingId {
360    #[inline]
361    fn heap_size_bytes(&self) -> u64 {
362        self.0.heap_size_bytes()
363    }
364}
365
366impl RecordingId {
367    pub fn as_str(&self) -> &str {
368        self.0.as_str()
369    }
370
371    /// A randomly generated app id
372    pub fn random() -> Self {
373        Self(Arc::new(format!("rec_{}", uuid::Uuid::new_v4().simple())))
374    }
375}
376
377impl std::fmt::Display for RecordingId {
378    #[inline]
379    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
380        self.0.fmt(f)
381    }
382}
383
384// ----------------------------------------------------------------------------
385
386/// Either the user-chosen name of a table, or an id that is created by the catalog server.
387#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
388#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
389pub struct TableId(Arc<String>);
390
391impl TableId {
392    pub fn new(id: String) -> Self {
393        Self(Arc::new(id))
394    }
395
396    pub fn as_str(&self) -> &str {
397        self.0.as_str()
398    }
399}
400
401impl From<&str> for TableId {
402    fn from(s: &str) -> Self {
403        Self(Arc::new(s.into()))
404    }
405}
406
407impl From<String> for TableId {
408    fn from(s: String) -> Self {
409        Self(Arc::new(s))
410    }
411}
412
413impl std::fmt::Display for TableId {
414    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
415        self.0.fmt(f)
416    }
417}
418
419// ----------------------------------------------------------------------------
420
421/// Command used for activating a blueprint once it has been fully transmitted.
422///
423/// This command serves two purposes:
424/// - It is important that a blueprint is never activated before it has been fully
425///   transmitted. Displaying, or allowing a user to modify, a half-transmitted
426///   blueprint can cause confusion and bad interactions with the view heuristics.
427/// - Additionally, this command allows fine-tuning the activation behavior itself
428///   by specifying whether the blueprint should be immediately activated, or only
429///   become the default for future activations.
430#[derive(Clone, Debug, PartialEq, Eq)] // `PartialEq` used for tests in another crate
431pub struct BlueprintActivationCommand {
432    /// The blueprint this command refers to.
433    pub blueprint_id: StoreId,
434
435    /// Immediately make this the active blueprint for the associated `app_id`.
436    ///
437    /// Note that setting this to `false` does not mean the blueprint may not still end
438    /// up becoming active. In particular, if `make_default` is true and there is no other
439    /// currently active blueprint.
440    pub make_active: bool,
441
442    /// Make this the default blueprint for the `app_id`.
443    ///
444    /// The default blueprint will be used as the template when the user resets the
445    /// blueprint for the app. It will also become the active blueprint if no other
446    /// blueprint is currently active.
447    pub make_default: bool,
448}
449
450impl BlueprintActivationCommand {
451    /// Make `blueprint_id` the default blueprint for its associated `app_id`.
452    pub fn make_default(blueprint_id: StoreId) -> Self {
453        Self {
454            blueprint_id,
455            make_active: false,
456            make_default: true,
457        }
458    }
459
460    /// Immediately make `blueprint_id` the active blueprint for its associated `app_id`.
461    ///
462    /// This also sets `make_default` to true.
463    pub fn make_active(blueprint_id: StoreId) -> Self {
464        Self {
465            blueprint_id,
466            make_active: true,
467            make_default: true,
468        }
469    }
470}
471
472/// The most general log message sent from the SDK to the server.
473///
474/// Note: this does not contain tables sent via [`TableMsg`], as these concepts are fundamentally
475/// different and should not be handled uniformly. For example, we don't want to store tables in
476/// `.rrd` files.
477#[must_use]
478#[derive(Clone, Debug, PartialEq)] // `PartialEq` used for tests in another crate
479// TODO(#8631): Remove `LogMsg`
480pub enum LogMsg {
481    /// A new recording has begun.
482    ///
483    /// Should usually be the first message sent.
484    SetStoreInfo(SetStoreInfo),
485
486    /// Log an entity using an [`ArrowMsg`].
487    //
488    // TODO(#6574): the store ID should be in the metadata here so we can remove the layer on top
489    ArrowMsg(StoreId, ArrowMsg),
490
491    /// Send after all messages in a blueprint to signal that the blueprint is complete.
492    ///
493    /// This is so that the viewer can wait with activating the blueprint until it is
494    /// fully transmitted. Showing a half-transmitted blueprint can cause confusion,
495    /// and also lead to problems with view heuristics.
496    BlueprintActivationCommand(BlueprintActivationCommand),
497}
498
499impl LogMsg {
500    pub fn store_id(&self) -> &StoreId {
501        match self {
502            Self::SetStoreInfo(msg) => &msg.info.store_id,
503            Self::ArrowMsg(store_id, _) => store_id,
504            Self::BlueprintActivationCommand(cmd) => &cmd.blueprint_id,
505        }
506    }
507
508    pub fn set_store_id(&mut self, new_store_id: StoreId) {
509        match self {
510            Self::SetStoreInfo(store_info) => {
511                store_info.info.store_id = new_store_id;
512            }
513            Self::ArrowMsg(store_id, _) => {
514                *store_id = new_store_id;
515            }
516            Self::BlueprintActivationCommand(cmd) => {
517                cmd.blueprint_id = new_store_id;
518            }
519        }
520    }
521
522    /// If we are an [`ArrowMsg`], return a mutable reference to the underlying
523    /// [`ArrowRecordBatch`].
524    pub fn arrow_record_batch_mut(&mut self) -> Option<&mut ArrowRecordBatch> {
525        match self {
526            Self::ArrowMsg(_, arrow_msg) => Some(&mut arrow_msg.batch),
527            _ => None,
528        }
529    }
530
531    pub fn insert_arrow_record_batch_metadata(&mut self, key: String, value: String) {
532        if let Some(record_batch) = self.arrow_record_batch_mut() {
533            record_batch.schema_metadata_mut().insert(key, value);
534        }
535    }
536}
537
538impl_into_enum!(SetStoreInfo, LogMsg, SetStoreInfo);
539impl_into_enum!(
540    BlueprintActivationCommand,
541    LogMsg,
542    BlueprintActivationCommand
543);
544
545// ----------------------------------------------------------------------------
546
547#[must_use]
548#[derive(Clone, Debug, PartialEq, Eq)]
549pub struct SetStoreInfo {
550    /// A time-based UID that is only used to help keep track of when these `StoreInfo` originated
551    /// and how they fit in the global ordering of events.
552    //
553    // NOTE: Using a raw `Tuid` instead of an actual `RowId` to prevent a nasty dependency cycle.
554    // Note that both using a `RowId` as well as this whole serde/msgpack layer as a whole are hacks
555    // that are destined to disappear anyhow as we are closing in on our network-exposed data APIs.
556    pub row_id: re_tuid::Tuid,
557
558    pub info: StoreInfo,
559}
560
561/// Information about a recording or blueprint.
562#[derive(Clone, Debug, PartialEq, Eq)]
563pub struct StoreInfo {
564    /// Should be unique for each recording.
565    ///
566    /// The store id contains both the application id (or dataset id) and the recording id (or
567    /// segment id).
568    pub store_id: StoreId,
569
570    /// If this store is the result of a clone, which store was it cloned from?
571    ///
572    /// A cloned store always gets a new unique ID.
573    ///
574    /// We currently only clone stores for blueprints:
575    /// when we receive a _default_ blueprints on the wire (e.g. from a recording),
576    /// we clone it and make the clone the _active_ blueprint.
577    /// This means all active blueprints are clones.
578    pub cloned_from: Option<StoreId>,
579
580    pub store_source: StoreSource,
581
582    /// The Rerun version used to encoded the RRD data.
583    ///
584    // NOTE: The version comes directly from the decoded RRD stream's header, duplicating it here
585    // would probably only lead to more issues down the line.
586    pub store_version: Option<CrateVersion>,
587}
588
589impl StoreInfo {
590    /// Creates a new store info using the current crate version.
591    pub fn new(store_id: StoreId, store_source: StoreSource) -> Self {
592        Self {
593            store_id,
594            cloned_from: None,
595            store_source,
596            store_version: Some(CrateVersion::LOCAL),
597        }
598    }
599
600    /// Creates a new store info without any versioning information.
601    pub fn new_unversioned(store_id: StoreId, store_source: StoreSource) -> Self {
602        Self {
603            store_id,
604            cloned_from: None,
605            store_source,
606            store_version: None,
607        }
608    }
609
610    /// Creates a new store info for testing purposes.
611    pub fn testing() -> Self {
612        // Do not use a version since it breaks snapshot tests on every update otherwise.
613        Self::new_unversioned(
614            StoreId::random(StoreKind::Recording, "test_app"),
615            StoreSource::Other("test".to_owned()),
616        )
617    }
618
619    /// Creates a new store info for testing purposes with a fixed store id.
620    ///
621    /// Most of the time we don't want to fix the store id since it is used as a key in static store subscribers, which might not get teared down after every test.
622    /// Use this only if the recording id may show up somewhere in the test output.
623    pub fn testing_with_recording_id(recording_id: impl Into<RecordingId>) -> Self {
624        // Do not use a version since it breaks snapshot tests on every update otherwise.
625        Self::new_unversioned(
626            StoreId::new(StoreKind::Recording, "test_app", recording_id),
627            StoreSource::Other("test".to_owned()),
628        )
629    }
630}
631
632impl StoreInfo {
633    /// Whether this `StoreInfo` is the default used when a user is not explicitly
634    /// creating their own blueprint.
635    pub fn is_app_default_blueprint(&self) -> bool {
636        self.application_id().as_str() == self.recording_id().as_str()
637    }
638
639    pub fn application_id(&self) -> &ApplicationId {
640        self.store_id.application_id()
641    }
642
643    pub fn recording_id(&self) -> &RecordingId {
644        self.store_id.recording_id()
645    }
646}
647
648#[derive(Clone, PartialEq, Eq)]
649pub struct PythonVersion {
650    /// e.g. 3
651    pub major: u8,
652
653    /// e.g. 11
654    pub minor: u8,
655
656    /// e.g. 0
657    pub patch: u8,
658
659    /// e.g. `a0` for alpha releases.
660    pub suffix: String,
661}
662
663impl std::fmt::Debug for PythonVersion {
664    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
665        std::fmt::Display::fmt(self, f)
666    }
667}
668
669impl std::fmt::Display for PythonVersion {
670    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
671        let Self {
672            major,
673            minor,
674            patch,
675            suffix,
676        } = self;
677        write!(f, "{major}.{minor}.{patch}{suffix}")
678    }
679}
680
681impl std::str::FromStr for PythonVersion {
682    type Err = PythonVersionParseError;
683
684    fn from_str(s: &str) -> Result<Self, Self::Err> {
685        if s.is_empty() {
686            return Err(PythonVersionParseError::MissingMajor);
687        }
688        let (major, rest) = s
689            .split_once('.')
690            .ok_or(PythonVersionParseError::MissingMinor)?;
691        if rest.is_empty() {
692            return Err(PythonVersionParseError::MissingMinor);
693        }
694        let (minor, rest) = rest
695            .split_once('.')
696            .ok_or(PythonVersionParseError::MissingPatch)?;
697        if rest.is_empty() {
698            return Err(PythonVersionParseError::MissingPatch);
699        }
700        let pos = rest.bytes().position(|v| !v.is_ascii_digit());
701        let (patch, suffix) = match pos {
702            Some(pos) => rest.split_at(pos),
703            None => (rest, ""),
704        };
705
706        Ok(Self {
707            major: major
708                .parse()
709                .map_err(PythonVersionParseError::InvalidMajor)?,
710            minor: minor
711                .parse()
712                .map_err(PythonVersionParseError::InvalidMinor)?,
713            patch: patch
714                .parse()
715                .map_err(PythonVersionParseError::InvalidPatch)?,
716            suffix: suffix.into(),
717        })
718    }
719}
720
721#[derive(Debug, thiserror::Error)]
722pub enum PythonVersionParseError {
723    #[error("missing major version")]
724    MissingMajor,
725
726    #[error("missing minor version")]
727    MissingMinor,
728
729    #[error("missing patch version")]
730    MissingPatch,
731
732    #[error("invalid major version: {0}")]
733    InvalidMajor(std::num::ParseIntError),
734
735    #[error("invalid minor version: {0}")]
736    InvalidMinor(std::num::ParseIntError),
737
738    #[error("invalid patch version: {0}")]
739    InvalidPatch(std::num::ParseIntError),
740}
741
742#[derive(Clone, Debug, PartialEq, Eq, Hash)]
743pub enum FileSource {
744    Cli,
745
746    /// The user clicked on a recording URI in the viewer.
747    Uri,
748
749    DragAndDrop {
750        /// The [`StoreId`] that the viewer heuristically recommends should be used when loading
751        /// this data source, based on the surrounding context.
752        recommended_store_id: Option<StoreId>,
753
754        /// Whether `SetStoreInfo`s should be sent, regardless of the surrounding context.
755        ///
756        /// Only useful when creating a recording just-in-time directly in the viewer (which is what
757        /// happens when importing things into the welcome screen).
758        force_store_info: bool,
759    },
760
761    FileDialog {
762        /// The [`StoreId`] that the viewer heuristically recommends should be used when loading
763        /// this data source, based on the surrounding context.
764        recommended_store_id: Option<StoreId>,
765
766        /// Whether `SetStoreInfo`s should be sent, regardless of the surrounding context.
767        ///
768        /// Only useful when creating a recording just-in-time directly in the viewer (which is what
769        /// happens when importing things into the welcome screen).
770        force_store_info: bool,
771    },
772
773    Sdk,
774}
775
776impl FileSource {
777    pub fn recommended_store_id(&self) -> Option<&StoreId> {
778        match self {
779            Self::FileDialog {
780                recommended_store_id,
781                ..
782            }
783            | Self::DragAndDrop {
784                recommended_store_id,
785                ..
786            } => recommended_store_id.as_ref(),
787            Self::Cli | Self::Uri | Self::Sdk => None,
788        }
789    }
790
791    #[inline]
792    pub fn force_store_info(&self) -> bool {
793        match self {
794            Self::FileDialog {
795                force_store_info, ..
796            }
797            | Self::DragAndDrop {
798                force_store_info, ..
799            } => *force_store_info,
800            Self::Cli | Self::Uri | Self::Sdk => false,
801        }
802    }
803}
804
805/// The source of a recording or blueprint.
806#[derive(Clone, Debug, PartialEq, Eq)]
807pub enum StoreSource {
808    Unknown,
809
810    /// The official Rerun C Logging SDK
811    CSdk,
812
813    /// The official Rerun Python Logging SDK
814    PythonSdk(PythonVersion),
815
816    /// The official Rerun Rust Logging SDK
817    RustSdk {
818        /// Rust version of the code compiling the Rust SDK
819        rustc_version: String,
820
821        /// LLVM version of the code compiling the Rust SDK
822        llvm_version: String,
823    },
824
825    /// Loading a file via CLI, drag-and-drop, a file-dialog, etc.
826    File {
827        file_source: FileSource,
828    },
829
830    /// Generated from the viewer itself.
831    Viewer,
832
833    /// Perhaps from some manual data ingestion?
834    Other(String),
835}
836
837impl std::fmt::Display for StoreSource {
838    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
839        match self {
840            Self::Unknown => "Unknown".fmt(f),
841            Self::CSdk => "C SDK".fmt(f),
842            Self::PythonSdk(version) => write!(f, "Python {version} SDK"),
843            Self::RustSdk { rustc_version, .. } => write!(f, "Rust SDK (rustc {rustc_version})"),
844            Self::File { file_source, .. } => match file_source {
845                FileSource::Cli => write!(f, "File via CLI"),
846                FileSource::Uri => write!(f, "File via URI"),
847                FileSource::DragAndDrop { .. } => write!(f, "File via drag-and-drop"),
848                FileSource::FileDialog { .. } => write!(f, "File via file dialog"),
849                FileSource::Sdk => write!(f, "File via SDK"),
850            },
851            Self::Viewer => write!(f, "Viewer-generated"),
852            Self::Other(string) => format!("{string:?}").fmt(f), // put it in quotes
853        }
854    }
855}
856
857// ---
858
859/// A table, encoded as a dataframe of Arrow record batches.
860///
861/// Tables have a [`TableId`], but don't belong to an application and therefore don't have an [`ApplicationId`].
862/// For now, the table is always sent as a whole, i.e. tables can't be streamed.
863///
864/// It's important to note that tables are not sent via the smart channel of [`LogMsg`], but use a separate `crossbeam`
865/// channel. The reasoning behind this is that tables are fundamentally different from recordings. For example,
866/// we don't want to store tables in `.rrd` files, as there are much better formats out there.
867#[must_use]
868#[derive(Clone, Debug, PartialEq)]
869pub struct TableMsg {
870    /// The id of the table.
871    pub id: TableId,
872
873    /// The table stored as an [`ArrowRecordBatch`].
874    pub data: ArrowRecordBatch,
875}
876
877impl re_byte_size::SizeBytes for TableMsg {
878    #[inline]
879    fn heap_size_bytes(&self) -> u64 {
880        let Self { id: _, data } = self;
881
882        data.heap_size_bytes()
883    }
884}
885
886impl TableMsg {
887    pub fn insert_arrow_record_batch_metadata(&mut self, key: String, value: String) {
888        self.data.schema_metadata_mut().insert(key, value);
889    }
890}
891
892// ---
893
894/// Build a ([`Timeline`], [`TimeInt`]) tuple from `log_time` suitable for inserting in a [`TimePoint`].
895#[inline]
896pub fn build_log_time(log_time: Timestamp) -> (Timeline, TimeInt) {
897    (
898        Timeline::log_time(),
899        TimeInt::new_temporal(log_time.nanos_since_epoch()),
900    )
901}
902
903/// Build a ([`Timeline`], [`TimeInt`]) tuple from `frame_nr` suitable for inserting in a [`TimePoint`].
904#[inline]
905pub fn build_frame_nr(frame_nr: impl TryInto<TimeInt>) -> (Timeline, TimeInt) {
906    (
907        Timeline::new("frame_nr", TimeType::Sequence),
908        TimeInt::saturated_temporal(frame_nr),
909    )
910}
911
912#[inline]
913pub fn build_index_value(value: impl TryInto<TimeInt>, time_type: TimeType) -> (Timeline, TimeInt) {
914    let timeline_name = match time_type {
915        TimeType::Sequence => "frame_nr",
916        TimeType::DurationNs => "duration",
917        TimeType::TimestampNs => "timestamp",
918    };
919
920    (
921        Timeline::new(timeline_name, time_type),
922        TimeInt::saturated_temporal(value),
923    )
924}
925
926impl SizeBytes for StoreId {
927    #[inline]
928    fn heap_size_bytes(&self) -> u64 {
929        let Self {
930            kind: _,
931            recording_id: id,
932            application_id,
933        } = self;
934
935        id.heap_size_bytes() + application_id.heap_size_bytes()
936    }
937}
938
939impl SizeBytes for PythonVersion {
940    #[inline]
941    fn heap_size_bytes(&self) -> u64 {
942        let Self {
943            major: _,
944            minor: _,
945            patch: _,
946            suffix,
947        } = self;
948
949        suffix.heap_size_bytes()
950    }
951}
952
953impl SizeBytes for FileSource {
954    #[inline]
955    fn heap_size_bytes(&self) -> u64 {
956        match self {
957            Self::Uri | Self::Sdk | Self::Cli => 0,
958            Self::DragAndDrop {
959                recommended_store_id,
960                force_store_info,
961            }
962            | Self::FileDialog {
963                recommended_store_id,
964                force_store_info,
965            } => recommended_store_id.heap_size_bytes() + force_store_info.heap_size_bytes(),
966        }
967    }
968}
969
970impl SizeBytes for StoreSource {
971    #[inline]
972    fn heap_size_bytes(&self) -> u64 {
973        match self {
974            Self::Unknown | Self::CSdk | Self::Viewer => 0,
975            Self::PythonSdk(python_version) => python_version.heap_size_bytes(),
976            Self::RustSdk {
977                rustc_version,
978                llvm_version,
979            } => rustc_version.heap_size_bytes() + llvm_version.heap_size_bytes(),
980            Self::File { file_source } => file_source.heap_size_bytes(),
981            Self::Other(description) => description.heap_size_bytes(),
982        }
983    }
984}
985
986impl SizeBytes for StoreInfo {
987    #[inline]
988    fn heap_size_bytes(&self) -> u64 {
989        let Self {
990            store_id,
991            cloned_from: _,
992            store_source,
993            store_version,
994        } = self;
995
996        store_id.heap_size_bytes()
997            + store_source.heap_size_bytes()
998            + store_version.heap_size_bytes()
999    }
1000}
1001
1002impl SizeBytes for SetStoreInfo {
1003    #[inline]
1004    fn heap_size_bytes(&self) -> u64 {
1005        let Self { row_id, info } = self;
1006
1007        row_id.heap_size_bytes() + info.heap_size_bytes()
1008    }
1009}
1010
1011impl SizeBytes for BlueprintActivationCommand {
1012    #[inline]
1013    fn heap_size_bytes(&self) -> u64 {
1014        0
1015    }
1016}
1017
1018impl SizeBytes for ArrowMsg {
1019    #[inline]
1020    fn heap_size_bytes(&self) -> u64 {
1021        let Self {
1022            chunk_id,
1023            batch,
1024            on_release: _,
1025        } = self;
1026
1027        chunk_id.heap_size_bytes() + batch.heap_size_bytes()
1028    }
1029}
1030
1031impl SizeBytes for LogMsg {
1032    #[inline]
1033    fn heap_size_bytes(&self) -> u64 {
1034        match self {
1035            Self::SetStoreInfo(set_store_info) => set_store_info.heap_size_bytes(),
1036            Self::ArrowMsg(store_id, arrow_msg) => {
1037                store_id.heap_size_bytes() + arrow_msg.heap_size_bytes()
1038            }
1039            Self::BlueprintActivationCommand(blueprint_activation_command) => {
1040                blueprint_activation_command.heap_size_bytes()
1041            }
1042        }
1043    }
1044}
1045
1046// ----------------------------------------------------------------------------
1047
1048#[cfg(test)]
1049mod tests {
1050    use super::*;
1051
1052    #[test]
1053    fn parse_python_version() {
1054        macro_rules! assert_parse_err {
1055            ($input:literal, $expected:pat) => {
1056                let actual = $input.parse::<PythonVersion>();
1057
1058                assert!(
1059                    matches!(actual, Err($expected)),
1060                    "actual: {actual:?}, expected: {}",
1061                    stringify!($expected)
1062                );
1063            };
1064        }
1065
1066        macro_rules! assert_parse_ok {
1067            ($input:literal, $expected:expr) => {
1068                let actual = $input.parse::<PythonVersion>().expect("failed to parse");
1069                assert_eq!(actual, $expected);
1070            };
1071        }
1072
1073        assert_parse_err!("", PythonVersionParseError::MissingMajor);
1074        assert_parse_err!("3", PythonVersionParseError::MissingMinor);
1075        assert_parse_err!("3.", PythonVersionParseError::MissingMinor);
1076        assert_parse_err!("3.11", PythonVersionParseError::MissingPatch);
1077        assert_parse_err!("3.11.", PythonVersionParseError::MissingPatch);
1078        assert_parse_err!("a.11.0", PythonVersionParseError::InvalidMajor(_));
1079        assert_parse_err!("3.b.0", PythonVersionParseError::InvalidMinor(_));
1080        assert_parse_err!("3.11.c", PythonVersionParseError::InvalidPatch(_));
1081        assert_parse_ok!(
1082            "3.11.0",
1083            PythonVersion {
1084                major: 3,
1085                minor: 11,
1086                patch: 0,
1087                suffix: String::new(),
1088            }
1089        );
1090        assert_parse_ok!(
1091            "3.11.0a1",
1092            PythonVersion {
1093                major: 3,
1094                minor: 11,
1095                patch: 0,
1096                suffix: "a1".to_owned(),
1097            }
1098        );
1099    }
1100}