8.4 KiB
8.4 KiB
64-bit UUID using Dual 32-bit Words (Microcontroller Compatible)
Perfect for 100,000+ items while maintaining compatibility with 32-bit microcontrollers!
Why Dual 32-bit Words?
- ✅ Microcontroller Compatible: Works on platforms without native 64-bit support
- ✅ Zero Collisions: ~0.0000000027% collision risk for 100k items
- ✅ 18 Quintillion Space: 18,446,744,073,709,551,616 unique IDs
- ✅ Efficient: Only two 32-bit operations, no 64-bit arithmetic required
- ✅ Serializable: Easy to save/load as two separate fields
Uuid64 Structure
struct Uuid64
{
uint32_t high; // Upper 32 bits
uint32_t low; // Lower 32 bits
};
Basic Usage
Generate UUIDs
// Random 64-bit UUID (virtually collision-free)
ed::Uuid64 uuid = app->GenerateRandomUuid64();
// Example: { high: 0x50626ea, low: 0x1b7c7646 }
// Sequential 64-bit UUID (guaranteed unique)
ed::Uuid64 seqUuid = app->GenerateSequentialUuid64();
// Example: { high: 0x1000000, low: 0x0 }
// { high: 0x1000000, low: 0x1 }
// { high: 0x1000000, low: 0x2 }
Convert to/from Hex String
// Convert to string
ed::Uuid64 uuid = app->GenerateRandomUuid64();
std::string hexStr = app->UuidToHexString64(uuid);
// Output: "0x50626ea1b7c7646"
// Parse from string
ed::Uuid64 parsed = app->HexStringToUuid64("0x50626ea1b7c7646");
// Result: { high: 0x50626ea, low: 0x1b7c7646 }
Access Individual Words
ed::Uuid64 uuid = app->GenerateRandomUuid64();
// Access 32-bit words directly
uint32_t highWord = uuid.high; // Upper 32 bits
uint32_t lowWord = uuid.low; // Lower 32 bits
// Construct from two 32-bit values
ed::Uuid64 custom(0x1234ABCD, 0x5678EF90);
Validation
ed::Uuid64 uuid = app->GenerateRandomUuid64();
// Check if valid (non-zero)
if (uuid.IsValid())
{
printf("UUID is valid\n");
}
// Compare UUIDs
ed::Uuid64 uuid1 = app->GenerateRandomUuid64();
ed::Uuid64 uuid2 = app->GenerateRandomUuid64();
if (uuid1 == uuid2)
printf("Same UUID\n");
if (uuid1 != uuid2)
printf("Different UUIDs\n");
Standard UUID Format (RFC4122) Conversion
Convert between standard UUID format and Uuid64:
// Parse standard UUID format (with dashes)
ed::Uuid64 uuid = app->StandardStringToUuid("9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d");
// Result: { high: 0x9bdd2b0d, low: 0x7b3dcb6d } (takes last 64 bits)
// Convert to standard format (zero-padded)
std::string standardStr = app->UuidToStandardString(uuid);
// Output: "00000000-0000-0000-9bdd2b0d-7b3dcb6d"
// Parse with first 64 bits instead
ed::Uuid64 uuid2 = app->StandardStringToUuid("9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", false);
// Result: { high: 0x9b1deb4d, low: 0x3b7d4bad } (takes first 64 bits)
For detailed examples, see STANDARD_UUID_CONVERSION.md
Use in Maps and Sets
// Uuid64 has comparison operators for std::map/std::set
std::map<ed::Uuid64, Node*> nodeMap;
std::set<ed::Uuid64> uniqueIds;
ed::Uuid64 id = app->GenerateRandomUuid64();
nodeMap[id] = myNode;
uniqueIds.insert(id);
Microcontroller-Friendly Operations
Without 64-bit Arithmetic
// Generate on MCU - uses only 32-bit operations
ed::Uuid64 id = app->GenerateSequentialUuid64();
// Access as two 32-bit values
uint32_t high = id.high;
uint32_t low = id.low;
// Send over network as two separate 32-bit packets
SendPacket(high);
SendPacket(low);
// Receive and reconstruct
uint32_t receivedHigh = ReceivePacket();
uint32_t receivedLow = ReceivePacket();
ed::Uuid64 reconstructed(receivedHigh, receivedLow);
With 64-bit Support (Optional)
// If platform supports uint64_t, you can convert
ed::Uuid64 uuid = app->GenerateRandomUuid64();
uint64_t fullValue = uuid.ToUint64();
// And back
ed::Uuid64 reconstructed = ed::Uuid64::FromUint64(fullValue);
JSON Serialization Examples
Option 1: Separate Fields (Recommended for MCU)
// Save
json nodeData;
ed::Uuid64 id = app->GenerateRandomUuid64();
nodeData["id_high"] = id.high;
nodeData["id_low"] = id.low;
// Load
ed::Uuid64 loadedId(
nodeData["id_high"].get<uint32_t>(),
nodeData["id_low"].get<uint32_t>()
);
Option 2: Hex String
// Save
json nodeData;
ed::Uuid64 id = app->GenerateRandomUuid64();
nodeData["id"] = app->UuidToHexString64(id);
// Load
std::string hexId = nodeData["id"].get<std::string>();
ed::Uuid64 loadedId = app->HexStringToUuid64(hexId);
Option 3: Single 64-bit Value (if platform supports)
// Save (requires uint64_t support)
json nodeData;
ed::Uuid64 id = app->GenerateRandomUuid64();
nodeData["id"] = id.ToUint64();
// Load
uint64_t value = nodeData["id"].get<uint64_t>();
ed::Uuid64 loadedId = ed::Uuid64::FromUint64(value);
Real-World Examples
Example 1: Node IDs for Large Graph (100k+ nodes)
// Use sequential for guaranteed uniqueness and debugging
ed::Uuid64 nodeId = app->GenerateSequentialUuid64();
Node* node = new Node();
node->SetUuid64(nodeId);
printf("Created node: %s\n", app->UuidToHexString64(nodeId).c_str());
// Output: Created node: 0x100000000000000
Example 2: Random IDs for Distributed System
// Each client generates random IDs - virtually no collision
ed::Uuid64 clientId = app->GenerateRandomUuid64();
ed::Uuid64 sessionId = app->GenerateRandomUuid64();
// 100k items across 1000 clients = ~0% collision probability
Example 3: Sending Over Serial (MCU)
// Transmit UUID as two 32-bit values
void SendUuid(const ed::Uuid64& uuid)
{
Serial.write((uint8_t*)&uuid.high, 4); // Send 4 bytes
Serial.write((uint8_t*)&uuid.low, 4); // Send 4 bytes
}
// Receive UUID as two 32-bit values
ed::Uuid64 ReceiveUuid()
{
uint32_t high, low;
Serial.readBytes((uint8_t*)&high, 4);
Serial.readBytes((uint8_t*)&low, 4);
return ed::Uuid64(high, low);
}
Example 4: Persistent Storage (EEPROM/Flash)
// Write to EEPROM (8 bytes total)
void SaveUuidToEEPROM(const ed::Uuid64& uuid, int address)
{
EEPROM.put(address, uuid.high); // 4 bytes
EEPROM.put(address + 4, uuid.low); // 4 bytes
}
// Read from EEPROM
ed::Uuid64 LoadUuidFromEEPROM(int address)
{
uint32_t high, low;
EEPROM.get(address, high);
EEPROM.get(address + 4, low);
return ed::Uuid64(high, low);
}
Collision Analysis for 100k Items
| UUID Type | Space | Collision Risk |
|---|---|---|
| 32-bit Random | 4.28 billion | 68.9% ❌ |
| 32-bit Sequential | 4.28 billion | 0% ✅ |
| 64-bit Random | 18.4 quintillion | ~0% ✅ |
| 64-bit Sequential | 18.4 quintillion | 0% ✅ |
Calculation for 64-bit random UUIDs:
P(collision) = 1 - e^(-n²/(2d))
n = 100,000
d = 18,446,744,073,709,551,616
P(collision) ≈ 0.0000000027% (essentially zero)
Performance Characteristics
| Operation | 32-bit UUID | 64-bit UUID (Dual Word) |
|---|---|---|
| Generation | 1x random call | 2x random calls |
| Comparison | 1 compare | 2 compares |
| Storage | 4 bytes | 8 bytes |
| Collision Risk (100k) | 68.9% | ~0% |
When to Use
Use 64-bit (Dual 32-bit) UUIDs when:
- ✅ You need 100k+ items with random IDs
- ✅ Working with distributed systems
- ✅ Collision risk must be virtually zero
- ✅ Still need microcontroller compatibility
Use 32-bit UUIDs when:
- ✅ Total items < 10,000
- ✅ Using sequential generation
- ✅ Need minimal memory (4 bytes vs 8 bytes)
Migration Path
// If you have existing 32-bit UUIDs
uint32_t oldId = 0x50626ea;
// Convert to 64-bit format (backward compatible)
ed::Uuid64 newId(0, oldId); // high=0, low=old_id
// Or use high word for type/category
ed::Uuid64 nodeId(0x0001, oldId); // Type: 0x0001 = Node
ed::Uuid64 linkId(0x0002, oldId); // Type: 0x0002 = Link
Summary
The Uuid64 structure provides:
- 🎯 Microcontroller compatibility (no 64-bit math required)
- 🎯 Zero collision risk for 100k+ items
- 🎯 Simple API (just two uint32_t fields)
- 🎯 Easy serialization (8 bytes, or two separate fields)
- 🎯 Comparison operators for use in STL containers
Perfect for embedded systems that need large-scale unique identifiers!