re_log_types 0.31.3

The basic building blocks of the Rerun data types and tables.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
//! The different types that make up the rerun log format.
//!
//! ## Feature flags
#![doc = document_features::document_features!()]
//!
//! ## Mono-components
//!
//! Some components, mostly transform related ones, are "mono-components".
//! This means that Rerun makes assumptions that depend on this component
//! only taking on a singular value for all instances of an Entity. Where possible,
//! exposed APIs will force these components to be logged as a singular instance.
//! However, it is an error with undefined behavior to manually use lower-level
//! APIs to log a batched mono-component.
//!
//! This requirement is especially apparent with transforms:
//! Each entity must have a unique transform chain,
//! e.g. the entity `foo/bar/baz` is has the transform that is the product of
//! `foo.transform * foo/bar.transform * foo/bar/baz.transform`.

pub mod arrow_msg;
mod entry_id;
mod entry_name;
pub mod example_components;
pub mod hash;
mod index;
pub mod path;

// mod data_cell;
// mod data_row;
// mod data_table;
mod instance;
mod vec_deque_ext;

use std::sync::Arc;

use arrow::array::RecordBatch as ArrowRecordBatch;
use re_build_info::CrateVersion;
use re_byte_size::SizeBytes;

pub use re_types_core::TimelineName;

pub use self::arrow_msg::{ArrowMsg, ArrowRecordBatchReleaseCallback};
pub use self::entry_id::{EntryId, EntryIdOrName};
pub use self::entry_name::{EntryName, InvalidEntryNameError};
pub use self::index::{
    AbsoluteTimeRange, AbsoluteTimeRangeF, Duration, NonMinI64, TimeCell, TimeInt, TimePoint,
    TimeReal, TimeType, Timeline, TimelinePoint, Timestamp, TimestampFormat, TimestampFormatKind,
    TryFromIntError,
};
pub use self::instance::Instance;
pub use self::path::*;
pub use self::vec_deque_ext::{VecDequeInsertionExt, VecDequeRemovalExt, VecDequeSortingExt};

pub mod external {
    pub use {arrow, re_tuid, re_types_core};
}

#[macro_export]
macro_rules! impl_into_enum {
    ($from_ty: ty, $enum_name: ident, $to_enum_variant: ident) => {
        impl From<$from_ty> for $enum_name {
            #[inline]
            fn from(value: $from_ty) -> Self {
                Self::$to_enum_variant(value)
            }
        }
    };
}

// ----------------------------------------------------------------------------

/// What kind of store this is.
///
/// `Recording` stores contain user-data logged via the SDK.
///
/// `Blueprint` stores contain the data which describes the layout of the visualization. It can be
/// logged using the Blueprint API, and is also generated by the viewer as it is interacted with.
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum StoreKind {
    /// A recording of user-data.
    Recording,

    /// Data associated with the blueprint state.
    Blueprint,
}

impl std::fmt::Display for StoreKind {
    #[inline]
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Recording => "Recording".fmt(f),
            Self::Blueprint => "Blueprint".fmt(f),
        }
    }
}

impl std::str::FromStr for StoreKind {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "recording" => Ok(Self::Recording),
            "blueprint" => Ok(Self::Blueprint),
            unknown => Err(format!("{unknown:?} is not a valid StoreKind")),
        }
    }
}

#[test]
fn store_kind_str_roundtrip() {
    {
        let kind = StoreKind::Recording;
        assert_eq!(kind, kind.to_string().parse().unwrap());
    }

    {
        let kind = StoreKind::Blueprint;
        assert_eq!(kind, kind.to_string().parse().unwrap());
    }
}

