1use std::{
8 sync::{
9 OnceLock,
10 atomic::{AtomicU64, Ordering},
11 },
12 time::{SystemTime, UNIX_EPOCH},
13};
14
15use crate::id::Symbol;
16
17#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
23pub enum Ref {
24 Symbol(Symbol),
26 Content(ContentId),
28 Handle(HandleId),
30 Coord(Coordinate),
32}
33
34#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
36pub struct ContentId {
37 pub algorithm: Symbol,
39 pub bytes: [u8; 32],
41}
42
43impl ContentId {
44 pub fn from_bytes(algorithm: Symbol, bytes: [u8; 32]) -> Self {
46 Self { algorithm, bytes }
47 }
48}
49
50#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
52pub struct HandleId(pub u128);
53
54impl HandleId {
55 pub fn fresh() -> Self {
57 let salt = u128::from(*PROCESS_SALT.get_or_init(make_process_salt));
58 let counter = u128::from(NEXT_HANDLE.fetch_add(1, Ordering::Relaxed));
59 Self((salt << 64) | counter)
60 }
61}
62
63#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
65pub struct Coordinate {
66 pub space: Symbol,
68 pub ordinal: ContentId,
70}
71
72static NEXT_HANDLE: AtomicU64 = AtomicU64::new(1);
73static PROCESS_SALT: OnceLock<u64> = OnceLock::new();
74
75fn make_process_salt() -> u64 {
76 let nanos = SystemTime::now()
77 .duration_since(UNIX_EPOCH)
78 .map_or(0, |duration| duration.as_nanos());
79 let folded = (nanos ^ (nanos >> 64)) & u128::from(u64::MAX);
80 let time_salt = u64::try_from(folded).unwrap_or(0);
81 time_salt ^ (u64::from(std::process::id()) << 32)
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 fn content_id(byte: u8) -> ContentId {
89 ContentId::from_bytes(Symbol::qualified("core", "sha256"), [byte; 32])
90 }
91
92 #[test]
93 fn ref_id_equal_symbols_compare_equal() {
94 let left = Ref::Symbol(Symbol::qualified("core", "Bool"));
95 let right = Ref::Symbol(Symbol::qualified("core", "Bool"));
96
97 assert_eq!(left, right);
98 }
99
100 #[test]
101 fn ref_id_equal_content_ids_compare_equal() {
102 let left = content_id(1);
103 let right = content_id(1);
104
105 assert_eq!(left, right);
106 }
107
108 #[test]
109 fn ref_id_equal_coordinates_compare_equal() {
110 let left = Coordinate {
111 space: Symbol::qualified("rank", "natural"),
112 ordinal: content_id(2),
113 };
114 let right = Coordinate {
115 space: Symbol::qualified("rank", "natural"),
116 ordinal: content_id(2),
117 };
118
119 assert_eq!(left, right);
120 }
121
122 #[test]
123 fn ref_id_handle_never_equals_content_id() {
124 let handle = Ref::Handle(HandleId::fresh());
125 let content = Ref::Content(content_id(3));
126
127 assert_ne!(handle, content);
128 }
129
130 #[test]
131 fn ref_id_fresh_handles_are_distinct() {
132 assert_ne!(HandleId::fresh(), HandleId::fresh());
133 }
134}