triblespace-core 0.35.0

The triblespace core implementation.
use rand::thread_rng;
use rand::RngCore;
use std::time::SystemTime;
use std::time::UNIX_EPOCH;

use super::ExclusiveId;
use super::Id;

/// # Universal Forgettable Ordered IDs (UFOIDs)
///
/// UFOIDs are 128-bit identifiers generated by combining a 32-bit
/// timestamp with 96 bits of cryptographic randomness. The timestamp is the
/// lower 32 bits of milliseconds since the UNIX epoch, effectively cycling
/// (modulo 2^32) every approximately 50 days. This provides high locality
/// for items created close in time. The 96 bits of cryptographic randomness
/// ensure global uniqueness, even when multiple IDs are generated simultaneously.
///
/// ## Efficient Garbage Collection
///
/// The 32-bit timestamp allows for efficient identification of older IDs (modulo the rollover).
/// This property is particularly valuable in systems that need to manage large volumes of
/// ephemeral data, such as caches or robotics applications.
/// By leveraging the timestamp's locality, systems can reduce computational overhead
/// and improve memory management when handling high-throughput workloads.
///
/// ## Rollover Handling
///
/// The 32-bit timestamp rolls over approximately every 50 days, meaning that
/// timestamps will reset to zero after reaching their maximum value.
/// To determine the relative distance between two timestamps, you need to provide the
/// current time as a reference point.  
/// You can use the function [`timestamp_distance`]
/// to handle the modulo (2^32) space accurately and account for the cyclic nature
/// of these timestamps.
///
/// Rollover scenarios should be anticipated in testing to prevent logical errors,
/// such as treating newer IDs as older ones. This edge case is designed to occur
/// relatively frequently to ensure more resilient system designs.
///
/// ## Usage Example
///
/// ```rust
/// use triblespace_core::id::ufoid::ufoid;
///
/// let id1 = ufoid();
/// let id2 = ufoid();
/// assert_ne!(id1, id2);
/// ```
pub fn ufoid() -> ExclusiveId {
    let mut rng = thread_rng();
    let now_in_sys = SystemTime::now();
    let now_since_epoch = now_in_sys
        .duration_since(UNIX_EPOCH)
        .expect("time went backwards");
    let now_in_ms = now_since_epoch.as_millis();

    let mut id = [0; 16];
    id[0..4].copy_from_slice(&(now_in_ms as u32).to_be_bytes());
    rng.fill_bytes(&mut id[4..16]);

    ExclusiveId::force(Id::new(id).expect("The probability time and rng = 0 should be neglegible."))
}

/// Computes the difference between two UFOID timestamps relative to `now`.
///
/// UFOID timestamps only store the lower 32 bits of the UNIX time in
/// milliseconds. This counter overflows about every 50 days. This helper uses
/// wrapping arithmetic so it still works if `ts1` or `ts2` has overflowed in the
/// integer domain.
///
/// Because the absolute time of that rollover is lost, `ts1` and `ts2` must
/// both be newer than one full rollover period relative to `now` for the result
/// to make sense. In other words, the timestamps should have been created in the
/// last ~50 days; older IDs should have been garbage collected.
pub fn timestamp_distance(now: u32, ts1: u32, ts2: u32) -> i64 {
    let d1 = now.wrapping_sub(ts1) as i64;
    let d2 = now.wrapping_sub(ts2) as i64;
    d1 - d2
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn unique() {
        assert!(ufoid() != ufoid());
    }
}