/// A unique id per store.
///
/// The kind of store is part of the id, and can be either a [`StoreKind::Recording`] or a
/// [`StoreKind::Blueprint`].
///
/// ## Uniqueness
///
/// All data associated with a given `StoreId`, regardless of its source, is considered to belong
/// to the same logical recording. As such, when ingested in the viewer, they will be pooled into
/// a single store and displayed as a single, in-viewer recording. This can be very confusing if it
/// happens inadvertently. However, the ability to pool data sharing the same `StoreId` can be very
/// powerful. As a result, we do not want to _enforce_ uniqueness. Still, it is important to
/// understand what a `StoreId` is, and, to avoid footguns, default to make them unique.
///
/// In the context of the logging SDK, the application id is a mandatory, user-defined string. By
/// default, the recording id is a UUID, which ensures unique-by-default behavior. The user
/// can override the recording id though. In that case, the user is responsible for making the
/// application id/recording id pair unique or not, based on their needs.
///
/// In the context of remote recordings (aka a dataset's segment), the application id is the
/// dataset entry id, and the recording id is the segment id. The former is a UUID, and the latter
/// is, by definition, unique within the dataset entry. As a result, the uniqueness of the `StoreId`
/// is always guaranteed in this case.
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct StoreId {
    kind: StoreKind,
    application_id: ApplicationId,
    recording_id: RecordingId,
}

/// More compact debug representation of a [`StoreId`].
impl std::fmt::Debug for StoreId {
    #[inline]
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_tuple("StoreId")
            .field(&self.kind)
            .field(&self.application_id.as_str())
            .field(&self.recording_id.as_str())
            .finish()
    }
}

impl StoreId {
    #[inline]
    pub fn new(
        kind: StoreKind,
        application_id: impl Into<ApplicationId>,
        recording_id: impl Into<RecordingId>,
    ) -> Self {
        Self {
            kind,
            application_id: application_id.into(),
            recording_id: recording_id.into(),
        }
    }

    #[inline]
    pub fn recording(
        application_id: impl Into<ApplicationId>,
        recording_id: impl Into<RecordingId>,
    ) -> Self {
        Self::new(StoreKind::Recording, application_id, recording_id)
    }

    /// Create a [`Self`] for the default blueprint of a given application id
    #[inline]
    pub fn default_blueprint(application_id: ApplicationId) -> Self {
        let recording_id = application_id.as_recording_id();
        Self::new(StoreKind::Blueprint, application_id, recording_id)
    }

    /// Generate a random [`StoreId`] with the provided application id.
    ///
    /// Note: the application id is required here because fully random store ids are often a
    /// logically incorrect thing to build (the notable exceptions being tests).
    #[inline]
    pub fn random(kind: StoreKind, application_id: impl Into<ApplicationId>) -> Self {
        Self {
            kind,
            recording_id: RecordingId::random(),
            application_id: application_id.into(),
        }
    }

    #[inline]
    pub fn empty_recording() -> Self {
        Self::new(StoreKind::Recording, "<EMPTY>", "<EMPTY>")
    }

    #[inline]
    pub fn with_recording_id(self, recording_id: impl Into<RecordingId>) -> Self {
        Self {
            kind: self.kind,
            recording_id: recording_id.into(),
            application_id: self.application_id,
        }
    }

    #[inline]
    pub fn with_application_id(self, application_id: impl Into<ApplicationId>) -> Self {
        Self {
            kind: self.kind,
            recording_id: self.recording_id,
            application_id: application_id.into(),
        }
    }

    #[inline]
    pub fn from_uuid(kind: StoreKind, application_id: ApplicationId, uuid: uuid::Uuid) -> Self {
        Self::new(kind, application_id, uuid.simple().to_string())
    }

    #[inline]
    pub fn is_empty_recording(&self) -> bool {
        self.kind == StoreKind::Recording && self.recording_id.as_str() == "<EMPTY>"
    }

    #[inline]
    pub fn is_default_blueprint(&self) -> bool {
        self.kind == StoreKind::Blueprint
            && self.application_id.as_str() == self.recording_id.as_str()
    }

    #[inline]
    pub fn kind(&self) -> StoreKind {
        self.kind
    }

    #[inline]
    pub fn is_recording(&self) -> bool {
        self.kind == StoreKind::Recording
    }

    #[inline]
    pub fn is_blueprint(&self) -> bool {
        self.kind == StoreKind::Blueprint
    }

