Files
deargui-vpl/examples/blueprints-example/utilities/UUID64_USAGE.md
T

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

// 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!