Skip to main content

re_viewer_context/
blueprint_id.rs

1use std::hash::BuildHasher as _;
2
3use re_log_types::{EntityPath, EntityPathPart};
4
5pub trait BlueprintIdRegistry {
6    fn registry_name() -> &'static str;
7    fn registry_path() -> &'static EntityPath;
8}
9
10/// A unique id for a type of Blueprint contents.
11#[derive(
12    Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Deserialize, serde::Serialize,
13)]
14pub struct BlueprintId<T: BlueprintIdRegistry> {
15    id: uuid::Uuid,
16    #[serde(skip)]
17    _registry: std::marker::PhantomData<T>,
18}
19
20impl<T: BlueprintIdRegistry> re_byte_size::SizeBytes for BlueprintId<T> {
21    fn heap_size_bytes(&self) -> u64 {
22        0
23    }
24
25    fn is_pod() -> bool {
26        true
27    }
28}
29
30impl<T: BlueprintIdRegistry> BlueprintId<T> {
31    pub fn invalid() -> Self {
32        Self {
33            id: uuid::Uuid::nil(),
34            _registry: std::marker::PhantomData,
35        }
36    }
37
38    pub fn random() -> Self {
39        Self {
40            id: uuid::Uuid::new_v4(),
41            _registry: std::marker::PhantomData,
42        }
43    }
44
45    pub const fn from_bytes(bytes: uuid::Bytes) -> Self {
46        Self {
47            id: uuid::Uuid::from_bytes(bytes),
48            _registry: std::marker::PhantomData,
49        }
50    }
51
52    pub fn from_entity_path(path: &EntityPath) -> Self {
53        if !path.is_child_of(T::registry_path()) {
54            return Self::invalid();
55        }
56
57        path.last()
58            .and_then(|last| uuid::Uuid::parse_str(last.unescaped_str()).ok())
59            .map_or_else(Self::invalid, |id| Self {
60                id,
61                _registry: std::marker::PhantomData,
62            })
63    }
64
65    pub fn hashed_from_str(s: &str) -> Self {
66        use std::hash::{Hash as _, Hasher as _};
67
68        let salt1: u64 = 0x307b_e149_0a3a_5552;
69        let salt2: u64 = 0x6651_522f_f510_13a4;
70
71        let hash1 = {
72            let mut hasher = ahash::RandomState::with_seeds(1, 2, 3, 4).build_hasher();
73            salt1.hash(&mut hasher);
74            s.hash(&mut hasher);
75            hasher.finish()
76        };
77
78        let hash2 = {
79            let mut hasher = ahash::RandomState::with_seeds(1, 2, 3, 4).build_hasher();
80            salt2.hash(&mut hasher);
81            s.hash(&mut hasher);
82            hasher.finish()
83        };
84
85        let uuid = uuid::Uuid::from_u64_pair(hash1, hash2);
86
87        uuid.into()
88    }
89
90    pub fn gpu_readback_id(self) -> re_renderer::GpuReadbackIdentifier {
91        re_log_types::hash::Hash64::hash(self.id).hash64()
92    }
93
94    #[inline]
95    pub fn as_entity_path(&self) -> EntityPath {
96        T::registry_path()
97            .iter()
98            .cloned()
99            .chain(std::iter::once(EntityPathPart::new(self.id.to_string())))
100            .collect()
101    }
102
103    #[inline]
104    pub fn registry() -> &'static EntityPath {
105        T::registry_path()
106    }
107
108    #[inline]
109    pub fn registry_part() -> &'static EntityPathPart {
110        &T::registry_path().as_slice()[0]
111    }
112
113    #[inline]
114    pub fn uuid(&self) -> uuid::Uuid {
115        self.id
116    }
117
118    #[inline]
119    pub fn hash(&self) -> u64 {
120        re_log_types::hash::Hash64::hash(self.id).hash64()
121    }
122}
123
124impl<T: BlueprintIdRegistry> From<uuid::Uuid> for BlueprintId<T> {
125    #[inline]
126    fn from(id: uuid::Uuid) -> Self {
127        Self {
128            id,
129            _registry: std::marker::PhantomData,
130        }
131    }
132}
133
134impl<T: BlueprintIdRegistry> From<re_sdk_types::datatypes::Uuid> for BlueprintId<T> {
135    #[inline]
136    fn from(id: re_sdk_types::datatypes::Uuid) -> Self {
137        Self {
138            id: id.into(),
139            _registry: std::marker::PhantomData,
140        }
141    }
142}
143
144impl<T: BlueprintIdRegistry> From<BlueprintId<T>> for re_sdk_types::datatypes::Uuid {
145    #[inline]
146    fn from(id: BlueprintId<T>) -> Self {
147        id.id.into()
148    }
149}
150
151impl<T: BlueprintIdRegistry> std::fmt::Display for BlueprintId<T> {
152    #[inline]
153    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154        write!(f, "{}({})", T::registry_name(), self.id.simple())
155    }
156}
157
158impl<T: BlueprintIdRegistry> std::fmt::Debug for BlueprintId<T> {
159    #[inline]
160    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161        write!(f, "{}({})", T::registry_name(), self.id.simple())
162    }
163}
164
165// ----------------------------------------------------------------------------
166/// Helper to define a new [`BlueprintId`] type.
167macro_rules! define_blueprint_id_type {
168    ($type:ident, $registry:ident, $registry_name:expr) => {
169        #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
170        pub struct $registry;
171
172        impl $registry {
173            const REGISTRY: &'static str = $registry_name;
174        }
175
176        impl BlueprintIdRegistry for $registry {
177            fn registry_name() -> &'static str {
178                stringify!($type)
179            }
180
181            fn registry_path() -> &'static EntityPath {
182                static REGISTRY_PATH: std::sync::LazyLock<EntityPath> =
183                    std::sync::LazyLock::new(|| $registry::REGISTRY.into());
184                &REGISTRY_PATH
185            }
186        }
187
188        pub type $type = BlueprintId<$registry>;
189    };
190}
191
192// ----------------------------------------------------------------------------
193// Definitions for the different [`BlueprintId`] types.
194define_blueprint_id_type!(ViewId, ViewIdRegistry, "view");
195define_blueprint_id_type!(ContainerId, ContainerIdRegistry, "container");
196
197// ----------------------------------------------------------------------------
198// Builtin `ViewId`s.
199
200/// A dummy view for shared blueprint data between views.
201///
202/// This is currently not exposed for any api to interact with, but there is technically nothing
203/// stopping us from manually adding it.
204pub const GLOBAL_VIEW_ID: ViewId = ViewId::from_bytes([
205    0x5C, 0x0D, 0xCA, 0x6A, 0xE6, 0x3F, 0x9C, 0xF7, 0xF6, 0x57, 0x26, 0x02, 0x59, 0x04, 0x74, 0xCC,
206]);
207
208// ----------------------------------------------------------------------------
209// Tests
210#[cfg(test)]
211mod tests {
212    use super::*;
213
214    #[test]
215    fn test_blueprint_id() {
216        let id = ViewId::random();
217        let path = id.as_entity_path();
218        assert!(path.is_child_of(&EntityPath::parse_forgiving("view/")));
219
220        let id = ContainerId::random();
221        let path = id.as_entity_path();
222        assert!(path.is_child_of(&EntityPath::parse_forgiving("container/")));
223
224        let roundtrip = ContainerId::from_entity_path(&id.as_entity_path());
225        assert_eq!(roundtrip, id);
226
227        let crossed = ContainerId::from_entity_path(&ViewId::random().as_entity_path());
228        assert_eq!(crossed, ContainerId::invalid());
229    }
230}