    #[inline]
    pub fn recording_id(&self) -> &RecordingId {
        &self.recording_id
    }

    #[inline]
    pub fn application_id(&self) -> &ApplicationId {
        &self.application_id
    }
}

// ----------------------------------------------------------------------------

/// The user-chosen name of the application doing the logging.
///
/// Application IDs are really schema names.
/// Every recording using the same schema (approximately!) could share the same blueprint.
///
/// In the context of a remote recording, this is the dataset entry id.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct ApplicationId(Arc<String>);

impl From<&str> for ApplicationId {
    fn from(s: &str) -> Self {
        Self(Arc::new(s.to_owned()))
    }
}

impl From<String> for ApplicationId {
    fn from(s: String) -> Self {
        Self(Arc::new(s))
    }
}

impl SizeBytes for ApplicationId {
    #[inline]
    fn heap_size_bytes(&self) -> u64 {
        self.0.heap_size_bytes()
    }
}

impl ApplicationId {
    /// The default [`ApplicationId`] if the user hasn't set one.
    ///
    /// Currently: `"unknown_app_id"`.
    pub fn unknown() -> Self {
        static UNKNOWN_APP_ID: std::sync::LazyLock<ApplicationId> =
            std::sync::LazyLock::new(|| ApplicationId(Arc::new("unknown_app_id".to_owned())));

        UNKNOWN_APP_ID.clone()
    }

    pub fn as_str(&self) -> &str {
        self.0.as_str()
    }

    /// A randomly generated app id
    pub fn random() -> Self {
        Self(Arc::new(format!("app_{}", uuid::Uuid::new_v4().simple())))
    }

    fn as_recording_id(&self) -> RecordingId {
        RecordingId(Arc::clone(&self.0))
    }
}

impl std::fmt::Display for ApplicationId {
    #[inline]
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}

// ----------------------------------------------------------------------------

/// The recording id for a recording.
///
/// In the context of a recording from the logging SDK, it is by default a uuid, but it is not
/// required to be so. It may be a user-chosen name as well. In the context of a remote recording,
/// this is the segment id.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct RecordingId(Arc<String>);

impl From<&str> for RecordingId {
    fn from(s: &str) -> Self {
        Self(Arc::new(s.to_owned()))
    }
}

impl From<String> for RecordingId {
    fn from(s: String) -> Self {
        Self(Arc::new(s))
    }
}

impl SizeBytes for RecordingId {
    #[inline]
    fn heap_size_bytes(&self) -> u64 {
        self.0.heap_size_bytes()
    }
}

impl RecordingId {
    pub fn as_str(&self) -> &str {
        self.0.as_str()
    }

    /// A randomly generated app id
    pub fn random() -> Self {
        Self(Arc::new(format!("rec_{}", uuid::Uuid::new_v4().simple())))
    }
}

impl std::fmt::Display for RecordingId {
    #[inline]
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}

// ----------------------------------------------------------------------------

/// Either the user-chosen name of a table, or an id that is created by the catalog server.
#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TableId(Arc<String>);

impl TableId {
    pub fn new(id: String) -> Self {
        Self(Arc::new(id))
    }

    pub fn as_str(&self) -> &str {
        self.0.as_str()
    }
}

impl From<&str> for TableId {
    fn from(s: &str) -> Self {
        Self(Arc::new(s.into()))
    }
}

impl From<String> for TableId {
    fn from(s: String) -> Self {
        Self(Arc::new(s))
    }
}

impl std::fmt::Display for TableId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}

// ----------------------------------------------------------------------------

/// Command used for activating a blueprint once it has been fully transmitted.
///
/// This command serves two purposes:
/// - It is important that a blueprint is never activated before it has been fully
///   transmitted. Displaying, or allowing a user to modify, a half-transmitted
///   blueprint can cause confusion and bad interactions with the view heuristics.
/// - Additionally, this command allows fine-tuning the activation behavior itself
///   by specifying whether the blueprint should be immediately activated, or only
///   become the default for future activations.
#[derive(Clone, Debug, PartialEq, Eq)] // `PartialEq` used for tests in another crate
pub struct BlueprintActivationCommand {
    /// The blueprint this command refers to.
    pub blueprint_id: StoreId,

