Another project
1use core::num::NonZeroU64;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
6#[serde(transparent)]
7pub struct WidgetId(NonZeroU64);
8
9const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
10const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
11
12impl WidgetId {
13 pub const ROOT: Self = match NonZeroU64::new(0xB0FE_B0FE_B0FE_B0FE) {
14 Some(n) => Self(n),
15 None => panic!("ROOT seed must be non-zero"),
16 };
17
18 #[must_use]
19 pub const fn from_raw(raw: NonZeroU64) -> Self {
20 Self(raw)
21 }
22
23 #[must_use]
24 pub const fn raw(self) -> NonZeroU64 {
25 self.0
26 }
27
28 #[must_use]
29 pub fn child(self, key: WidgetKey) -> Self {
30 self.mix(key.as_str(), 0)
31 }
32
33 #[must_use]
34 pub fn child_indexed(self, key: WidgetKey, index: u64) -> Self {
35 self.mix(key.as_str(), index)
36 }
37
38 #[must_use]
39 pub fn child_named(self, key: WidgetKey, name: &str) -> Self {
40 self.child(key).mix(name, 0)
41 }
42
43 fn mix(self, key: &str, index: u64) -> Self {
44 let parent = self.0.get().to_le_bytes();
45 let suffix = index.to_le_bytes();
46 let raw = parent
47 .iter()
48 .chain(key.as_bytes().iter())
49 .chain(suffix.iter())
50 .fold(FNV_OFFSET, |h, b| {
51 (h ^ u64::from(*b)).wrapping_mul(FNV_PRIME)
52 })
53 | 1;
54 match NonZeroU64::new(raw) {
55 Some(n) => Self(n),
56 None => panic!("`| 1` forces non-zero"),
57 }
58 }
59}
60
61#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
62#[serde(transparent)]
63pub struct WidgetKey(&'static str);
64
65impl WidgetKey {
66 #[must_use]
67 pub const fn new(s: &'static str) -> Self {
68 Self(s)
69 }
70
71 #[must_use]
72 pub const fn as_str(self) -> &'static str {
73 self.0
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::{WidgetId, WidgetKey};
80
81 const PANEL: WidgetKey = WidgetKey::new("panel");
82 const ITEM: WidgetKey = WidgetKey::new("item");
83
84 #[test]
85 fn root_is_stable() {
86 assert_eq!(WidgetId::ROOT, WidgetId::ROOT);
87 }
88
89 #[test]
90 fn child_is_deterministic() {
91 let a = WidgetId::ROOT.child(PANEL);
92 let b = WidgetId::ROOT.child(PANEL);
93 assert_eq!(a, b);
94 }
95
96 #[test]
97 fn child_distinguishes_keys() {
98 let a = WidgetId::ROOT.child(PANEL);
99 let b = WidgetId::ROOT.child(ITEM);
100 assert_ne!(a, b);
101 }
102
103 #[test]
104 fn child_indexed_distinguishes_indices() {
105 let a = WidgetId::ROOT.child_indexed(ITEM, 0);
106 let b = WidgetId::ROOT.child_indexed(ITEM, 1);
107 assert_ne!(a, b);
108 }
109
110 #[test]
111 fn child_path_depends_on_parent() {
112 let a = WidgetId::ROOT.child(PANEL).child(ITEM);
113 let b = WidgetId::ROOT.child(ITEM);
114 assert_ne!(a, b);
115 }
116
117 #[test]
118 fn child_zero_indexed_matches_child_unindexed() {
119 let a = WidgetId::ROOT.child(PANEL);
120 let b = WidgetId::ROOT.child_indexed(PANEL, 0);
121 assert_eq!(a, b);
122 }
123
124 #[test]
125 fn child_named_distinguishes_names() {
126 let a = WidgetId::ROOT.child_named(ITEM, "alpha");
127 let b = WidgetId::ROOT.child_named(ITEM, "beta");
128 assert_ne!(a, b);
129 }
130
131 #[test]
132 fn child_named_is_deterministic() {
133 let a = WidgetId::ROOT.child_named(ITEM, "alpha");
134 let b = WidgetId::ROOT.child_named(ITEM, "alpha");
135 assert_eq!(a, b);
136 }
137
138 #[test]
139 fn child_named_differs_from_child_indexed() {
140 let a = WidgetId::ROOT.child_named(ITEM, "alpha");
141 let b = WidgetId::ROOT.child_indexed(ITEM, 0);
142 assert_ne!(a, b);
143 }
144
145 #[test]
146 fn child_value_is_pinned_to_fnv() {
147 let id = WidgetId::ROOT.child(PANEL);
148 let raw = id.raw().get();
149 assert_eq!(raw & 1, 1, "low bit must be forced on");
150 assert_eq!(raw, 0x185e_529f_9def_b5c5, "FNV-1a hash must stay frozen");
151 let other = WidgetId::ROOT.child(ITEM).raw().get();
152 assert_ne!(raw, other);
153 }
154}