arms-core 0.1.0

ARMS: Attention Reasoning Memory Store - A spatial memory fabric for AI. Position IS relationship.
Documentation
//! # 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");
    }
}