    /// Immediately make this the active blueprint for the associated `app_id`.
    ///
    /// Note that setting this to `false` does not mean the blueprint may not still end
    /// up becoming active. In particular, if `make_default` is true and there is no other
    /// currently active blueprint.
    pub make_active: bool,

    /// Make this the default blueprint for the `app_id`.
    ///
    /// The default blueprint will be used as the template when the user resets the
    /// blueprint for the app. It will also become the active blueprint if no other
    /// blueprint is currently active.
    pub make_default: bool,
}

impl BlueprintActivationCommand {
    /// Make `blueprint_id` the default blueprint for its associated `app_id`.
    pub fn make_default(blueprint_id: StoreId) -> Self {
        Self {
            blueprint_id,
            make_active: false,
            make_default: true,
        }
    }

    /// Immediately make `blueprint_id` the active blueprint for its associated `app_id`.
    ///
    /// This also sets `make_default` to true.
    pub fn make_active(blueprint_id: StoreId) -> Self {
        Self {
            blueprint_id,
            make_active: true,
            make_default: true,
        }
    }
}

/// The most general log message sent from the SDK to the server.
///
/// Note: this does not contain tables sent via [`TableMsg`], as these concepts are fundamentally
/// different and should not be handled uniformly. For example, we don't want to store tables in
/// `.rrd` files.
#[must_use]
#[derive(Clone, Debug, PartialEq)] // `PartialEq` used for tests in another crate
// TODO(#8631): Remove `LogMsg`
pub enum LogMsg {
    /// A new recording has begun.
    ///
    /// Should usually be the first message sent.
    SetStoreInfo(SetStoreInfo),

    /// Log an entity using an [`ArrowMsg`].
    //
    // TODO(#6574): the store ID should be in the metadata here so we can remove the layer on top
    ArrowMsg(StoreId, ArrowMsg),

    /// Send after all messages in a blueprint to signal that the blueprint is complete.
    ///
    /// This is so that the viewer can wait with activating the blueprint until it is
    /// fully transmitted. Showing a half-transmitted blueprint can cause confusion,
    /// and also lead to problems with view heuristics.
    BlueprintActivationCommand(BlueprintActivationCommand),
}

impl LogMsg {
    pub fn store_id(&self) -> &StoreId {
        match self {
            Self::SetStoreInfo(msg) => &msg.info.store_id,
            Self::ArrowMsg(store_id, _) => store_id,
            Self::BlueprintActivationCommand(cmd) => &cmd.blueprint_id,
        }
    }

    pub fn set_store_id(&mut self, new_store_id: StoreId) {
        match self {
            Self::SetStoreInfo(store_info) => {
                store_info.info.store_id = new_store_id;
            }
            Self::ArrowMsg(store_id, _) => {
                *store_id = new_store_id;
            }
            Self::BlueprintActivationCommand(cmd) => {
                cmd.blueprint_id = new_store_id;
            }
        }
    }

    /// If we are an [`ArrowMsg`], return a mutable reference to the underlying
    /// [`ArrowRecordBatch`].
    pub fn arrow_record_batch_mut(&mut self) -> Option<&mut ArrowRecordBatch> {
        match self {
            Self::ArrowMsg(_, arrow_msg) => Some(&mut arrow_msg.batch),
            _ => None,
        }
    }

    pub fn insert_arrow_record_batch_metadata(&mut self, key: String, value: String) {
        if let Some(record_batch) = self.arrow_record_batch_mut() {
            record_batch.schema_metadata_mut().insert(key, value);
        }
    }
}

impl_into_enum!(SetStoreInfo, LogMsg, SetStoreInfo);
impl_into_enum!(
    BlueprintActivationCommand,
    LogMsg,
    BlueprintActivationCommand
);

// ----------------------------------------------------------------------------

