1use core::num::NonZeroU64;
4
5use crate::{AsIdSalt, IdSalt};
6
7pub trait AsId: core::hash::Hash + core::fmt::Debug {}
12
13impl<T: core::hash::Hash + core::fmt::Debug> AsId for T {}
14
15#[derive(Clone, Copy, Hash, Eq, PartialEq)]
43#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
44pub struct Id(NonZeroU64);
45
46impl nohash_hasher::IsEnabled for Id {}
47
48impl Id {
49 pub const NULL: Self = Self(NonZeroU64::MAX);
55
56 #[inline]
58 const fn from_hash(hash: u64) -> Self {
59 if let Some(nonzero) = NonZeroU64::new(hash) {
60 Self(nonzero)
61 } else {
62 Self(NonZeroU64::MIN) }
64 }
65
66 pub fn new(source: impl AsId) -> Self {
68 let id = Self::from_hash(ahash::RandomState::with_seeds(1, 2, 3, 4).hash_one(&source));
69
70 #[cfg(debug_assertions)]
71 id_source::insert_root(id, &source);
72
73 id
74 }
75
76 pub fn with(self, salt: impl AsIdSalt) -> Self {
78 use core::hash::{BuildHasher as _, Hasher as _};
79 let mut hasher = ahash::RandomState::with_seeds(1, 2, 3, 4).build_hasher();
80 hasher.write_u64(self.value());
81 hasher.write_u64(IdSalt::new(&salt).value());
82 let id = Self::from_hash(hasher.finish());
83
84 #[cfg(debug_assertions)]
85 id_source::insert_child(id, self, &salt);
86
87 id
88 }
89
90 pub fn short_debug_format(&self) -> String {
92 format!("{:04X}", self.value() as u16)
93 }
94
95 #[inline(always)]
99 pub fn value(&self) -> u64 {
100 self.0.get()
101 }
102
103 pub fn accesskit_id(&self) -> accesskit::NodeId {
104 self.value().into()
105 }
106
107 #[doc(hidden)]
121 #[expect(unsafe_code)]
122 pub unsafe fn from_high_entropy_bits(value: u64) -> Self {
123 Self(NonZeroU64::new(value).expect("Id must be non-zero."))
124 }
125}
126
127impl core::fmt::Debug for Id {
128 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
129 if *self == Self::NULL {
130 return write!(f, "Id::NULL");
131 }
132 #[cfg(debug_assertions)]
133 if let Some(source) = id_source::get(*self) {
134 return f.write_str(&source);
135 }
136 write!(f, "id_{:04X}", self.value() as u16)
137 }
138}
139
140impl From<&'static str> for Id {
144 #[inline]
145 fn from(string: &'static str) -> Self {
146 Self::new(string)
147 }
148}
149
150impl From<String> for Id {
151 #[inline]
152 fn from(string: String) -> Self {
153 Self::new(string)
154 }
155}
156
157pub type IdSet = nohash_hasher::IntSet<Id>;
161
162pub type IdMap<V> = nohash_hasher::IntMap<Id, V>;
164
165#[cfg(debug_assertions)]
172mod id_source {
173 use super::{AsId, AsIdSalt, Id, IdMap};
174 use epaint::mutex::RwLock;
175 use std::sync::LazyLock;
176
177 static SOURCE_MAP: LazyLock<RwLock<IdMap<String>>> = LazyLock::new(RwLock::default);
178
179 pub(super) fn insert_root(id: Id, source: &impl AsId) {
180 if SOURCE_MAP.read().contains_key(&id) {
181 return;
182 }
183 let formatted = format!("Id::new({source:?})");
185 SOURCE_MAP.write().insert(id, formatted);
186 }
187
188 pub(super) fn insert_child(id: Id, parent: Id, salt: &impl AsIdSalt) {
189 if SOURCE_MAP.read().contains_key(&id) {
190 return;
191 }
192 let cached_parent_repr = SOURCE_MAP.read().get(&parent).cloned();
195 let parent_repr = cached_parent_repr.unwrap_or_else(|| format!("{parent:?}"));
196 let formatted = format!("{parent_repr}.with({salt:?})");
197 SOURCE_MAP.write().insert(id, formatted);
198 }
199
200 pub(super) fn get(id: Id) -> Option<String> {
201 SOURCE_MAP.read().get(&id).cloned()
202 }
203}
204
205#[test]
206fn id_size() {
207 assert_eq!(core::mem::size_of::<Id>(), 8);
208 assert_eq!(core::mem::size_of::<Option<Id>>(), 8);
209}
210
211#[cfg(test)]
212#[cfg(debug_assertions)]
213mod debug_format_tests {
214 use crate::IdSalt;
215
216 use super::Id;
217
218 #[test]
219 fn root_string() {
220 let id = Id::new("foo");
221 assert_eq!(format!("{id:?}"), r#"Id::new("foo")"#);
222 }
223
224 #[test]
225 fn root_integer() {
226 let id = Id::new(42_i32);
227 assert_eq!(format!("{id:?}"), "Id::new(42)");
228 }
229
230 #[test]
231 fn root_id_salt() {
232 let id = Id::new(IdSalt::new("foo"));
233 assert_eq!(format!("{id:?}"), r#"Id::new(IdSalt::new("foo"))"#);
234 }
235
236 #[test]
237 fn with_one_child() {
238 let id = Id::new("parent").with("child");
239 assert_eq!(format!("{id:?}"), r#"Id::new("parent").with("child")"#);
240 }
241
242 #[test]
243 fn with_chain() {
244 let id = Id::new("a").with("b").with("c").with(7_i32);
245 assert_eq!(
246 format!("{id:?}"),
247 r#"Id::new("a").with("b").with("c").with(7)"#
248 );
249 }
250
251 #[test]
252 fn nested_id_as_source() {
253 let inner = Id::new("foo");
254 let outer = Id::new(inner);
255 assert_eq!(format!("{outer:?}"), r#"Id::new(Id::new("foo"))"#);
256 }
257
258 #[test]
259 fn null_prints_as_null() {
260 assert_eq!(format!("{:?}", Id::NULL), "Id::NULL");
261 }
262
263 #[test]
264 fn null_as_parent() {
265 let id = Id::NULL.with("foo");
266 assert_eq!(format!("{id:?}"), r#"Id::NULL.with("foo")"#);
267 }
268}