1use core::num::NonZeroU64;
2
3pub trait AsIdSalt: core::hash::Hash + core::fmt::Debug {}
8
9impl<T: core::hash::Hash + core::fmt::Debug> AsIdSalt for T {}
10
11#[derive(Clone, Copy, Hash, Eq, PartialEq)]
25#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
26pub struct IdSalt(NonZeroU64);
27
28impl nohash_hasher::IsEnabled for IdSalt {}
29
30impl IdSalt {
31 pub fn new(source: impl AsIdSalt) -> Self {
33 let id_salt = Self::from_hash(ahash::RandomState::with_seeds(5, 6, 7, 8).hash_one(&source));
34
35 #[cfg(debug_assertions)]
36 id_salt_source::maybe_insert(id_salt, &source);
37
38 id_salt
39 }
40
41 #[inline]
43 const fn from_hash(hash: u64) -> Self {
44 if let Some(nonzero) = NonZeroU64::new(hash) {
45 Self(nonzero)
46 } else {
47 Self(NonZeroU64::MIN) }
49 }
50
51 #[inline(always)]
55 pub fn value(&self) -> u64 {
56 self.0.get()
57 }
58}
59
60impl core::fmt::Debug for IdSalt {
61 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
62 #[cfg(debug_assertions)]
63 if let Some(source) = id_salt_source::get(*self) {
64 return write!(f, "IdSalt::new({source})");
65 }
66 write!(f, "salt_{:04X}", self.value() as u16)
67 }
68}
69
70#[cfg(debug_assertions)]
76mod id_salt_source {
77 use super::{AsIdSalt, IdSalt};
78 use epaint::mutex::RwLock;
79 use nohash_hasher::IntMap;
80 use std::sync::LazyLock;
81
82 static SOURCE_MAP: LazyLock<RwLock<IntMap<IdSalt, String>>> = LazyLock::new(RwLock::default);
83
84 pub(super) fn maybe_insert(id_salt: IdSalt, source: &impl AsIdSalt) {
85 if !SOURCE_MAP.read().contains_key(&id_salt) {
86 let formatted = format!("{source:?}");
87 SOURCE_MAP.write().insert(id_salt, formatted);
88 }
89 }
90
91 pub(super) fn get(id_salt: IdSalt) -> Option<String> {
92 SOURCE_MAP.read().get(&id_salt).cloned()
93 }
94}
95
96#[cfg(test)]
97#[cfg(debug_assertions)]
98mod tests {
99 use super::IdSalt;
100
101 #[test]
102 fn debug_format_string_source() {
103 let salt = IdSalt::new("foo");
104 assert_eq!(format!("{salt:?}"), r#"IdSalt::new("foo")"#);
105 }
106
107 #[test]
108 fn debug_format_integer_source() {
109 let salt = IdSalt::new(42_i32);
110 assert_eq!(format!("{salt:?}"), "IdSalt::new(42)");
111 }
112
113 #[test]
114 fn debug_format_nested_salt() {
115 let inner = IdSalt::new("foo");
116 let outer = IdSalt::new(inner);
117 assert_eq!(format!("{outer:?}"), r#"IdSalt::new(IdSalt::new("foo"))"#);
118 }
119
120 #[test]
121 fn debug_format_triple_nested_salt() {
122 let a = IdSalt::new("foo");
123 let b = IdSalt::new(a);
124 let c = IdSalt::new(b);
125 assert_eq!(
126 format!("{c:?}"),
127 r#"IdSalt::new(IdSalt::new(IdSalt::new("foo")))"#
128 );
129 }
130
131 #[test]
132 fn debug_format_tuple_source() {
133 let salt = IdSalt::new(("foo", 7_i32));
134 assert_eq!(format!("{salt:?}"), r#"IdSalt::new(("foo", 7))"#);
135 }
136}