#[must_use]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SetStoreInfo {
    /// A time-based UID that is only used to help keep track of when these `StoreInfo` originated
    /// and how they fit in the global ordering of events.
    //
    // NOTE: Using a raw `Tuid` instead of an actual `RowId` to prevent a nasty dependency cycle.
    // Note that both using a `RowId` as well as this whole serde/msgpack layer as a whole are hacks
    // that are destined to disappear anyhow as we are closing in on our network-exposed data APIs.
    pub row_id: re_tuid::Tuid,

    pub info: StoreInfo,
}

/// Information about a recording or blueprint.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StoreInfo {
    /// Should be unique for each recording.
    ///
    /// The store id contains both the application id (or dataset id) and the recording id (or
    /// segment id).
    pub store_id: StoreId,

    /// If this store is the result of a clone, which store was it cloned from?
    ///
    /// A cloned store always gets a new unique ID.
    ///
    /// We currently only clone stores for blueprints:
    /// when we receive a _default_ blueprints on the wire (e.g. from a recording),
    /// we clone it and make the clone the _active_ blueprint.
    /// This means all active blueprints are clones.
    pub cloned_from: Option<StoreId>,

    pub store_source: StoreSource,

    /// The Rerun version used to encoded the RRD data.
    ///
    // NOTE: The version comes directly from the decoded RRD stream's header, duplicating it here
    // would probably only lead to more issues down the line.
    pub store_version: Option<CrateVersion>,
}

impl StoreInfo {
    /// Creates a new store info using the current crate version.
    pub fn new(store_id: StoreId, store_source: StoreSource) -> Self {
        Self {
            store_id,
            cloned_from: None,
            store_source,
            store_version: Some(CrateVersion::LOCAL),
        }
    }

    /// Creates a new store info without any versioning information.
    pub fn new_unversioned(store_id: StoreId, store_source: StoreSource) -> Self {
        Self {
            store_id,
            cloned_from: None,
            store_source,
            store_version: None,
        }
    }

    /// Creates a new store info for testing purposes.
    pub fn testing() -> Self {
        // Do not use a version since it breaks snapshot tests on every update otherwise.
        Self::new_unversioned(
            StoreId::random(StoreKind::Recording, "test_app"),
            StoreSource::Other("test".to_owned()),
        )
    }

    /// Creates a new store info for testing purposes with a fixed store id.
    ///
    /// 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.
    /// Use this only if the recording id may show up somewhere in the test output.
    pub fn testing_with_recording_id(recording_id: impl Into<RecordingId>) -> Self {
        // Do not use a version since it breaks snapshot tests on every update otherwise.
        Self::new_unversioned(
            StoreId::new(StoreKind::Recording, "test_app", recording_id),
            StoreSource::Other("test".to_owned()),
        )
    }
}

impl StoreInfo {
    /// Whether this `StoreInfo` is the default used when a user is not explicitly
    /// creating their own blueprint.
    pub fn is_app_default_blueprint(&self) -> bool {
        self.application_id().as_str() == self.recording_id().as_str()
    }

    pub fn application_id(&self) -> &ApplicationId {
        self.store_id.application_id()
    }

    pub fn recording_id(&self) -> &RecordingId {
        self.store_id.recording_id()
    }
}

#[derive(Clone, PartialEq, Eq)]
pub struct PythonVersion {
    /// e.g. 3
    pub major: u8,

    /// e.g. 11
    pub minor: u8,

    /// e.g. 0
    pub patch: u8,

    /// e.g. `a0` for alpha releases.
    pub suffix: String,
}

impl std::fmt::Debug for PythonVersion {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        std::fmt::Display::fmt(self, f)
    }
}

impl std::fmt::Display for PythonVersion {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let Self {
            major,
            minor,
            patch,
            suffix,
        } = self;
        write!(f, "{major}.{minor}.{patch}{suffix}")
    }
}

