1use std::{
2 borrow::Cow,
3 fmt::{self, Debug, Display, LowerHex, UpperHex},
4 num::{NonZeroU64, ParseIntError},
5 str::FromStr,
6 sync::Mutex,
7 time::{Duration, SystemTime},
8};
9
10use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
11
12#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
15#[repr(transparent)]
16pub struct AssetId(NonZeroU64);
17
18impl Serialize for AssetId {
19 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
20 where
21 S: Serializer,
22 {
23 use std::io::Write;
24
25 if serializer.is_human_readable() {
26 let mut hex = [0u8; 16];
27 write!(std::io::Cursor::new(&mut hex[..]), "{:016x}", self.0).expect("Must fit");
28 let hex = std::str::from_utf8(&hex).expect("Must be UTF-8");
29 serializer.serialize_str(hex)
30 } else {
31 serializer.serialize_u64(self.0.get())
32 }
33 }
34}
35
36impl<'de> Deserialize<'de> for AssetId {
37 fn deserialize<D>(deserializer: D) -> Result<AssetId, D::Error>
38 where
39 D: Deserializer<'de>,
40 {
41 if deserializer.is_human_readable() {
42 let hex = Cow::<str>::deserialize(deserializer)?;
43 hex.parse().map_err(Error::custom)
44 } else {
45 let value = NonZeroU64::deserialize(deserializer)?;
46 Ok(AssetId(value))
47 }
48 }
49}
50
51#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
52pub enum ParseAssetIdError {
53 #[error(transparent)]
54 ParseIntError(#[from] ParseIntError),
55
56 #[error("AssetId cannot be zero")]
57 ZeroId,
58}
59
60impl FromStr for AssetId {
61 type Err = ParseAssetIdError;
62 fn from_str(s: &str) -> Result<Self, ParseAssetIdError> {
63 let value = u64::from_str_radix(s, 16)?;
64 match NonZeroU64::new(value) {
65 None => Err(ParseAssetIdError::ZeroId),
66 Some(value) => Ok(AssetId(value)),
67 }
68 }
69}
70
71#[derive(Debug)]
72pub struct ZeroIDError;
73
74impl AssetId {
75 pub fn new(value: u64) -> Option<Self> {
76 NonZeroU64::new(value).map(AssetId)
77 }
78
79 pub fn value(&self) -> NonZeroU64 {
80 self.0
81 }
82}
83
84impl From<NonZeroU64> for AssetId {
85 fn from(value: NonZeroU64) -> Self {
86 AssetId(value)
87 }
88}
89
90impl TryFrom<u64> for AssetId {
91 type Error = ZeroIDError;
92
93 fn try_from(value: u64) -> Result<Self, ZeroIDError> {
94 match NonZeroU64::try_from(value) {
95 Ok(value) => Ok(AssetId(value)),
96 Err(_) => Err(ZeroIDError),
97 }
98 }
99}
100
101impl Debug for AssetId {
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 LowerHex::fmt(&self.0.get(), f)
104 }
105}
106
107impl UpperHex for AssetId {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 UpperHex::fmt(&self.0.get(), f)
110 }
111}
112
113impl LowerHex for AssetId {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 LowerHex::fmt(&self.0.get(), f)
116 }
117}
118
119impl Display for AssetId {
120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 LowerHex::fmt(&self.0.get(), f)
122 }
123}
124
125pub struct AssetIdContext {
129 epoch: SystemTime,
131
132 node: u16,
134
135 data: Mutex<ContextSyncData>,
137}
138
139struct ContextSyncData {
140 counter: u16,
141 last_timestamp: u64,
142}
143
144impl AssetIdContext {
145 pub fn new(epoch: SystemTime, node: u16) -> Self {
146 assert!(epoch <= SystemTime::now());
147
148 AssetIdContext {
149 epoch,
150 node: node & 0x3FF,
151 data: Mutex::new(ContextSyncData {
152 counter: 0,
153 last_timestamp: 0,
154 }),
155 }
156 }
157
158 pub fn generate(&self) -> AssetId {
160 loop {
161 let timestamp = SystemTime::now()
162 .duration_since(self.epoch)
163 .expect("Epoch must be in relatively distant past")
164 .as_millis() as u64;
165
166 let mut guard = self.data.lock().unwrap();
167
168 if guard.last_timestamp > timestamp {
169 panic!("Time goes backwards");
170 }
171
172 if guard.last_timestamp == timestamp {
173 if guard.counter == 0xFFF {
174 std::thread::sleep(Duration::from_millis(1));
176 continue;
177 }
178
179 guard.counter += 1;
180 } else {
181 guard.counter = 1;
182 }
183
184 let counter = guard.counter as u64;
185
186 let node = self.node as u64;
187 let id = (timestamp << 22) | (node << 12) | counter;
188 let id = NonZeroU64::new(id.wrapping_mul(ID_MUL)).expect("Zero id cannot be generated");
189 return AssetId(id);
190 }
191 }
192}
193
194const ID_MUL: u64 = 0xF89A4B715E26C30D;
198
199#[allow(unused)]
200const GUARANTEE_LEAST_SIGNIFICANT_BIT_OF_ID_MUL_IS_SET: [(); (ID_MUL & 1) as usize] = [()];