1use crate::{Error, Result};
6use std::fmt;
7
8#[derive(Clone, Copy, PartialEq, Eq, Hash)]
10pub struct UnityGuid([u8; 16]);
11
12impl UnityGuid {
13 pub fn new() -> Self {
15 let uuid = uuid::Uuid::new_v4();
16 Self(*uuid.as_bytes())
17 }
18
19 pub fn from_hex(s: &str) -> Result<Self> {
21 if s.len() != 32 {
22 return Err(Error::InvalidGuid(format!(
23 "GUID must be 32 hex characters, got {}",
24 s.len()
25 )));
26 }
27
28 let mut bytes = [0u8; 16];
29 for (i, chunk) in s.as_bytes().chunks(2).enumerate() {
30 let hex_str = std::str::from_utf8(chunk)
31 .map_err(|_| Error::InvalidGuid("Invalid UTF-8".to_string()))?;
32 bytes[i] = u8::from_str_radix(hex_str, 16)
33 .map_err(|_| Error::InvalidGuid(format!("Invalid hex: {}", hex_str)))?;
34 }
35
36 Ok(Self(bytes))
37 }
38
39 pub fn from_path(path: &str) -> Self {
41 use std::collections::hash_map::DefaultHasher;
42 use std::hash::{Hash, Hasher};
43
44 let mut hasher = DefaultHasher::new();
45 path.hash(&mut hasher);
46 let hash1 = hasher.finish();
47
48 hash1.hash(&mut hasher);
50 let hash2 = hasher.finish();
51
52 let mut bytes = [0u8; 16];
53 bytes[0..8].copy_from_slice(&hash1.to_le_bytes());
54 bytes[8..16].copy_from_slice(&hash2.to_le_bytes());
55
56 Self(bytes)
57 }
58
59 pub fn to_hex(&self) -> String {
61 self.0.iter().map(|b| format!("{:02x}", b)).collect()
62 }
63
64 pub fn as_bytes(&self) -> &[u8; 16] {
66 &self.0
67 }
68}
69
70impl Default for UnityGuid {
71 fn default() -> Self {
72 Self::new()
73 }
74}
75
76impl fmt::Display for UnityGuid {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 write!(f, "{}", self.to_hex())
79 }
80}
81
82impl fmt::Debug for UnityGuid {
83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 write!(f, "UnityGuid({})", self.to_hex())
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn test_guid_generation() {
94 let guid1 = UnityGuid::new();
95 let guid2 = UnityGuid::new();
96 assert_ne!(guid1, guid2);
97 assert_eq!(guid1.to_hex().len(), 32);
98 }
99
100 #[test]
101 fn test_guid_from_hex() {
102 let hex = "0123456789abcdef0123456789abcdef";
103 let guid = UnityGuid::from_hex(hex).unwrap();
104 assert_eq!(guid.to_hex(), hex);
105 }
106
107 #[test]
108 fn test_deterministic_guid() {
109 let guid1 = UnityGuid::from_path("Assets/Terrain/heightmap.raw");
110 let guid2 = UnityGuid::from_path("Assets/Terrain/heightmap.raw");
111 let guid3 = UnityGuid::from_path("Assets/Terrain/other.raw");
112
113 assert_eq!(guid1, guid2);
114 assert_ne!(guid1, guid3);
115 }
116}