impl std::str::FromStr for PythonVersion {
    type Err = PythonVersionParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s.is_empty() {
            return Err(PythonVersionParseError::MissingMajor);
        }
        let (major, rest) = s
            .split_once('.')
            .ok_or(PythonVersionParseError::MissingMinor)?;
        if rest.is_empty() {
            return Err(PythonVersionParseError::MissingMinor);
        }
        let (minor, rest) = rest
            .split_once('.')
            .ok_or(PythonVersionParseError::MissingPatch)?;
        if rest.is_empty() {
            return Err(PythonVersionParseError::MissingPatch);
        }
        let pos = rest.bytes().position(|v| !v.is_ascii_digit());
        let (patch, suffix) = match pos {
            Some(pos) => rest.split_at(pos),
            None => (rest, ""),
        };

        Ok(Self {
            major: major
                .parse()
                .map_err(PythonVersionParseError::InvalidMajor)?,
            minor: minor
                .parse()
                .map_err(PythonVersionParseError::InvalidMinor)?,
            patch: patch
                .parse()
                .map_err(PythonVersionParseError::InvalidPatch)?,
            suffix: suffix.into(),
        })
    }
}

#[derive(Debug, thiserror::Error)]
pub enum PythonVersionParseError {
    #[error("missing major version")]
    MissingMajor,

    #[error("missing minor version")]
    MissingMinor,

    #[error("missing patch version")]
    MissingPatch,

    #[error("invalid major version: {0}")]
    InvalidMajor(std::num::ParseIntError),

    #[error("invalid minor version: {0}")]
    InvalidMinor(std::num::ParseIntError),

    #[error("invalid patch version: {0}")]
    InvalidPatch(std::num::ParseIntError),
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum FileSource {
    Cli,

    /// The user clicked on a recording URI in the viewer.
    Uri,

    DragAndDrop {
        /// The [`StoreId`] that the viewer heuristically recommends should be used when loading
        /// this data source, based on the surrounding context.
        recommended_store_id: Option<StoreId>,

        /// Whether `SetStoreInfo`s should be sent, regardless of the surrounding context.
        ///
        /// Only useful when creating a recording just-in-time directly in the viewer (which is what
        /// happens when importing things into the welcome screen).
        force_store_info: bool,
    },

    FileDialog {
        /// The [`StoreId`] that the viewer heuristically recommends should be used when loading
        /// this data source, based on the surrounding context.
        recommended_store_id: Option<StoreId>,

        /// Whether `SetStoreInfo`s should be sent, regardless of the surrounding context.
        ///
        /// Only useful when creating a recording just-in-time directly in the viewer (which is what
        /// happens when importing things into the welcome screen).
        force_store_info: bool,
    },

    Sdk,
}

impl FileSource {
    pub fn recommended_store_id(&self) -> Option<&StoreId> {
        match self {
            Self::FileDialog {
                recommended_store_id,
                ..
            }
            | Self::DragAndDrop {
                recommended_store_id,
                ..
            } => recommended_store_id.as_ref(),
            Self::Cli | Self::Uri | Self::Sdk => None,
        }
    }

    #[inline]
    pub fn force_store_info(&self) -> bool {
        match self {
            Self::FileDialog {
                force_store_info, ..
            }
            | Self::DragAndDrop {
                force_store_info, ..
            } => *force_store_info,
            Self::Cli | Self::Uri | Self::Sdk => false,
        }
    }
}

/// The source of a recording or blueprint.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum StoreSource {
    Unknown,

    /// The official Rerun C Logging SDK
    CSdk,

    /// The official Rerun Python Logging SDK
    PythonSdk(PythonVersion),

    /// The official Rerun Rust Logging SDK
    RustSdk {
        /// Rust version of the code compiling the Rust SDK
        rustc_version: String,

        /// LLVM version of the code compiling the Rust SDK
        llvm_version: String,
    },

    /// Loading a file via CLI, drag-and-drop, a file-dialog, etc.
    File {
        file_source: FileSource,
    },

    /// Generated from the viewer itself.
    Viewer,

    /// Perhaps from some manual data ingestion?
    Other(String),
}

