Last active
November 19, 2024 03:15
-
-
Save wildthink/617c4471bef4bdb8c36f3ea82fec69a6 to your computer and use it in GitHub Desktop.
Int64 Unique sequence generator as a fn(Date.now, Int16 tag)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Unique64.swift | |
// | |
// Created by Jason Jobe on 11/17/24. | |
// | |
import Foundation | |
public protocol SystemEntity: Identifiable where ID == Int64 {} | |
public extension Identifiable where ID == Int64 { | |
@_disfavoredOverload | |
static func eid(tag: Int16 = 0) -> Int64 { | |
return Unique64.shared.next(tag: tag) | |
} | |
} | |
/* Example: | |
Extend SystemEntity to override the create id | |
logic and generator if desired. | |
``` | |
protocol MyEntityRef: SystemEntity {} | |
extension SystemEntity where Self: MyEntityRef { | |
static func eid(tag: Int16 = 0) -> Int64 { | |
// Provide your own generator here | |
// NOTE: you can use Self.Type to get the tag | |
print ("SystemEntity", String(describing: Self.self)) | |
return Unique64.shared.next(tag: tag) | |
} | |
} | |
struct It: Identifiable { | |
var id: Int64 = eid(tag: 23) | |
} | |
struct MyEntity: MyEntityRef { | |
var id: Int64 = eid() | |
} | |
``` | |
*/ | |
/** | |
• Date.timeIntervalSinceReferenceDate provides the time in seconds as a Double. | |
• Multiply the interval to scale it to the desired precision (e.g., microseconds). | |
• Convert it to an Int64, ensuring that the lower 16 bits are cleared by masking or shifting. | |
*/ | |
public struct Unique64 { | |
private var last: Int64 = 0 | |
private let lock = NSLock() // Ensure thread safety | |
/// Returns the current time as a 64-bit integer with lower 16 bits set to zero. | |
func now() -> Int64 { | |
// Get the current time in seconds since reference date | |
let interval = Date.timeIntervalSinceReferenceDate | |
// Convert to microseconds and clear lower 16 bits | |
let scaledInterval = Int64(interval * 1_000_000) & ~0xFFFF | |
return scaledInterval | |
} | |
/// Generates the next unique 64-bit value with a 16-bit tag. | |
public mutating func next(tag: Int16 = 0) -> Int64 { | |
lock.lock() // Begin critical section | |
defer { lock.unlock() } // Ensure lock is released | |
// Generate the base time value | |
var next = now() | |
// Ensure the sequence is monotonically increasing | |
while !(last < next) { | |
// Resolve collision by incrementing 17th bit | |
// next = next.increment(bit: 16) | |
next = next + (1 << 16) | |
} | |
// Update last value | |
last = next | |
// Add the tag to the lower 16 bits | |
return last | Int64(tag) | |
} | |
} | |
public extension Unique64 { | |
nonisolated(unsafe) | |
static var shared = Unique64() | |
} | |
/** | |
Explanation | |
1. date Property: | |
• Masks out the lower 16 bits using self & ~0xFFFF to isolate the timestamp. | |
• Converts the remaining timestamp (in microseconds) back to seconds by dividing by 1,000,000. | |
• Uses the timeIntervalSinceReferenceDate to create a Date object. | |
• Includes a sanity check to ensure the time interval is valid (non-negative). | |
2. tag16 Property: | |
• Extracts the lower 16 bits using self & 0xFFFF and converts the result to Int16. | |
*/ | |
public extension Int64 { | |
/// Extracts the `Date` component from a `Unique64`-formatted value. | |
/// | |
/// Assumes the value is encoded as microseconds since the reference date with the lower 16 bits reserved for the tag. | |
var date: Date? { | |
let timestamp = self & ~0xFFFF // Mask out the lower 16 bits to get the timestamp | |
let timeInterval = Double(timestamp) / 1_000_000 // Convert from microseconds to seconds | |
guard timeInterval >= 0 else { return nil } // Sanity check for a valid time interval | |
return Date(timeIntervalSinceReferenceDate: timeInterval) | |
} | |
/// Extracts the 16-bit tag from a `Unique64`-formatted value. | |
/// | |
/// Assumes the lower 16 bits represent the tag. | |
var tag16: Int16 { | |
return Int16(self & 0xFFFF) // Extract the lower 16 bits | |
} | |
} | |
func unique64Check() { | |
var uniqueGenerator = Unique64() | |
let zeroTag = uniqueGenerator.next(tag: 0) | |
let first = uniqueGenerator.next(tag: 42) | |
let second = uniqueGenerator.next(tag: 1) | |
print("Now ", Date()) | |
print("Tag 0 ", zeroTag.date!, "0\(zeroTag.tag16)", zeroTag) | |
print(String(zeroTag, radix: 16)) | |
print("first ", first.date!, first.tag16, first) | |
print(String(first, radix: 16)) | |
print("second", second.date!, "0\(second.tag16)", second) | |
print(String(second, radix: 16)) | |
print("first < second", first < second) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment