1use std::sync::Arc;
2
3use ahash::{HashMap, HashSet};
4use itertools::Itertools as _;
5
6use re_byte_size::SizeBytes;
7use re_string_interner::InternedString;
8
9use crate::{EntityPathPart, hash::Hash64};
10
11#[derive(Copy, Clone, Eq, PartialOrd, Ord)]
15pub struct EntityPathHash(Hash64);
16
17impl re_byte_size::SizeBytes for EntityPathHash {
18 #[inline]
19 fn heap_size_bytes(&self) -> u64 {
20 self.0.heap_size_bytes()
21 }
22}
23
24impl EntityPathHash {
25 pub const NONE: Self = Self(Hash64::ZERO);
27
28 #[inline]
30 pub fn from_u64(i: u64) -> Self {
31 Self(Hash64::from_u64(i))
32 }
33
34 #[inline]
35 pub fn hash64(&self) -> u64 {
36 self.0.hash64()
37 }
38
39 #[inline]
40 pub fn is_some(&self) -> bool {
41 *self != Self::NONE
42 }
43
44 #[inline]
45 pub fn is_none(&self) -> bool {
46 *self == Self::NONE
47 }
48}
49
50impl std::hash::Hash for EntityPathHash {
51 #[inline]
52 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
53 self.0.hash(state);
54 }
55}
56
57impl std::cmp::PartialEq for EntityPathHash {
58 #[inline]
59 fn eq(&self, other: &Self) -> bool {
60 self.0.eq(&other.0)
61 }
62}
63
64impl nohash_hasher::IsEnabled for EntityPathHash {}
65
66impl std::fmt::Debug for EntityPathHash {
67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 write!(f, "EntityPathHash({:016X})", self.hash64())
69 }
70}
71
72#[derive(Clone, Eq)]
100pub struct EntityPath {
101 hash: EntityPathHash,
103
104 parts: Arc<Vec<EntityPathPart>>,
107}
108
109impl EntityPath {
110 #[inline]
111 pub fn root() -> Self {
112 Self::from(vec![])
113 }
114
115 #[inline]
117 pub fn properties() -> Self {
118 Self::from(vec![EntityPathPart::properties()])
119 }
120
121 #[inline]
123 pub fn recording_properties() -> Self {
124 Self::from(vec![
125 EntityPathPart::properties(),
126 EntityPathPart::recording(),
127 ])
128 }
129
130 #[inline]
134 pub fn is_reserved(&self) -> bool {
135 self.iter()
136 .next()
137 .is_some_and(|part| part.unescaped_str().starts_with(RESERVED_NAMESPACE_PREFIX))
138 }
139
140 #[inline]
141 pub fn new(parts: Vec<EntityPathPart>) -> Self {
142 Self::from(parts)
143 }
144
145 pub fn from_file_path_as_single_string(file_path: &std::path::Path) -> Self {
150 Self::from_single_string(file_path.to_string_lossy().to_string())
151 }
152
153 pub fn from_file_path(file_path: &std::path::Path) -> Self {
157 use clean_path::Clean as _;
158 Self::new(
159 file_path
160 .clean()
161 .iter()
162 .map(|p| EntityPathPart::from(p.to_string_lossy().to_string()))
163 .collect(),
164 )
165 }
166
167 pub fn from_single_string(string: impl Into<InternedString>) -> Self {
171 Self::new(vec![EntityPathPart::new(string)])
172 }
173
174 #[inline]
175 pub fn iter(&self) -> impl DoubleEndedIterator<Item = &EntityPathPart> + ExactSizeIterator {
176 self.parts.iter()
177 }
178
179 #[inline]
180 pub fn last(&self) -> Option<&EntityPathPart> {
181 self.parts.last()
182 }
183
184 #[inline]
185 pub fn as_slice(&self) -> &[EntityPathPart] {
186 self.parts.as_slice()
187 }
188
189 #[inline]
190 pub fn to_vec(&self) -> Vec<EntityPathPart> {
191 self.parts.to_vec()
192 }
193
194 #[inline]
195 pub fn is_root(&self) -> bool {
196 self.parts.is_empty()
197 }
198
199 #[inline]
201 pub fn starts_with(&self, prefix: &Self) -> bool {
202 if self.hash == prefix.hash {
203 return true; }
205
206 prefix.len() <= self.len() && self.iter().zip(prefix.iter()).all(|(a, b)| a == b)
207 }
208
209 #[inline]
211 pub fn is_descendant_of(&self, other: &Self) -> bool {
212 other.len() < self.len() && self.iter().zip(other.iter()).all(|(a, b)| a == b)
213 }
214
215 #[inline]
217 pub fn is_child_of(&self, other: &Self) -> bool {
218 other.len() + 1 == self.len() && self.iter().zip(other.iter()).all(|(a, b)| a == b)
219 }
220
221 #[inline]
223 #[allow(clippy::len_without_is_empty)]
224 pub fn len(&self) -> usize {
225 self.parts.len()
226 }
227
228 #[inline]
229 pub fn hash(&self) -> EntityPathHash {
230 self.hash
231 }
232
233 #[inline]
235 pub fn hash64(&self) -> u64 {
236 self.hash.hash64()
237 }
238
239 #[must_use]
241 pub fn parent(&self) -> Option<Self> {
242 self.parts
243 .len()
244 .checked_sub(1)
245 .map(|n_minus_1| Self::new(self.parts[..n_minus_1].to_vec()))
246 }
247
248 pub fn join(&self, other: &Self) -> Self {
249 self.iter().chain(other.iter()).cloned().collect()
250 }
251
252 pub fn incremental_walk<'a>(
256 start: Option<&'_ Self>,
257 end: &'a Self,
258 ) -> impl Iterator<Item = Self> + 'a + use<'a> {
259 re_tracing::profile_function!();
260 if start.is_none_or(|start| end.is_descendant_of(start)) {
261 let first_ind = start.map_or(0, |start| start.len() + 1);
262 let parts = end.as_slice();
263 itertools::Either::Left((first_ind..=end.len()).map(|i| Self::from(&parts[0..i])))
264 } else {
265 itertools::Either::Right(std::iter::empty())
266 }
267 }
268
269 pub fn common_ancestor(&self, other: &Self) -> Self {
273 let mut common = Vec::new();
274 for (a, b) in self.iter().zip(other.iter()) {
275 if a == b {
276 common.push(a.clone());
277 } else {
278 break;
279 }
280 }
281 Self::new(common)
282 }
283
284 pub fn common_ancestor_of<'a>(mut entities: impl Iterator<Item = &'a Self>) -> Self {
286 let first = entities.next().cloned().unwrap_or(Self::root());
287 entities.fold(first, |acc, e| acc.common_ancestor(e))
288 }
289
290 pub fn short_names_with_disambiguation(
295 entities: impl IntoIterator<Item = Self>,
296 ) -> HashMap<Self, String> {
297 struct ShortenedEntity {
298 entity: EntityPath,
299
300 num_part: usize,
302 }
303
304 impl ShortenedEntity {
305 fn ui_string(&self) -> String {
306 if self.entity.parts.is_empty() {
307 return "/".to_owned();
308 }
309
310 self.entity
311 .iter()
312 .rev()
313 .take(self.num_part)
314 .rev()
315 .map(|part| part.ui_string())
316 .join("/")
317 }
318 }
319
320 let mut str_to_entities: HashMap<String, ShortenedEntity> = HashMap::default();
321 let mut known_bad_labels: HashSet<String> = HashSet::default();
322
323 for entity in entities {
324 let mut shortened = ShortenedEntity {
325 entity,
326 num_part: 1,
327 };
328
329 loop {
330 let new_label = shortened.ui_string();
331
332 if str_to_entities.contains_key(&new_label) || known_bad_labels.contains(&new_label)
333 {
334 known_bad_labels.insert(new_label.clone());
340
341 if let Some(mut existing_shortened) = str_to_entities.remove(&new_label) {
342 existing_shortened.num_part += 1;
343 str_to_entities.insert(existing_shortened.ui_string(), existing_shortened);
344 }
345
346 shortened.num_part += 1;
347 if shortened.ui_string() == new_label {
348 break;
351 }
352 } else {
353 break;
354 }
355 }
356
357 str_to_entities.insert(shortened.ui_string(), shortened);
358 }
359
360 str_to_entities
361 .into_iter()
362 .map(|(str, entity)| (entity.entity, str))
363 .collect()
364 }
365}
366
367impl SizeBytes for EntityPath {
368 #[inline]
369 fn heap_size_bytes(&self) -> u64 {
370 0 }
372}
373
374impl FromIterator<EntityPathPart> for EntityPath {
375 fn from_iter<T: IntoIterator<Item = EntityPathPart>>(parts: T) -> Self {
376 Self::new(parts.into_iter().collect())
377 }
378}
379
380impl From<Vec<EntityPathPart>> for EntityPath {
381 #[inline]
382 fn from(path: Vec<EntityPathPart>) -> Self {
383 Self {
384 hash: EntityPathHash(Hash64::hash(&path)),
385 parts: Arc::new(path),
386 }
387 }
388}
389
390impl From<&[EntityPathPart]> for EntityPath {
391 #[inline]
392 fn from(path: &[EntityPathPart]) -> Self {
393 Self::from(path.to_vec())
394 }
395}
396
397impl From<&str> for EntityPath {
398 #[inline]
399 fn from(path: &str) -> Self {
400 Self::parse_forgiving(path)
401 }
402}
403
404impl From<String> for EntityPath {
405 #[inline]
406 fn from(path: String) -> Self {
407 Self::parse_forgiving(&path)
408 }
409}
410
411impl From<EntityPath> for String {
412 #[inline]
413 fn from(path: EntityPath) -> Self {
414 path.to_string()
415 }
416}
417
418impl From<re_types_core::datatypes::EntityPath> for EntityPath {
419 #[inline]
420 fn from(value: re_types_core::datatypes::EntityPath) -> Self {
421 Self::parse_forgiving(&value.0)
422 }
423}
424
425impl From<&EntityPath> for re_types_core::datatypes::EntityPath {
426 #[inline]
427 fn from(value: &EntityPath) -> Self {
428 Self(value.to_string().into())
429 }
430}
431
432impl<Idx> std::ops::Index<Idx> for EntityPath
433where
434 Idx: std::slice::SliceIndex<[EntityPathPart]>,
435{
436 type Output = Idx::Output;
437
438 #[inline]
439 fn index(&self, index: Idx) -> &Self::Output {
440 &self.parts[index]
441 }
442}
443
444impl std::ops::Div<Self> for EntityPath {
447 type Output = Self;
448
449 #[inline]
450 fn div(self, other: Self) -> Self::Output {
451 self.join(&other)
452 }
453}
454
455impl std::ops::Div<Self> for &EntityPath {
456 type Output = EntityPath;
457
458 #[inline]
459 fn div(self, other: Self) -> Self::Output {
460 self.join(other)
461 }
462}
463
464impl std::ops::Div<EntityPathPart> for EntityPath {
465 type Output = Self;
466
467 #[inline]
468 fn div(self, other: EntityPathPart) -> Self::Output {
469 self.join(&Self::new(vec![other]))
470 }
471}
472
473impl std::ops::Div<EntityPathPart> for &EntityPath {
474 type Output = EntityPath;
475
476 #[inline]
477 fn div(self, other: EntityPathPart) -> Self::Output {
478 self.join(&EntityPath::new(vec![other]))
479 }
480}
481
482use re_types_core::Loggable;
485
486use super::entity_path_part::RESERVED_NAMESPACE_PREFIX;
487
488re_types_core::macros::impl_into_cow!(EntityPath);
489
490impl Loggable for EntityPath {
491 #[inline]
492 fn arrow_datatype() -> arrow::datatypes::DataType {
493 re_types_core::datatypes::Utf8::arrow_datatype()
494 }
495
496 fn to_arrow_opt<'a>(
497 _data: impl IntoIterator<Item = Option<impl Into<std::borrow::Cow<'a, Self>>>>,
498 ) -> re_types_core::SerializationResult<arrow::array::ArrayRef>
499 where
500 Self: 'a,
501 {
502 Err(re_types_core::SerializationError::not_implemented(
503 "rerun.controls.EntityPath",
504 "EntityPaths are never nullable, use `to_arrow()` instead",
505 ))
506 }
507
508 #[inline]
509 fn to_arrow<'a>(
510 data: impl IntoIterator<Item = impl Into<std::borrow::Cow<'a, Self>>>,
511 ) -> re_types_core::SerializationResult<arrow::array::ArrayRef>
512 where
513 Self: 'a,
514 {
515 re_types_core::datatypes::Utf8::to_arrow(
516 data.into_iter()
517 .map(Into::into)
518 .map(|ent_path| re_types_core::datatypes::Utf8(ent_path.to_string().into())),
519 )
520 }
521
522 fn from_arrow(
523 array: &dyn ::arrow::array::Array,
524 ) -> re_types_core::DeserializationResult<Vec<Self>> {
525 Ok(re_types_core::datatypes::Utf8::from_arrow(array)?
526 .into_iter()
527 .map(|utf8| Self::from(utf8.to_string()))
528 .collect())
529 }
530}
531
532#[cfg(feature = "serde")]
535impl serde::Serialize for EntityPath {
536 #[inline]
537 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
538 self.parts.serialize(serializer)
539 }
540}
541
542#[cfg(feature = "serde")]
543impl<'de> serde::Deserialize<'de> for EntityPath {
544 #[inline]
545 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
546 let parts = Vec::<EntityPathPart>::deserialize(deserializer)?;
547 Ok(Self::new(parts))
548 }
549}
550
551impl std::cmp::PartialEq for EntityPath {
554 #[inline]
555 fn eq(&self, other: &Self) -> bool {
556 self.hash == other.hash }
558}
559
560impl std::hash::Hash for EntityPath {
561 #[inline]
562 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
563 self.hash.hash(state);
564 }
565}
566
567impl nohash_hasher::IsEnabled for EntityPath {}
568
569impl std::cmp::Ord for EntityPath {
572 #[inline]
573 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
574 self.parts.cmp(&other.parts)
575 }
576}
577
578impl std::cmp::PartialOrd for EntityPath {
579 #[inline]
580 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
581 Some(self.parts.cmp(&other.parts))
582 }
583}
584
585impl std::fmt::Debug for EntityPath {
588 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
589 write!(f, "{self}")
591 }
592}
593
594impl std::fmt::Display for EntityPath {
595 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
596 use std::fmt::Write as _;
597
598 if self.is_root() {
599 f.write_char('/')
600 } else {
601 for comp in self.iter() {
603 f.write_char('/')?;
604 comp.escaped_string().fmt(f)?;
605 }
606 Ok(())
607 }
608 }
609}
610
611#[cfg(test)]
614mod tests {
615 use super::*;
616
617 #[test]
618 fn test_root() {
619 assert_eq!(EntityPath::root(), EntityPath::from("/"));
620 }
621
622 #[test]
623 fn test_properties() {
624 let path = EntityPath::properties();
625 assert_eq!(path, EntityPath::from("/__properties"));
626 assert!(path.is_reserved());
627 }
628
629 #[test]
630 fn test_recording_properties() {
631 let path = EntityPath::recording_properties();
632 assert_eq!(path, EntityPath::from("/__properties/recording"),);
633 assert!(path.is_reserved());
634 }
635
636 #[test]
637 fn test_incremental_walk() {
638 assert_eq!(
639 EntityPath::incremental_walk(None, &EntityPath::root()).collect::<Vec<_>>(),
640 vec![EntityPath::root()]
641 );
642 assert_eq!(
643 EntityPath::incremental_walk(Some(&EntityPath::root()), &EntityPath::root())
644 .collect::<Vec<_>>(),
645 vec![]
646 );
647 assert_eq!(
648 EntityPath::incremental_walk(None, &EntityPath::from("foo")).collect::<Vec<_>>(),
649 vec![EntityPath::root(), EntityPath::from("foo")]
650 );
651 assert_eq!(
652 EntityPath::incremental_walk(Some(&EntityPath::root()), &EntityPath::from("foo"))
653 .collect::<Vec<_>>(),
654 vec![EntityPath::from("foo")]
655 );
656 assert_eq!(
657 EntityPath::incremental_walk(None, &EntityPath::from("foo/bar")).collect::<Vec<_>>(),
658 vec![
659 EntityPath::root(),
660 EntityPath::from("foo"),
661 EntityPath::from("foo/bar")
662 ]
663 );
664 assert_eq!(
665 EntityPath::incremental_walk(
666 Some(&EntityPath::from("foo")),
667 &EntityPath::from("foo/bar/baz")
668 )
669 .collect::<Vec<_>>(),
670 vec![EntityPath::from("foo/bar"), EntityPath::from("foo/bar/baz")]
671 );
672 }
673
674 #[test]
675 fn test_common_ancestor() {
676 assert_eq!(
677 EntityPath::from("foo/bar").common_ancestor(&EntityPath::from("foo/bar")),
678 EntityPath::from("foo/bar")
679 );
680 assert_eq!(
681 EntityPath::from("foo/bar").common_ancestor(&EntityPath::from("foo/bar/baz")),
682 EntityPath::from("foo/bar")
683 );
684 assert_eq!(
685 EntityPath::from("foo/bar/baz").common_ancestor(&EntityPath::from("foo/bar")),
686 EntityPath::from("foo/bar")
687 );
688 assert_eq!(
689 EntityPath::from("foo/bar/mario").common_ancestor(&EntityPath::from("foo/bar/luigi")),
690 EntityPath::from("foo/bar")
691 );
692 assert_eq!(
693 EntityPath::from("mario/bowser").common_ancestor(&EntityPath::from("luigi/bowser")),
694 EntityPath::root()
695 );
696 }
697
698 #[test]
699 fn test_short_names_with_disambiguation() {
700 fn run_test(entities: &[(&str, &str)]) {
701 let paths = entities
702 .iter()
703 .map(|(entity, _)| EntityPath::from(*entity))
704 .collect_vec();
705 let result = EntityPath::short_names_with_disambiguation(paths.clone());
706
707 for (path, shortened) in paths.iter().zip(entities.iter().map(|e| e.1)) {
708 assert_eq!(result[path], shortened);
709 }
710 }
711
712 run_test(&[("foo/bar", "bar"), ("qaz/bor", "bor")]);
715
716 run_test(&[
717 ("hello/world", "world"),
718 ("bim/foo/bar", "foo/bar"),
719 ("bim/qaz/bar", "qaz/bar"),
720 ("a/x/y/z", "a/x/y/z"),
721 ("b/x/y/z", "b/x/y/z"),
722 ("c/d/y/z", "d/y/z"),
723 ]);
724
725 run_test(&[("/", "/"), ("/a", "a")]);
726
727 run_test(&[("/", "/"), ("/", "/")]);
729 run_test(&[("a/b", "a/b"), ("a/b", "a/b")]);
730 }
731}