impl std::fmt::Display for StoreSource {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Unknown => "Unknown".fmt(f),
            Self::CSdk => "C SDK".fmt(f),
            Self::PythonSdk(version) => write!(f, "Python {version} SDK"),
            Self::RustSdk { rustc_version, .. } => write!(f, "Rust SDK (rustc {rustc_version})"),
            Self::File { file_source, .. } => match file_source {
                FileSource::Cli => write!(f, "File via CLI"),
                FileSource::Uri => write!(f, "File via URI"),
                FileSource::DragAndDrop { .. } => write!(f, "File via drag-and-drop"),
                FileSource::FileDialog { .. } => write!(f, "File via file dialog"),
                FileSource::Sdk => write!(f, "File via SDK"),
            },
            Self::Viewer => write!(f, "Viewer-generated"),
            Self::Other(string) => format!("{string:?}").fmt(f), // put it in quotes
        }
    }
}

// ---

/// A table, encoded as a dataframe of Arrow record batches.
///
/// Tables have a [`TableId`], but don't belong to an application and therefore don't have an [`ApplicationId`].
/// For now, the table is always sent as a whole, i.e. tables can't be streamed.
///
/// It's important to note that tables are not sent via the smart channel of [`LogMsg`], but use a separate `crossbeam`
/// channel. The reasoning behind this is that tables are fundamentally different from recordings. For example,
/// we don't want to store tables in `.rrd` files, as there are much better formats out there.
#[must_use]
#[derive(Clone, Debug, PartialEq)]
pub struct TableMsg {
    /// The id of the table.
    pub id: TableId,

    /// The table stored as an [`ArrowRecordBatch`].
    pub data: ArrowRecordBatch,
}

impl re_byte_size::SizeBytes for TableMsg {
    #[inline]
    fn heap_size_bytes(&self) -> u64 {
        let Self { id: _, data } = self;

        data.heap_size_bytes()
    }
}

impl TableMsg {
    pub fn insert_arrow_record_batch_metadata(&mut self, key: String, value: String) {
        self.data.schema_metadata_mut().insert(key, value);
    }
}

// ---

/// Build a ([`Timeline`], [`TimeInt`]) tuple from `log_time` suitable for inserting in a [`TimePoint`].
#[inline]
pub fn build_log_time(log_time: Timestamp) -> (Timeline, TimeInt) {
    (
        Timeline::log_time(),
        TimeInt::new_temporal(log_time.nanos_since_epoch()),
    )
}

/// Build a ([`Timeline`], [`TimeInt`]) tuple from `frame_nr` suitable for inserting in a [`TimePoint`].
#[inline]
pub fn build_frame_nr(frame_nr: impl TryInto<TimeInt>) -> (Timeline, TimeInt) {
    (
        Timeline::new("frame_nr", TimeType::Sequence),
        TimeInt::saturated_temporal(frame_nr),
    )
}

#[inline]
pub fn build_index_value(value: impl TryInto<TimeInt>, time_type: TimeType) -> (Timeline, TimeInt) {
    let timeline_name = match time_type {
        TimeType::Sequence => "frame_nr",
        TimeType::DurationNs => "duration",
        TimeType::TimestampNs => "timestamp",
    };

    (
        Timeline::new(timeline_name, time_type),
        TimeInt::saturated_temporal(value),
    )
}

impl SizeBytes for StoreId {
    #[inline]
    fn heap_size_bytes(&self) -> u64 {
        let Self {
            kind: _,
            recording_id: id,
            application_id,
        } = self;

        id.heap_size_bytes() + application_id.heap_size_bytes()
    }
}

impl SizeBytes for PythonVersion {
    #[inline]
    fn heap_size_bytes(&self) -> u64 {
        let Self {
            major: _,
            minor: _,
            patch: _,
            suffix,
        } = self;

        suffix.heap_size_bytes()
    }
}

