File size: 4,739 Bytes
8ef2d83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
//! # Id
//!
//! Unique identifier for placed points.
//!
//! Format: 128 bits = [timestamp_ms:48][counter:16][random:64]
//! - Timestamp provides natural temporal ordering
//! - Counter prevents collisions within same millisecond
//! - Random portion adds uniqueness
//! - Sortable by time when compared
//! - No external dependencies (not UUID, just bytes)

use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};

/// Global counter for uniqueness within same millisecond
static COUNTER: AtomicU64 = AtomicU64::new(0);

/// Unique identifier for a placed point
///
/// 128 bits, timestamp-prefixed for natural time ordering.
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
pub struct Id([u8; 16]);

impl Id {
    /// Generate a new Id for the current moment
    ///
    /// Uses current timestamp + counter + random bytes for uniqueness.
    pub fn now() -> Self {
        let timestamp = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_millis() as u64;

        // Atomically increment counter for uniqueness
        let counter = COUNTER.fetch_add(1, Ordering::Relaxed);

        let mut bytes = [0u8; 16];

        // First 6 bytes: timestamp (48 bits)
        bytes[0] = (timestamp >> 40) as u8;
        bytes[1] = (timestamp >> 32) as u8;
        bytes[2] = (timestamp >> 24) as u8;
        bytes[3] = (timestamp >> 16) as u8;
        bytes[4] = (timestamp >> 8) as u8;
        bytes[5] = timestamp as u8;

        // Next 2 bytes: counter (16 bits) - ensures uniqueness within millisecond
        bytes[6] = (counter >> 8) as u8;
        bytes[7] = counter as u8;

        // Remaining 8 bytes: pseudo-random based on timestamp and counter
        let random_seed = timestamp
            .wrapping_mul(6364136223846793005)
            .wrapping_add(counter);
        bytes[8] = (random_seed >> 56) as u8;
        bytes[9] = (random_seed >> 48) as u8;
        bytes[10] = (random_seed >> 40) as u8;
        bytes[11] = (random_seed >> 32) as u8;
        bytes[12] = (random_seed >> 24) as u8;
        bytes[13] = (random_seed >> 16) as u8;
        bytes[14] = (random_seed >> 8) as u8;
        bytes[15] = random_seed as u8;

        Self(bytes)
    }

    /// Create an Id from raw bytes
    pub fn from_bytes(bytes: [u8; 16]) -> Self {
        Self(bytes)
    }

    /// Get the raw bytes
    pub fn as_bytes(&self) -> &[u8; 16] {
        &self.0
    }

    /// Extract the timestamp component (milliseconds since epoch)
    pub fn timestamp_ms(&self) -> u64 {
        ((self.0[0] as u64) << 40)
            | ((self.0[1] as u64) << 32)
            | ((self.0[2] as u64) << 24)
            | ((self.0[3] as u64) << 16)
            | ((self.0[4] as u64) << 8)
            | (self.0[5] as u64)
    }

    /// Create a nil/zero Id (useful for testing)
    pub fn nil() -> Self {
        Self([0u8; 16])
    }

    /// Check if this is a nil Id
    pub fn is_nil(&self) -> bool {
        self.0 == [0u8; 16]
    }
}

impl std::fmt::Display for Id {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        // Display as hex string
        for byte in &self.0 {
            write!(f, "{:02x}", byte)?;
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::thread;
    use std::time::Duration;

    #[test]
    fn test_id_creation() {
        let id = Id::now();
        assert!(!id.is_nil());
    }

    #[test]
    fn test_id_timestamp() {
        let before = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_millis() as u64;

        let id = Id::now();

        let after = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_millis() as u64;

        let ts = id.timestamp_ms();
        assert!(ts >= before);
        assert!(ts <= after);
    }

    #[test]
    fn test_id_ordering() {
        let id1 = Id::now();
        thread::sleep(Duration::from_millis(2));
        let id2 = Id::now();

        // id2 should be greater (later timestamp)
        assert!(id2 > id1);
    }

    #[test]
    fn test_id_from_bytes() {
        let bytes = [1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
        let id = Id::from_bytes(bytes);
        assert_eq!(id.as_bytes(), &bytes);
    }

    #[test]
    fn test_id_nil() {
        let nil = Id::nil();
        assert!(nil.is_nil());
        assert_eq!(nil.timestamp_ms(), 0);
    }

    #[test]
    fn test_id_display() {
        let id = Id::from_bytes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
        let display = format!("{}", id);
        assert_eq!(display, "000102030405060708090a0b0c0d0e0f");
    }
}