impl SizeBytes for FileSource {
    #[inline]
    fn heap_size_bytes(&self) -> u64 {
        match self {
            Self::Uri | Self::Sdk | Self::Cli => 0,
            Self::DragAndDrop {
                recommended_store_id,
                force_store_info,
            }
            | Self::FileDialog {
                recommended_store_id,
                force_store_info,
            } => recommended_store_id.heap_size_bytes() + force_store_info.heap_size_bytes(),
        }
    }
}

impl SizeBytes for StoreSource {
    #[inline]
    fn heap_size_bytes(&self) -> u64 {
        match self {
            Self::Unknown | Self::CSdk | Self::Viewer => 0,
            Self::PythonSdk(python_version) => python_version.heap_size_bytes(),
            Self::RustSdk {
                rustc_version,
                llvm_version,
            } => rustc_version.heap_size_bytes() + llvm_version.heap_size_bytes(),
            Self::File { file_source } => file_source.heap_size_bytes(),
            Self::Other(description) => description.heap_size_bytes(),
        }
    }
}

impl SizeBytes for StoreInfo {
    #[inline]
    fn heap_size_bytes(&self) -> u64 {
        let Self {
            store_id,
            cloned_from: _,
            store_source,
            store_version,
        } = self;

        store_id.heap_size_bytes()
            + store_source.heap_size_bytes()
            + store_version.heap_size_bytes()
    }
}

impl SizeBytes for SetStoreInfo {
    #[inline]
    fn heap_size_bytes(&self) -> u64 {
        let Self { row_id, info } = self;

        row_id.heap_size_bytes() + info.heap_size_bytes()
    }
}

impl SizeBytes for BlueprintActivationCommand {
    #[inline]
    fn heap_size_bytes(&self) -> u64 {
        0
    }
}

impl SizeBytes for ArrowMsg {
    #[inline]
    fn heap_size_bytes(&self) -> u64 {
        let Self {
            chunk_id,
            batch,
            on_release: _,
        } = self;

        chunk_id.heap_size_bytes() + batch.heap_size_bytes()
    }
}

impl SizeBytes for LogMsg {
    #[inline]
    fn heap_size_bytes(&self) -> u64 {
        match self {
            Self::SetStoreInfo(set_store_info) => set_store_info.heap_size_bytes(),
            Self::ArrowMsg(store_id, arrow_msg) => {
                store_id.heap_size_bytes() + arrow_msg.heap_size_bytes()
            }
            Self::BlueprintActivationCommand(blueprint_activation_command) => {
                blueprint_activation_command.heap_size_bytes()
            }
        }
    }
}

// ----------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parse_python_version() {
        macro_rules! assert_parse_err {
            ($input:literal, $expected:pat) => {
                let actual = $input.parse::<PythonVersion>();

                assert!(
                    matches!(actual, Err($expected)),
                    "actual: {actual:?}, expected: {}",
                    stringify!($expected)
                );
            };
        }

        macro_rules! assert_parse_ok {
            ($input:literal, $expected:expr) => {
                let actual = $input.parse::<PythonVersion>().expect("failed to parse");
                assert_eq!(actual, $expected);
            };
        }

        assert_parse_err!("", PythonVersionParseError::MissingMajor);
        assert_parse_err!("3", PythonVersionParseError::MissingMinor);
        assert_parse_err!("3.", PythonVersionParseError::MissingMinor);
        assert_parse_err!("3.11", PythonVersionParseError::MissingPatch);
        assert_parse_err!("3.11.", PythonVersionParseError::MissingPatch);
        assert_parse_err!("a.11.0", PythonVersionParseError::InvalidMajor(_));
        assert_parse_err!("3.b.0", PythonVersionParseError::InvalidMinor(_));
        assert_parse_err!("3.11.c", PythonVersionParseError::InvalidPatch(_));
        assert_parse_ok!(
            "3.11.0",
            PythonVersion {
                major: 3,
                minor: 11,
                patch: 0,
                suffix: String::new(),
            }
        );
        assert_parse_ok!(
            "3.11.0a1",
            PythonVersion {
                major: 3,
                minor: 11,
                patch: 0,
                suffix: "a1".to_owned(),
            }
        );
    }
}